今天主要討論一下,對于分布式服務(wù),站點如何平滑的上下線問題。 

在分布式服務(wù)下,我們會用nginx做負載均衡, 業(yè)務(wù)站點訪問某服務(wù)站點的時候, 統(tǒng)一走nginx, 然后nginx根據(jù)一定的輪詢策略,將請求路由到后端一臺指定的服務(wù)器上。 
 
這樣的架構(gòu)是沒有問題的, 但是我們這里考慮幾個問題, 
1. 網(wǎng)站上下線問題:我們網(wǎng)站平時更新站點的時候是直接覆蓋文件,然后重啟, 那這樣會造成一些請求中斷,如果是非核心邏輯那還好, 如果是核心邏輯,那請求中斷,會影響一些數(shù)據(jù)一致性,比如資金, 交易,訂單等。  
 2. 動態(tài)加減機器,比如某個站點訪問量大,要新增機器,那就需要修改nginx的配置,然后reload, 這樣會中斷連接。 雖然reload很快,但是還是會有一瞬間的請求中斷。 
 
對于第一個問題,我們可以在請求量少的時候去更新, 但是這種在一些服務(wù)穩(wěn)定的公司可用, 對于互聯(lián)網(wǎng)企業(yè),可能2-3天就一個版本, 而且需要立刻上線, 如果每次都要等到凌晨4點去更新, 可能整個的開發(fā)節(jié)奏都被帶慢了。 
對于第二個問題, 對于可以預(yù)見的流量,比如大促來臨,可以提前3天放在請求量少的時候更新。 
 
最近幾年,隨著SOA的普及和微服務(wù)的出現(xiàn),特別是dubbo的出現(xiàn),服務(wù)治理的概念被提出來。 服務(wù)治理是一個很宏大的概念,包括服務(wù)注冊,服務(wù)自動發(fā)現(xiàn),服務(wù)路由,服務(wù)依賴,集群容錯,服務(wù)降級,服務(wù)監(jiān)測,服務(wù)審批等,當然不是每個服務(wù)中心都必須實現(xiàn)這些東西, 公司可以根據(jù)自己的實際需求來定制實現(xiàn)。 
基于以上這些情況, 我計劃實現(xiàn)一個工具,這個工具首先解決站點上下線和動態(tài)擴容問題,也就是說在不需要重啟nginx的情況下,并且在保證請求不丟失的情況下來更新站點。 同時帶有部分服務(wù)治理功能。 
 
 
服務(wù)上線
1. 在一個新服務(wù)上線的時候,一般會提前申請幾臺機器, 運維會在nginx上新增server,并新增server對應(yīng)的upstream ,正常情況下upstream應(yīng)該配置是后端服務(wù)器的IP,但是這里不配置(如果允許,甚至這一步都可以省略)。 
2. 服務(wù)部署好并啟動,在啟動的時候,向注冊中心注冊自身的服務(wù)信息,包括IP和端口。 
3. 注冊中心收到請求后,會對服務(wù)進行健康檢測,確保提供的服務(wù)沒有問題,則將服務(wù)狀態(tài)標示為預(yù)上線狀態(tài)。 
4. 在后臺管理中心,就可以將預(yù)上線的服務(wù)設(shè)置為上線,服務(wù)管理中心會調(diào)用nginx的上線接口,將服務(wù)IP新增或者更新到upstream中,服務(wù)就可以提供訪問。   
 
服務(wù)更新
假如我們現(xiàn)在有一個服務(wù)需要更新,則執(zhí)行以下步驟:
1. 在后臺管理中心,將一個服務(wù)設(shè)為下線,此時服務(wù)中心會調(diào)用nginx的下線接口,將指定服務(wù)器的IP設(shè)置為下線。 
2. 在等待1分鐘后,確保沒有新連接連過來,則可以開始更新服務(wù)站點。
3. 更新完畢后,再手動設(shè)為上線,此時服務(wù)中心會調(diào)用nginx的上線接口,將指定服務(wù)器的IP設(shè)置為上線。當然對于成熟的服務(wù),這些都可以自動化,有些公司會有一些自動化發(fā)布工具, 與自動化發(fā)布工具集成,可以一鍵下線,更新并上線。 
 
