jinfeng_wang

          G-G-S,D-D-U!

          BlogJava 首頁(yè) 新隨筆 聯(lián)系 聚合 管理
            400 Posts :: 0 Stories :: 296 Comments :: 0 Trackbacks
          http://www.tuicool.com/articles/VfQbauB

          遠(yuǎn)程服務(wù)依賴

          依賴分為兩種,本地的lib依賴,遠(yuǎn)程的服務(wù)依賴。

          本地的依賴其實(shí)是很復(fù)雜的問(wèn)題。從操作系統(tǒng)的apt-get,到各種語(yǔ)言的pip, npm。包管理是無(wú)窮無(wú)盡的問(wèn)題。但是所有的本地依賴已經(jīng)被docker終結(jié)了。無(wú)論是依賴了什么,全部給你打包起來(lái),從操作系統(tǒng)開始。除了你依賴的cpu指令集沒(méi)法給你打包成鏡像了,其他都給打包了。

          docker之后,依賴問(wèn)題就只剩遠(yuǎn)程服務(wù)依賴的問(wèn)題。這個(gè)問(wèn)題就是服務(wù)注冊(cè)發(fā)現(xiàn)與調(diào)度需要解決的問(wèn)題。從軟件工程的角度來(lái)說(shuō),所有的解耦問(wèn)題都可以通過(guò)抽取lib的方式解決。lib也可以實(shí)現(xiàn)獨(dú)立的發(fā)布周期,良好定義的IDL接口。所以如果非必要,請(qǐng)不要把lib依賴升級(jí)成網(wǎng)絡(luò)服務(wù)依賴的角度。除非是從非功能性需求的角度,比如獨(dú)立的擴(kuò)縮容,支持scale out這些。很多時(shí)候微服務(wù)是因?yàn)榛趌ib的工具鏈支持不全,使得大家義無(wú)反顧地走上了拆分網(wǎng)絡(luò)服務(wù)的不歸路。

          名字服務(wù)

          服務(wù)名又稱之為Service Qualifier,是一個(gè)人類可理解的英文標(biāo)識(shí)。所謂的服務(wù)注冊(cè)和發(fā)現(xiàn)就是在一個(gè)Service Qualifier下注冊(cè)一堆Endpoint。一個(gè)Endpoint就是一個(gè)ip+端口的網(wǎng)絡(luò)服務(wù)。就是一個(gè)非常類似DNS的名字服務(wù),其實(shí)DNS本身就可以做服務(wù)的注冊(cè)和發(fā)現(xiàn),用SRV類型記錄。

          名字服務(wù)的存在意義是簡(jiǎn)化服務(wù)的使用方,也就是主調(diào)方。過(guò)去在使用方的代碼里需要填入一堆ip加端口的配置,現(xiàn)在有了名字服務(wù)就可以只填一個(gè)服務(wù)名,實(shí)際在運(yùn)行時(shí)用服務(wù)名找到那一堆endpoint。

          從名字服務(wù)的角度來(lái)講并不比DNS要強(qiáng)多少。可能也就是通過(guò)“服務(wù)發(fā)現(xiàn)的lib”幫你把ip和端口都獲得了。而DNS默認(rèn)lib(也就是libc的getHostByName)只支持host獲取,并不能獲得port。當(dāng)然既然你都外掛了一個(gè)服務(wù)發(fā)現(xiàn)的lib了,和libc做對(duì)比也就優(yōu)勢(shì)公平了。

          lib提供的接口類似

          $endpoints = listServiceEnpoints('redis'); echo($endpoints[0]['ip]);

          甚至可以直接提供拼接url的接口

          $url = getServiceUrl('order', '/newOrder'); # http://xxx:yyy/newOrder

          比DNS更快的廣播速度

          傳統(tǒng)DNS的服務(wù)發(fā)現(xiàn)機(jī)制是緩存加上TTL過(guò)期時(shí)間,新的endpoint要傳播到使用方需要各級(jí)緩存的刷新。而且即便endpoint沒(méi)有更新,因?yàn)門TL到期了也要去上游刷新。為了減少網(wǎng)絡(luò)間定時(shí)刷新endpoint的流量,一般TTL都設(shè)得比較長(zhǎng)。

          而另外一個(gè)極端是gossip協(xié)議。所有人連接到所有人。一個(gè)服務(wù)的endpoint注冊(cè)了,可以通過(guò)gossip協(xié)議很快廣播到全部的節(jié)點(diǎn)上去。但是gossip的缺點(diǎn)是不基于訂閱的。無(wú)論我是不是使用這個(gè)服務(wù),我都會(huì)被動(dòng)地被gossip這個(gè)服務(wù)的endpoint。這樣就造成了無(wú)謂的網(wǎng)絡(luò)間帶寬的開銷。

          比較理想的更新方式是基于訂閱的。如果業(yè)務(wù)對(duì)某個(gè)服務(wù)進(jìn)行了發(fā)現(xiàn),那么緩存服務(wù)器就保持一個(gè)訂閱關(guān)系獲得最新的endpoint。這樣可以比定時(shí)刷新更及時(shí),也消耗更小。這個(gè)方面要黑一下etcd 2.0,它的基于http連接的watch方案要求每個(gè)watch獨(dú)占一個(gè)tcp連接,嚴(yán)重限制了watch的數(shù)量。而etcd 3.0基于gRPC的實(shí)現(xiàn)就修復(fù)了這個(gè)問(wèn)題。而consul的msgpack rpc從一開始就是復(fù)用tcp連接的。

          圖中的observer是類似的zookeeper的observer角色,是為了幫權(quán)威服務(wù)器分擔(dān)watch壓力的存在。也就是說(shuō)服務(wù)發(fā)現(xiàn)的核心其實(shí)是一個(gè)基于訂閱的層級(jí)消息網(wǎng)絡(luò)。服務(wù)注冊(cè)和發(fā)現(xiàn)并不承諾任何的一致性,它只是盡力地進(jìn)行分發(fā),并不保證所有的節(jié)點(diǎn)對(duì)一個(gè)服務(wù)的endpoint是哪些有一致的view,因?yàn)檫@并沒(méi)有價(jià)值。因?yàn)橐粋€(gè)qualifier下的多個(gè)endpoint by design 就是等價(jià)的,只要有足夠的endpint能夠承擔(dān)負(fù)載,對(duì)于abc三個(gè)endpoint具體是讓ab可見,還是bc可見,并無(wú)任何影響。

          服務(wù)發(fā)現(xiàn)agent的高可用

          DNS的方案是在每臺(tái)機(jī)器上裝一個(gè)dnsmasq做為緩存服務(wù)器。服務(wù)發(fā)現(xiàn)也是類似的,在每臺(tái)機(jī)器上有一個(gè)agent進(jìn)程。如果dnsmasq掛了,dns域名就會(huì)解析失敗,這樣的可用性是不夠的。服務(wù)發(fā)現(xiàn)的agent會(huì)把服務(wù)的配置和endpoint dump一份成本機(jī)的文件,服務(wù)發(fā)現(xiàn)的lib在無(wú)法訪問(wèn)agent的時(shí)候會(huì)降級(jí)去讀取本機(jī)的文件,從而保證足夠的可用性。當(dāng)然你要愿意搞什么共享內(nèi)存,也沒(méi)人阻攔。

          無(wú)法實(shí)現(xiàn)對(duì)dns服務(wù)器的降級(jí)。因?yàn)槟呐率墙导?jí)到 /etc/hosts 的實(shí)現(xiàn),其一個(gè)巨大的缺陷是 /etc/hosts 對(duì)于一個(gè)域名只能填一個(gè)ip,無(wú)法滿足擴(kuò)展性。而如果這一個(gè)ip填的是代理服務(wù)器的話,則失去了做服務(wù)發(fā)現(xiàn)的意義,都有代理了那就讓代理去發(fā)現(xiàn)服務(wù)好了。

          更進(jìn)一步,很多基于zk的方案是把服務(wù)發(fā)現(xiàn)的agent和業(yè)務(wù)進(jìn)程做到一個(gè)進(jìn)程里去了。所以就不需要擔(dān)心外掛的進(jìn)程是否還存活的問(wèn)題了。

          軟負(fù)載均衡

          這點(diǎn)上和DNS是類似的。理論來(lái)說(shuō)ttl設(shè)置為0的DNS服務(wù)器也可以起到負(fù)載均衡的作用。通過(guò)把權(quán)重分發(fā)到服務(wù)發(fā)現(xiàn)的agent上,可以讓業(yè)務(wù)“每次發(fā)現(xiàn)”的endpoint都不一樣,從而達(dá)到均衡負(fù)載的作用。權(quán)重的實(shí)現(xiàn)通過(guò)簡(jiǎn)單的隨機(jī)算法就可以實(shí)現(xiàn)。

          通過(guò)軟負(fù)載均衡理論上可以實(shí)現(xiàn)小流量,灰度地讓一個(gè)新的endpoint加入集群。也可以實(shí)現(xiàn)某一些endpoint承擔(dān)更大的調(diào)用量,以達(dá)到在線壓測(cè)的目的。

          不要小瞧了這么一點(diǎn)調(diào)權(quán)的功能。能夠中央調(diào)度,智能調(diào)度流量,是非常有用的。

          故障檢測(cè)(減endpoint)

          故障檢測(cè)其實(shí)是好做的。無(wú)非就是一個(gè)qualifier下掛了很多個(gè)endpoint,根據(jù)某種探活機(jī)制摘掉其中已經(jīng)無(wú)法提供正常服務(wù)的endpoint。摘除最好是軟摘除,這樣不會(huì)出現(xiàn)一個(gè)閃失把所有endpoint全摘掉的問(wèn)題。比如zookeeper的臨時(shí)節(jié)點(diǎn)就是硬摘除,不可取。

          本地探活

          在業(yè)務(wù)拿到endpoint之后,做完了rpc可以知道這個(gè)endpoint是否可用。這個(gè)時(shí)候?qū)ndpoint的健康狀態(tài)本地做一個(gè)投票累積。如果endpoint連續(xù)不可用則標(biāo)記為故障,被臨時(shí)摘除。過(guò)一段時(shí)間之后再重新放出小黑屋,進(jìn)行探活。這個(gè)過(guò)程和nginx對(duì)upstream的被動(dòng)探活是非常類似的。

          被動(dòng)探活的好處是非常敏感而且真實(shí)可信(不可用就是我不能調(diào)你,就是不可用),本地投票完了立即就可以判定故障。缺陷是每個(gè)主調(diào)方都需要獨(dú)立去進(jìn)行重復(fù)的判定。對(duì)于故障的endpoint,為了探活其是否存活需要以latency做為代價(jià)。

          被動(dòng)探活不會(huì)和具體的rpc機(jī)制綁定。無(wú)論是http還是thrift,無(wú)論是redis還是mysql,只要是網(wǎng)絡(luò)調(diào)用都可以通過(guò)rpc后投票的方式實(shí)現(xiàn)被動(dòng)探活。

          主動(dòng)探活

          主動(dòng)探活比較難做,而且效果也未必好:

          • 所有的主動(dòng)探活的問(wèn)題都在于需要指定如何去探測(cè)。不是tcp連接得上就算是能提供服務(wù)的。

          • 主動(dòng)探活受到網(wǎng)絡(luò)路由的影響,a可以訪問(wèn)b,并不帶表c也可以訪問(wèn)b

          • 主動(dòng)探測(cè)帶來(lái)額外的網(wǎng)絡(luò)開銷,探測(cè)不能過(guò)于頻繁

          • 主動(dòng)探測(cè)的發(fā)起者過(guò)少則容易對(duì)發(fā)起者產(chǎn)生很大的探活壓力,需要很高的性能

          本地主動(dòng)探活

          consul 的本機(jī)主動(dòng)探活是一個(gè)很有意思的組合。避免了主動(dòng)探活的一些缺點(diǎn),可以是被動(dòng)探活的一些補(bǔ)充。

          心跳探活

          無(wú)論是zookeeper那樣一來(lái)tcp連接的心跳(tcp連接的保持其實(shí)也是定時(shí)ttl發(fā)ip包保持的)。還是etcd,consul支持的基于ttl的心跳。都是類似的。

          gossip探活

          改進(jìn)版本的心跳。減少整體的網(wǎng)絡(luò)間通信量。

          服務(wù)注冊(cè)(加endpoint)

          服務(wù)endpoint注冊(cè)比endpoint摘除要難得多。

          無(wú)狀態(tài)服務(wù)注冊(cè)

          無(wú)狀態(tài)服務(wù)的注冊(cè)沒(méi)有任何約束。不管是中央管理服務(wù)注冊(cè)表,用web界面注冊(cè)。還是和部署系統(tǒng)聯(lián)動(dòng),在進(jìn)程啟動(dòng)時(shí)自動(dòng)注冊(cè)都可以做。

          有狀態(tài)服務(wù)的注冊(cè)

          有狀態(tài)服務(wù),比如redis的某個(gè)分片的master。其有兩個(gè)約束:

          • 一致性:同一個(gè)分片不能有兩個(gè)master

          • 可用性:分片不能沒(méi)有master,當(dāng)master掛了,要自發(fā)選舉出新的master

          除非是在數(shù)據(jù)層協(xié)議上做ack(paxos,raft)或者協(xié)議本身支持沖突解決(crdt),否則基于服務(wù)注冊(cè)來(lái)實(shí)現(xiàn)的分布式要么犧牲一致性,要么犧牲可用性。

          有狀態(tài)服務(wù)的注冊(cè)需求,和普通的注冊(cè)發(fā)現(xiàn)需求是本質(zhì)不同的。有狀態(tài)服務(wù)需要的是一個(gè)一致性決策機(jī)制,在consistency和availability之間取平衡。這個(gè)機(jī)制可以是外掛一個(gè)zookeeper,也可以是集群的數(shù)據(jù)節(jié)點(diǎn)自身做一個(gè)gossip的投票機(jī)制。

          而普通的注冊(cè)和發(fā)現(xiàn)就是要給廣播渠道,提供visibility。盡可能地讓endpoint曝光到其使用方那。不同的問(wèn)題需要的解決方案是不同的。對(duì)于有狀態(tài)服務(wù)的注冊(cè)表需要非常可靠的故障檢測(cè)機(jī)制,不能隨意摘除master。而用于廣播的服務(wù)注冊(cè)表則很隨意,故障檢測(cè)機(jī)制也可以做到盡可能錯(cuò)殺三千不放過(guò)一個(gè)。廣播的機(jī)制需要解決的問(wèn)題是大集群,怎么讓服務(wù)可見。而數(shù)據(jù)節(jié)點(diǎn)的選主要解決的是相對(duì)小的集群,怎么保持一致地情況下盡量可用。拿zookeeper的臨時(shí)節(jié)點(diǎn)這樣的機(jī)制放在大集群背景下,去做無(wú)狀態(tài)節(jié)點(diǎn)探活就是技術(shù)用錯(cuò)了地方。

          比如kafka,其有狀態(tài)服務(wù)部分的注冊(cè)和發(fā)現(xiàn)是用zookeeper實(shí)現(xiàn)的。而無(wú)狀態(tài)服務(wù)的注冊(cè)與發(fā)現(xiàn)是用data node自身提供集群的metadata來(lái)實(shí)現(xiàn)的。也就是消費(fèi)者和生產(chǎn)者是不需要從zookeeper里去集群分片信息的(也就是服務(wù)注冊(cè)表),而是從data node拿。這個(gè)時(shí)候data node其是充當(dāng)了一個(gè)服務(wù)發(fā)現(xiàn)的agent的作用。如果不用data node干這個(gè)活,我們把data node的內(nèi)容放到DNS里去,其實(shí)也是可以work的。只是這些存儲(chǔ)的給業(yè)務(wù)使用的客戶端lib已經(jīng)把這些邏輯寫好了,沒(méi)有人會(huì)去修改這個(gè)默認(rèn)行為了。

          但是廣播用途的服務(wù)注冊(cè)和發(fā)現(xiàn),比如DNS不是只提供visibility而不能保證任何consistency嗎?那我讀到分片信息是舊的,把slave當(dāng)master用了怎么辦呢?所有做得好的存儲(chǔ)分片選主方案,在data node上自己是知道自己的角色的。如果你使用錯(cuò)了,像redis cluster會(huì)回一個(gè)move指令,相當(dāng)于http 302讓你去別的地方做這個(gè)操作。kafka也是類似的。

          接入方式

          libc只支持getHostByName,任何更高級(jí)的服務(wù)發(fā)現(xiàn)都需要挖空心思想怎么簡(jiǎn)化接入。反正操作系統(tǒng)和語(yǔ)言自身的工具鏈上是沒(méi)有標(biāo)準(zhǔn)的支持的。每個(gè)公司都有一套自己的玩法。行業(yè)嚴(yán)重缺乏標(biāo)準(zhǔn)。

          無(wú)論哪種方式都是要修改業(yè)務(wù)代碼的。即便是用proxy方式接入,業(yè)務(wù)代碼里也得寫死固定的proxy ip才行。從可讀性的角度來(lái)說(shuō),固定proxy ip的可讀性是最差的,而用服務(wù)名或者域名是可讀性最好的。

          給每種語(yǔ)言寫lib

          最笨拙的方法,也是最保險(xiǎn)的。業(yè)務(wù)代碼直接寫服務(wù)名,獲得endpoint。

          探活也就是硬改各種rpc的lib,在調(diào)用后面加上投票的代碼。

          復(fù)用libc的getHostByName

          因?yàn)樗械恼Z(yǔ)言基本上都支持DNS域名解析。利用這一層的接口,用鉤子換掉lib的實(shí)際實(shí)現(xiàn)。業(yè)務(wù)代碼里寫域名,端口固定。

          socket的鉤子要難做得多,而且僅僅tcp4層探活也是不夠的(http 500了往往也要認(rèn)為對(duì)方是掛了的)。

          實(shí)際上考慮golang這種沒(méi)有l(wèi)ibc的,java這種自己緩存域名結(jié)果的,鉤子的方案其實(shí)沒(méi)有想得那么美好。

          本地 proxy

          proxy其實(shí)是一種簡(jiǎn)化服務(wù)發(fā)現(xiàn)接入方式的手段。業(yè)務(wù)可以不用知道服務(wù)名,而是使用固定的ip和端口訪問(wèn)。由proxy去做服務(wù)發(fā)現(xiàn),把請(qǐng)求轉(zhuǎn)給對(duì)方。

          http的proxy也很成熟,在proxy里對(duì)rpc結(jié)果進(jìn)行跳票也有現(xiàn)成的工具(比如nginx)。很多公司都是這種本地proxy的架構(gòu),比如airbnb,yelp,eleme,uber。當(dāng)用lib方式接業(yè)務(wù)接不動(dòng)的時(shí)候,大家都會(huì)往這條路上轉(zhuǎn)的。

          遠(yuǎn)程 proxy

          遠(yuǎn)程proxy的缺陷是固定ip導(dǎo)致了路由是固定的。這條路由上的所有路由器和交換機(jī)都是故障點(diǎn)。無(wú)法做到多條網(wǎng)絡(luò)路由冗余容錯(cuò)。而且需要用lvs做虛ip,也引入了運(yùn)維成本。

          而且遠(yuǎn)程proxy無(wú)法支持分區(qū)部署多套環(huán)境。除非引入bgp anycast這樣妖孽的實(shí)現(xiàn)。讓同一個(gè)ip在不同的idc里路由到不同的服務(wù)器。

          分區(qū)部署

          國(guó)內(nèi)大部分的網(wǎng)游都是分區(qū)分服的。這種架構(gòu)就是一種簡(jiǎn)化的存儲(chǔ)層數(shù)據(jù)分片。存儲(chǔ)層的數(shù)據(jù)分片一般都做得非常完善,可以做到key級(jí)別的搬遷(當(dāng)你訪問(wèn)key的時(shí)候告訴你我可以響應(yīng),還是告訴你搬遷到哪里去了),可以做到訪問(wèn)錯(cuò)了shard告訴你正確的shard在哪里。而分區(qū)部署往往是沒(méi)有這么完善的。

          所以為了支持分區(qū)部署。往往是給不同分區(qū)的服務(wù)區(qū)不同的服務(wù)名。比如模塊叫 chat,那么給hb_set(華北大區(qū))的chat模塊就命名為hb_set.chat,給hn_set(華南大區(qū))的chat模塊就命名為hn_set.chat。當(dāng)時(shí)如果我們是gamesvr模塊,需要訪問(wèn)chat模塊,代碼都是同一份,我怎么知道應(yīng)該訪問(wèn)hn_set.chat還是hb_set.chat呢?這個(gè)就需要讓gamesvr先知道自己所在的set,然后去訪問(wèn)同set下的其他模塊。

          again,這種分法也就是因?yàn)榉謪^(qū)部署做為一個(gè)大的組合系統(tǒng)沒(méi)法像一個(gè)孤立地存儲(chǔ)做得那么好。像kafka的broker,哪怕你訪問(wèn)的不是它的本地分片,它可以幫你去做proxy連接到正確的分片上。而我們沒(méi)法要求一個(gè)組合出來(lái)的業(yè)務(wù)系統(tǒng)也做到這么完備地程度。所以湊合著用吧。

          但是這種分法也有問(wèn)題。有一些模塊如果不是分區(qū)的,是全局的怎么辦?這個(gè)時(shí)候服務(wù)發(fā)現(xiàn)就得起一個(gè)路由表的作用,把不同分區(qū)的服務(wù)通過(guò)路由串起來(lái)。

          posted on 2016-12-15 15:46 jinfeng_wang 閱讀(310) 評(píng)論(0)  編輯  收藏 所屬分類: 2016-thinking
          主站蜘蛛池模板: 呼和浩特市| 博爱县| 峡江县| 墨脱县| 湖南省| 辽源市| 叙永县| 措美县| 长武县| 尼勒克县| 龙胜| 秦皇岛市| 肃宁县| 彰化县| 祁连县| 罗定市| 佳木斯市| 徐州市| 阿克陶县| 佛山市| 西丰县| 雷州市| 忻州市| 枣阳市| 离岛区| 盐城市| 中江县| 镇原县| 莱阳市| 贵定县| 浮山县| 嫩江县| 茌平县| 湘潭市| 南乐县| 徐州市| 白山市| 同仁县| 九龙坡区| 汨罗市| 延川县|