- Knative最佳實踐
- (澳)Jacques Chester
- 1864字
- 2022-08-16 16:50:25
1.4 計數器應用
到目前為止,我們已經介紹了Knative的不少優點:更輕松的部署、更輕松的事件機制、漸進式開發、火星獨角獸(每個人都向開發人員承諾的常見特性)等,但沒有提供任何具體的細節。為了證明上述特性,下面我們從一個簡單的例子(計數器應用)開始,介紹Knative如何使工作變得更快、更智能、更輕松。計數器[3]如圖1.4所示。

圖1.4 計數器
第一次看到計數器變化的時候,筆者就覺得它很神奇,其實也不神奇,它只是一個CGI程序(Web應用),可能是使用Perl語言編寫的[4]。CGI程序是Knative的主要應用場景之一。下面是為博客首頁[5]添加點擊率計數器的代碼。
清單1.1 博客首頁HTML文件

首先介紹請求和響應的基本流程,如圖1.5所示。主頁的訪問者從Web服務器獲取HTML文檔。該文檔包含一些網頁樣式,最重要的是,包含計數器。

圖1.5 請求和響應的基本流程
圖1.5中展示的具體流程如下:
①瀏覽器向主頁服務器發出GET請求。
②主頁服務器返回主頁的HTML文件。
③瀏覽器找到hits.png圖片標簽,然后通過GET請求獲取hits.png。
④文件存儲返回hits.png。
早期的程序中,Web服務端的所有流程都是串行的,即當訪問者發送請求時,CGI服務端的/CGI-BIN/hitctr.pl進程會渲染圖像,然后返回,這可能會花費1~2s的時間。如果網絡差,那么花費的時間可能會更長。
不過現在來說,花費這么長的時間對訪問者來說是不可接受的:沒有人愿意等待幾秒等圖片渲染完。于是有了異步請求,即接受請求后,Web服務器立刻返回HTML響應,計數器圖片則由其他服務響應。
Web服務器如何實現這種功能呢?實際上,Web服務器并不會實現這種功能,它只表示發生了點擊動作。記住,Web服務器只是響應Web界面,而不是渲染圖片。Web服務器通過CloudEvent格式發送異步消息(new_hit)。
那么消息發送到哪里了呢?消息會被發送到事件代理(Broker)。事件代理是Knative事件模塊的消息匯聚點,它僅用于過濾和轉發事件,具體是什么事件它不會關注。觸發器(Trigger)來定義誰會接收什么事件這些具體細節。每個觸發器都定義了對哪些事件感興趣,以及事件的接收方是誰。當事件到達事件代理時,它會使用觸發器的過濾器(Filter)。如果匹配完成,則將事件發送到訂閱者(Subscriber),如圖1.6所示。

圖1.6 代理和觸發器的工作流程
觸發器使得處理多個事件流成為可能。Web服務器不知道也不關注new_hit事件會被誰消費。有了new_hit事件,就可以用來計數了。不同于之前的同步阻塞調用,現在可以用Perl腳本異步處理計數功能了。
既然已經使用了事件功能,不妨用得更徹底一些。畢竟渲染圖片并不是計數應用的關鍵能力(比如執行SQL語句UPDATE時,并不能取回圖片)。這里我們通過統計服務來消費new_hit事件,然后發出一個新的統計事件,這個事件會被其他訂閱者來消費,消息流轉過程如圖1.7所示。

圖1.7 消息流轉過程
①主頁服務發送new_hit事件。
②觸發器匹配到new_hit事件,事件代理會將事件轉發到計數器應用。
③計數器應用更新內部計數器,然后發送hits事件(包含總的點擊數)。
④另一個觸發器匹配到hits事件,事件代理會將該事件轉發到圖片渲染器服務。
⑤圖片渲染器渲染出一個新的圖片,然后更新文件服務器中的hits.png。
現在,如果訪問者重新加載其瀏覽器,就會看到命中計數器已經更新了。
困難點
把所有的流程都匯總在一起,如圖1.8所示。
注意,圖1.8中用了兩組標號。一組用于Web請求響應(圖的左側),另一組用于事件流(圖的右側)。這是很重要的一點:Web服務是同步的,事件流是異步的,這個區別很重要,如圖1.9所示。

圖1.8 數據流匯總圖

圖1.9 同步調用效率低,異步調用數據一致性無法保證
由于事件流是異步的,因此無法保證hits.png在下一位訪問者請求之前更新。比如,訪問者可能會看到0001336,重新加載后看到的還是0001336[6]。除此之外,一個訪問者可能看不到任何變化,另一位訪問者可能注意到計數器跳躍增長,因為后面提交的圖片會覆蓋前面提交的圖片。不僅如此,訪客還可能看到計數減少,因為0001338的渲染可能在0001337的渲染之前就已經完成了,或者是事件未按順序到達,或者是某些事件甚至從未收到。
還記得前面說過計數器應用(Hit Counter)會記錄點擊總數嗎?如果計數器應用在內存中保存點擊總數,則是有問題的。比如當沒有請求時,Knative的自動縮放器會把計數器應用的實例縮容到零,內存中保存的點擊總數自然就消失了。計數器應用冷啟動之后點擊總數為零。但是另一方面,如果有多個計數器應用實例,則這些實例都是單獨計數的。準確的圖片點擊數取決于流量路由到哪個計數器應用實例,并不是我們期望的總數。
我們正在討論的是無狀態系統,解決上述問題的思路就是把數據保存在共享位置,而不是代碼邏輯中。比如每個計數器應用都使用Redis遞增一個公共值。否則邏輯可能會變得比較復雜:每個實例都監聽事件[7],只有當收到的數據大于自己內存中的數據時,才將數據更新為輸入的計數,同時要保證系統中沒有循環事件。