歡迎光臨
每天分享高質量文章

微服務架構下靜態資料通用快取機制

在分佈式系統中,特別是最近很火的微服務架構下,有沒有或者能不能總結出一個業務靜態資料的通用快取處理機制或方案,這篇文章將結合一些實際的研發經驗,嘗試理清其中存在的關鍵問題以及探尋通用的解決之道。
什麼是靜態資料

 

這裡靜態資料是指不經常發生變化或者變化頻率比較低的資料,比如車型庫、用戶基本信息、車輛基本信息等,車型庫這種可能每個月會更新一次,用戶和車輛基本信息的變化來源於用戶註冊、修改,這個操作的頻率相對也是比較低的。
另外這類資料的另一個特點是要求準確率和實時性都比較高,不能出現丟失、錯誤,以及過長時間的陳舊讀。
具體是不是應該歸類為靜態資料要看具體的業務,以及對變化頻率高低的劃分標準。在這裡的業務定義中,上邊這幾類資料都歸為靜態資料。

 

為什麼需要快取

 

在面向用戶或車聯網的業務場景中,車型信息、用戶基本信息和車輛基本信息有著廣泛而高頻的業務需求,很多資料都需要對其進行關聯處理。在這裡快取的目的就是為了提高資料查詢效率。靜態資料通常都儲存在關係型資料庫中,這類資料庫的IO效率普遍不高,應對高併發的查詢往往捉襟見肘。使用快取可以極大的提升讀操作的吞吐量,特別是KV類的快取,沒有複雜的關係操作,時間複雜度一般都在O(1)。註意這裡說的快取指記憶體快取。
當然除了使用快取,還可以通過其它手段來提高IO吞吐量,比如讀寫分離,分庫分表,但是這類面向關係型資料庫的方案更傾向於同時提高讀寫效率,對於單純提升讀吞吐量的需求,這類方案不夠徹底,不能在有限的資源情況下發揮更好的作用。

 

通用快取機制

 

下麵將直接給出一個我認為的通用處理機制,然後會對其進行分析。
對於某個具體的業務,其涉及到六個核心程式:
  • 業務服務:提供對某種業務資料的操作接口,比如車輛服務,提供對車輛基本信息的增刪改查服務。

  • 關係資料庫:使用若干表持久化業務資料,比如SQLServer、MySQL、Oracle等。

  • 持久化佇列:可獨立部署的佇列程式,支持資料持久化,比如RabbitMQ、RocketMQ、Kafka等。

  • 快取處理程式:從佇列接收資料,然後寫入快取。

  • 資料一致處理程式:負責檢查快取資料庫和關係型資料庫中資料是否一致,如果不一致則使用關係資料庫進行更新。

  • 快取資料庫(Redis):支持持久化的快取資料庫,這裡直接選了Redis,這個基本是業界標準了。

以及兩個外部定義:
  • 資料生產者:業務靜態資料的來源,可以理解為前端APP、Web系統的某個功能或者模塊。

  • 資料消費者:需要使用這些業務靜態資料的服務或者系統,比如報警系統需要獲取車輛對應的用戶信息以便發送報警。

下麵以問答的形式來說明為什麼是這樣一種機制。
為什麼需要業務服務?
既然是微服務架構,當然離不開服務了,因為這裡探討的是業務靜態資料,所以是業務服務。不過為了更好的理解,這裡還是簡單說下服務出現的原因。
當今業務往往需要在多個終端進行使用,比如PC、手機、平板等,既有網頁的形式,又有APP的形式,另外某個資料可能在多種不同的業務被需要,如果將資料操作分佈在多個程式中很可能產生資料不一致的情況,另外代碼不可避免的冗餘,讀寫性能更很難控制,變更也基本上是不敢變的。通過一個業務服務可以將對業務資料的操作有序的管理起來,並通過接口的形式對外提供操作能力,代碼不用冗餘了,性能也好優化了,資料不一致也得到了一定的控制,編寫上層應用的人也舒服了。
為什麼不是行程內快取?
很多開發語言都提供了行程內快取的支持,即使沒有提供直接操作快取的包或庫,也可以通過靜態變數的方式來實現。對資料的查詢請求直接在行程記憶體完成,效率可以說是杠杠滴了。但是行程內快取存在兩個問題:
  • 快取資料的大小:行程可以快取資料的大小受限於系統可用記憶體,同時如果機器上部署了多個服務,某個服務使用了太多的記憶體,則可能會影響其它服務的正常訪問,因此不適合大量資料的快取。

  • 快取雪崩:快取同時大量過期或者行程重啟的情況下,可能產生大量的快取穿透,過多的請求打到關係資料庫上,可能導致關係資料庫的崩潰,引發更大的不可用問題。

