2021年是我司着重關注生產穩定性的一年,得物社區服務早期是由PHP語言構建的單體應用支撐着日活百萬用戶,隨着高速的發展在性能跟業務上已逐漸不能知足將來的需求與規劃,在第一階段上社區與架構團隊同窗提供了php + yaf、Java + spring cloud、Go + grpc + K8S的技術選型方案,考慮到服務性能與遷移成本,最終選擇了 Go + grpc + K8S 做爲此項工程的首選爲社區微服務構建創建起了里程碑。php
隨着業務的發展,對穩定性要求愈來愈高,爲加強業務服務的自治能力,提升集羣的穩定性與可控性,且考慮最低成本的接入方式,同時考慮社區與交易系統(Dubbo技術棧)有着千絲萬縷的關係,最終但願能完成兩個集羣系統的輕鬆融合,故選用應用層框架Dubbo-go來實Golang服務的註冊與發現。spring
Golang微服務架構,你們可能比較熟悉的是Go Micro和Go Kit(還有Gizmo),確實,Go Micro的社區活躍度,Go Kit的GitHub Star數也18k以上,但這裏並沒選擇,主要是Go Micro提供了許多的功能,開箱即用,但靈活性受限;go Kit雖被追捧,可是咱們並不是是從新起Golang服務,應用層框架限制過於嚴格,代碼遷移成本將會很是高。考慮以上困難,最終選了一個還在成長期的Dubbo-go。api
Dubbo-go是目前Dubbo多語言生態最火熱的項目之一,已隨着Dubbo生態加入Apache基金會,截止目前已有很多一二線互聯網公司使用(釘釘、攜程、塗鴉、開課吧等),社區活躍度較高,響應開發者需求較快,有較快且貼合開發着需求的版本迭代速度。
圖一*架構
Dubbo-go主項目,主要是基於Dubbo的分層代碼設計,上圖是Dubbo-go的代碼分層,基本上與Java版本Dubbo現有的分層一致,因此Dubbo-go也繼承了Dubbo的一些優良特性,好比整潔的代碼架構、易於擴展、完善的服務治理功能。框架
目前Dubbo-go已經實現了Dubbo的經常使用功能(如負責均衡、集羣策略、服務多版本多實現、服務多註冊中心多協議發佈、泛化調用、服務降級熔斷等),其中服務註冊發現已經支持zookeeper/etcd/consul/nacos 主流注冊中心。這裏不展開詳細介紹。ide
根據現有項目以gRPC爲服務調用的背景前提下,考慮到對業務代碼侵入程度,且作到兼容原有方案正常使用,兩套gRPC實現下可切換自由,作到生產環境切換Rpc治理框架的實時性與可控性,下降生產環境風險,故結合Dubbo-go自身支持gRPC協議入手知足以上需求。註冊中心選型爲Nacos,與目前現有中間件保持統一,同時知足配置部分配置項需求。
圖二*微服務
這裏咱們要先思考兩個問題,一個是Dubbo-go的集成如何兼容原有gRPC方案,保持兩套方案可同時在線支持生產,第二個問題是兩套gRPC之間如何實現實時切換。性能
在實現此項需求前,咱們先來談談gRPC自身特性,gRPC是谷歌開源的一個 RPC 框架,面向移動和HTTP/2設計,內容交換格式採用ProtoBuf(Google Protocol Buffers),開源已久,提供了一種靈活、高效、自動序列化結構數據的機制,做用與XML,Json相似,但使用二進制,(反)序列化速度快,壓縮效率高,傳輸協議採用http2,性能比http1.1提高不少。spa
在根據介紹的 gRPC 的相關特性能夠看出來,gRPC已經解決了codec和transport兩層的問題,結合圖一看,從cluster層往上,是沒有gRPC相關涉及的地方,從圖1裏面能夠看出要作gRPC相關適配,在 protocol這一層是最合適的,咱們能夠如同DubboProtocol同樣,擴展出來一個gRPCProtocol,這個gRPC protocol大致上至關於一個Adapter,將底層的gRPC的實現和咱們自身的Dubbo-go結合在一塊兒。
圖三*插件
基於上述,Dubbo-go幫助咱們解決了gRPC的相關整合,至關於在gRPC基礎之上包裝了Dubbo-go治理層,而咱們從gRPC的ProtoBuf修改做爲切入點開始,Dubbo-go官方基於Google protobuf 擴展插件定義了Dubbo-go gRPC所使用的 protobuf 自定義邏輯代碼,完成兼容性問題便可。
// HelloWorldServiceClientImpl is the client API for HelloWorldService service. type HelloWorldServiceClientImpl struct { SayHello func(ctx context.Context, in *SayHelloReq, out *SayHelloResp) error //... } // service Reference func (c *HelloWorldServiceClientImpl) Reference() string { return "helloWorldServiceImpl" } // GetDubboStub func (c *HelloWorldServiceClientImpl) GetDubboStub(cc *grpc.ClientConn) HelloWorldServiceClient { return NewHelloWorldServiceClient(cc) } // Server interface type HelloWorldServiceProviderBase struct { proxyImpl protocol.Invoker } // set invoker proxy func (s *HelloWorldServiceProviderBase) SetProxyImpl(impl protocol.Invoker) { s.proxyImpl = impl } // get invoker proxy func (s *HelloWorldServiceProviderBase) GetProxyImpl() protocol.Invoker { return s.proxyImpl }
實時切換,起初是爲了在壓測環境方便兩套不一樣實現的gRPC方案可實時切換作壓測數據收集,後期是抱着敬畏生產的態度,在生產環境剛接入Dubbo-go時將Rpc框架切換支持服務、方法自由切換,從穩定性出發,選擇性的切換觀測服務穩定性狀態。
此項需求的接入,一樣是從gRPC的ProtoBuf修改做爲切入點開始,同時基於Nacos配置中心實現,咱們將原有gRPC的客戶端調用和 Dubbo-go的客戶端調用封裝成一個統一實例化入口(這裏簡稱ClinetDubbo),客戶端全部方法新增一份繼承 ClinetDubbo 具體實現(由protobuf擴展插件統一腳本生成),實現內容大體爲獲取ClinetDubbo中的兩套gRPC客戶端,此時經過Nacos配置獲取配置中心所打開的客戶端是哪一套,根據判斷實現走具體的gRPC鏈路。
圖四*
左側gRPC常規鏈路,右側Dubbo-go治理模型鏈路
// ClientDubbo type HelloWorldServiceClientDubbo struct { GrpcClient HelloWorldServiceClient DubboClient *HelloWorldServiceClientImpl Open bool Caller string } // 具體的方法實現 func (c *HelloWorldServiceClientDubbo) SayHello(ctx context.Context, req *SayHelloReq, opts ...grpc.CallOption) (*SayHelloResp, error) { serverName := c.DubboClient.Reference() //獲取 nacos配置源數據 serverCfg := nacosCfg.GetServerCfg() if !c.Open { c.Open = serverCfg.AllOpen } cfg := serverCfg.ServiceCfg // 判斷調用鏈路 if !c.Open && !cfg[serverName].Open && (cfg[serverName].Consumers == nil || !cfg[serverName].Consumers[c.Caller]) && !cfg[serverName].Method["SayHello"].Open && (cfg[serverName].Method["SayHello"].Consumer == nil || !cfg[serverName].Method["SayHello"].Consumer[c.Caller]) { // 原gRPC鏈路 return c.GrpcClient.SayHello(ctx, req, opts...) } // Dubbo-go治理鏈路 out := new(SayHelloResp) err := c.DubboClient.SayHello(ctx, req, out) return out, err }
如下是基於現有項目結構集成Dubbo-go框架示例:
type HelloWorldService struct { *pb.UnimplementedHelloWorldServiceServer *pb.HelloWorldServiceClientImpl *pb.HelloWorldServiceProviderBase } func NewHelloWorldService() *HelloWorldService { return &HelloWorldService{ HelloWorldServiceProviderBase: &pb.HelloWorldServiceProviderBase{}, } }
基於原有服務提供的基礎之上加入Dubbo-go擴展部分,提供服務註冊。
//原有Grpc var HelloWorldCli HelloWorldServiceClient //Dubbo-go var HelloWorldProvider = &HelloWorldServiceClientImpl{} func GetHelloWorldCli() HelloWorldServiceClient { if HelloWorldCli == nil { HelloWorldCli = NewHelloWorldClient(grpc_client.GetGrpcClient(...)) } return &HelloWorldServiceClientDubbo{ GrpcClient: HelloWorldCli, DubboClient: HelloWorldProvider, Caller: dubboCfg.Caller, Open: false, } }
GetHelloWorldCli()簡單封裝了客戶端調用,此方法最終返回 HelloWorldServiceClientDubbo 結構體的方法,客戶端發起調用進入以 HelloWorldServiceClientDubbo 實現的具體方法中,根據配置項判斷執行具體gRPC調用鏈路。
func main() { //provider config.SetProviderService(rpc.NewHelloWorldService(), ...) // 設置服務消費者 config.SetConsumerService(api..., ....) // 加載dubbo config.Load() }
以上就是社區服務集成Dubbo-go的總體思路與方案,咱們會發如今現有項目中須要改動的代碼量不多,且對業務方方代碼無任何侵入。
Dubbo-go集成,加強了業務服務gRPC調用過程當中治理能力,基於cluster增長了服務集羣的容錯能力,實現了應用服務之間容錯能力的可配置性;完善且統一了社區服務原始新老架構服務的全鏈路監控和服務指標監控告警;加強了業務夥伴對集羣內服務的透明化、可控性,在遇到問題後總體的鏈路梳理上有了更多的可參考信息。
基於Dubbo總體生態,可輕鬆支持Golang與Java的Rpc互通,作到跨語言Rpc調用。
圖五*
Dubbo-go做爲一個微服務框架,自身包含治理能力,這部分能力如何與K8S融洽結合。
K8S提供了pod/endpoint/service三層維度的資源,能夠經過監聽pod/endpoint/service三層維度資源的事件,做出合理的處理以達到服務治理的目的,不須要引入額外組件,經過監聽k8s中最細粒度資源pod的事件,經過k8s apiserver獲取pod列表,只是經過apiserver使用etcd的服務註冊和服務通知能力,其餘繼續使用Dubbo-go的服務治理能力,模型簡單,不須要實現額外的模塊,幾乎不須要對 Dubbo做出改動。
文|小柯
關注得物技術,攜手走向技術的雲端