1. 程式人生 > >程式解耦的必要性

程式解耦的必要性

https://mp.weixin.qq.com/s/6pCJFrNOk4HJYlS5qu4K4A?

架構設計中,大家都不喜歡耦合,但有哪些典型的耦合是我們系統架構設計中經常出現的,又該如何優化?這裡列舉了6個點:IP、jar包、資料庫、服務、訊息、擴容。這些點,如果設計不慎,都會導致系統一些耦合,這些點基本都是大家實際遇到的痛點,今天我將跟大家分享如何用常見的方案去解除這些耦合。

 

 

 

一、如何找到系統中的耦合

 

什麼是耦合?就是每每我們作為技術人,在心中罵上下游、罵兄弟部門,說這個東西跟我有什麼關係?為什麼我要配合來做這個事情?這裡面就非常有可能是系統中存在耦合的地方。

 

明明我們不應該聯動,但兄弟部門要做一個事情,上下游要做一個事情,我卻要被動地配合來做這個事情。還有可能這個配合的範圍特別特別的大,那就說明耦合非常非常的重。下面來看具體的五六個案例。

 

二、典型耦合與對應解耦實踐

 

1、IP耦合

 

第一個案例,特別常見。原來線上有服務或者有條資料庫,因為各種原因,例如磁碟硬體有故障,要換一臺機器,然後運維給了我們一臺機器,我們把資料庫或者把服務給部署好了。部署好了IP要換,原來有個舊IP,現在有個新IP。那就有很多上游依賴我,我IP換了怎麼辦?就找到上游說我的IP換了,麻煩上游部門改配置重啟一下,連到我新的IP上去。

 

不知道大家工作中會不會遇到這樣的場景,這時如果你作為上游的呼叫方,不管你調資料庫還是調服務,你心裡可能就在罵他了,明明是你IP變了,為什麼配合重啟、配合改配置的人是我?特別是如果一個基礎服務或者一個基礎資料庫,依賴它的人很多,那麼你可能要找到這些依賴它的人,可能有A部門、B部門、C部門,所有業務都依賴你,你要全部找一遍,全部重啟。所以這個因為IP配置使得上下游耦合在一起的案例,它的耦合範圍其實是非常廣的,我們都覺得很討厭。

 

我們的希望是:你改一個IP,能不能我不動,你自己升級了,我流量就默默遷移過去,這是一個非常直觀的理解上下游的耦合。

 

 

內網IP修改為內網域名,這是我們的實踐,強烈的建議大家回去馬上幹這個事情。為什麼我們IP要修改、要重啟?很有可能是我們將IP寫在了自己的配置檔案中。如果我們把這個內網IP變為內網域名,那麼我們是不是就可以不讓上游配合去改配置重啟呢?

 

假設我們現在不用IP了,用域名了。現在換了一臺機器域名沒變,IP指向變了。我們可以讓運維統一將內網DNS切到新的機器上面去,並將舊機器的連線切斷,重連後就會自動連到新機器上去了。這樣的話只要運維配合就可以完成遷移,對於所有上游的呼叫方、服務的呼叫方、資料的呼叫方都不需要動,這是第一個案例。

 

 

我們的最佳實踐是強烈建議使用內網域名來替換內網的IP,連服務、連資料庫統統取走。

 

2、公共庫耦合

 

第二個案例是公共庫,這個公共庫可能是一個跟業務相關的通用業務庫,比如使用者的業務、支付的業務,這些業務寫在了一個jar包裡,各個業務線通過這個jar包來實現相關的一些業務邏輯。所有的業務方因為這個公共庫耦合了,不管你是so、dll,還是jar包程式碼,不同語言的公共庫方式不一樣,本質是上游通過這個公共庫耦合在一起。

 

 

