曾宇平 dotNET跨平臺(tái) 今天首先感謝張隊(duì)@geffzhang公眾號轉(zhuǎn)發(fā)了上一篇文章,希望廣大.neter多多推廣dapr,,讓云原生更快更好的在.net這片土地上落地生根,。 書接上回通過Dapr實(shí)現(xiàn)一個(gè)簡單的基于.net的微服務(wù)電商系統(tǒng),今天來分享一下這套電商demo的通訊部分到底是如何工作的,,看看它是如何屏蔽與dapr繁瑣的溝通工作讓開發(fā)者專注于解決業(yè)務(wù)問題的,。 首先我們再回顧一下dapr的sidecar是如何與應(yīng)用相互協(xié)同的。和istio類似,,dapr的sidecar注入可以分為自動(dòng)注冊和手動(dòng)注冊,,下面以手動(dòng)加注解注冊的方式我們來聊一聊dapr的工作邏輯。首先當(dāng)我們設(shè)置一個(gè)應(yīng)用(deployment)的時(shí)候,,在template-metadata配置了dapr相關(guān)注解之后,,凡是安裝dapr集群的k8s會(huì)自動(dòng)將dapr的sidecar注冊到我們的pod中,如下圖: 當(dāng)服務(wù)啟動(dòng)后,,我們可以用kubectl describe po xxx的方式看到當(dāng)前該pod會(huì)產(chǎn)生兩個(gè)容器: 凡是了解k8s的開發(fā)人員應(yīng)該知道,。在同一個(gè)pod之中,container實(shí)例之間的通訊應(yīng)該是基于同一個(gè)虛擬內(nèi)網(wǎng)的,,通俗的說就是兩者通訊可以直接通過localhost:port的方式,,這是dapr與應(yīng)用交互的基礎(chǔ)。和istio通過iptables 來做流量劫持讓Envoy代理可以攔截所有的進(jìn)出Pod的流量,即將入站流量重定向到 Sidecar,再攔截應(yīng)用容器的出站流量經(jīng)過 Sidecar 處理的方案相比,,dapr選擇了一個(gè)更加靈活的方式,,也就是它只是主動(dòng)暴露一個(gè)端口(默認(rèn)3500),將是否和dapr通訊的選擇權(quán)留給了應(yīng)用本身,。 當(dāng)我們發(fā)起一個(gè)rpc請求時(shí),,實(shí)際上我們是通過http(or grpc這里不展開)的方式,訪問了了http://localhost:3500/v1.0/{invoke}/{servicename}/method/{path} 這么一個(gè)地址。sidecar通過解析這個(gè)地址得到遠(yuǎn)程服務(wù)名{servicename},以及一個(gè)謂詞{invoke}以及遠(yuǎn)程服務(wù)的endpoint:{path},。它會(huì)通過內(nèi)部的dns服務(wù)名查詢servicename得到一個(gè)該服務(wù)在集群內(nèi)的實(shí)例列表,,通過負(fù)載均衡的方式發(fā)起一個(gè)下游調(diào)用。這個(gè)下游調(diào)用也并非直接像普通k8s應(yīng)用內(nèi)通過調(diào)用service name的方式去調(diào)用下游pod的container,,而是訪問下游pod內(nèi)的sidecar,,通過sidecar再去訪問pod內(nèi)的應(yīng)用實(shí)例。他們之間的調(diào)用關(guān)系如圖所示: 通過這樣的設(shè)計(jì),實(shí)際上應(yīng)用只需要和daprd這個(gè)sidcar打交道即可,。同時(shí)dapr實(shí)現(xiàn)了通過謂詞解析成不同的服務(wù)類型實(shí)現(xiàn),。比如服務(wù)間調(diào)用通過謂詞:{invoke}、狀態(tài)讀寫的謂詞是{state},、訂閱發(fā)布的謂詞是{publish},、{subscribe},幾乎所有的行為都可以分解成謂詞+服務(wù)名+endpoint這種模式(所有api可參考:https://docs./reference/api/),,這也是實(shí)現(xiàn)這套通訊框架的基礎(chǔ),。 所以剩下的事情就比較簡單了,dapr通訊基于http/grpc,,所以我們只需要啟動(dòng)一套kestrel+httpclient or grpc service/client即可簡單快捷的接入dapr,。首先我們還是看看整個(gè)repo(https://github.com/sd797994/Oxygen-Dapr)的結(jié)構(gòu): Oxygen這部分主要是包含通用工具層、IOC依賴注入(基于autofac),、本地代理生成器ProxyGenerator,。Client主要包含一些遠(yuǎn)程服務(wù)attr標(biāo)記以及客戶端代理工廠。而在Mesh這個(gè)單獨(dú)分層里主要是對Dapr的Actor實(shí)現(xiàn)了相關(guān)封裝,、Service層比較簡單,,只是在hostbuilder啟動(dòng)了一個(gè)kestrel并獲取所有標(biāo)記了遠(yuǎn)程服務(wù)的接口來構(gòu)建路由字典方便將我們的Application服務(wù)暴露成restapi。 本地代理ProxyGenerator實(shí)現(xiàn)比較簡單,,使用了微軟自帶的代理類DispatchProxy,。通過Autofac依賴注入接口的時(shí)候?qū)⒔涌诤痛眍悓?shí)現(xiàn)注冊到ioc容器中,這樣當(dāng)我們通過IServiceProxyFactory.CreateProxy時(shí)實(shí)際上是從ioc容器中拿到的DispatchProxy實(shí)例,,這樣調(diào)用任意該接口的方法都會(huì)被路由到DispatchProxy實(shí)例,從而實(shí)現(xiàn)方法攔截并最終通過RemoteMessageSender類型里的HttpClient發(fā)起對dapr的sidecar請求,。 Client層的ServerProxyFactory也比較簡單,,其實(shí)就三個(gè)東西,一個(gè)是IServiceProxyFactory,這個(gè)主要用于發(fā)起對遠(yuǎn)程rpc和actor的調(diào)用,、一個(gè)是IEventBus以及IStateManager,,分別用于發(fā)布事件和調(diào)用dapr的狀態(tài)管理器。 Service.Kestrel層主要是通過啟動(dòng)時(shí)由RequestDelegateFactory.CreateDelegate的方式將所有注冊為remoteservice的接口實(shí)現(xiàn)為其構(gòu)造一個(gè)Func<Tservice, Tin, Task<Tout>>這樣的匿名委托并將其路由鍵和該委托注冊到一個(gè)全局靜態(tài)字典中,。當(dāng)收到請求時(shí)通過kv鍵值對的方式查詢當(dāng)前key(router)對應(yīng)的匿名委托,,并通過ioc容器構(gòu)造一個(gè)Tservice實(shí)例(為什么要請求時(shí)創(chuàng)建一個(gè)實(shí)例?因?yàn)檫@樣可以模擬MVC創(chuàng)建controller的方式將Tservice作為一個(gè)scope生命周期的對象創(chuàng)建出來,,避免Tservice內(nèi)部的構(gòu)造函數(shù)依賴的非單例對象生命周期失效) 整個(gè)請求收發(fā)流程如下: 1,、當(dāng)客戶端通過IServiceProxyFactory.CreateProxy<IxxxServcice>()時(shí)獲取到該接口的DispatchProxy實(shí)例。 2,、實(shí)例解析各種參數(shù)后發(fā)起一個(gè)http調(diào)用,,http請求localhost:3500的sidecar后等待回調(diào)。 3、sidecar將請求組裝后發(fā)給下游sidecar并由下游sidecar轉(zhuǎn)發(fā)給pod內(nèi)的應(yīng)用,。 4,、應(yīng)用收到請求后解析path得到對應(yīng)的RequestDelegate,調(diào)用RequestDelegate將請求打到具體的xxxServcice服務(wù)上,由服務(wù)完成具體的業(yè)務(wù),。 Mesh.Dapr則是對Actor行為的一個(gè)具體封裝,,由于原始的dapr sdk需要繼承BasicActor然后進(jìn)行各種actor作業(yè),我采用了另外一種方式,,通過emit靜態(tài)代理的方式創(chuàng)建了一個(gè)Actor服務(wù),,由其代為接收actor請求后再轉(zhuǎn)發(fā)給具體的xxxServcice。同時(shí)這個(gè)Actor服務(wù)會(huì)啟動(dòng)一個(gè)timer,,當(dāng)timer到期時(shí)會(huì)進(jìn)行一次model的版本檢查,,當(dāng)版本變化后(一般是由于xxxServcice被調(diào)用),會(huì)通知xxxServcice繼承自基類并重寫的SaveData方法,,由xxxServcice自身考慮是否需要做業(yè)務(wù)層的持久化(默認(rèn)Actor代理服務(wù)會(huì)自動(dòng)持久化到dapr的狀態(tài)設(shè)備里),,這一步是完全異步的并不會(huì)阻塞Actor代理原方法的執(zhí)行,另外在Actor的使用中,,我們也盡量避免在同步調(diào)用時(shí)去讀取第三方的設(shè)備可能導(dǎo)致IO阻塞actor,。在源碼中涉及對actor調(diào)用xxxServcice異步的支持,我主要參考了async/await生成狀態(tài)機(jī)的方式創(chuàng)建了一個(gè)ActorAsyncStateMachine,,由該狀態(tài)機(jī)來完成actor服務(wù)調(diào)用xxxServcice的async/await實(shí)現(xiàn),。 sample包含一對客戶端/服務(wù)端案例包含上述涉及的所有遠(yuǎn)程call,大家可以多參考一下,。 Dapr原始提供了一套sdk用于遠(yuǎn)程服務(wù),,該框架主要是用于實(shí)現(xiàn)rpc以及對dapr這些api的自定義封裝,當(dāng)使用這套框架后我們就可以不用再考慮創(chuàng)建具體的webapi控制器,,由iapplicationservice申明遠(yuǎn)程服務(wù)后框架即可自動(dòng)生成代理服務(wù)即可,。 該框架的實(shí)現(xiàn)方式當(dāng)然還有諸多不完善或者我沒考慮到的地方,主要起到一個(gè)拋磚引玉的作用,,另外也是通過這個(gè)來了解dapr是如何統(tǒng)一了我們網(wǎng)絡(luò)編程模型的,只有更了解dapr才能更好的使用和推廣它,。慣例,歡迎fork+star: https://github.com/sd797994/Oxygen-Dapr https://github.com/sd797994/Oxygen-Dapr.EshopSample |
|