在微服務世界中,每個人都使用緩存,緩存無處不在。緩存可以提高性能,減少后端負載,或者減少down機時間。有許多方法可以配置系統中的緩存,緩沖應該被放在系統的哪個層上?根據以往成功經驗,系統中您應該只在一個地方使用緩存。不應該同時在多個層中組合模式和緩存,例如同樣的內容在HTTP層和應用程序級別同時做緩存。這種方法可能導致更多的緩存失效問題,并使您的系統更容易出錯,且難于調試。
如果您在一個特定的層上使用緩存,那么您可以選擇使用哪種模式。最保守的方法是老式的客戶機-服務器(或云)模式,這個問題的正確答案不止一個。您可以將緩存放在每個服務中,或者作為一個完全獨立的緩存服務器。您還可以將它放在每個服務的前面,甚至作為屬于服務的sidecar容器等等。本文下面,讓我們總結一下您在微服務世界多種方式的緩存體系結構。
嵌入式緩存
最簡單的緩存模式是嵌入式緩存。
嵌入式緩存
在上圖中,流程如下:
1.請求進入負載平衡器。
2.負載均衡器將請求轉發給應用程序服務之一。
3.應用程序服務接收請求,并檢查是否相同的請求已經執行(并存儲在緩存)?
如果是,然后返回緩存數據。反之,則執行業務操作,并把結果數據存儲在緩存中,并返回結果數據。
業務操作可以是任何值得緩存的內容。例如,執行計算、查詢數據庫或調用外部web服務等。
這種緩存邏輯非常簡單,我們可以使用內置的數據結構或一些緩存庫(如Guava cache)為其快速編寫代碼。我們還可以將緩存放在應用程序層中,并使用大多數web框架提供的緩存功能。例如,對于Spring,添加緩存層只需要向方法添加@Cacheable注釋。
嵌入式緩存方法有一個嚴重的問題。假設有一個向我們的系統發出的請求,它第一次被轉發到頂部的應用程序服務A。然后,同樣的請求出現,但這一次負載平衡器將其轉發給底部的應用程序服務B。這種情況下,我們收到了兩次相同的請求,但是必須執行兩次業務邏輯,因為圖中的兩個緩存是分別完成的。為了處理這樣的問題,可以使用嵌入分布式緩存。
嵌入分布式緩存
嵌入式分布式緩存仍然是嵌入式緩存的模式;但是,這一次我們將使用Hazelcast(Hazelcast 是由Hazelcast公司開發和維護的開源產品,可以為基于jvm環境運行的各種應用提供分布式集群和分布式緩存服務)而不是默認的非分布式緩存庫。從現在開始,所有緩存(嵌入到所有應用程序中)形成一個分布式緩存集群。因為Hazelcast是用JAVA編寫的,所以您可以將它與Spring一起使用;
您需要做的就是添加以下CacheManager配置。
通過這幾行代碼,我們讓Spring為它提供的所有緩存功能使用Hazelcast。
使用嵌入式緩存(分布式和非分布式)很簡單,因為它不需要任何額外的配置或部署。而且,您總是可以獲得低延遲的數據傳輸,因為緩存在物理上運行在相同的JVM中。稍后我們將更仔細地研究這個解決方案的優缺點。
下面讓我們介紹另一個完全不同的緩存模式,客戶機-服務器。
客戶端/服務器式緩存
此時,圖中所示流程如下:
1.請求進入負載均衡組件并被轉發到應用程序服務
2.應用程序使用緩存客戶機連接到緩存服務器
3.如果沒有找到值,則執行通常的業務邏輯,緩存值并返回響應
該體系結構與經典的數據庫體系結構相似。我們有一個中心服務器(或者更準確地說是一組服務器),應用程序連接到該服務器。如果我們將客戶機-服務器模式與嵌入式緩存進行比較,主要有兩個區別:
•首先,緩存服務器在我們的體系結構中是一個單獨的單元,這意味著我們可以單獨管理它(向上/向下伸縮、備份、安全)。然而,這也意味著它通常需要單獨的項目事務處工作(甚至單獨的項目事務處團隊)。
•第二個區別是應用程序使用緩存客戶端庫與緩存通信,這意味著我們不再局限于基于jvm的語言。有一個定義良好的協議,服務器部分的編程語言可以與客戶端部分不同。這實際上是許多緩存解決方案(如redis或Memcached)僅為其部署提供這種模式的原因之一。
我之前提到過,嵌入式緩存和客戶機-服務器緩存的第一個區別是前者是單獨管理的。一個單獨的Ops團隊甚至可以管理它,或者您可以更進一步,將管理部分轉移到云計算中。
云端緩存
就架構而言,云類似于客戶機-服務器,不同之處在于服務器部分被移到組織之外,由云提供商管理,因此您不必擔心所有的組織問題。
如果您對某個示例感興趣,可以在Hazelcast云平臺上創建一個Hazelcast集群,然后,您可以在這里找到一個完整的客戶機應用程序。
最有趣的部分是Spring配置:
@Bean CacheManager cacheManager() { ClientConfig clientConfig = new ClientConfig(); clientConfig.getNetworkConfig().getCloudConfig() .setEnabled(true) .setDiscoveryToken("KSXFDTi5HXPJGR0wRAjLgKe45tvEEhd"); clientConfig.setGroupConfig(new GroupConfig("test-cluster", "b2f9845")); return new HazelcastCacheManager( HazelcastClient.newHazelcastClient(clientConfig)); }
使用客戶機-服務器模式很簡單,使用云模式更簡單。它們都帶來了類似的好處,比如將緩存數據與應用程序分離、獨立管理(向上/向下擴展、備份)以及使用任何編程語言的可能性。然而,有一件事變得更加困難——延遲。對于嵌入式模式,緩存始終與應用程序位于同一臺機器上(甚至在同一JVM中)。然而,當服務器部分被分離時,我們現在需要考慮它的物理位置。最好的選擇是使用相同的本地網絡(或者在云解決方案中使用相同的VPC)。
現在,讓我們轉移到一個新的稍微不尋常的模式,緩存作為一個邊車。
邊車式緩存(Sidecar)
上面的圖表是特定于Kubernetes的,因為Sidecar模式主要出現在Kubernetes環境中(但不限于)。在Kubernetes中,部署單元稱為POD。這個POD包含一個或多個容器,這些容器總是部署在相同的物理機器上。
通常,一個POD只包含一個容器和應用程序本身。然而,在某些情況下,您不僅可以包含應用程序容器,還可以包含一些提供附加功能的附加容器。這些容器稱為邊車容器。
流程如下:
1.請求到達Kubernetes服務(負載平衡器)并被轉發到其中一個吊艙。
2.請求到達應用程序容器,應用程序使用緩存客戶機連接到緩存容器(從技術上講,緩存服務器總是在localhost上可用)。
這個解決方案混合了嵌入式模式和客戶機-服務器模式。
它類似于嵌入式緩存,因為:
•緩存始終與應用程序位于同一臺機器上(低延遲)。
•資源池和管理活動在緩存和應用程序之間共享。
•緩存集群發現不是問題(它總是在本地主機上可用)。
它也類似于客戶機-服務器模式,因為:
•應用程序可以用任何編程語言編寫(它使用緩存客戶端庫進行通信)。
•緩存和應用程序有一些隔離。
現在讓我們討論一個完全不同的模式,反向代理。
反向代理緩存
到目前為止,在前面每個場景中,應用程序都清楚自己使用了緩存。然而,這一次,我們將緩存部分放在應用程序前面,所以流程如下:
1.請求進入負載平衡器。
2.負載均衡器檢查這樣的請求是否已經緩存。
3.如果是,則返回響應,而不將請求轉發給應用程序。
這樣的緩存解決方案是基于協議級別的,所以在大多數情況下,它是基于HTTP的,這有一些好的和壞的含義:
•好的方面是,您可以將緩存層指定為配置,因此不需要更改應用程序中的任何代碼。
•不好的是,您不能使用任何基于應用程序的代碼來使緩存失效,因此失效必須基于超時(以及標準HTTP TTL、ETag等)。
Nginx提供了成熟的反向代理緩存解決方案;然而,緩存中保存的數據不是分布式的,不是高可用性的,數據存儲在磁盤上。
我們可以對反向代理模式做的一個改進是將HTTP反向代理注入到sidecar中。你可以這樣做:
反向代理邊車
同樣,當涉及到Sidecar時,該圖僅限于Kubernetes環境。流程如下:
1.請求進入Kubernetes服務(負載平衡器)并被轉發到其中一個pod。
2.在POD中,接收請求的是反向代理緩存容器(而不是應用程序容器)。
3.反向代理緩存容器檢查這樣的請求是否已經緩存。
4.如果是,則發送緩存的響應(甚至不將請求轉發給應用程序容器)。
應用程序容器甚至不知道緩存的存在。考慮一下本文開頭介紹的微服務系統。使用此模式,我們可以查看整個系統并指定(在Kubernetes配置文件中)應該緩存服務2v1和服務1。
前還沒有成熟的HTTP反向代理緩存Sidecar解決方案,然而,我相信它會變得越來越流行,因為一些項目已經在積極地進行一些穩定的實現。
優點和缺點
我們提到了許多可以在微服務系統中使用的緩存模式。
優缺點列表: