連流量染色都沒有,你說要搞微服務?

語言: CN / TW / HK

一、序言

在當下盛行的微服務架構下,服務數量多導致的依賴問題經常會成為開發過程中的絆腳石。也經常會在各種技術交流會上聽到類似的話題,大家都在積極的討論這種問題如何去解決。於是決定給大家介紹下流量染色的原理以及能解決微服務架構下開發過程中的哪些問題。

二、流量染色的概念

流量染色說白了就是為對請求的流量打上標籤進行染色,然後這個請求在整個鏈路中都會攜帶這個標籤資訊,可以通過標籤進行流量的排程等功能。

基於流量染色可以實現很多功能,比如灰度邏輯,藍綠部署,泳道隔離等。

這裡簡單講下流量染色跟微服務的關係,免得大家覺得這是一篇標題黨的文章。試想一下,如果是一個單體應用,還能有流量染色的應用場景嗎?請求的大致流程就是App -> 負載均衡 -> 應用,整個鏈路就很簡單,流量染色在這個場景下完全無用武之地。只有在服務數量眾多的情況下,一個業務功能涉及到N個服務,才需要對流量進行染色控制來解決我們開發,測試等過程中遇到的問題。

三、基於流量染色的應用

測試環境多套部署的痛點,只需增量部署

目前,我們的測試環境除了經常用的T環境,還有很多MF環境。而MF環境基本上是在獨立的需求中會用到,正常的版本迭代都是走T環境。

  • 問題一:各環境配置不同

這樣就會導致一個問題,很多功能是在T環境進行測試的,當有獨立需求需要在MF環境中測試時,就需要部署對應的服務,部署過程中經常會碰到各種配置的缺失或者錯誤的情況,導致應用無法啟動。

  • 問題二:沒有改動的服務也要部署

有一個需求需要在MF環境測試,服務也部署好了,聯調的時候卻發現依賴的下游服務都沒有部署。但這些服務在這個需求是沒有改動的,依賴的介面也是已經上線了的功能。

如果沒有在對應的環境部署,整個鏈路就無法調通。所以這個時候又要去找對應的下游,讓下游去部署這些服務,下游部署的時候可能也會出現各環境配置不同的問題,導致整個聯調前期的耗時較長,影響專案進度。

  • 流量染色如何解決上述問題?

比如說當前在開發一個需求,然後會在要改動的應用中配置一個版本,這個版本資訊會儲存在註冊中心的元資料裡面。

然後就去建立一個屬於這個需求的泳道(獨立環境)進行部署,只需要部署這個需求改動的應用即可。這個應用依賴的下游應用不需要部署,在當前環境找不到對應的服務提供者就去路由到穩定環境,如果穩定環境中也沒有就報錯。

研發本地啟動隨意註冊問題

研發有的時候會在本地啟動服務,主要是用來除錯某個問題,好處就是能夠快速復現測試環境的問題,及時發現問題程式碼。

由於本地啟動的服務也會註冊到註冊中心裡面,這樣測試環境的請求就有可能會路由到研發本地啟動的這個服務上,研發本地的這個服務程式碼有可能不是最新的,導致呼叫異常。

這個問題目前常用的解決方案是通過在本地啟動時遮蔽掉服務的註冊功能,也就是不註冊上去,這樣就不會被正常的測試請求路由到。

如果有了流量染色的功能,研發本地啟動服務的時候指定一個屬於自己的版本號,只要不跟正常測試的版本一致即可。正常測試的請求就不會路由到研發註冊的這個例項上。

應用級別的灰度

針對介面級別的灰度,目前都是在應用內進行灰度控制。但是應用級別的,目前沒有特別好的方式來控制灰度。比如有一個技改需求,需要將Redis的Client從Lettuce換成Jedis,這種場景的灰度就是應用級別的,目前的做法就是釋出一個節點,然後結束髮布流程,具體能被灰度到的量是由服務例項的總數量來決定的,沒辦法靈活控制。

如果有流量染色,可以新發一個節點,這個節點的版本升級一下,比如之前的版本是V1,那麼新發的就是V2版本。首先V1版本肯定是承載生產所有流量的,可以通過閘道器進行控制讓流量按某種方式轉發到V2版本,比如使用者白名單,地區,使用者比例等等。有問題也可以隨時將流量切回V1,非常方便。

服務的優雅下線

服務要想無損進行優雅下線,還是需要做很多工作的,比如目前釋出時會先將要釋出的服務從註冊中心登出掉,但是應用內部還是會有服務例項資訊的快取,需要等到一定的時間快取完成清除後,對應的目標例項才不會被請求到。

如果基於染色去實現的話,將需要下線的例項資訊(IP:PORT)通過配置中心推送給閘道器進行染色處理,染色資訊跟隨著請求貫穿整個鏈路,應用內的負載均衡元件,MQ等中介軟體會對要下線的目標例項資訊進行過濾,這樣就不會有流量到要下線的例項上去。

