<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    jinfeng_wang

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

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

    遠程服務依賴

    依賴分為兩種,本地的lib依賴,遠程的服務依賴。

    本地的依賴其實是很復雜的問題。從操作系統的apt-get,到各種語言的pip, npm。包管理是無窮無盡的問題。但是所有的本地依賴已經被docker終結了。無論是依賴了什么,全部給你打包起來,從操作系統開始。除了你依賴的cpu指令集沒法給你打包成鏡像了,其他都給打包了。

    docker之后,依賴問題就只剩遠程服務依賴的問題。這個問題就是服務注冊發現與調度需要解決的問題。從軟件工程的角度來說,所有的解耦問題都可以通過抽取lib的方式解決。lib也可以實現獨立的發布周期,良好定義的IDL接口。所以如果非必要,請不要把lib依賴升級成網絡服務依賴的角度。除非是從非功能性需求的角度,比如獨立的擴縮容,支持scale out這些。很多時候微服務是因為基于lib的工具鏈支持不全,使得大家義無反顧地走上了拆分網絡服務的不歸路。

    名字服務

    服務名又稱之為Service Qualifier,是一個人類可理解的英文標識。所謂的服務注冊和發現就是在一個Service Qualifier下注冊一堆Endpoint。一個Endpoint就是一個ip+端口的網絡服務。就是一個非常類似DNS的名字服務,其實DNS本身就可以做服務的注冊和發現,用SRV類型記錄。

    名字服務的存在意義是簡化服務的使用方,也就是主調方。過去在使用方的代碼里需要填入一堆ip加端口的配置,現在有了名字服務就可以只填一個服務名,實際在運行時用服務名找到那一堆endpoint。

    從名字服務的角度來講并不比DNS要強多少。可能也就是通過“服務發現的lib”幫你把ip和端口都獲得了。而DNS默認lib(也就是libc的getHostByName)只支持host獲取,并不能獲得port。當然既然你都外掛了一個服務發現的lib了,和libc做對比也就優勢公平了。

    lib提供的接口類似

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

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

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

    比DNS更快的廣播速度

    傳統DNS的服務發現機制是緩存加上TTL過期時間,新的endpoint要傳播到使用方需要各級緩存的刷新。而且即便endpoint沒有更新,因為TTL到期了也要去上游刷新。為了減少網絡間定時刷新endpoint的流量,一般TTL都設得比較長。

    而另外一個極端是gossip協議。所有人連接到所有人。一個服務的endpoint注冊了,可以通過gossip協議很快廣播到全部的節點上去。但是gossip的缺點是不基于訂閱的。無論我是不是使用這個服務,我都會被動地被gossip這個服務的endpoint。這樣就造成了無謂的網絡間帶寬的開銷。

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

    圖中的observer是類似的zookeeper的observer角色,是為了幫權威服務器分擔watch壓力的存在。也就是說服務發現的核心其實是一個基于訂閱的層級消息網絡。服務注冊和發現并不承諾任何的一致性,它只是盡力地進行分發,并不保證所有的節點對一個服務的endpoint是哪些有一致的view,因為這并沒有價值。因為一個qualifier下的多個endpoint by design 就是等價的,只要有足夠的endpint能夠承擔負載,對于abc三個endpoint具體是讓ab可見,還是bc可見,并無任何影響。

    服務發現agent的高可用

    DNS的方案是在每臺機器上裝一個dnsmasq做為緩存服務器。服務發現也是類似的,在每臺機器上有一個agent進程。如果dnsmasq掛了,dns域名就會解析失敗,這樣的可用性是不夠的。服務發現的agent會把服務的配置和endpoint dump一份成本機的文件,服務發現的lib在無法訪問agent的時候會降級去讀取本機的文件,從而保證足夠的可用性。當然你要愿意搞什么共享內存,也沒人阻攔。

    無法實現對dns服務器的降級。因為哪怕是降級到 /etc/hosts 的實現,其一個巨大的缺陷是 /etc/hosts 對于一個域名只能填一個ip,無法滿足擴展性。而如果這一個ip填的是代理服務器的話,則失去了做服務發現的意義,都有代理了那就讓代理去發現服務好了。

    更進一步,很多基于zk的方案是把服務發現的agent和業務進程做到一個進程里去了。所以就不需要擔心外掛的進程是否還存活的問題了。

    軟負載均衡

    這點上和DNS是類似的。理論來說ttl設置為0的DNS服務器也可以起到負載均衡的作用。通過把權重分發到服務發現的agent上,可以讓業務“每次發現”的endpoint都不一樣,從而達到均衡負載的作用。權重的實現通過簡單的隨機算法就可以實現。

    通過軟負載均衡理論上可以實現小流量,灰度地讓一個新的endpoint加入集群。也可以實現某一些endpoint承擔更大的調用量,以達到在線壓測的目的。

    不要小瞧了這么一點調權的功能。能夠中央調度,智能調度流量,是非常有用的。

    故障檢測(減endpoint)

    故障檢測其實是好做的。無非就是一個qualifier下掛了很多個endpoint,根據某種探活機制摘掉其中已經無法提供正常服務的endpoint。摘除最好是軟摘除,這樣不會出現一個閃失把所有endpoint全摘掉的問題。比如zookeeper的臨時節點就是硬摘除,不可取。

    本地探活

    在業務拿到endpoint之后,做完了rpc可以知道這個endpoint是否可用。這個時候對endpoint的健康狀態本地做一個投票累積。如果endpoint連續不可用則標記為故障,被臨時摘除。過一段時間之后再重新放出小黑屋,進行探活。這個過程和nginx對upstream的被動探活是非常類似的。

    被動探活的好處是非常敏感而且真實可信(不可用就是我不能調你,就是不可用),本地投票完了立即就可以判定故障。缺陷是每個主調方都需要獨立去進行重復的判定。對于故障的endpoint,為了探活其是否存活需要以latency做為代價。

    被動探活不會和具體的rpc機制綁定。無論是http還是thrift,無論是redis還是mysql,只要是網絡調用都可以通過rpc后投票的方式實現被動探活。

    主動探活

    主動探活比較難做,而且效果也未必好:

    • 所有的主動探活的問題都在于需要指定如何去探測。不是tcp連接得上就算是能提供服務的。

    • 主動探活受到網絡路由的影響,a可以訪問b,并不帶表c也可以訪問b

    • 主動探測帶來額外的網絡開銷,探測不能過于頻繁

    • 主動探測的發起者過少則容易對發起者產生很大的探活壓力,需要很高的性能

    本地主動探活

    consul 的本機主動探活是一個很有意思的組合。避免了主動探活的一些缺點,可以是被動探活的一些補充。

    心跳探活

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

    gossip探活

    改進版本的心跳。減少整體的網絡間通信量。

    服務注冊(加endpoint)

    服務endpoint注冊比endpoint摘除要難得多。

    無狀態服務注冊

    無狀態服務的注冊沒有任何約束。不管是中央管理服務注冊表,用web界面注冊。還是和部署系統聯動,在進程啟動時自動注冊都可以做。

    有狀態服務的注冊

    有狀態服務,比如redis的某個分片的master。其有兩個約束:

    • 一致性:同一個分片不能有兩個master

    • 可用性:分片不能沒有master,當master掛了,要自發選舉出新的master

    除非是在數據層協議上做ack(paxos,raft)或者協議本身支持沖突解決(crdt),否則基于服務注冊來實現的分布式要么犧牲一致性,要么犧牲可用性。

    有狀態服務的注冊需求,和普通的注冊發現需求是本質不同的。有狀態服務需要的是一個一致性決策機制,在consistency和availability之間取平衡。這個機制可以是外掛一個zookeeper,也可以是集群的數據節點自身做一個gossip的投票機制。

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

    比如kafka,其有狀態服務部分的注冊和發現是用zookeeper實現的。而無狀態服務的注冊與發現是用data node自身提供集群的metadata來實現的。也就是消費者和生產者是不需要從zookeeper里去集群分片信息的(也就是服務注冊表),而是從data node拿。這個時候data node其是充當了一個服務發現的agent的作用。如果不用data node干這個活,我們把data node的內容放到DNS里去,其實也是可以work的。只是這些存儲的給業務使用的客戶端lib已經把這些邏輯寫好了,沒有人會去修改這個默認行為了。

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

    接入方式

    libc只支持getHostByName,任何更高級的服務發現都需要挖空心思想怎么簡化接入。反正操作系統和語言自身的工具鏈上是沒有標準的支持的。每個公司都有一套自己的玩法。行業嚴重缺乏標準。

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

    給每種語言寫lib

    最笨拙的方法,也是最保險的。業務代碼直接寫服務名,獲得endpoint。

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

    復用libc的getHostByName

    因為所有的語言基本上都支持DNS域名解析。利用這一層的接口,用鉤子換掉lib的實際實現。業務代碼里寫域名,端口固定。

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

    實際上考慮golang這種沒有libc的,java這種自己緩存域名結果的,鉤子的方案其實沒有想得那么美好。

    本地 proxy

    proxy其實是一種簡化服務發現接入方式的手段。業務可以不用知道服務名,而是使用固定的ip和端口訪問。由proxy去做服務發現,把請求轉給對方。

    http的proxy也很成熟,在proxy里對rpc結果進行跳票也有現成的工具(比如nginx)。很多公司都是這種本地proxy的架構,比如airbnb,yelp,eleme,uber。當用lib方式接業務接不動的時候,大家都會往這條路上轉的。

    遠程 proxy

    遠程proxy的缺陷是固定ip導致了路由是固定的。這條路由上的所有路由器和交換機都是故障點。無法做到多條網絡路由冗余容錯。而且需要用lvs做虛ip,也引入了運維成本。

    而且遠程proxy無法支持分區部署多套環境。除非引入bgp anycast這樣妖孽的實現。讓同一個ip在不同的idc里路由到不同的服務器。

    分區部署

    國內大部分的網游都是分區分服的。這種架構就是一種簡化的存儲層數據分片。存儲層的數據分片一般都做得非常完善,可以做到key級別的搬遷(當你訪問key的時候告訴你我可以響應,還是告訴你搬遷到哪里去了),可以做到訪問錯了shard告訴你正確的shard在哪里。而分區部署往往是沒有這么完善的。

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

    again,這種分法也就是因為分區部署做為一個大的組合系統沒法像一個孤立地存儲做得那么好。像kafka的broker,哪怕你訪問的不是它的本地分片,它可以幫你去做proxy連接到正確的分片上。而我們沒法要求一個組合出來的業務系統也做到這么完備地程度。所以湊合著用吧。

    但是這種分法也有問題。有一些模塊如果不是分區的,是全局的怎么辦?這個時候服務發現就得起一個路由表的作用,把不同分區的服務通過路由串起來。

    posted on 2016-12-15 15:46 jinfeng_wang 閱讀(308) 評論(0)  編輯  收藏 所屬分類: 2016-thinking
    主站蜘蛛池模板: 黄页网站在线看免费| 国产成人高清精品免费鸭子 | 成人国产网站v片免费观看| 国产成人精品日本亚洲| 欧洲乱码伦视频免费| 一级做a爰片性色毛片免费网站| 亚洲熟女一区二区三区| 18勿入网站免费永久| 一级女性全黄久久生活片免费| 中文字幕亚洲精品资源网| 国产午夜免费秋霞影院| 久操视频免费观看| 精品久久久久久久久亚洲偷窥女厕| 亚洲中文字幕无码一区| 无码专区永久免费AV网站 | 国产成人免费ā片在线观看| 东方aⅴ免费观看久久av| 亚洲欧美国产欧美色欲| 日韩亚洲人成在线综合日本| 国产高清免费在线| 88av免费观看入口在线| 特级毛片全部免费播放a一级| 亚洲综合久久1区2区3区| 亚洲午夜精品第一区二区8050| 又粗又大又黑又长的免费视频| 亚洲高清免费视频| 亚洲日本VA午夜在线电影| 亚洲色欲色欲综合网站| 亚洲国产成人久久综合野外| 无码专区永久免费AV网站| 免费一级毛片无毒不卡| 五级黄18以上免费看| 亚洲综合色一区二区三区| 亚洲视频在线观看免费视频| 亚洲精品亚洲人成在线观看| 免费一区二区视频| 国内外成人免费视频| 男女免费观看在线爽爽爽视频 | 亚洲欧美日韩一区二区三区在线| 亚洲视频在线观看免费视频| 国产精品亚洲精品日韩已满|