對SSH框架系統進行微服務改進
本文假定你已經閱讀過之前的文章:
- 為什么要重構到微服務
- 重構中的外部準備工作
- 重構中的內部準備工作
- 使用微服務架構重構支付網關
上一篇文章使用微服務架構重構支付網關是從橫向的角度來分析如何分解服務以及建立微服務之間的關系。這篇文章從縱向詳細介紹如何對SSH框架的支付系統實施具體的技改。這里不涉及具體代碼寫法,重點在于說明方法論。雖然以SSH(Apache Struts + Springframework + Hibernate) 框架為例,也適合各種常用的web架構 (Apache Struts/Spring MVC / Apache Velocity + Springframework + Mybatis/Hibernate) 。
選取入手模塊
如果遺留系統規模龐大,那應該如何挑選入手點?以支付系統為例,如前文所述,支付系統一般包括賬戶,交易,訂單,優惠券,錢包,支付渠道,清結算,支付網關,運營系統等模塊。模塊很多,如何選擇突破點? 我們采取的方法是尋找對外依賴最小的模塊,由此開始調整。 首先模塊依賴關系整理出來。
從上圖可以看出來,賬戶系統在依賴樹中是處于樹根的位置,對它的調整相對容易。只要保持對外接口不變即可。
重構策略
重構如同飛行中更換引擎,必須非常小心,我們采取的策略是:小步快跑,積小勝為大勝。
- 每一個改進點,需在1~3天內完成,不能超過一個周。
- 每次改進,均可直接上線運行,不需要長時間的AB測試。
在功能也就是對外接口不變的前提下,開始進行拆分工作。 在結構上,原SSH系統是一個大項目,所有代碼分層分模塊堆在一起。微服務系統需要將它們拆分。直觀的拆分方法是按層,按模塊同構的拆分成各個獨立運行和維護的系統。由此帶來了一系列的調整。在SSH架構下,重構可以采用自上而下的方法進行,這樣可以確保每一層的重構都有明確的輸入輸出,并且是可測試的。
整體上,重構分為三個步驟:
- 參考原有系統的DAO層和業務邏輯層,實現基礎服務,一般是使用RPC來實現.
- 將對外的接口層進行重構,調整為調用RPC服務。
- 將原有服務切一部分流量到新服務上進行試運行。
- 試運行成功,全部流量都切過來。 舊服務廢棄。
API網關
在SSH架構下,對API網關一般是通過NGINX的rewtite模塊來實現,邏輯簡單,人工維護即可。而一旦接口層按照業務來拆分后,網關路由邏輯復雜多了,通過人工維護配置文件難度激增,需要調整成自動注冊更新路由的方式。也就是每個服務需要將自己提供的服務和API注冊到API網關上, API網關需要自動識別并加載新的路由。
針對這個需求,我們開發了一個connector, 其工作原理如下:
- 服務在啟動完成后,注冊到zookeeper上。
- connector監聽 zookeeper,一旦有變更,則獲取服務列表,更新nginx.conf文件
- connector在更新完成nginx.conf文件后,執行nginx的reload命令,讓配置生效。
- load balancer 將服務打到nginx上,nginx可以按照新配置來執行服務路由。
在服務關閉時,執行類似的操作。第一步是在服務關閉前,將服務從zookeeper上刪除。注意必須在服務關閉前刪除,否則會發生服務不可用的錯誤。 服務注冊項從zookeeper上刪除,并且本地服務沒有流量后,才能關閉服務。connector可以和nginx部署在同一臺機器上。
服務接口調整
使用Spring MVC實現的Controller或者使用Apache Struts實現的Action,需要按照業務進行組合,拆分到具體項目中,原則上,一個項目不應該有超過5個接口,避免接口過于復雜。本次調整需要做的工作包括:
- 增加服務注冊機制,在服務啟動時將服務注冊到zookeeper上;
- 增加服務退出機制,在服務關閉時將服務解除注冊;
- 將服務按照業務組合,建立對應的項目;
- 將服務依賴的業務邏輯層打包到同一個jar中,作為后續改進的基礎。
- 服務上線,替換掉現有的服務。
業務邏輯層調整
在springframework框架下,業務邏輯層被實現為服務層和DAO層之間的橋梁。 對于絕大多數應用來說,業務邏輯層都是非常薄的一層封裝,調整為微服務架構下,業務邏輯層有三種處理方式:
- 抽象為獨立的RPC服務,對于功能比較復雜業務邏輯,可以使用這種方式。
- 下沉到DAO層,如果邏輯上涉及數據訪問操作多,或者需要事務處理的,可以合并到DAO中,一同實現為RPC。
- 上浮到接口層。如果業務邏輯比較簡單,也可以上浮。
DAO層的重構
DAO層的重構的工作量比較大。 需要將原來訪問數據庫的邏輯,調整為遠程RPC調用:
針對DAO的接口,開發RPC服務,將數據訪問邏輯通過RPC來隔離;
提供DAO的RPC接口客戶端,替換原DAO服務接口的實現。
這樣在業務邏輯層和服務層中調用的DAO,調整為RPC調用,將數據訪問邏輯和業務邏輯分離。這樣在DAO層就可以根據業務需要選用合適的存儲數據庫。
性能優化
1.完成上述調整,這才是萬里長征走完的第一步。接下來就是碼農們最心愛的性能優化了。 對于大部分線上應用來說,性能優化主要的工作是選擇合適的存儲介質來滿足性能的需求。
2.數據可以直接寫入MySQL或者其他的持久化存儲。這個庫也會被稱之為主庫。但是如果寫入性能要求高,可以調整為先寫入內存數據庫,再同步到持久化存儲中。
3.線上數據訪問,指根據ID或者其他的某個屬性值來讀取1-2條數據。一般不要直接從MySQL等持久化存儲中出,需要采用couchbase,redis等內存數據庫。
4.線上數據檢索,檢索和訪問需要分開。按照關鍵字、時間等條件的檢索,一般用Elastic來滿足。
線上數據列表,如最新、最熱、推薦等,都需要將數據預先計算好,放在couchbase或者redis等內存數據庫中,讀取的時候直接從庫中出數據,不能執行實時計算。
通過這幾個步驟,就可以優化線上服務的性能,最終呢,瓶頸應該不是在數據庫或者CPU上,而是在帶寬上。 那這就是砸錢的商務行為了。 這樣處理,帶來的最大的問題是空間浪費,是典型的以空間換時間的做法。 技術上的挑戰,那就是數據一致性問題。 對于一致性要求不強的需求,這個做法是沒問題的。 那如何在不同存儲之間同步數據呢?主要的做法有:
5.使用數據庫本身自帶的同步機制。好處是一般不需要開發,問題是數據庫的同步機制,如MysQL、HBase的replication機制,僅支持少數類型的備庫,對數據庫本身也有壓力。
6.使用公共同步工具,如阿里的canal。
7.使用消息中間件來實現數據同步。
采用消息中間件目前主流的做法,適合于對數據實時性要求不高的場景。 如下圖所示,在數據寫入的服務中,完成寫入后,拋出消息。其他數據庫通過接受消息來更新數據。 優點是系統靈活,無論是同DC還是跨DC的情況都可以正常工作。 對數據同步的情況,可以通過MQ提供的監控系統也能夠了解。 缺點是開發工作量大,數據同步實時性不高。
如果時間不夠
上述重構是非常理想的場景了,那如果時間不足,只能部分重構,應該如何處理?一種方式是針對DAO層來改進。 一般來說,重構往往意味著數據結構的變更。另一方面,由于數據寫入的服務相對數據讀取服務要少得多,所以可以采用的策略是:
- 調整DAO服務中數據寫入操作,將數據寫入到新的庫表中;
- 采用MQ來實現新庫表和老庫表的數據同步。 參見上圖。
- 老的DAO服務中數據讀取的操作保持不變。
畢竟大部分的服務并不需要太高的性能需求。 只需要將性能要求高的服務進行重構,實現讀寫分離即可。重構方式如上描述。
總之,技改的難度在于如何梳理出頭緒來。 本文算是拋磚引玉,歡迎大家熱烈討論。
【本文為51CTO專欄作者“鳳凰牌老熊”的原創稿件,轉載請通過微信公眾號“鳳凰牌老熊”聯系作者本人】