在分佈式系統中,特別是最近很火的微服務架構下,有沒有或者能不能總結出一個業務靜態數據的通用緩存處理機制或方案,這篇文章將結合一些實際的研發經驗,嘗試理清其中存在的關鍵問題以及探尋通用的解決之道。
什麼是靜態數據前端
這裏靜態數據是指不常常發生變化或者變化頻率比較低的數據,好比車型庫、用戶基本信息、車輛基本信息等,車型庫這種可能每月會更新一次,用戶和車輛基本信息的變化來源於用戶註冊、修改,這個操做的頻率相對也是比較低的。數據庫
另外這類數據的另外一個特色是要求準確率和實時性都比較高,不能出現丟失、錯誤,以及過長時間的陳舊讀。緩存
具體是否是應該歸類爲靜態數據要看具體的業務,以及對變化頻率高低的劃分標準。在這裏的業務定義中,上邊這幾類數據都歸爲靜態數據。性能優化
爲何須要緩存網絡
在面向用戶或車聯網的業務場景中,車型信息、用戶基本信息和車輛基本信息有着普遍而高頻的業務需求,不少數據都須要對其進行關聯處理。在這裏緩存的目的就是爲了提升數據查詢效率。靜態數據一般都保存在關係型數據庫中,這類數據庫的IO效率廣泛不高,應對高併發的查詢每每捉襟見肘。使用緩存能夠極大的提高讀操做的吞吐量,特別是KV類的緩存,沒有複雜的關係操做,時間複雜度通常都在O(1)。注意這裏說的緩存指內存緩存。數據結構
固然除了使用緩存,還能夠經過其它手段來提升IO吞吐量,好比讀寫分離,分庫分表,可是這類面向關係型數據庫的方案更傾向於同時提升讀寫效率,對於單純提高讀吞吐量的需求,這類方案不夠完全,不能在有限的資源狀況下發揮更好的做用。架構
通用緩存機制併發
下面將直接給出一個我認爲的通用處理機制,而後會對其進行分析。分佈式
對於某個具體的業務,其涉及到六個核心程序:微服務
業務服務:提供對某種業務數據的操做接口,好比車輛服務,提供對車輛基本信息的增刪改查服務。關係數據庫:使用若干表持久化業務數據,好比SQLServer、MySQL、Oracle等。
持久化隊列:可獨立部署的隊列程序,支持數據持久化,好比RabbitMQ、RocketMQ、Kafka等。
緩存處理程序:從隊列接收數據,而後寫入緩存。
數據一致處理程序:負責檢查緩存數據庫和關係型數據庫中數據是否一致,若是不一致則使用關係數據庫進行更新。
緩存數據庫(Redis):支持持久化的緩存數據庫,這裏直接選了Redis,這個基本是業界標準了。
Java架構交流學習圈:874811168 面向1-3年經驗 Java開發人員 幫助突破瓶頸 提高思惟能力
以及兩個外部定義:
數據生產者:業務靜態數據的來源,能夠理解爲前端APP、Web系統的某個功能或者模塊。
數據消費者:須要使用這些業務靜態數據的服務或者系統,好比報警系統須要獲取車輛對應的用戶信息以便發送報警。
下面以問答的形式來講明爲何是這樣一種機制。
爲何須要業務服務?
既然是微服務架構,固然離不開服務了,由於這裏探討的是業務靜態數據,因此是業務服務。不過爲了更好的理解,這裏仍是簡單說下服務出現的緣由。
當今業務每每須要在多個終端進行使用,好比PC、手機、平板等,既有網頁的形式,又有APP的形式,另外某個數據可能在多種不一樣的業務被須要,若是將數據操做分佈在多個程序中極可能產生數據不一致的狀況,另外代碼不可避免的冗餘,讀寫性能更很難控制,變動也基本上是不敢變的。經過一個業務服務能夠將對業務數據的操做有序的管理起來,並經過接口的形式對外提供操做能力,代碼不用冗餘了,性能也好優化了,數據不一致也獲得了必定的控制,編寫上層應用的人也舒服了。
爲何不是進程內緩存?
不少開發語言都提供了進程內緩存的支持,即便沒有提供直接操做緩存的包或庫,也能夠經過靜態變量的方式來實現。對數據的查詢請求直接在進程內存完成,效率能夠說是槓槓滴了。可是進程內緩存存在兩個問題:
緩存數據的大小:進程能夠緩存數據的大小受限於系統可用內存,同時若是機器上部署了多個服務,某個服務使用了太多的內存,則可能會影響其它服務的正常訪問,所以不適合大量數據的緩存。
緩存雪崩:緩存同時大量過時或者進程重啓的狀況下,可能產生大量的緩存穿透,過多的請求打到關係數據庫上,可能致使關係數據庫的崩潰,引起更大的不可用問題。
爲何是Redis?
Redis這類數據庫能夠解決進程內緩存的兩個問題:
獨立部署,不影響其它業務,還能夠作集羣,內存擴容比較方便。支持數據持久化,即便Redis重啓了,緩存的數據自身就能夠很快恢復。
另外Redis提供了很好的讀寫性能,以及方便的水平擴容能力,還支持多種經常使用數據結構,使用起來比較方便,能夠說是通用緩存首選。
爲何須要隊列?
隊列在這裏的目的是爲了解耦,坦白的說這個方案中能夠沒有隊列,業務服務在關係數據庫操做完成後,直接更新到緩存也是能夠的。 之因此加上這個隊列是因爲當前的業務開發有很明顯的系統拆分的需求,特別是在微服務架構下,爲了下降服務之間的耦合,使用隊列是個經常使用選擇,在某些開發模型中也是很推崇的,好比Actor模型。
舉個例子,好比新註冊一個用戶,須要贈送其300積分,同時還要給其發個註冊成功的郵件,若是將註冊用戶、贈送積分、發成功郵件都寫到一塊兒執行,會產生兩個問題:一是註冊操做耗時增長,二是其中某個處理引起總體不可用的概率增大,三是程序的擴展性很差;通多引入隊列,將註冊信息分別發到積分隊列和通知隊列,而後由積分模塊和通知模塊分別處理,用戶、積分、通知三個模塊的耦合下降了,相互影響變小了,之後再增長註冊後的其它處理也就是增長個隊列的事,總體的擴展性獲得了加強。
隊列做爲一種經常使用的解耦方案,在緩存這裏雖然產生的影響不大,可是除了緩存不免同時還會有其它業務處理,因此爲了統一處理機制,這裏保留了下來。(既然用了,就把它發揚光大。)
爲何隊列須要持久化?
持久化是爲了解決網絡抖動或者崩潰致使數據丟失的問題,在數據從業務服務到隊列,隊列自身處理,再從隊列到緩存處理程序,中間均可能丟失數據。爲了解決丟失數據的問題,須要發送時確認、隊列自身持久化、接收時確認;可是須要注意確認機制可能會致使重複數據的產生,由於在未收到確認時就須要從新發送或接收,而數據實際上可能被正常處理,只是確認丟失了;確認機制還會下降隊列的吞吐量,可是根據咱們的定義業務靜態數據的變動頻率應該不高,若是同時還須要較高的併發分片是個不錯的選擇。
這裏持久化隊列推薦選擇RabbitMQ,雖然吞吐量支持的不是很大,可是各方面綜合不錯,併發夠用就好。
爲何須要數據一致檢查程序?
在業務服務操做完關係數據庫後,數據發送到隊列以前(或者不用隊列就是直接寫入緩存以前),業務服務崩潰了,這時候數據就不能更新到緩存了。還有一種狀況是Redis發生了故障轉移,master中的更新沒有同步到slaver。經過引入這麼一個檢查程序,定時的檢查關係數據庫數據和緩存數據的差異,若是緩存數據比較陳舊,則更新之。這樣提供了一種極端狀況下的挽救措施。
這個檢查程序的運行頻率須要綜合考慮數據庫壓力和可以承受的數據陳舊時間,不能把數據庫查死了,也不能陳舊過久致使大量數據不一致。能夠經過設置上次檢查時間點的方式,每次只檢查從上次檢查時間點(或者最近幾回,防止Redis故障轉移數據未同步的問題)到本次檢查時間點發生變動的數據,這樣每次檢查只對增量變動,效率更高。
同時須要理解在分佈式系統中,微服務架構下,數據不一致是常常出現的,必須在一致性和可用性之間作出權衡,盡力去下降影響,好比使用準實時或最終一致性。
只要數據一致檢查程序是否是就夠了?
假設沒有緩存處理程序,經過定時同步關係數據庫和緩存數據庫是否是就夠了呢?這仍是取決於業務,若是是車型庫這種數據,增長一個新的車型,原本以前就沒有,時間上並非很敏感,這個是能夠的。可是對於新增了用戶或者車輛,數據消費者仍是但願可以立刻使用最新的數據進行處理,越快越好,這時使用同步或者準同步更新就能更加貼近需求。Java架構交流學習圈:956058372
爲何不用緩存過時機制?
使用緩存過時機制能夠不須要緩存處理程序和數據一致檢查程序,業務服務首先從Redis查詢數據,若是數據存在就直接返回,若是不存在則從關係數據庫查詢,而後寫入Redis,而後再返回,這也是一種經常使用的緩存處理機制,網上能夠查詢到不少,不少人用的也很好。
可是緩存的過時時間是個問題:緩存多長時間過時,設置的短能夠下降數據的陳舊,可是會增長緩存穿透的機率,即便採用隨機的緩存過時時間,在Redis重啓或者故障轉移的狀況下仍是會可能致使緩存雪崩,雪崩的狀況下采用數據預熱機制,也可能會致使服務更長時間的不可用;設置的長能夠提高緩存的使用率,可是增長了數據陳舊,在上邊對靜態數據的定義中對其準確率和實時性都有較高的要求,業務上能不能接受須要考慮。並且若是操做數據和查詢存在波動的峯谷,是否是要引入動態TTL的機制,以達到緩存使用和直接訪問數據庫的一種平衡,這就須要權衡業務需求和技術方案。
總結
經過上邊的這些問題問答,再來看看上面提出的微服務架構下靜態數據通用緩存處理機制。
經過業務服務來包裝對數據的操做,無論是操做關係數據庫仍是緩存數據庫,數據消費者其實不須要關心,它只關心業務服務能不能提供高併發實時數據的查詢能力。
利用分佈式系統中常用隊列進行解耦的方式,業務服務不幹寫入緩存的事,增長一個隊列訂閱數據變動,而後從隊列取數據寫入緩存數據庫。
對於絕大部分正常的狀況,經過隊列更新緩存數據和業務服務中更新緩存數據,其實時性是差很少的,同時實現了業務操做和寫緩存的解耦。
在極端崩潰致使數據不一致的狀況下,經過數據一致檢查程序進行補救,儘快更新緩存數據。
如今業務服務能夠經過訪問Redis緩存來提供對靜態數據的高併發準實時查詢能力,緩存中不存在的數據就是不存在,沒有緩存穿透。
對於微服務架構而言,這個機制藉助隊列這種通用的解耦方式,獨立了緩存更新處理,經過準實時更新和定時檢查,保證了緩存的實時性和極端狀況下較短期內達到最終一致,經過緩存的持久化機制消除了緩存穿透和雪崩,在緩存的數據較大或讀取併發較高時支持水平擴容,能夠認爲對業務靜態數據提供了一種普遍適用的緩存處理機制。
這個方案在某些狀況下多是沒有必要的,好比你要緩存一個全國限行的城市列表,使用一個進程內緩存就夠了。
最後剩下的就是工做量的問題了,這個會給開發和維護帶來複雜性,隊列有沒有用的順手的,人手是否是夠,業務需求是什麼樣的,須要考慮清楚。
在這裏順便給你們推薦一個架構交流羣:956058372,裏面會分享一些資深架構師錄製的視頻錄像:有Spring,MyBatis,Netty源碼分析,高併發、高性能、分佈式、微服務架構的原理,JVM性能優化這些成爲架構師必備的知識體系。