我們曾經碰到什麼樣的情況呢?58有招聘、房產、二手很多業務線,使用者的一些操作,登入、查詢資訊、修改資訊可能都是相通的,所以我們有一個user.jar,對所有使用者的操作可能通過這個jar包去做。然後有個業務線,比如說招聘,他可能修改了使用者的操作的一些程式碼,修改了這個jar包。修改之後,上線之前會進行測試,但招聘只會測試自己的業務,不會測試兄弟業務線的業務,導致上線的結果是,上線後兄弟業務線全掛了,於是就出現了一個很有意思的場景, A和B的業務老大在群裡面說怎麼業務都掛了,然後有研發兄跳出拉解釋說C部門上線了,所以我們都掛了,這個解釋是很難說通的。

 

為什麼兄弟部門好好的,他上線了他沒問題,而我們掛了,就是因為jar包耦合在一起,可能我們也在心裡會默默地罵他們,修改程式碼的是你,沒問題的也是你,有問題的是我,我其實什麼都沒動,我很委屈。多個上游因為jar包耦合在了一起,那有什麼樣的優化方法?

 

  • 如果程式碼庫個性很強

 

 

如果這個jar包、這個公共庫的個性比較強,如果時候偏招聘的、房產的、二手的,我們的建議是把這些個性的程式碼拆分到各個業務線自己的jar包裡面去,這樣的話,你修改的那一塊隻影響你自己,至少不會擴大影響範圍,這個需要對業務進行剖析,把個性的地方拿出來。如果長時間解決不了,我剛剛說的那種耦合如果頻發,出現的次數特別多,最差的情況下我們可以copy程式碼,比如說拷三份,但這個不推薦。

 

我們的建議還是抽取其中的個性部分,把原來的一個business的jar包變成三個加包,每一塊只跟一塊業務相關。

 

  • 如果公共庫通用性很強

 

 

那如果這個庫的共性比較強,我們建議通用的部分下沉獨立一個service,這個service對上游提供介面,我每次測試你也要測試介面的相容性;如果是新的業務,我們建議新增介面,這樣至少不會對舊有的程式碼產生影響,通過service或RPC呼叫的方式來解除耦合。

 

3、資料庫耦合

 

 

第三個案例應該也是大家會遇到比較多的情況,資料庫的耦合。我先說一下業務場景:業務A、業務B、業務C,這裡還是舉例使用者的業務,有些使用者的資料是通用的,存在table-user裡,而個性的資料我們存在個性的資料庫裡,比如業務A我們可能有個table-A、業務B有table-B、業務C有table-C。

 

假設我的業務線既要取個性的資料,又要取共性的資料,我們的程式碼往往這麼寫,個性表join個性表,UID相同,UID等於我的使用者1、2、3,個性的資料和共性的資料一起抽取出來,沒有任何問題。業務線B、業務線C也是這麼做的。所以你會發現join語句其實導致了user的table和業務線A、B、C的table耦合到一個數據庫例項裡。

 

會導致什麼問題呢?比如A業務線要上線一個功能,這個功能沒有索引,全表都要掃描,資料庫CPU100%,資料庫例項IO效能下降,影響業務。B和C都影響。即某個業務線的資料庫效能急劇下降導致所有業務都受影響。這時DBA兄弟、運維兄弟殺過來說效能不行了,我再給你兩臺機器,給我兩個例項,你會發現沒用,所有表都耦合在一個例項裡,給機器也拆不開,擴不了容。

 

 

2015年我調去58到家時,當時整個58到家有一個庫叫做58到家庫,裡面有幾百個表,效能越來越低,但因為各種join又必須耦合在一個例項裡,很悲慘。我們怎麼做呢?垂直切分與服務化。你會發現跟jar包解耦非常相似,垂直拆分。

 

比如說使用者的基礎資料,我抽向一個使用者的服務,user最基礎的資料庫只能夠被這個服務鎖訪問,資料庫私有是服務化的一個特點。

 

此時業務線原來的業務怎麼樣滿足?原來是業務方直接一個join既取了共有的資料又取了私有的資料,此時原來的一次資料庫訪問變成了兩次資料庫訪問,第一次取個性資料,第二次取共性資料,然後業務層拼裝。

 