服務(wù)運行期間
在服務(wù)運行過程中,會有一個健康檢測的服務(wù)對所有提供服務(wù)的站點進行健康檢測,一旦檢測到有問題,就執(zhí)行下線邏輯。 直到問題被解決,最后執(zhí)行上線流程。 
 
動態(tài)加減機器
在服務(wù)運行過程中,可能因為某些原因,服務(wù)請求飆高(前提是這些請求都是合法的),超過了當前集群的承載能力,當系統(tǒng)檢測到這些情況后,可以動態(tài)擴充機器,比如現(xiàn)在流行的docker,在啟動容器的時候,同時啟動應(yīng)用,應(yīng)用在啟動的時候,將自身信息注冊給注冊中心,注冊中心再將這些信息同步到nginx,應(yīng)用就可以提供訪問,整體上就可以實現(xiàn)彈性計算。 
 
為什么不實現(xiàn)服務(wù)動態(tài)發(fā)現(xiàn)?
   這里可以看到圖中已經(jīng)有一個服務(wù)注冊中心。 既然有了服務(wù)注冊中心了, 那可以讓業(yè)務(wù)站點連接服務(wù)注冊中心來獲取真實的服務(wù)IP,然后繞過nginx來連接服務(wù),這里之所以沒有這樣做,是因為:
    1.  實現(xiàn)服務(wù)動態(tài)發(fā)現(xiàn),這個需要和RPC框架配合,而且需要做服務(wù)的軟負載,失敗重連,限流等,整個項目設(shè)計就上升了一個復雜度, 考慮到有些項目還未使用RPC,并且不想對原有的項目有過多的侵入, 所以這里不做實現(xiàn)。 但是并不意味沒有這些功能,服務(wù)的負載, 失敗重連, 限流,其實這些功能在nginx中同樣也有,可以直接使用,所以沒有必要重新再開發(fā)。 
    2.  實現(xiàn)服務(wù)動態(tài)發(fā)現(xiàn),獲取到真實的服務(wù)IP,然后直連,這些一般是在流量特別大,nginx上出現(xiàn)短板的時候使用,但實際情況,一般很少會耗盡nginx的性能,即使有,也可以通過ngxin水平擴展來實現(xiàn),所以這里依然使用nginx作為負載均衡。 
 
這里講一下這個項目的關(guān)鍵點:
1. 服務(wù)的注冊和健康檢測這個沒有技術(shù)難點,這里不做解釋。
2. 關(guān)于操作nginx上下線,這里的確是一個難點,因為nginx本身并沒有提供這些上下線API,需要openresty并配合一些第三方擴展來實現(xiàn)。 這里主要用到了兩個擴展模塊:ngx_http_dyups_module  lua-upstream-nginx-module
  ngx_http_dyups_module(https://github.com/yzprofile/ngx_http_dyups_module)提供了粗粒度的upstream管理方法,可以對整個upstream進行新增,刪除。 
  lua-upstream-nginx-module(https://github.com/openresty/lua-upstream-nginx-module) ,則提供了細粒度的管理方式,可以對某一個服務(wù)IP進行管理,其中提供的set_peer_down方法,可以對upstream中的某個ip進行上下線。
3. 也可以使用ngx_dynamic_upstream(https://github.com/cubicdaiya/ngx_dynamic_upstream)
這些插件有一個共同點,那就是在不需要重啟nginx的基礎(chǔ)上, 動態(tài)修改nginx的配置。 
1. 最后我想請大伙討論一下,你們公司是怎么上下線的, 是直接覆蓋,還是有其他策略。 歡迎在評論區(qū)討論。