OCS(online charging system,線上計費系統)在進行雲化改造的過程中,從實用主義角度出發,微服務架構並不是我們的目標。雖然我們也對系統進行了容器化改造(Docker),並根據業務進程的功能將系統分成了好幾類的容器,但這一切多是出於對系統中的某些處理節點進行動態擴縮容的需要,跟微... ...
C++分散式實時應用框架——微服務架構的演進
版權聲明:本文版權及所用技術歸屬smartguys團隊所有,對於抄襲,非經同意轉載等行為保留法律追究的權利!
OCS(online charging system,線上計費系統)在進行雲化改造的過程中,從實用主義角度出發,微服務架構並不是我們的目標。雖然我們也對系統進行了容器化改造(Docker),並根據業務進程的功能將系統分成了好幾類的容器,但這一切多是出於對系統中的某些處理節點進行動態擴縮容的需要,跟微服務半點關係沒有。隨著系統改造 的深入,系統的通訊關係複雜程度開始超過我們之前的估計。如果說數量眾多的功能節點還有人可以勉強掌握,這些節點間錯綜複雜的通訊關係連線已超過程式員可以駕馭的範疇。在討論如何簡化程式員實現整個系統各類節點的通訊關係的配置過程中,節點微服務化的理念漸漸進入我們的腦海之中……
下麵先給大家介紹下我們所面臨的困境,下麵的圖是我們系統一部分節點的通訊關係總圖(註意,只是其中一部分):
還記得第二篇《基於ZeroMQ的實時通訊平臺》中那個我們引以為傲的通訊配置文件嗎,就是程式中所有的通訊連接關係不再是寫死在代碼中,而是通過AppInit.json配置文件進行配置,程式啟動的時候再由CDRAF進行實時載入。當初酷炫的功能,現在卻成我們的惡夢。此時AppInit.json這個文件已到達1700多行,你沒看錯,一個配置文件1700多行,並且還不是全部,還會繼續變大。
"OLC" : { "AUTO_START" : "YES", "ENDPOINTS" : [ { // 用於與SmartMonitor建立心跳 "name" : "MonitorSUB", "zmq_socket_action" : "CONNECT", // ZMQ的連接模式 "zmq_socket_type" : "ZMQ_SUB" // ZMQ的通訊模式 }, { // 下發消息給OCDis,這邊存在轉發功能,支持業務實現按條件轉發 "downstream" : [ "OCDis2OLC"], "name" : "NE2OLC", // 根據這個名字在業務代碼中實現轉發 "zmq_socket_action" : "BIND", "zmq_socket_type" : "ZMQ_STREAM" }, { // OLC到OCDis的鏈路 "name" : "OCDis2OLC", "statistics_on" : true, "zmq_socket_action" : "CONNECT", "zmq_socket_type" : "ZMQ_DEALER" }, { // OCDis回OLC的鏈路,之所以來去分開,主要用於實現優雅啟停功能(啟停節點保證不丟消息) "name" : "OCDis2OLC_Backway", "statistics_on" : true, "zmq_socket_action" : "CONNECT", "zmq_socket_type" : "ZMQ_DEALER", "backway_pair" : "OCDis2OLC" }, { // 用於與SmartMonitor的命令消息鏈路 "name" : "OLC2Monitor", "zmq_socket_action" : "CONNECT", "zmq_socket_type" : "ZMQ_DEALER" }, ], "ENDPOINT_TO_MONITOR" : "OLC2Monitor", "INSTANCE_GROUP" : [ { "instance_endpoints_address" : [ { "endpoint_name" : "NE2OLC", "zmq_socket_address" : "tcp://*:6701" }, { "endpoint_name" : "OCDis2OLC", "zmq_socket_address" : [ "tcp://127.0.0.1:7201" // 跨機的IP地址與埠,配合狀態中心可實現自動管理,無需人工參與配置 ] }, { "endpoint_name" : "OCDis2OLC_Backway", "zmq_socket_address" : [ "tcp://127.0.0.1:7202" ] }, { "endpoint_name" : "OLC2Monitor", "zmq_socket_address" : "ipc://Monitor2Business_IPC" }, { "endpoint_name" : "MonitorSUB", "zmq_socket_address" : "ipc://MonitorPUB" } ], "instance_group_name" : "1" } ] },
一個業務程式員如果要調整系統中某個程式的通訊連接,一定得盯著上面那副圖研究半天,並且要搞明白“CONNECT”、“BIND"、”ZMQ_ROUTER"、“ZMQ_DEALER"等等這些zeromq專業辭彙的含義,才可能進行準確配置,我們隱隱感到這已是一個mission impossible。如何簡化這個配置文件,如何對系統的複雜度進行分層,讓不同層級的人員僅僅只需關註自身層級情況,再通過我們的CDRAF最終將這些散落的配置、代碼組成一個完成可運行的系統才是我們現在亟需解決的問題。相信這也是每個系統架構師所面臨的問題,當一個系統的複雜度超過單個人可承受能力範圍,就要對這個系統進行適當分層,分模塊。讓每個人去管理一小部分複雜點,並且大家只需實現好自己的模塊,無需去關心別的模塊的實現細節。通過事先設計好的介面,各個模塊可以相互協作,整體系統是可以依此完美地運行的。這裡CDARF正是起這麼一個不同模塊的橋梁(介面)的作用。
一、節點間通訊模式的統一
原來節點內的應用程式都是通訊全能應用程式,所謂全能是指應用程式既可以跟節點內的進程進行通訊也可以跟節點外的任意進程進行通訊。這樣乍看起來沒啥問題,但一旦節點數和進程數變多後,通訊關係將是一個指數級增長的過程。如下圖,如果再增加一個CDR節點,或者OCS節點,連接數都將增加非常多。
我們的解決辦法是統一節點的通訊模式,每個節點內都有一個Dis進程,統一對外負責跟其他節點進行通訊。在收到外部發給節點的消息後,根據功能和負載轉發給內部業務處理進程。業務進程如果有消息需要發往別的節點,就直接發給Dis進程,由它進行轉發。統一通訊模式帶來的好處除了在節點和進程增多後,通訊關係不會變得太複雜以外。由於模式統一, CDARF可以替業務程式員完成很多工作,直接的好處就是業務程式員不再需要配置很多與業務無關的配置。最大化的將通訊模塊的複雜度留給CDRAF去處理,業務程式員將更加專註於自身的業務邏輯。下麵的圖中其實系統開始已經有微服務的樣子,但我們希望做到的不僅是從系統架構上是微服務架構,在程式員開發程式的時候,也應該是帶著微服務思維的,我們的CDRAF應該提供這麼一種能力來支持這種開發模式。
二、配置文件的簡化
通訊模式統一後,我們對通訊配置文件進行了一次較大的簡化,從原來1700行減少到了200行左右。這當中省去了很多冗餘的配置項,通訊配置文件不再是對系統通訊簡單直接的對應,而更多的是對節點通訊能力的一種表述。
應用程式分為Dis和非Dis兩類,Dis類程式主要承擔節點間的通訊和節點內的消息轉發,非Dis類程式就是普通的業務處理進程。從下麵的文件中可以看到“OCDis”進程中分為“InterContainerEndpoints”和“InnerContainerEndpoints”兩大類,分別表示節點間的通訊和節點內的通訊。對於節點間的通訊,每個服務埠只要寫上相應的“服務名字”就可以以了,配置中的“OCDisCDRDis”表示OCSDis與CDRDis的通訊,“OLCDisOLCProxy”、“OCDis_SyDis_SNR”也是類似。當業務側程式需要對外提供一個服務(或者說與外部進行通訊),只需要寫一個服務名字,而如:埠、機器的IP地址、服務端還是客戶端、通訊模式等等都完全不需要去關心,這是多大一種便利。配置中的註釋部分是不需要業務程式員去填的,而是由CDRAF的狀態中心,根據集群節點的實時情況自動生成,併進行連接和維護。
{ "OCDis": { "MaxInstanceGroupNum": 3, "InterContainerEndpoints": { "OCDisCDRDis": { //"Port": [6001, 6002, 6003], //"Cluster": ["10.45.4.10:6001", "10.45.4.10:6001"] }, "OCDisOLCProxy": { //"Port": [6101, 6102, 6103], "DownStreams": ["OCDis2IN", "OCDis2PS", "OCDis2SMS", "OCDis2ISMP", "OCDis2IMS"], "router": true }, "OCDis_SyDis_SNR": { //"Peer": "ZSmartSyDis.OCDis_SyDis_SNR" } }, "InnerContainerEndpoints": { "OCPro_OCDis_CDR": { "DownStreams": ["OCDisCDRDis"] }, "OCPro_OCDis_SNR": { "DownStreams": ["OCDis_SyDis_SNR"] }, } }, "OCPro": { "Groups": ["IN", "PS", "SMS", "IMS", "ISMP"], "InnerContainerEndpoints": { "OCPro2OCDis": { "PeerMap": [ "OCDis.OCDis2IN", "OCDis.OCDis2PS", "OCDis.OCDis2SMS", "OCDis.OCDis2ISMP", "OCDis.OCDis2IMS" ] }, "OCPro_OCDis_SNR": {"Peer": "OCDis.OCPro_OCDis_SNR"}, "OCPro_OCDis_CDR": {"Peer": "OCDis"} } }, "CDRDis": { "InterContainerEndpoints": { "OCDisCDRDis" : { "DownStreams": ["CDRDisCDR"], //"Peer": "OCDis" } } }, "CDR": { "InnerContainerEndpoints": { "CDRDisCDR" : {"Peer": "CDRDis"} } } }
想像一下,對於每一個業務節點,開發人員僅需考慮節點內的業務實現邏輯,併為本節點對外所提供的服務起個名字,而不再需要關心這個服務到底是提供給誰,更不用操心誰會來連我的進程,怎麼連。這是多麼精妙的事情!我們不僅是從架構上做到了微服務架構,程式員在開發業務程式的時候,不需要去關心除了自身模塊以外的其它複雜信息,從此可以輕裝上陣,而不再需要負重前行。這應該就是CDRAF對微服務架構提供的最直接、最好的支持了,幫助業務程式員從傳統的開發模式轉變,進而適應微服務的思維方式。
三、節點間的通訊關係配置
上面我們提到配置文件只定義了節點的服務名,那麼這麼多的微服務節點是如何組合起來工作的?一個業務應用系統會由許多的微服務一起協同提供服務,這些服務對於每個不同的現場可能功能是不一樣的,或者說微服務集合是不一樣的。那麼,對這些微服務的組合的過程就像一個“編排”的過程。通過“編排”,選擇合適的微服務進行搭配組合提供服務,而編排的過程就是我們通訊建立的過程。下麵我們就來看一下CDRAF是如何做到“編排”功能的。
上面的第一張表,描述了所有的微服務列表,所有節點服務要向外通訊都必須到這張表中增加相應的服務名,這裡的服務名是與前面配置文件中的服務名相對應的。第二張表描述了這些微服務名之間的通訊關係,比如第二條記錄表達的是OCDis程式的OCDis2CDRDis到CDRDis的OCDis2CDRDis之間會有一個通訊關係。只要通過這個簡單的配置,就可以完成兩個節點間的通訊關係的建立。這樣的設計會帶來幾個好處。
1、對於一個複雜的系統,可能有幾十類微服務節點,運行實例可能有上百個,如果有上面的表二,就可以容器的從上面的數據中畫出整個集群的實時拓撲圖,這個對於系統的監控是十分重要的。
2、集群通訊關係的設計上升了一個等級,業務程式員只需要根據模塊介面設計提供相應的微服務節點,而不需要關心與其它微服務是如何協調工作的。而這些微服務如何“編排”提升到了架構師的工作範圍的層級。這顯然是對複雜度進行分層隔離很好的一個範例。
3、運維或者管理人員,通過表二的配置可以很容易地操作集群里的某個微服務下線或者上線。在一個龐大的集群裡面,如果某類微服務出故障,而CDARF提供了這麼一種手段可以去讓這類故障微服務下線,將給系統的穩定性帶來極大的可靠保證。
4.、原來集群所有的通訊都配置在一個文件中,在分散式系統中就涉及文件的全局一致性的問題。解決的方案可能是,如果要上線一個新類型的配置文件(新增節點、刪除節點、通訊關係改變等等),就要去更新所有在網節點的配置文件。但此時如果新的配置文件有bug,那麼可能導致整個集群的故障,並且為了升級某個功能去升級整個集群所有節點的配置也是極不合理的。在新的方案中,節點的配置只定義節點內的通訊和對外提供的微服務名。那麼如果要新增某種類型的微服務,不再需要去更新其它節點的配置,只需要將新節點上線,然後在上面的表一新增微服務名,表二增加連接關係就可以了。真正做到了增量升級!
未完待續……