之前的方式和之後的方式相比,之前的方式其實業務程式碼可能會更簡單一些,因為它是將這個業務邏輯放在了SQL語句中,但是導致資料庫耦合在了一起。後面這種方式就是業務的程式碼會更復雜,會變成多次訪問,將原來在SQL中進行的邏輯計算變成我們自己的程式碼的邏輯計算。此時業務有自己的庫,公共有公共的庫,你會發現很有可能這些庫早期也在一個例項,但是效能下降時可以很容易地新增例項,把其中一個公共的庫從一個例項裡放到另外一個例項,甚至新增一臺機器做到硬體的擴容。所以垂直切分是指業務側自己的資料庫放到自己的上去,公共的放到公共的上去,不要耦合在一個例項當中,這是一個比較典型的業務場景。

 

4、服務化耦合

 

 

第四個案例是服務化耦合的例子。服務化之後,如果業務程式碼拆分得不乾淨,即使你做了服務化也不能夠解除耦合。這裡舉一個服務化解耦不徹底的案例。

 

上面是ABC三個業務方,底下是一個通用的服務。假如你解耦不徹底,如果你這個通用的服務裡有業務側的程式碼,最典型的業務側的程式碼是什麼樣的?服務層switch case,根據呼叫方的型別走不通的業務邏輯程式碼。我們做服務化其實是想把共性的部分抽象下沉,是共性的部分會做的服務,但如果解耦不徹底,就會有傳入不同biz-type執行不同邏輯這樣的程式碼。

 

這會出現什麼問題呢?如果新增業務需求,你會發現很有可能要改程式碼的是底層的服務,比如說業務1來了一個需求,他過來找到你,說我這個需求有個擴充套件,麻煩你這邊升級一下。業務2和業務3相同,明明有需求的是業務方,為什麼修改程式碼的是我底層呢,業務需求方很多,所有業務需求側都是你來實現,你是忙不過來的。這時你可能在心中罵他。

 

 

這個的耦合範圍相對較小,因為只有一個基礎服務維護的是痛點。解決方案也很容易想到,當然是把業務個性化的case分支搬到上游去,底層只做通用的功能。業務程式碼上浮,這樣的話上游的業務迭代速度、迭代效率會提升,每塊業務有功能就會自己實現了,不需要兄弟部門去實現,沒有一個溝通的過程。這是服務化不徹底的一個常見的耦合的案例。

 

5、訊息通知耦合

 

 

第五個案例是訊息通知的耦合。我猜應該也很多公司會遇到過,有一些事件,這個事件可能要讓很多下游知曉,這裡舉一個我們曾經出現過的案例。58同城釋出帖子,釋出帖子的這個事件可能要周知很多方,例如有一個使用者分級的服務,他發了帖之後,這個使用者發帖的一些統計資料,一些資訊資料可能要進行更新。還要通知離線訊息反作弊的部門釋出一個帖子之後,可能做一些離線的分析和處理,看有沒有反作弊的嫌疑。甚至我們這個訊息可能要通知業務線,比如說招聘業務線,最近做了一些營銷活動,只要發招聘的帖子就給你獎積分。

 

帖子釋出服務,這本來應該是一個非常基礎的服務,它是否要承擔將帖子訊息同步給通知關注方的職責呢?

 

最早我們是怎麼實現的?58同城都是服務化的架構,通過RPC告訴你釋出一個帖子。所以我們的上游是帖子釋出的基礎服務,他會通知反作弊的部門說發了一個帖子,會通知資料統計的部門發個帖子,會通知業務線說發個帖子,這樣的架構其實是因為這個通知上下游耦合在了一起。

 

然後我們在什麼時候會偷偷地去罵這個下游呢?假設現在又新增了一個業務線,房產業務線也做營銷活動,也要關注帖子釋出,麻煩釋出的兄弟能不能呼叫一下我。釋出的兄弟會發現改的是釋出服務的程式碼,他原來要調123,他現在還要調4,有人有新增的需求還要調5。釋出服務的工程師很痛苦,明明有需求的是業務方,但修改程式碼的卻是我。

 

 