為什麼是Redis?
Redis這類資料庫可以解決行程內快取的兩個問題:
  • 獨立部署,不影響其它業務,還可以做集群,記憶體擴容比較方便。

  • 支持資料持久化,即使Redis重啟了,快取的資料自身就可以很快恢復。

另外Redis提供了很好的讀寫性能,以及方便的水平擴容能力,還支持多種常用資料結構,使用起來比較方便,可以說是通用快取首選。
為什麼需要佇列?
佇列在這裡的目的是為瞭解耦,坦白的說這個方案中可以沒有佇列,業務服務在關係資料庫操作完成後,直接更新到快取也是可以的。 之所以加上這個佇列是由於當前的業務開發有很明顯的系統拆分的需求,特別是在微服務架構下,為了降低服務之間的耦合,使用佇列是個常用選擇,在某些開發模型中也是很推崇的,比如Actor模型。
舉個例子,比如新註冊一個用戶,需要贈送其300積分,同時還要給其發個註冊成功的郵件,如果將註冊用戶、贈送積分、發成功郵件都寫到一起執行,會產生兩個問題:一是註冊操作耗時增加,二是其中某個處理引發整體不可用的幾率增大,三是程式的擴展性不好;通多引入佇列,將註冊信息分別發到積分佇列和通知佇列,然後由積分模塊和通知模塊分別處理,用戶、積分、通知三個模塊的耦合降低了,相互影響變小了,以後再增加註冊後的其它處理也就是增加個佇列的事,整體的擴展性得到了增強。
佇列作為一種常用的解耦方案,在快取這裡雖然產生的影響不大,但是除了快取難免同時還會有其它業務處理,所以為了統一處理機制,這裡保留了下來。(既然用了,就把它發揚光大。)
為什麼佇列需要持久化?
持久化是為瞭解決網絡抖動或者崩潰導致資料丟失的問題,在資料從業務服務到佇列,佇列自身處理,再從佇列到快取處理程式,中間都可能丟失資料。為瞭解決丟失資料的問題,需要發送時確認、佇列自身持久化、接收時確認;但是需要註意確認機制可能會導致重覆資料的產生,因為在未收到確認時就需要重新發送或接收,而資料實際上可能被正常處理,只是確認丟失了;確認機制還會降低佇列的吞吐量,但是根據我們的定義業務靜態資料的變更頻率應該不高,如果同時還需要較高的併發分片是個不錯的選擇。
這裡持久化佇列推薦選擇RabbitMQ,雖然吞吐量支持的不是很大,但是各方面綜合不錯,併發夠用就好。
為什麼需要資料一致檢查程式?
在業務服務操作完關係資料庫後,資料發送到佇列之前(或者不用佇列就是直接寫入快取之前),業務服務崩潰了,這時候資料就不能更新到快取了。還有一種情況是Redis發生了故障轉移,Master中的更新沒有同步到Slaver。通過引入這麼一個檢查程式,定時的檢查關係資料庫資料和快取資料的差別,如果快取資料比較陳舊,則更新之。這樣提供了一種極端情況下的輓救措施。
這個檢查程式的運行頻率需要綜合考慮資料庫壓力和能夠承受的資料陳舊時間,不能把資料庫查死了,也不能陳舊太久導致大量資料不一致。可以通過設置上次檢查時間點的方式,每次只檢查從上次檢查時間點(或者最近幾次,防止Redis故障轉移資料未同步的問題)到本次檢查時間點發生變更的資料,這樣每次檢查只對增量變更,效率更高。
同時需要理解在分佈式系統中,微服務架構下,資料不一致是經常出現的,必須在一致性和可用性之間做出權衡,儘力去降低影響,比如使用準實時或最終一致性。
只要資料一致檢查程式是不是就夠了?
假設沒有快取處理程式,通過定時同步關係資料庫和快取資料庫是不是就夠了呢?這還是取決於業務,如果是車型庫這種資料,增加一個新的車型,本來之前就沒有,時間上並不是很敏感,這個是可以的。但是對於新增了用戶或者車輛,資料消費者還是希望能夠馬上使用最新的資料進行處理,越快越好,這時使用同步或者準同步更新就能更加貼近需求。
為什麼不用快取過期機制?
使用快取過期機制可以不需要快取處理程式和資料一致檢查程式,業務服務首先從Redis查詢資料,如果資料存在就直接傳回,如果不存在則從關係資料庫查詢,然後寫入Redis,然後再傳回,這也是一種常用的快取處理機制,網上可以查詢到很多,很多人用的也很好。
但是快取的過期時間是個問題:快取多長時間過期,設置的短可以降低資料的陳舊,但是會增加快取穿透的概率,即使採用隨機的快取過期時間,在Redis重啟或者故障轉移的情況下還是會可能導致快取雪崩,雪崩的情況下採用資料預熱機制,也可能會導致服務更長時間的不可用;設置的長可以提升快取的使用率,但是增加了資料陳舊,在上邊對靜態資料的定義中對其準確率和實時性都有較高的要求,業務上能不能接受需要考慮。而且如果運算元據和查詢存在波動的峰谷,是不是要引入動態TTL的機制,以達到快取使用和直接訪問資料庫的一種平衡,這就需要權衡業務需求和技術方案。

 

