背景
前幾個月,使用Vertx重構了公司的一個子系統,該系統負責公司核心數據subscriber的采集、處理、存儲和搜索。這里介紹下重構該系統時的一些關鍵點。
架構
重構前系統部署圖:
重構前系統主要有2部分組成,這2部分都會對Subscriber數據操作:
Java APP: 是個定時任務觸發的APP,每2小時跑一次,每次啟動后做以下的事情:
- 從MongoDB里加載已有的數據到內存。
- 讀取准備好的Data文件,處理文件中的數據,和已加載的數據做合並,這個處理的過程會導致Subscriber數據有添加、刪除和更新操作。
- 處理完Data文件后,將數據持久化到MongoDB和Elasticsearch。
JBoss服務:
- 從Kafka中來的消息數據,批量處理Kafka消息,會添加和更新Subscriber數據到MongoDB和Elasticsearch中。
- 從其他系統觸發的API調用,接收一些API請求,這些請求也會增加、刪除和更新Subscriber數據。在更新的時候,因為查詢條件的多樣性,為了提高查詢Subscriber的速度,對MongoDB的Subscriber Collection建了各種索引。
重構原因
- 對同一種的數據的操作在2個不同的服務中,一種是war一種是jar,不利於維護。代碼在不同的Rep和Project下,維護也不方便。
- Jboss現在是單實例部署,Jboss中還部署有其他的服務,Jboss的穩定性直接影響了該服務。為了提高查詢速度,對數據建立了多種不同的索引,更新數據增加了數據庫的負擔。
- 定時任務JavaAPP每次在處理某個客戶的Subscriber數據時候,都要從MongoDB加載數據到內存,耗時沒有效率。因為是定時任務,對數據更新不夠及時,會影響其他依賴Subscriber數據的服務。
- 數據更新沖突:在定時任務把數據加載到內存,正在處理時,此時如果Jboss也在更新數據會導致數據更新沖突(現在采用了一個很tricky的方式解決)。
重構后系統部署圖
重構后,系統組件介紹和說明:
Adapter服務:
- 數據適配服務,統一接收來自文件、Kafka和API調用的數據,對數據進行預處理,發送相應的業務消息到Event Bus。
- 因為對Adapter的觸發方式只有2種,一種是REST API,另一種是連接Kafka讀取消息,所以Adapter服務可以通過部署多個實例來增加可用性,也順便提高了整體性能。
Mapper服務:
- 數據處理服務,按數據分片進行部署,分片規則可以按客戶大小來分,比如把10個大客戶的數據部署在一個實例上,200個小客戶的數據部署在另一個實例上,也可以按數據量平均分。
- 初始啟動時候加載所屬該實例的所有Subscriber數據到內存,從EventBus上接收來自Adapter的業務請求,處理請求並對數據進行更新。
- 數據更新策略:
- 來自API的請求都會實時的更新到MongoDB和Elasticsearch中。
- 來自文件和Kafka的數據更新量比較大,為了避免每次對數據更新的時候都去操作DB和ES,通過一個隊列緩存所有的更新。觸發隊列持久化條件:一種是當緩存Size達到閾值時候觸發,另一種是定時觸發,觸發后批量更新數據到MongoDB和Elasticsearch中。
- 內存、MongoDB和Elasticsearch中數據一致性:因為現在數據在內存中,使用了Vertx的MongoClient的異步回調機制,保證只有更新到MongoDB成功后才去更新Elasticsearch,保證MongoDB里數據的准確性是第一位的。而ElasticSearch中數據的准確性是通過“定時補償機制”去保證:有其他定時執行的腳本去定時檢查,並決定是否重新對某個客戶的數據重建索引。
重構之后的可改進項:
- 耗內存,所有的Subscriber數據都分片加載到了JVM里。這部分可以把數據放到其他的存儲中,比如redis,但就算是放到redis,也是耗內存。
- 如果系統出錯,會導致數據在MongoDB和ElasticSearch之間不一致,需要其他方式去做“數據一致性補償”。如果資源允許,可以把數據更新同步單獨拿出來實現,使用類似處理“分布式系統數據一致性”的方式來改善這一點。
小結
一些技術關鍵點
1 異步非阻塞
Vertx的異步非阻塞機制有很好的並發性能。網絡IO依賴了Netty,Java NIO的特性。
2 Vert.x-Web
Vertx-web可以很方便的去實現一個web app,很容易實現一些REST APIs。
3 Data access client
Vertx提供了訪問各種存儲的Client,這些client的API都是異步的,可以很方便的去訪問MongoDB,JDBC,Redis等。
4 Event Bus
Vertx的一個核心功能,重構這個子系統時也很依賴這個功能。Event Bus可用於不同Verticle之間的通信,也可以用於Vertx cluster之間的通信。
5 功能解耦
系統中各個子功能可以按不同的Verticle去實現,不同的Verticle可以通過EventBus去通信解耦。Vertx支持動態的加載和卸載Verticle,也就可以實現在運行時動態的加載卸載某些功能。
6 集群模式
Adapter服務和Mapper服務是通過Vertx的Cluster模式組成了集群,集群中節點發現和通信通過Hazelcast管理。使用Vertx實現的服務,可以單實例部署,也可以組成集群提供服務。
參考
- https://vertx.io/docs/vertx-core/java/
- https://vertx.io/docs/vertx-web/java/
- A gentle guide to asynchronous programming with Eclipse Vert.x for Java developers