就是這種因為訊息上下游耦合在一起,非常常見的解耦方案是通過MQ,這個案例裡的MQ以及下一個案例裡的配置中心是網際網路架構中兩個非常常見的解耦工具。MQ能夠做到上下游物理上和邏輯上都解耦,增加MQ之後,首先上游互不知道彼此的存在,它當然不會建立物理連線了,大家都與MQ建立物理連線,就是物理連線上解耦了;邏輯上也解耦了,訊息釋出方甚至不用知道哪些下游訂閱了這個訊息。新增訊息的訂閱方只需要找MQ就行了,上游不需要關注。所以MQ是一個非常常見的物理上解耦、邏輯上也解耦的利器。

 

6、下游擴容耦合

 

 

第六個案例,我相信也幾乎是所有的公司都會遇到的一個案例,它和第一個案例很像,但又不一樣。我們的第一個案例是說IP變化,上游調下游IP發生了變化,我們的建議是使用內網域名,而不是IP來做配置,來做上下游的連線解耦。

 

擴容換IP是一個場景,擴容又是第二個場景。現在有service1、service2、Web1,底層的service是個叢集,隨著業務、資料量、併發的增長,service要擴容了,我要新增兩個節點,假設我要新增IP4、IP5,你會發現案例一的場景又出現了,你得通知所有的上游麻煩幫忙增加兩個IP,增加兩個內網域名,因為我擴容了。擴容的明明是下游,但需要修改配置、需要重啟是上游。

 

我們早期的解決方案是怎麼樣的?這裡跟大家分享分享。我們對配置採用的是配置私藏的方式。一般對於每個上游來說,都有個自己的配置檔案,依賴於下游,這個配置檔案會放在上游的配置檔案裡。service2一般有一個配置conf,這個裡面寫了依賴於內網配置,內網域名是123,然後這個服務在啟動時可能通過配置把這個連線建立上。Web也是一樣,它有一個Web1.conf,大家想想自己所服務的公司是不是這樣的。它其實是一個數據的擴散,本來資料在這一份,但是你會發現這個資料擴散到不同的上游,每個人都儲存一份這樣的資料,我這個資料要變動時每個上游都需要變動。如果資料只存在一個地方,這一個地方變了就都變了,不用擔心資料的一致性。

 

其實如果你能夠知道上游是誰,通知你的上游去為使用者改善配置重啟還好,我們碰到的痛點是什麼?58同城幾千號人,業務幾百個,那麼多,我不知道誰依賴了我,如果我能知道123依賴了我,那我就告訴你就行了。現在我不知道誰依賴了我,因為你連線我,你不需要經過我的允許,你在手冊上看呼叫方式是什麼就看懂了。我們會增加IP,我怎麼通知你?

 

剛剛說根本的原因其實是一份配置資料擴散到了多個上游,那我們能不能將這個配置資料放在一個地方不擴散,我改了這一個地方就都改了?

 

 

解決方案其實還是配置中心,配置中心的細節我在這不展開講,網上可能也有一些公司的實踐,配置後臺、DB儲存等。

 

配置中心是一個典型的邏輯上解耦、但物理上不解耦的一個架構工具。我們的所有上游依賴於下游,還要建立物理的連線。你引入配置中心之後,它不是通過私有的配置,也不是通過全域性的配置檔案去讀取下游的IP,而是配置中心說我要訪問USER service,配置中心告訴他user service的內網域名是123,service的1、2、3還是按照內網的1、2、3,物理上還是連線user service,所有的上游都按照這種方式讀取下游的配置。

 

其實在配置中心側,他就能夠知道有哪些人連線了user service,他在配置中心的後臺就可以配哪些人我設定多少的限流,然後將這個限流可以同步到呼叫方的客戶端,當然也可以同步到服務端進行雙向的保護。如果user service進行擴容,比如我要增加幾個節點,我增加了4和5,那麼我在配置後臺說增加了4和5,後臺能夠知道哪些上游依賴了它,反向給後臺通知,就完全不需要上游去做了。這是我今天的最後一個案例。

 

三、總結

 

 

解耦之後系統能夠更美好一點,程式設計師心中能夠少一點怨氣,希望今天分享的主題及案例能夠幫助大家解決一些工作中的實際問題,謝謝大家。