總結

 

通過上邊的這些問題問答,再來看看上面提出的微服務架構下靜態資料通用快取處理機制。
  • 通過業務服務來包裝對資料的操作,不管是操作關係資料庫還是快取資料庫,資料消費者其實不需要關心,它只關心業務服務能不能提供高併發實時資料的查詢能力。

  • 利用分佈式系統中經常使用佇列進行解耦的方式,業務服務不乾寫入快取的事,增加一個佇列訂閱資料變更,然後從佇列取資料寫入快取資料庫。

  • 對於絕大部分正常的情況,通過佇列更新快取資料和業務服務中更新快取資料,其實時性是差不多的,同時實現了業務操作和寫快取的解耦。

  • 在極端崩潰導致資料不一致的情況下,通過資料一致檢查程式進行補救,儘快更新快取資料。

  • 現在業務服務可以通過訪問Redis快取來提供對靜態資料的高併發準實時查詢能力,快取中不存在的資料就是不存在,沒有快取穿透。

 

對於微服務架構而言,這個機制借助佇列這種通用的解耦方式,獨立了快取更新處理,通過準實時更新和定時檢查,保證了快取的實時性和極端情況下較短時間內達到最終一致,通過快取的持久化機制消除了快取穿透和雪崩,在快取的資料較大或讀取併發較高時支持水平擴容,可以認為對業務靜態資料提供了一種廣泛適用的快取處理機制。
這個方案在某些情況下可能是沒有必要的,比如你要快取一個全國限行的城市串列,使用一個行程內快取就夠了。
最後剩下的就是工作量的問題了,這個會給開發和維護帶來複雜性,佇列有沒有用的順手的,人手是不是夠,業務需求是什麼樣的,需要考慮清楚。
原文鏈接:https://my.oschina.net/u/3971241/blog/2254252

赞(0)

分享創造快樂