生產環境釋出提速

目前,主流的釋出都是滾動部署,滾動釋出的好處是成本低,不用額外增加部署的資源,一個蘿蔔一個坑,慢慢替換就是。不好的點在於釋出時間長,全鏈路依賴太嚴重,如果釋出之前依賴關係錯亂了,那就是一個線上故障。

要解決這個釋出速度的問題,可以基於流量染色來實現藍綠部署。也就是在釋出的時候重新部署一個V2的版本,這個V2版本的例項數量跟V1保持一致,由於這個V2版本是沒有流量的,所以不存在依賴關係,大家可以同時釋出,等到全部發完之後,就可以通過閘道器進行流量分發了,先分發一點點流量到V2版本進行驗證,如果沒有問題就可以慢慢放大流量,然後將V1版本的容器釋放掉。

釋出速度確實提升了,可是問題在於藍綠部署的成本太高了,資源成本要翻倍,雖然釋出後老的資源就回收了,但是你總的資源池還是得容納下這2個版本並行才行。

那有沒有折中的方式,既能提高發布 效率 又能不增加資源成本呢?

可以在釋出的時候採用替換的形式,先發布一半的例項,這一半的例項就是我們的V2版本,釋出時是沒有流量的,所以還是可以並行的去釋出。

釋出完成後,開始放量到V2版本,然後驗證。驗證之後就可以釋出另一半的例項了,這樣的方式總的資源是沒有變化的,但是有一個比較 嚴重的問題就是直接停掉了一半的例項,剩下的例項能不能支撐當前的流量,因為交易內的應用都是面向C端使用者的,流量很有可能在短時間內達到很高的量。

全鏈路壓測

全鏈路壓測對於電商業務來說必不可少,每年有N次大促,都需要提前進行壓測來確保大促的穩定。其中全鏈路壓測最核心的一點就是流量的區分,需要區分流量是正常的使用者請求還是壓測平臺的壓測流量。

只有區分了流量,才能將壓測流量進行對應的路由,比如資料庫,Redis等流量需要路由到影子庫中。基於流量染色就很容易給流量打標,從而區分流量的型別。

四、流量染色的實現

應用要有版本的概念

每個應用都需要有版本的概念,其實就跟每次迭代繫結即可。只不過是要將這個版本資訊放入專案中的配置檔案裡面,專案啟動的時候會將這個版本資訊跟自身的例項資訊一起註冊到註冊中心裡面,這些資訊一般稱之為元資料(Metadata)。

有了Metadata,在控制流量路由的時候才可以根據染色的資訊進行對應的匹配,比如某個請求指定了對訂單的呼叫要走V2版本,那麼在路由的時候怎麼匹配出V2版本的例項資訊呢?就需要依賴Metadata。

染色資訊全鏈路透傳

染色資訊全鏈路透傳這個很關鍵,如果不能全鏈路透傳就沒辦法在所有節點進行流量的路由控制。這個染色資訊的透傳其實跟分散式鏈路跟蹤是一樣的原理。

目前主流支援分散式鏈路跟蹤的有Skywalking,Jaeger等等,基本上都借鑑了Google Dapper的思想。每次請求都會在入口處生成一個唯一的TraceId,通過這個TraceId就可以將整個鏈路關聯起來,這個TraceId就需要在整個鏈路中進行傳遞,流量染色的資訊也是一樣需要全鏈路傳遞。

傳遞的手段一般分為兩種,一種是在獨立的Agent包中進行傳遞,一種是在基礎框架中進行埋點傳遞。如果內網之間採用Http進行介面的呼叫,那麼就在請求頭中將資訊進行傳遞。如果是用RPC的方式,則可以用RpcContext進行傳遞。

資訊傳遞到了應用中,在這個應用中還會繼續呼叫其他下游的介面,這個時候要繼續透傳,一般都是將資訊放入到ThreadLocal中,然後在發起介面呼叫的時候繼續透傳。這裡需要注意的就是用ThreadLocal要防止出現執行緒池切換的場景,否則ThreadLocal中的資訊會丟失。當然也有一些手段來解決ThreadLocal非同步場景下的資訊傳遞問題,比如使用transmittable-thread-local。

流量路由控制

當流量有了標籤資訊,剩下的工作就是要根據標籤資訊將請求路由到正確的例項上。如果內部框架是Spring Cloud體系,可以通過Ribbon去控制路由。如果是Dubbo體系,可以通過繼承Dubbo的AbstractRouter重新制定路由邏輯。如果是內部自研的RPC框架,肯定留有對應的擴充套件去控制路由。

五、總結

流量染色總體來說還是非常有用的,但這也是一個大的技術改造。除了在基礎框架層面要打通染色資訊的傳遞,更為重要的是各業務方的配合,當然如果是Agent方式的接入就更好了,不然每個業務方還要去升級包,確實有點煩。

-------------  END   -------------

掃描下方二維碼,加入技術群。暗號: 加群