9 Jul 2011

Web Caching

(Part 1)
Một caching server là server đứng giữa clients và web servers, trả lời request từ client thay cho web server nếu như:
1. Client request html object đã được lưu trữ trong bộ nhớ cache.
2. Cache object là version cùng với version được sinh ra từ web servers nếu request vào được đến web server (fresh).

Web cache có 3 loại: browser cache, proxy cache (forward proxy, transparent proxy), và gateway cache (reverse proxy). Ở đây chúng ta chỉ nhắc đến loại thứ 3: Reverse proxy cache, ngắn gọn là reverse proxy.
Một mô hình reverse proxy đơn giản như sau:
                           Clients -------- Reverse proxy --------- Web Servers


Khi client request url nào đó, request này được reverse proxy đón nhận, có 2 trường hợp:
1. Nếu URL đã có trong bộ nhớ cache, và vẫn *được cho* là còn mới, reverse proxy trả lời request trên mà không cần phải tạo thêm một request nữa đến Web server bên trong.
2. Nếu URL được request không có trong bộ nhớ cache, reverse proxy truy vấn web servers, lưu câu trả lời (nếu URL tồn tại), và trả lời request của client.

(Thật ra phần này có thêm nhiều chi tiết như HTTP headers và đi vào từng headers cụ thể. Nhưng khi viết xong đọc lại thì hóa ra nó có cả trong RFC và hàng hàng xa số các trang web, blog cá nhân khác, nên lại xóa đi).


Khi phải phục vụ một số lượng người dùng lớn, đồng nghĩa với việc Reverse proxy, và web server với mô hình bên trên không đáp ứng được nhu cầu phục vụ quá nhiều số requests/s, người quản lí thường mở rộng hệ thống cache bằng cách add thêm nhiều cache servers vào mô hình bên trên.
                                                             Cache A
Requests --- Load Balancer   -------Cache B ------ Web Servers
                                                           Cache C

Mô hình này tương tự mộ hình bên trên, chỉ khác là request khi đến load balancer, sẽ được route vào bên trong cache farm dựa theo một thuật toán balancing nào đó: round robin | server load | weight | priority...
Cache farm cũng sẽ trả lời nếu URL đã có trên cache, hoặc truy vấn Web servers nếu URL không tồn tại trên cache. Mô hình này được dùng khá nhiều, và giảm tải rất nhiều cho hệ thống web server bên trong, cũng như phục vụ được số requests lớn hơn *rất* nhiều so với mô hình ban đầu.
Tuy nhiên vẫn có hạn chế. Lưu ý, ta sẽ không nhắc đến hạn chế về mặt Single Point of failure ở Load balancer, nếu có, có lẽ sẽ được nhắc đến ở một bài khác.
Quay lại mô hình bên trên, nếu Client A truy vấn URL(1) lần đầu tiên (hoặc URL(1) đã được thay đổi, cache object được đánh dấu là Stale), sẽ được LB route đến Cache A, lúc này Cache A sẽ kiểm tra bộ nhớ cache của mình, tìm không thấy cache object được yêu cầu, và truy vấn đền Web Servers bên trong. Việc này sẽ tương tự nếu có Client B và client C gọi cũng URL(1) bên trên được route đến Cache B và Cache C.
Ta thấy rõ sự bất bình thường ở đây:
1. Có một tác vụ được lặp đi lặp lại với N Reverse proxies nếu có N requests truy vấn cùng một URL.
2. Với N requests, web servers bên trong phải xử lí N tác vụ tính toán: tạo ra N threads (hoặc processes), gọi CGI hoặc FCGI processes để xử lí, truy vấn database và trả về cùng một HTML object.

Như vậy là hoàn toàn không cần thiết. Ta nghĩ đến một mô hình mới: Cache Farm.
Một cache farm có mô hình hoàn toàn giống bên trên, chỉ khác là các Reverse proxies có quan hệ họ hàng (Parent, sibling)  với nhau và với các web server bên trong (tùy cách implement cache). Cache cluster hoạt động dựa trên một protocol đặc biệt: ICP (Internet Caching Protocol). Protocol này được sử dụng nhằm mục đích chia sẽ cache objects giữa các caching servers với nhau dựa vào mối quan hệ được ấn định từ trước. Thuật ngữ được nhắc đến trong bài này hoàn toàn dùng lại từ SQUID cache (Traffic server dùng child cache và parent cache, mỗi child cache hoặc parent cache được gọi là một node trong cluster), mối quan hệ này được miêu tả như sau:
 - Parent: Một cache peer sẽ gửi một ICP request đến parent cache nếu như URL được client request không tìm thấy trên bất cứ sibling cache peer nào. Và nếu như URL này vẫn không được tìm thấy ở parent cache, parent sẽ forward ICP_QUERY hoặc tạo một HTTP request đến parent cache khác mà nó được chỉ định.
 - Sibling: Nếu client request một URL nào đó không tìm thấy ở local cache, sibling cache sẽ gửi nhiều ICP_QUERY request đến các anh chị (và parent cache) của nó để truy vấn URL trên. Nếu các anh/chị nào trả lời với một ICP_HIT response, sibling cache sẽ yêu cầu object này từ server gửi ICP_HIT response sớm nhất và nhận về ICP_HIT_OBJ response. Trong trường hợp sibling cache không nhận được ICP_HIT response từ bất cứ anh/chị nào, nó sẽ gửi request đến parent cache. Sibling cache không có quyền forward ICP_MISS đến parent cache trong trường hợp ICP_MISS này đến từ một sibling cache khác.

