目錄導讀
- 變更通知在開源SpringBoot/SpringCloud微服務中的最佳實踐
- 1. 什么是變更通知
- 2. 變更通知的場景分析
- 3. 變更通知的技術方案
- 3.1 變更通知的技術實現方案
- 4. 變更通知的最佳實踐總結
- 5. 參考資料
變更通知在開源SpringBoot/SpringCloud微服務中的最佳實踐
1. 什么是變更通知
變更通知
是指數據發生變化后,相對實時地通知到關聯端的技術實現方案;變更通知
是在服務拆分的發展過程中,逐漸衍生出的,解耦關聯服務的一種技術解決方案;- 微服務架構中常說的
配置中心
就是變更通知
的技術集成方案;
2. 變更通知的場景分析
變更通知
首先是數據發生了變化,數據變化后的通知實現,數據變化的交互場景列舉分析如下:
通知類型 | 模塊1 | 模塊1數據變更 | 模塊2 | 模塊2實時變更 | 技術實現方式 | 本文討論范圍 |
---|---|---|---|---|---|---|
消息通知 | 前端1 | ? | 前端2 | ? | 前端總線等 | ? |
消息保存 | 前端1 | ? | 后端2 | ? | Post Ajax請求等 | ? |
消息通知 | 后端1 | ? | 前端2 | ? | WebSocket等推送到前端(推數據),也叫消息推送 | ? |
消息通知 | 后端1 | ? | 前端2 | ? | 前端Http主動輪詢(拉數據) | ? |
變更通知 | 后端1 | ? | 后端2 | ? | Redis/ZooKeeper/Etcd/Nacos/MQ等 | ? |
總結:
變更通知
首先是數據(也可叫消息,下同。)發生了變化,然后引起了關聯方同步變化;數據變化
可以發生在前端
和后端
之間的任意組合之間,包括前端+前端
、前端+后端
、后端+后端
,一般只有后端+后端
的連鎖反應叫變更通知
,這也是本文關注的重點;- 上表串聯了所有常規的交互過程,大家也可以從中理解
消息通知
和變更通知
之間的關聯關系:只是不同交互方式下的數據變化的叫法罷了;- 前端感知后端的數據變化,主要有
前端輪詢
和后端推送
兩種,二者技術實現差異較大,前者技術實現簡單,但是后端資源消耗較大,無法承擔高并發;后者技術實現較復雜,但是點對點通信效率高(長連接)。這不是本文講述的重點,但希望能夠類比分析下各種交互方式下的技術方案,做到一通百通;變更通知
主要關注的是后端數據變化,但是不是所有的后端數據變化都需要變更通知
。一般來說,跨了服務實例,才比較適用變更通知
,單個服務實例內,完全可以通過接口引用等方式自行獲取到變化的數據;- 本文重點關注的是
變更通知
而不是配置中心,像Nacos
、SpringCloud-Config
、Apollo
等配置中心
并不會過多介紹;
- 本文主要關注后端與后端之間的
數據變化
機制及技術實現,其發展階段如下:
變更類型 | 應用發展階段 | 技術方案 | 技術實現 | 說明 |
---|---|---|---|---|
主動獲取 | 單實例 | 共享數據庫 | 查詢數據庫 | 數據庫查詢量不大 |
主動獲取 | 單實例 | 共享內存緩存 | 查詢時先查緩存 | 數據庫查詢量非常大 |
主動獲取 | 多實例 | 分布式緩存 | 使用讀寫性能極高的分布式緩存組件 | 如:Memcached/Redis |
變更通知 | 多實例 | 消息中間件 | Redis/ZooKeeper/Etcd/MQ/binlog+canal等 | 訂閱通知機制 |
總結:
變更通知
是隨著服務的高并發、分布式發展而發展的,在單體架構時,因為都在一個服務內,僅通過數據庫或者共享緩存即可達到數據共享的目的,不一定需要變更通知
;- 在微服務架構中,也可以采用共享緩存方案,而不是必須使用
變更通知
。變更通知
適用于并發高、實時性要求高,且服務解耦的場景;實時
是一個相對概念,在變更通知
語境里,一般是指異步監聽的方式獲取變化的數據(專業術語叫訂閱通知
);
3. 變更通知的技術方案
變更通知
中間件種類較多,基于本人的理解,對比列舉如下:
中間件類型 | 實現特點 | 適用場景 | 不足 | 補充說明 |
---|---|---|---|---|
Redis | 基于key的訂閱/通知 | 并發高、消息量大 | NoSQL可讀性差,持久化不是必選項,存在數據丟失和審計風險 | Redis是極度常用的高效內存組件,建議優選; |
ZooKeeper | 基于分布式臨時Node創建的訂閱/通知 | 可靠性高、實時性高 | 使用的是內存存儲,不適合高并發和大量數據的消息變更場景 | 一般是項目中有ZooKeeper,正好可以用作變更通知組件,而不是因為變更通知訴求而引入ZooKeeper |
Etcd | 基于分布式的Key/Value創建的訂閱/通知 | 可靠性高、實時性高 | 使用的是內存存儲,不適合高并發和大量數據的消息變更場景 | Etcd的實現參考了ZooKeeper,一個是Go語言編寫、一個是Java語言 |
MQ | 異步消息協議 | 可靠性高、并發高、消息量大 | 組件較重 | 包括:Kafka/RocketMQ/RabbitMQ/ActiveMQ等,非常適合電商優惠卷等場景使用 |
binlog+canal | 針對MySQL的數據變更監聽方案 | 直接監聽數據庫表字段變化 | 只適用于MySQL,而MySQL使用量正逐年下降 | 只是監聽了MySQL的數據表變化,一般還需要配合其它的變更通知組件來配合使用,如:ZooKeeper |
配置中心 | 服務端推送 | 把配置中心組件當成業務配置中心 | 組件重、可靠性低、實時性低 | 配置中心 嚴格來說是個變更通知的解決方案,而上面列舉的中間件是純技術組件,二者的維度不太一樣;配置中心 一般和注冊中心 配合使用,因為它本身也需要注冊至服務中心,如:Apollo 、SpringCloud-Config ;有些干脆就被注冊中心 兼任,如Nacos |
總結:
變更通知
一般來說是微服務中的增強功能,不建議因為有變更通知
需求就新增一個組件。如果系統中已經有了Redis,就建議優先選擇Redis,因為其性能高、消息存儲量大。但Redis是NoSQL存儲結構,可讀性較差;Redis也有可能沒有開啟持久化導致數據丟失;Redis也缺失了類似關系數據庫自帶的操作審計,一旦數據出現了異常,將很難知道是誰做了什么;ZooKeeper
/Etcd
則比較適合系統中已經引入該組件了,且變更通知
消息數量較小的場景;MQ
比較適用于可靠性高、消息量巨大的場景,值得單獨引入。如:大型電商的活動卡券配置等場景;binlog+canal
這個特定組合一般不建議單獨使用,一般是canal
把變更數據發送給其它變更通知
Server,然后在業務模塊訂閱變更通知
Server的這個變化數據,并做相應的業務處理。其它Server可以是ZooKeeper/Etcd/Redis等;- 一般來說,
配置中心
包含了單獨的配置規則界面和變更通知
的能力,拆箱即用,效果當然較好。但并不是所有的變更通知
都需要重量級的配置中心
,不是非要在配置中心
去配置變更數據的,就都沒有必要用它;業務需要使用到配置中心
組件時,建議在選定注冊中心后,再來決策選擇其配置中心
;
變更通知
有非常多的實現方式,講講本人實際經歷的業務場景:
場景類型 | 業務訴求 | 微服務架構 | 微服務技術棧 | 方案說明 |
---|---|---|---|---|
場景1 | DB數據變更立即觸發定時任務 | 微服務架構 | spring+mysql+binlog+canal+ZooKeeper | 基于spring自研微服務框架 |
場景2-1 | 業務閾值在1天內生效 | 微服務云原生架構 | SpringBoot+Redis+PostgreSQL | 1.依賴k8s提供服務發現等; 2.依賴redis限流; |
場景2-2 | 業務閾值在1天內生效 | 微服務私部署架構 | SpringBoot | 1.部署至客戶機房,不依賴Redis/DB; 2.使用Nginx做負載均衡,也不需要服務注冊和發現; |
場景3 | 業務閾值在5分鐘內生效 | 微服務云原生架構 | SpringBoot+Redis | 1.依賴k8s提供服務發現等; 2.依賴redis限流; |
場景4 | 業務閾值在立即生效 | SpringCloud微服務架構 | SpringBoot+Redis+Nacos | 1.依賴Nacos提供服務發現等; 2.依賴redis限流; |
- 補充說下上述業務場景的技術選型限定條件:
場景1:DB數據變更立即觸發定時任務
:當時剛剛時興微服務,我所在公司1的部門基于spring自研了一個低代碼平臺,我們需要實時監聽數據的變化,以觸發不同的定時任務,正好平臺中也引入了canal開源組件,用于監聽mysql的binlog
變化,于是就選定了canal方案,這樣就不用定時輪詢數據庫了,也不用和增刪改數據的服務耦合了;場景2:業務閾值在1天內生效
:場景2-1
和場景2-2
其實對應同一個目標:我所在公司2的部門希望設計一套架構、一套代碼,既能滿足云上微服務架構,也能夠支持私部署微服務架構。當時云上選型為云原生微服務架構,完全基于k8s+非侵入式的鏈路追蹤中間件,我們基本上只使用最簡單的SpringBoot+Redis即可;而私部署則繼續沿用了這套架構和代碼,只不過移除了k8s、redis、服務注冊和服務發現,僅使用Nginx做服務負載均衡;場景3:業務閾值在5分鐘內生效
:則是我所在公司2的業務團隊認為一天生效對業務影響較大,需要調整為5分鐘內生效;場景4:業務閾值在立即生效
:則是我所在公司2的另一個新項目,在場景3
代碼架構的基礎上,使用SpringCloud套件替換了k8s,同時業務也更復雜,業務上必須保證實時生效;
- 單獨來說上面的每個業務場景,都可以多種技術實現。本人僅站在過來人的角度,逐一展開分析。
3.1 變更通知的技術實現方案
-
場景1
的實現方案:單獨部署了一個Canal服務,用于監聽binlog變化,在Canal服務中又集成了ZooKeeper客戶端,Canal收到變化的數據后,通過ZooKeeper推送至訂閱的業務微服務; -
場景2
的實現方案:經過分析,私部署不需要變更通知,因為私部署不帶數據庫,業務閾值是配在yaml中,修改后隔離重啟服務即可;云原生微服務則因為數據刷新后1天內生效即可,但為了考慮私部署和云原生架構的統一,所以采用了Guava+持久層的本地緩存方案,Guava緩存的有效期設置為24小時,過期后就會重新從PostgreSQL/yaml中獲取。但由于云原生的運維不接收直接修改數據庫閾值數據,于是又配套開發了一個小的命令行工具(也可以做成Web運維平臺),用于專門更改數據庫表的字段值。從中可以看出:
- 因為要兼顧云原生和私部署場景,所以需要選擇兩種場景下都能使用的本地緩存方案:Guava;
- 因為緩存刷新的時效要求低,不使用
變更通知
也是完全可行的。
-
場景3
的實現方案:場景3
其實是場景2
的延續,只是現在數據生效的時間從1天變成了5分鐘,考慮技術方案的延續性,在云原生方案中,新增了一個定時任務+Redis,用于刷新緩存,交互邏輯如下圖所示:
說明:
- 第1次請求到業務服務時,Guava緩存中也沒有數據,則需要業務服務查詢一次Database并把數據緩存至Guava,流程為藍色①→②箭頭所示;
- 第2次請求到業務服務時,Guava緩存中已經有了數據,則只需要直接返回數據即可,流程為綠色①所示;
- 當用運維工具更新數據時,同時也會清理掉Redis的緩存標記,一旦ScheduleJob獲取的Redis Key(采用redis的setNX語法)不一致時,則會讓Guava緩存失效,流程為紅色的①→②→③→④箭頭所示;
- 后面再有請求過來時,會重新執行上面的第1步和第2步;
總結:
場景3
僅在場景2
的基礎上迭代增加了虛線框框中的2個小功能點代碼就可以了,代碼延續性較好;- ScheduleJob設置為1分鐘就可以滿足5分鐘內生效的業務訴求了;
- 回過頭來看看
場景2
:其實并沒有做變更通知
,只是采用了Guava自帶的緩存過期機制而已;場景3
其實也可以采用Guava自帶的緩存過期機制,但是會導致微服務需要頻繁的穿透緩存去查詢數據庫,得不償失;場景3
的實際做法則兼顧了準實時性和便利性:只有命令行工具變更了數據時,緩存才會刷新。另外場景3
也沒有做到變更通知
,只是變相的達到了準實時
的變更通知
的效果。
-
場景4
的實現方案:場景4
是在復用場景3
的代碼框架的基礎上,要求做到數據變更實時通知。此時項目中雖然已引入了SpringCloud的多個組件,但是業務參數配置都有單獨的配置界面,無須使用配置中心
。考慮到Redis其實也有變更通知的能力,此處正好可以在場景3
的基礎上繼續迭代,去掉ScheduleJob,增加Redis訂閱通知的代碼即可。
4. 變更通知的最佳實踐總結
- 需要搞清楚什么是
變更通知
,什么是配置中心
,不要因為有變更通知
的需求就上配置中心
,這樣有可能把系統搞得非常復雜; - 好的設計都要順勢而為,首先需要了解系統的需求到底是什么,是不是一定要做
變更通知
,如本人列舉的項目實踐中就多次未使用變更通知
組件,但是達到了變更通知
的效果; - 如果只需要做
變更通知
,不需要獨立的配置中心
,建議優選Redis,因為它可以兼顧業務限流、高速緩存、不規則數據的處理(NoSQL)等,很有可能Redis就已經存在于項目中了; - 如果數據量非常龐大,還要支持復雜的規則,比如消息確認和重傳等,則建議采用MQ(Kafka/RocketMQ/RabbitMQ/ActiveMQ);
- 有些場景下的
變更通知
非常適合使用配置中心
,如:SpringCloud-Gateway的路由規則yaml配置,就非常適合放在配置中心(如:Nacos等);當然如果使用的是k8s,則建議直接使用其ConfigMap;
5. 參考資料
- [1]深入淺出阿里數據同步神器:Canal原理+配置+實戰全網最全解析!
- [2]圖文解析 Nacos 配置中心的實現
- [3]apollo 基本原理
- [4]Spring Cloud Config 原理簡介和實現