Nói như vậy có nghĩa là trong hầu hết mọi trường hợp, ta đều cần cấu hình một sibling cache có nhiều sibling cache và ít nhất một parent cache, đề tránh trường hơp reverse proxy trả lời client bằng một response với ERROR code (Not found).
Cũng nên chú ý không nên cấu hình quá nhiều sibling và parent cho một cache peer, ICP request được gửi đồng thời tới nhiều sibling và parent cache, sẽ làm tăng latency của network, hoặc nghẽn trong trường hợp có quá nhiều request  miss cache.

ICP protocol được phát triển từ rất sớm, và một trong những ưu điểm của protocol này, cũng như mong muốn của những người tạo ra nó là làm sao cho packet có kích thước bé nhất có thể, nhanh nhất có thể, và đường nhiên phải carry đủ các thông tin cần thiết.

Caching software hiện nay mà hầu hết chúng ta vẫn đang sử dụng là Squid (lầu đời nhất), Apache Traffic Server (ban đầu là Yahoo Traffic server, sau Yahoo contribute software này cho cộng đồng Open Source và được Apache Foundation bảo trợ), Varnish Cache, và thật ra không phải caching software nhưng lại hoạt động rất tốt nếu được config là reverse proxy: Nginx và rất nhiền các commercial caching software khác.
Trong đó: SQUID và Traffic server được implement và support ICP ngay từ những ngày đầu trong đó ATS còn sử dụng một protocol khác là RPC, Varnish không support ( https://www.varnish-software.com/blog/why-icp-isnt-happening-and-generally-bad-idea ) và nginx không nên support (vì bản thân là web server, caching chỉ là additional feature).

Một nguyên tắc khi xây dựng hệ thống:"Khi chúng ta gắn thêm cho hệ thống một tính năng, thì đồng thời với tính năng đó, khả năng gây ra lỗi tăng thêm 20%". Vì thế, bạn đừng ngạc nhiên nếu khi đọc đến đây, mình sẽ bảo bạn rằng mình không dùng ICP.
Lí do:
1. Nếu đọc link liên kết đến varnish blog bạn sẽ thấy rằng họ hoàn toàn có lí khi cho rằng việc request và nhận một cache object thông qua HTTP protocol so với hạ tầng network như hiện nay (Gbps) chỉ là chuyện nhỏ, nếu HTTP có chậm hơn ICP thì độ chậm cũng chỉ bé hơn vài milisenconds. Và quả thật là như vậy. Nếu bạn đã từng tính xem một SQUID cache cần tốn bao nhiêu thời gian để có thể "clone" một website tin tức có vài chục chuyện mục và vài vài trăm trang HTML + content (dựa vào user
request), bạn sẽ thấy rằng nhận định trên hoàn toàn hợp lí.
2. Nếu ta sử dụng ICP, đồng nghĩa với việc gắn thêm một service nữa vào hệ thống, sử dụng 2 protocols để cùng làm một việc (fetching cache). Tính phức tạp của hệ thống tắng lên, chưa kể đến việc phải maintain làm sao để hệ thống dùng ICP cho hiệu quả.

VaRnish có nhắc đến việc duplicate content nếu dùng ICP, đơn giản vì varnish không lưu cùng một cache object trên tất cả các node mà dùng một hash table để lưu trữ vị trí cache trên từng node, tuy nhiên nếu bạn đọc tiếp bài này, thì có nghĩa bạn chấp nhận việc content duplication trên tất cả các node.

Ta sẽ không nhắc đến khía cạnh tốt/không tốt của việc sử dụng hash table kia, công thêm kiến trúc của varnish được xem là excellent architecture for modern operation system, Varnish được ca ngợi trên tất cả các mặt trận.

(Mình không dùng Varnish).

Không dùng ICP (và tất nhiên cả HTCP) , có nghĩa ta dùng HTTP. Việc sử dụng HTTP đơn giản vì bản thân back-end server đã full support HTTP protocol (vì là web server), bản thân các node trong cache farm cũng full support HTTP protocol (vì là HTTP proxy). Mô hình ban đầu là cách một web cache thay mặt client request object dựa trên client request đến back-end server, lưu object vào cache memory đồng thời trả lời request của client với cache object đó.
Vậy nếu ta add thêm một node vào cache farm, đơn giản cấu hình cache farm này nếu miss cache thì gọi web cache thứ 1 và back-end server. Việc này tương tự nếu ta lại add thêm một node mới.

Chú ý là luôn cần chỉ định backend server là một cache peer, để giảm tải dựa trên thuật toán balancing sẵn có của mỗi proxy cache (người viết luôn dùng round robin), và backend server luôn là server có khả năng trả về response với content mới nhất mà client cần.








No comments:

Disqus