概述
在介紹LOOM之前, 說(shuō)一下我對(duì)一個(gè)互聯(lián)網(wǎng)公司多語(yǔ)言開(kāi)發(fā)環(huán)境的感受. 現(xiàn)在互聯(lián)網(wǎng)高速發(fā)展的時(shí)代,產(chǎn)品快速迭代會(huì)幫助我們快速產(chǎn)出產(chǎn)品原型,快速獲取用戶反饋,快速搶占市場(chǎng). 我們需要使用適合的語(yǔ)言去做一些特定領(lǐng)域的事情. 在小型開(kāi)發(fā)團(tuán)隊(duì)中,大概10人左右的開(kāi)放團(tuán)隊(duì)中, 開(kāi)發(fā)語(yǔ)言的選型不用過(guò)多的考慮管理成本,只要對(duì)產(chǎn)品的快速迭代有幫助的都可以使用.隨著團(tuán)隊(duì)逐漸壯大, 我們需要更多的考慮不同開(kāi)發(fā)團(tuán)隊(duì)之間的溝通成本, 招聘新人的培訓(xùn)成本, 由人員更替產(chǎn)生的交接成本等. 這個(gè)時(shí)候多語(yǔ)言開(kāi)發(fā)環(huán)境帶來(lái)的弊端逐漸顯現(xiàn)出來(lái), 我們需要在這中間找到一個(gè)平衡點(diǎn).
在公司發(fā)展到一定規(guī)模, 肯定會(huì)產(chǎn)生很多業(yè)務(wù)系統(tǒng), 這些業(yè)務(wù)系統(tǒng)需要互相之間進(jìn)行調(diào)用來(lái)完成協(xié)作. Thrift幫助我們?cè)诋悩?gòu)語(yǔ)言直接的溝通建立了橋梁. 只是簡(jiǎn)單的RPC調(diào)用在服務(wù)比較少的階段還能夠接受,但是隨著服務(wù)逐漸的增多,調(diào)用關(guān)系越來(lái)越復(fù)雜, 我們迫切的需要一個(gè)框架可以對(duì)所有服務(wù)進(jìn)行統(tǒng)一的管理,監(jiān)控,預(yù)警. 這是LOOM誕生的直接需求. “LOOM,織布機(jī)”,寓意可以將各個(gè)網(wǎng)狀服務(wù)進(jìn)行很好的治理.
LOOM的大部分需求直接采用的阿里巴巴的服務(wù)治理框架—DUBBO, 這里向DUBBO的所有開(kāi)發(fā)人員致敬, DUBBO的出現(xiàn)帶動(dòng)了SOA概念真正的落地, 相信國(guó)內(nèi)很多互聯(lián)網(wǎng)公司都在用DUBBO進(jìn)行服務(wù)治理. LOOM里面關(guān)于RPC相關(guān)的工作,我們直接使用了Twitter的finagle-thrift, 它可以幫助我們非常簡(jiǎn)單的寫(xiě)出高性能的異步的thrift調(diào)用. 另外我們可以直接通過(guò)zipkin來(lái)進(jìn)行異構(gòu)語(yǔ)言之間的調(diào)用鏈信息的收集.
loom服務(wù)治理需求
一期需求
在大規(guī)模服務(wù)化之前龄糊,應(yīng)用可能只是通過(guò)Thrift郑叠,簡(jiǎn)單的暴露和引用遠(yuǎn)程服務(wù)问慎,通過(guò)配置服務(wù)的URL地址進(jìn)行調(diào)用谬莹,通過(guò)HAProxy等軟件進(jìn)行負(fù)載均衡锈锤。服務(wù)治理平臺(tái)主要的需求可以通過(guò)下面幾點(diǎn)來(lái)體現(xiàn):
當(dāng)服務(wù)越來(lái)越多時(shí)级乐,服務(wù)URL配置管理變得非常困難嘁信,負(fù)載均衡器的單點(diǎn)壓力也越來(lái)越大彼念。此時(shí)需要一個(gè)服務(wù)注冊(cè)中心,動(dòng)態(tài)的注冊(cè)和發(fā)現(xiàn)服務(wù)腌逢,使服務(wù)的位置透明降淮。并通過(guò)在消費(fèi)方獲取服務(wù)提供方地址列表,實(shí)現(xiàn)軟負(fù)載均衡和Failover搏讶,降低對(duì)HAProxy負(fù)載均衡器的依賴佳鳖,也能減少部分成本。
當(dāng)進(jìn)一步發(fā)展媒惕,服務(wù)間依賴關(guān)系變得錯(cuò)蹤復(fù)雜系吩,甚至分不清哪個(gè)應(yīng)用要在哪個(gè)應(yīng)用之前啟動(dòng),架構(gòu)師都不能完整的描述應(yīng)用的架構(gòu)關(guān)系妒蔚。這時(shí)穿挨,需要自動(dòng)畫(huà)出應(yīng)用間的依賴關(guān)系圖月弛,以幫助架構(gòu)師理清理關(guān)系。
接著科盛,服務(wù)的調(diào)用量越來(lái)越大帽衙,服務(wù)的容量問(wèn)題就暴露出來(lái),這個(gè)服務(wù)需要多少機(jī)器支撐贞绵?什么時(shí)候該加機(jī)器厉萝?為了解決這些問(wèn)題.
(1). 要將服務(wù)現(xiàn)在每天的調(diào)用量,響應(yīng)時(shí)間榨崩,都統(tǒng)計(jì)出來(lái)谴垫,作為容量規(guī)劃的參考指標(biāo)。
(2). 要可以動(dòng)態(tài)調(diào)整權(quán)重, 開(kāi)啟,禁用服務(wù)母蛛,在線上弹渔,將某臺(tái)機(jī)器的權(quán)重一直加大,并在加大的過(guò)程中記錄響應(yīng)時(shí)間的變化溯祸,直到響應(yīng)時(shí)間到達(dá)閾值肢专,記錄此時(shí)的訪問(wèn)量,再以此訪問(wèn)量乘以機(jī)器數(shù)反推總?cè)萘俊?/p>
二期需求
一期項(xiàng)目中, 我們完成了服務(wù)提供者, 消費(fèi)者從啟動(dòng)開(kāi)始總調(diào)用量的統(tǒng)計(jì). 其實(shí)我們可以收集更詳細(xì)的信息, 可以細(xì)化到哪個(gè)應(yīng)用中的哪個(gè)方法的什么時(shí)候被調(diào)用,調(diào)用時(shí)長(zhǎng)是多少.通過(guò)這些信息,我們可以通過(guò)實(shí)時(shí)計(jì)算出某臺(tái)機(jī)器的某個(gè)方法響應(yīng)時(shí)間變慢了等操作.這些操作會(huì)分幾個(gè)部分:
原始數(shù)據(jù)的收集
方法名,類名,參數(shù)類型,參數(shù)個(gè)數(shù),開(kāi)始時(shí)間, 結(jié)束時(shí)間, 執(zhí)行時(shí)間, ip, port, pid,服務(wù)名基于原始數(shù)據(jù)的統(tǒng)計(jì), 每個(gè)方法從服務(wù)啟動(dòng)開(kāi)始的調(diào)用次數(shù).
界面展示:
(1). 針對(duì)某個(gè)服務(wù), 某個(gè)機(jī)器上的方法調(diào)用列表如下:
方法名.
每分鐘生產(chǎn)力,成功/失敗.
調(diào)用成功次數(shù)/失敗次數(shù).
(2). 匯總頁(yè)
統(tǒng)計(jì)服務(wù)方法相應(yīng)時(shí)間分別在5, 200ms, 500ms, 1000ms, 1500ms, 30000ms 方法列表
eg: 服務(wù)響應(yīng)時(shí)長(zhǎng)在 [0,200) 之內(nèi)的
方法名
ip:port
pid
所屬服務(wù)名
執(zhí)行次數(shù)
注冊(cè)中心Zookeeper數(shù)據(jù)結(jié)構(gòu)
serviceName 是定位服務(wù)的唯一標(biāo)識(shí), 各個(gè)語(yǔ)言程序以該名稱為基準(zhǔn), 該節(jié)點(diǎn)的value值將存儲(chǔ)該服務(wù)的描述信息, 也需要進(jìn)行encode操作.
-
serviceName下面會(huì)有四個(gè)靜態(tài)節(jié)點(diǎn)
(1). providers節(jié)點(diǎn)
所有服務(wù)提供者的url記錄在該節(jié)點(diǎn)下- URL樣例:
provider://192.168.1.103:12307/loom-test-providerA?application=loom-test-providerA&category=providers&owner=panda&pid=4078×tamp=1420708343991&version=1.0
- 參數(shù)說(shuō)明
- application: 服務(wù)所在程序的名稱, 只是在loom-admin中進(jìn)行分組管理.
- category : 標(biāo)識(shí)是提供者, 帶有該標(biāo)識(shí)的統(tǒng)一注冊(cè)到providers節(jié)點(diǎn)下.
- owner: 服務(wù)所有者.
- pid: 服務(wù)所在進(jìn)程的pid.
- timestamp: 服務(wù)啟動(dòng)注冊(cè)上zookeeper的時(shí)間點(diǎn).
- version: 服務(wù)的版本, 用來(lái)進(jìn)行灰度發(fā)布時(shí)版本不兼容也能發(fā)布的需求.
(2). consumers 節(jié)點(diǎn)
所有服務(wù)消費(fèi)者的url注冊(cè)到該節(jié)點(diǎn)下.- URL樣例:
consumer://192.168.1.107/loom-test-consumerA?application=loom-test-providerA&category=consumers&timeout=30000&pid=4078×tamp=1420708343991&version=1.0
- 參數(shù)說(shuō)明
- application: 消費(fèi)者所在程序的名稱, 只是在loom-admin中進(jìn)行分組管理.
- category : 標(biāo)識(shí)是消費(fèi)者, 帶有該標(biāo)識(shí)的統(tǒng)一注冊(cè)到consumers節(jié)點(diǎn)下.
- timeout: 消費(fèi)者調(diào)用服務(wù)多長(zhǎng)時(shí)間認(rèn)為是超時(shí),該參數(shù)根據(jù)各自客戶端需求來(lái)配置, 也可以沒(méi)有..
- pid: 消費(fèi)者所在進(jìn)程的pid.
- timestamp: 消費(fèi)者啟動(dòng)注冊(cè)上zookeeper的時(shí)間點(diǎn).
- version: 消費(fèi)者消費(fèi)服務(wù)的版本, 用來(lái)進(jìn)行灰度發(fā)布時(shí)版本不兼容也能發(fā)布的需求.
注: consumers下的臨時(shí)節(jié)點(diǎn)對(duì)loom的作用只是可以觀察到有多少個(gè)消費(fèi)者在消費(fèi)這個(gè)服務(wù).
(3). configurators 節(jié)點(diǎn)
該節(jié)點(diǎn)會(huì)記錄每個(gè)服務(wù)提供者的權(quán)重, 是靜態(tài)節(jié)點(diǎn), 即使服務(wù)丟失, 重新啟動(dòng)也不會(huì)丟失之前的設(shè)置.- URL樣例:
override://192.168.1.103:12307/loom-test-providerA?application=loom-test-providerA&category=configurators&pid=4078&dynamic=false&weight =100&version=1.0
- 參數(shù)說(shuō)明:
- application: 消費(fèi)者所在程序的名稱, 只是在loom-admin中進(jìn)行分組管理.
- category : 標(biāo)識(shí)是路由信息, 帶有該標(biāo)識(shí)的統(tǒng)一注冊(cè)到configurators節(jié)點(diǎn)下.
- disabled: 是否禁用該消費(fèi)者.
- pid: 消費(fèi)者所在進(jìn)程的pid.
- dynamic: 是否是動(dòng)態(tài)臨時(shí)節(jié)點(diǎn),當(dāng)注冊(cè)方退出時(shí)焦辅,數(shù)據(jù)依然保存在注冊(cè)中心博杖,必填。
- weight: 該服務(wù)提供者的權(quán)重值.
- version: 消費(fèi)者消費(fèi)服務(wù)的版本, 用來(lái)進(jìn)行灰度發(fā)布時(shí)版本不兼容也能發(fā)布的需求.
- enabled: 覆蓋規(guī)則是否生效筷登,可不填剃根,缺省生效:true. override節(jié)點(diǎn)是有覆蓋操作的,當(dāng)服務(wù)提供者啟動(dòng)時(shí)需要根據(jù)ip,port, version,作為判斷與provider下的節(jié)點(diǎn)信息進(jìn)行合并后提供給消費(fèi)者(只是提供給消費(fèi)者,而不重寫(xiě)provider節(jié)點(diǎn)).
(4). routers 節(jié)點(diǎn)
該節(jié)點(diǎn)會(huì)記錄該服務(wù)消費(fèi)者的路由信息, 目前只是記錄是否禁用該消費(fèi)者, 將來(lái)可能會(huì)針對(duì)該服務(wù)調(diào)用者設(shè)置服務(wù)的黑白名單等路由規(guī)則.- URL樣例:
route://192.168.1.107/loom-test-consumerA?application=loom-test-providerA&category=routers&disabled=false&pid=4078&dynamic=false&version=*
- 參數(shù)說(shuō)明:
- application: 消費(fèi)者所在程序的名稱, 只是在loom-admin中進(jìn)行分組管理.
- category : 標(biāo)識(shí)是路由信息, 帶有該標(biāo)識(shí)的統(tǒng)一注冊(cè)到routers節(jié)點(diǎn)下.
- disabled: 是否禁用該消費(fèi)者.
- pid: 消費(fèi)者所在進(jìn)程的pid.
- dynamic: 是否是動(dòng)態(tài)臨時(shí)節(jié)點(diǎn).
- version: 消費(fèi)者消費(fèi)服務(wù)的版本, 用來(lái)進(jìn)行灰度發(fā)布時(shí)版本不兼容也能發(fā)布的需求.
(5). 備注
所有url為了避免中文亂碼,關(guān)鍵字符, 統(tǒng)一encode編碼.
loom核心功能
服務(wù)注冊(cè)
服務(wù)啟動(dòng)時(shí)只需要設(shè)置啟動(dòng)端口, zookeeper地址, 即可通過(guò)loom將服務(wù)資源信息注冊(cè)到zookeeper中, 相同服務(wù)的提供者統(tǒng)一注冊(cè)到
/loom/serviceName/providers
節(jié)點(diǎn)下, 注冊(cè)節(jié)點(diǎn)必須為動(dòng)態(tài)臨時(shí)節(jié)點(diǎn).
負(fù)載均衡
- 服務(wù)消費(fèi)者,根據(jù)服務(wù)名稱到/loom/serviceName/providers下訂閱服務(wù)提供者列表. 根據(jù)weight實(shí)現(xiàn)負(fù)載均衡算法, 我們需要實(shí)現(xiàn)默認(rèn)的負(fù)載均衡算法為Random算法, 并可以根據(jù)權(quán)重的調(diào)整對(duì)各個(gè)提供者的命中率進(jìn)行調(diào)整.
- 服務(wù)消費(fèi)者需要注冊(cè)到
/loom/serviceName/consumers
節(jié)點(diǎn)下, 注冊(cè)節(jié)點(diǎn)為動(dòng)態(tài)臨時(shí)節(jié)點(diǎn).
服務(wù)治理
- 我們通過(guò)loom-admin對(duì)某個(gè)服務(wù)提供者進(jìn)行權(quán)重的設(shè)置, 用來(lái)控制某個(gè)服務(wù)的訪問(wèn)量.
- 可以在loom-admin中禁用啟用某個(gè)服務(wù)消費(fèi)者.
- 可以統(tǒng)計(jì)使用的服務(wù)列表, 消費(fèi)服務(wù)的程序列表.
loom 測(cè)試用例
功能測(cè)試
權(quán)重負(fù)載均衡測(cè)試:
場(chǎng)景描述:
(1) 四臺(tái)服務(wù)提供者機(jī)器
(2) 權(quán)重都為100時(shí), 發(fā)起10萬(wàn)次請(qǐng)求, 每臺(tái)服務(wù)器都在25000左右, 誤差不超過(guò)100次
(3) 發(fā)起10萬(wàn)次請(qǐng)求, 四臺(tái)服務(wù)器權(quán)重和命中次數(shù)分別為: 400:53223 ; 200:26864 ; 100:13269 ; 50:6644, 誤差不超過(guò)100次服務(wù)提供者禁用, 啟用測(cè)試:
如果禁用某個(gè)提供者, 那么根據(jù)剩下的所有提供者的權(quán)重重新計(jì)算負(fù)載均衡值.服務(wù)提供者和消費(fèi)者version測(cè)試:
在消費(fèi)者的配置中指定需要消費(fèi)的服務(wù)版本, 那么運(yùn)行程序時(shí),則是獲取該版本的服務(wù)提供者, 并且只根據(jù)相同版本的權(quán)重進(jìn)行負(fù)載均衡.服務(wù)消費(fèi)者的禁用,啟用測(cè)試:
如果禁用之后, 消費(fèi)者不應(yīng)該再訪問(wèn)該提供者, 并且權(quán)重會(huì)根據(jù)現(xiàn)有提供者進(jìn)行調(diào)整.某臺(tái)服務(wù)提供者下線, 消費(fèi)者會(huì)自動(dòng)更新提供者列表,然后并且更新
服務(wù)消費(fèi)者的禁用, 啟用.
多注冊(cè)中心驗(yàn)證
增加計(jì)數(shù)統(tǒng)計(jì),只統(tǒng)計(jì)每分鐘的生產(chǎn)率和調(diào)用失敗率,該指標(biāo)只是可以讓管理員直觀的看到服務(wù)啟動(dòng)之后生產(chǎn)力
性能測(cè)試(注: 未經(jīng)說(shuō)明的都是基于scala-2.9.2版本進(jìn)行的壓測(cè))
- A、B方法堵塞測(cè)試
場(chǎng)景描述:
(1) 同一個(gè)接口中存在兩個(gè)方法前方,A和B狈醉。A方法有延遲,B方法正常惠险。
(2) 對(duì)該接口(返回值是future層)添加aop代理
(3) 在aop中對(duì)返回值進(jìn)行處理苗傅。
(4) 調(diào)用A方法時(shí)正常,并發(fā)調(diào)用B方法時(shí)班巩,線程在10個(gè)左右的時(shí)候渣慕,總會(huì)出現(xiàn)個(gè)別線程的返回值在等待A方法執(zhí)行完畢。
問(wèn)題原因:
在對(duì)future坐Aop代理時(shí)抱慌,方法返回值是future類型逊桦。此類型是不堵塞線程的。在aop代理類中認(rèn)為此線程已經(jīng)結(jié)束抑进,實(shí)際上又用該線程等待future的返回值强经。等調(diào)用B的時(shí)候帮掉,有可能用重復(fù)使用該線程族扰,導(dǎo)致堵塞历涝。
解決辦法:
(1) 不要在future層做代理鸽照。應(yīng)該保持finagle的干凈性。如果有需求码秉,建議在實(shí)現(xiàn)類中做代理。
(2) 如果非要在future中加入代理鸡号,請(qǐng)使用future的回調(diào)函數(shù)addEventListener转砖。
修改后,此現(xiàn)象消失鲸伴。
- 單服務(wù),單機(jī)器壓力測(cè)試
場(chǎng)景描述:
(1) 通過(guò)loom對(duì)外提供thrift服務(wù).
(2) 測(cè)試服務(wù)中的一個(gè)方法, 該方法只打印方法的入?yún)? 如果打印成功, 則可以確認(rèn)為方法調(diào)用成功.
(3) 壓測(cè)機(jī)器基本配置, cpu: 2G 4核, memory: 16G, 服務(wù)JVM啟動(dòng)內(nèi)存: 256M
(4) 模擬500個(gè)獨(dú)立用戶, 壓測(cè)兩個(gè)小時(shí).
結(jié)果:
每秒相應(yīng)6000多次, 單次請(qǐng)求耗時(shí):60ms
- 單服務(wù),單機(jī)器增加newrelic后的壓力測(cè)試
場(chǎng)景描述:
(1) 通過(guò)loom對(duì)外提供thrift服務(wù), newrelic監(jiān)控其中一個(gè)執(zhí)行打印日志的方法.
(2) 測(cè)試服務(wù)中的一個(gè)方法, 該方法只打印方法的入?yún)? 如果打印成功, 則可以確認(rèn)為方法調(diào)用成功.
(3) 壓測(cè)機(jī)器基本配置, cpu: 2G 4核, memory: 16G, 服務(wù)JVM啟動(dòng)內(nèi)存: 256M
(4) 模擬500個(gè)獨(dú)立用戶, 壓測(cè)兩個(gè)小時(shí).
結(jié)果:
每秒相應(yīng)6000多次, 單次請(qǐng)求耗時(shí):80ms
- 單服務(wù),2臺(tái)機(jī)器提供一個(gè)服務(wù)
場(chǎng)景描述:
(1) 增加zipkin監(jiān)控
(2) jvm內(nèi)存上限為256M
(3) jvm內(nèi)存上限為1024M
結(jié)果: 不同的jvm設(shè)置,都會(huì)造成內(nèi)存溢出, 通過(guò)分析堆日志,是zipkin線程引起的.下圖為,通過(guò)分析內(nèi)存溢出時(shí)的堆棧信息得出的結(jié)果圖.之后又進(jìn)行了一次針對(duì)zipkin的壓測(cè), jvm 啟動(dòng)內(nèi)存為1G,具體數(shù)據(jù)如下:- 模擬1個(gè)用戶, 2000tps, 不會(huì)出現(xiàn)full gc
- 模擬5個(gè)用戶, 7500tps, 不會(huì)出現(xiàn)full gc
- 模擬10個(gè)用戶, 300tps, 頻繁出現(xiàn)full gc, 系統(tǒng)大部分時(shí)間都在出來(lái)full gc
- 模擬500個(gè)用戶, 服務(wù)瞬間full gc,不會(huì)接受其他請(qǐng)求.
-
單服務(wù), 2臺(tái)服務(wù)器提供這個(gè)服務(wù)
場(chǎng)景描述:
(1) 增加newrelic監(jiān)控
(2) 在server端啟動(dòng)finagle服務(wù)時(shí),添加了hostConnectionMaxLifeTime(new Duration(300 * Duration.NanosPerSecond()))
每個(gè)鏈接最大存活時(shí)間為5分鐘, 這是每次loadrunner到五分鐘時(shí)都會(huì)停止.
(3) 壓測(cè)16小時(shí).
(4) 客戶端線程池大小為300
(5) 壓測(cè)機(jī)器為8核cpu
(6) loadrunner模擬500獨(dú)立用戶訪問(wèn)結(jié)果: 請(qǐng)求rpm 60萬(wàn)/分鐘, 每臺(tái)服務(wù)器接收請(qǐng)求為30萬(wàn), cpu大約在10% jvm內(nèi)存在256M.
如果是長(zhǎng)連接請(qǐng)求, 每個(gè)鏈接會(huì)有最大存活時(shí)間, 到時(shí),服務(wù)端會(huì)關(guān)閉,所以服務(wù)端不要設(shè)置鏈接生命周期.
長(zhǎng)時(shí)間壓測(cè)時(shí),會(huì)出現(xiàn)處理請(qǐng)求的數(shù)量逐漸下降.cpu在10%左右.
cpu一直空閑,而處理請(qǐng)求的數(shù)量在逐漸下降,可能和調(diào)用的方法為空有關(guān), 下面在方法中做一些簡(jiǎn)單的計(jì)算.在三個(gè)小時(shí)內(nèi)請(qǐng)求數(shù)比較穩(wěn)定, 之后會(huì)逐漸下降.
下圖為loadrunner壓測(cè)趨勢(shì)結(jié)果
-
多服務(wù)壓力測(cè)試
場(chǎng)景描述:
(1) 服務(wù)調(diào)用鏈為: start=>a=>b=>c,d=>a, 具體如下圖:
(2) 機(jī)器配置8核,32G內(nèi)存,兩臺(tái)
(3) 增加newrelic監(jiān)控
(4) loadrunner模擬500獨(dú)立用戶訪問(wèn)
(5) 客戶端線程池大小設(shè)置為8個(gè)(因?yàn)槭?核cpu)
之所以從300變?yōu)?個(gè),是因?yàn)殚_(kāi)啟線程池大小為300個(gè)時(shí),調(diào)用的服務(wù)執(zhí)行的是空方法時(shí),接受請(qǐng)求的訪問(wèn)量會(huì)越來(lái)越大.這時(shí)查看服務(wù)器的cpu和內(nèi)存消耗都不太高,這是懷疑可能是方法執(zhí)行太快,8核cpu處理300個(gè)線程,這樣就會(huì)出現(xiàn)在cpu內(nèi)部頻繁出現(xiàn)線程切換, 從而降低機(jī)器的整體吞吐量. 我們?yōu)榱私档蚦pu上下文切換次數(shù),將線程總數(shù)限制在8個(gè). 后來(lái)為了驗(yàn)證是否是這個(gè)問(wèn)題,又寫(xiě)了一個(gè)服務(wù),該服務(wù)方法只做了簡(jiǎn)單的100次加計(jì)算,消費(fèi)該服務(wù)的線程設(shè)為8, 進(jìn)行500用戶壓測(cè)2個(gè)小時(shí),機(jī)器整體吞吐量維持在 5000/s.但是在這樣的條件下,2個(gè)小時(shí)之后又出現(xiàn)了吞吐量逐漸下降,但是并不多.因?yàn)槭?個(gè)線程,整體吞吐量并沒(méi)有上去.我繼續(xù)調(diào)整線程數(shù), 通常多線程可以設(shè)為cpu核數(shù)的4倍,這時(shí)設(shè)為32個(gè)線程. 壓測(cè)兩個(gè)小時(shí),這時(shí)吞吐量在10000/s,整體性能比較平穩(wěn).下圖為多服務(wù)壓測(cè)時(shí),服務(wù)上cpu每秒上下文切換次數(shù). 因?yàn)橥掏铝勘容^大, cpu頻繁進(jìn)行上下文切換, 具體是什么引起的上下文切換,還沒(méi)有具體跟蹤.現(xiàn)在初步懷疑是因?yàn)閒inagle進(jìn)行通信時(shí)頻繁調(diào)用系統(tǒng)的方法引起的.
- 壓測(cè)64小時(shí)
結(jié)果: 這次壓測(cè),發(fā)現(xiàn)前3個(gè)小時(shí),可以維持在10000/s 的生產(chǎn)力, 之后61個(gè)小時(shí)逐漸下降,到48小時(shí)時(shí)維持在3300/s的生產(chǎn)力.這期間我一直觀察服務(wù)器的負(fù)載.cpu隊(duì)列并沒(méi)有顯示出大量的任務(wù)堆積,也就是說(shuō)服務(wù)器狀態(tài)良好,沒(méi)有滿負(fù)荷運(yùn)轉(zhuǎn). 這時(shí)開(kāi)始懷疑是壓測(cè)入口的loadrunner機(jī)器出的問(wèn)題.開(kāi)始驗(yàn)證,我通過(guò)其他機(jī)器對(duì)loom集群添加額外的壓力,通過(guò)newrelic監(jiān)控發(fā)現(xiàn)服務(wù)器的生產(chǎn)力又上去了. 從而推斷,生產(chǎn)力持續(xù)下降是因?yàn)閘oadrunner輸出的壓力持續(xù)下降引起的. 這時(shí)找壓測(cè)人員進(jìn)行問(wèn)題排查,發(fā)現(xiàn)運(yùn)行l(wèi)oadrunner的機(jī)器負(fù)載過(guò)高,并且滿負(fù)載的時(shí)間點(diǎn)正好是loom集群生產(chǎn)力下降的時(shí)間點(diǎn).
多服務(wù)壓測(cè), 使cpu達(dá)到100%
觀察zookeeper是否還能獲取服務(wù)的提供者和消費(fèi)者的心跳.基于scala-2.10的性能測(cè)試:
場(chǎng)景描述: 1000/s 的訪問(wèn)壓力
機(jī)器配置8核, 4G內(nèi)存.
服務(wù)器所在的jvm狀況如下圖:
對(duì)象回收正常, 老年代執(zhí)行了唯一一次回收, 還是我手動(dòng)進(jìn)行的GC.
下圖是loadrunner壓測(cè)8小時(shí)統(tǒng)計(jì)圖
下圖是loadrunner壓測(cè)14小時(shí)統(tǒng)計(jì)圖
下兩圖為loadrunner壓測(cè)48小時(shí)統(tǒng)計(jì)圖
結(jié)果: 在每秒1000訪問(wèn)量的壓力下 48小時(shí)連續(xù)測(cè)試, scala-2.10版的loom表現(xiàn)正常, 響應(yīng)時(shí)間, 內(nèi)存回收都表現(xiàn)正常.
loom接入說(shuō)明
前提:服務(wù)提供者和服務(wù)消費(fèi)著都必須是基于finagle生成的代碼府蔗,并且依賴finagle和thrift相關(guān)的包。
-
在pom文件中加入如下代碼:
<dependency> <groupId>com.xxx</groupId> <artifactId>loom</artifactId> <version>1.1.5-SNAPSHOT</version> </dependency>
<!-- 如果是基于scala-2.10則引入--> <dependency> <groupId>com.xxx</groupId> <artifactId>loom</artifactId> <version>1.1.5-2.10-SNAPSHOT</version> </dependency>
添加配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:loom="http://xxx.com/schema/loom"
default-autowire="byName" default-lazy-init="true"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://xxx.com/schema/loom http://xxx.com/schema/loom/loom.xsd">
<!--id是此應(yīng)用的應(yīng)用名稱汞窗,請(qǐng)修改為自己系統(tǒng)的名稱, 可以將該應(yīng)用的負(fù)責(zé)人填寫(xiě)在owner屬性中-->
<loom:application id="XXXXXXXXX" owner="xxxx" />
<!-- 如果配置該節(jié)點(diǎn)則是開(kāi)啟了簡(jiǎn)單的統(tǒng)計(jì)功能,可以統(tǒng)計(jì)某一個(gè)服務(wù)提供者和消費(fèi)者的每分鐘成功調(diào)用測(cè)試, 失敗的調(diào)用次數(shù),建議此功能打開(kāi) -->
<loom:count id="count" ip="#{configManager.getRedisConfig('trade_public_redis','14.1').ip}"
port="#{configManager.getRedisConfig('trade_public_redis','14.1').port}"
pwd="#{configManager.getRedisConfig('trade_public_redis','14.1').pwd}"/>
<!-- 如果配置該節(jié)點(diǎn)則是開(kāi)啟了zipkin監(jiān)控功能,默認(rèn)是注視掉的,如果需要請(qǐng)打開(kāi),采樣率默認(rèn)是0.01 -->
<!-- <loom:monitor id="monitor01" url="#{configManager.getConfigValue('trade_public_zipkin','zipkinUrl')}"
port="#{configManager.getConfigValue('trade_public_zipkin','zipkinPort')}"
samplerate="0.01"/> -->
<!--zookeeper集群地址, 如果address為空則默認(rèn)不實(shí)例化該注冊(cè)中心,用以測(cè)試環(huán)境脫離注冊(cè)中心時(shí)使用,必須配置-->
<loom:registry id="zk01" protocol="zookeeper"
address="#{configManager.getConfigValue('trade_public_zookeeper','zk01')}"
timeout="20000"/>
<!-- 如果想作為一個(gè)task不提供服務(wù), 但是需要注冊(cè)到zookeeper中, 則可以是不配置ref和api,端口,threads, 建議port設(shè)為0000, 這時(shí)服務(wù)名一定不能是其他真實(shí)服務(wù)的名稱,可以有一個(gè)約定標(biāo)識(shí), 比如:xxx-task -->
<!--服務(wù)提供者配置, 如果上面的registry中的address設(shè)置為空,則該服務(wù)將脫離注冊(cè)中心,不會(huì)注冊(cè)到注冊(cè)中心,可以直接通過(guò)端口對(duì)外提供服務(wù), -->
<!-- threads 建議是cpu核數(shù)的四倍, 服務(wù)端提供服務(wù)時(shí),服務(wù)超時(shí)時(shí)間, 默認(rèn)30秒 -->
<loom:service id="XXXXX"
ref=“xXXImpl"
api="com.xxx.finagle.thrift.XXXX.XXXServ"
port="12301"
threads="32"
version="1.0"
owner="XXXX"
registry="zk01"
timeout="30000"
remark="這里填寫(xiě)對(duì)該服務(wù)的描述"/>
<!--消費(fèi)其他服務(wù)配置,這里surl為具體的服務(wù)地址(比如: finagle://192.168.10.5:12306),如果填寫(xiě)具體地址則會(huì)脫離注冊(cè)中心, 該場(chǎng)景是為了簡(jiǎn)化測(cè)試時(shí)脫離注冊(cè)中心 -->
<loom:reference id="xxxxReference" sid="XXXXXXX"
api="com.xxx.finagle.thrift.XXXX.XXXServ" version="1.0"
registry="zk01" surl="" timeout="30000"/>
</beans>
在資源文件夾下添加loomContext.xml并在啟動(dòng)時(shí)加載姓赤,文件內(nèi)容如上
添加此配置后工程啟動(dòng)時(shí)會(huì)自動(dòng)將服務(wù)注冊(cè)到注冊(cè)中心,并且自動(dòng)啟動(dòng)相關(guān)服務(wù)仲吏,如果原來(lái)代碼中有啟動(dòng)服務(wù)的不铆,請(qǐng)關(guān)閉。
-
配置文件說(shuō)明:
loom:application
id:應(yīng)用程序的唯一id
owner: 應(yīng)用負(fù)責(zé)人, 可以是簡(jiǎn)單的字符loom:registry裹唆,注冊(cè)中心誓斥,可以配置多個(gè)。
id:注冊(cè)中心的id
protocol:注冊(cè)中心類型许帐,目前只支持“zookeeper”
address:注冊(cè)中心地址列表
timeout:注冊(cè)中心請(qǐng)求超時(shí)時(shí)間loom:count, 開(kāi)啟服務(wù)提供者和調(diào)用者的每分鐘生產(chǎn)力統(tǒng)計(jì),該統(tǒng)計(jì)信息需要redis進(jìn)行發(fā)送.
id: count的id,唯一標(biāo)識(shí),每一個(gè)應(yīng)用只能配置一個(gè)count
ip: 用來(lái)發(fā)送統(tǒng)計(jì)信息的服務(wù)器ip,可以認(rèn)為是redis的ip
port: redis的端口
pwd: redis的密碼loom:monitor, 開(kāi)啟finagle的zipkin跟蹤.
id: monitor的id, 唯一標(biāo)識(shí), 每個(gè)應(yīng)用只配置一個(gè)zipkin的跟蹤
url: zipkin的地址
port: zipkin的端口
samplerate: 采樣率.如果不填,默認(rèn)為0.01loom:service 對(duì)外暴漏的服務(wù)信息劳坑,可以配置多個(gè),配置多個(gè)時(shí)端口號(hào)不能重復(fù)
id:服務(wù)的唯一id
ref:提供服務(wù)的實(shí)現(xiàn)類名稱成畦,第一個(gè)字母小寫(xiě)距芬。此實(shí)現(xiàn)類必須繼承api里面配置的服務(wù)iface接口。并且要在spring容器中注入
如果不設(shè)置該設(shè)置,或則設(shè)置為空,則會(huì)認(rèn)為是類似task的應(yīng)用,不會(huì)啟動(dòng)finagle服務(wù),只是將該應(yīng)用注冊(cè)到zookeeper中
api:提供服務(wù)的服務(wù)類名循帐,此類是finagle生成的thrift接口文件全路徑框仔。
port:對(duì)外提供的服務(wù)的端口號(hào)
threads:提供服務(wù)的線程池大小
version:服務(wù)的版本號(hào)
owner:該服務(wù)的負(fù)責(zé)人
registry:該服務(wù)注冊(cè)的注冊(cè)中心id
remark:對(duì)該服務(wù)的簡(jiǎn)單描述
timeout: 服務(wù)端提供服務(wù)時(shí),服務(wù)超時(shí)時(shí)間, 默認(rèn)30秒loom:reference 調(diào)用外部服務(wù)配置≈粞可以配置多個(gè)
id:消費(fèi)者在spring容器中的唯一id
sid:目標(biāo)服務(wù)的服務(wù)id
api:消費(fèi)服務(wù)的服務(wù)類名存和,此類是finagle生成的代碼
version:調(diào)用目標(biāo)服務(wù)的版本號(hào)
timeout:調(diào)用服務(wù)的超時(shí)時(shí)間(在Await.result中也可以進(jìn)行設(shè)置), 默認(rèn)是30秒
registry:此消費(fèi)者要去哪個(gè)注冊(cè)中心獲取服務(wù)提供者列表
surl:服務(wù)提供者地址。如果配置此值衷旅,此消費(fèi)者直接消費(fèi)該地址得服務(wù), 如果設(shè)置了該配置,則不會(huì)從zookeeper獲取服務(wù)提供者地址,而是會(huì)直接訪問(wèn)這個(gè)地址的服務(wù)
owner: 該消費(fèi)者負(fù)責(zé)人,默認(rèn)為空, 直接取application中的owner顯示, 該屬性是配合agent使用,直接使用loom的用戶不需要關(guān)系和填寫(xiě). -
注意:
從之前的通過(guò)lvs, HAProxy等軟負(fù)載去消費(fèi)服務(wù)的方式, 遷移到通過(guò)loom來(lái)消費(fèi)服務(wù)的方式的過(guò)程, 建議分為兩個(gè)過(guò)程- 指定surl地址為之前的軟負(fù)載地址, 運(yùn)行一段時(shí)間.
- 在第一個(gè)步驟穩(wěn)定運(yùn)行一段時(shí)間之后, 再將surl置空, 通過(guò)loom來(lái)消費(fèi)服務(wù), 如果生產(chǎn)環(huán)境有問(wèn)題, 請(qǐng)立即將surl再指回軟負(fù)載地址.
- 對(duì)外提供服務(wù)
新建實(shí)現(xiàn)類捐腿,實(shí)現(xiàn)接口 loom:service節(jié)點(diǎn)中api配置的值的.iface 例如:
此類名必須和loom:service節(jié)點(diǎn)里面的ref值保持一致。(ref值第一個(gè)字母要小寫(xiě))@Service public class MemberServiceImpl implements MemberServ.Iface
- 調(diào)用外部服務(wù)
在需要調(diào)用的類的上方添加如下代碼柿顶,注入thrift接口茄袖。@Resource(name = "orderServReference") OrderServ.ServiceIface orderServReference;
name值必須是 loom:reference節(jié)點(diǎn)的id。尖括號(hào)里面是要調(diào)用的服務(wù)類名
使用方法如下:
可以通過(guò)orderServReference直接獲取thrift接口中的方法.
同步調(diào)用:
String res = Await.result(orderServReference.方法名(參數(shù)));
同步調(diào)用可以設(shè)置超時(shí)時(shí)間:
String res = Await.result(orderServReference.方法名(參數(shù)),new Duration(10 * Duration.NanosPerSecond()));//十秒超時(shí)
異步調(diào)用:
```
orderServReference.方法名(參數(shù)).addEventListener(new FutureEventListener<String>() {//這里的泛型為返回值類型
//如果成功,res為該方法的返回值, 可以拿到該值做后續(xù)的業(yè)務(wù)處理
@Override
public void onSuccess(String res) {
}
//如果失敗
@Override
public void onFailure(Throwable throwable) {
}
});
```
- 記錄某個(gè)服務(wù)提供者方法的每分鐘的評(píng)價(jià)調(diào)用時(shí)長(zhǎng)
我們?cè)趕pring配置文件中又一個(gè)service結(jié)點(diǎn),其中的ref屬性中的業(yè)務(wù)邏輯實(shí)現(xiàn)類.在該類中的方法上添加@InvokeTimeTrace 則loom可以和eagleye配合收集該方法每分鐘的調(diào)用次數(shù),平均時(shí)長(zhǎng)等.
-
在任意一次RPC調(diào)用過(guò)程中添加附加信息到調(diào)用鏈中
使用場(chǎng)景: 如果服務(wù)A 調(diào)用了服務(wù)B, 這個(gè)時(shí)候B作為服務(wù)提供者出現(xiàn)了一些問(wèn)題(也可以傳遞一些普通非異常的附加信息), 我需要將該異常作為附屬信息添加到調(diào)用鏈中.
那么就可以在B的業(yè)務(wù)邏輯中添加如下代碼.// 記錄異常信息 com.xxx.loom.zipkin.Trace.recordFault("creatOrder", "NullPointException ..."); // 記錄普通附加信息(也可以通過(guò)該方法記錄異常信息) com.xxx.loom.zipkin.Trace.recordBinary("testBinary", "hello Binary, test b");
loom-admin使用說(shuō)明
loom-admin項(xiàng)目是loom中的子項(xiàng)目, 用來(lái)對(duì)注冊(cè)中心中的服務(wù)提供者, 消費(fèi)者進(jìn)行管理. 下面是針對(duì)該管理中心的核心功能介紹
- 首頁(yè)應(yīng)用關(guān)系展示
- 服務(wù)提供者管理
當(dāng)某個(gè)服務(wù)集群?jiǎn)?dòng)時(shí), 會(huì)將各自服務(wù)提供者的地址注冊(cè)到zookeeper上, 這時(shí)我們可以在提供者維度中看到所有服務(wù)的提供者.如上圖:
(1) 在大促前期, 如果我們需要估算生產(chǎn)服務(wù)器的負(fù)載容量, 我們可以通過(guò)對(duì)某一個(gè)服務(wù)提供者進(jìn)行倍權(quán)操作,并隨時(shí)觀察該服務(wù)器的負(fù)載情況和服務(wù)的提供情況,當(dāng)出現(xiàn)性能瓶頸時(shí),這時(shí)將是該服務(wù)提供者的最大容量,我們可以根據(jù)該容量反推出我們需要多少服務(wù)器來(lái)應(yīng)對(duì)大促情況下的高并發(fā)請(qǐng)求.
(2) 當(dāng)發(fā)現(xiàn)某一個(gè)服務(wù)器的負(fù)載明顯高于其他服務(wù)提供者,則可以將該服務(wù)提供者進(jìn)行半權(quán)操作, 以此來(lái)降低該服務(wù)器的服務(wù)請(qǐng)求.降低提供服務(wù)出錯(cuò)的幾率.
(3) 當(dāng)要對(duì)服務(wù)進(jìn)行發(fā)版或者發(fā)現(xiàn)嚴(yán)重問(wèn)題時(shí), 需要先在管理界面上將該服務(wù)提供者進(jìn)行禁用, 然后再停止服務(wù), 這樣可以避免因?yàn)榉?wù)已經(jīng)停止,還有鏈接訪問(wèn)該服務(wù)并造成出錯(cuò)的可能.
(4) 可以直接對(duì)某一個(gè)字段進(jìn)行實(shí)時(shí)檢索.
- 服務(wù)消費(fèi)者管理
我們可以在消費(fèi)者維度查看某個(gè)服務(wù)都被哪些應(yīng)用消費(fèi)了,并且可以定位到具體的ip,應(yīng)用名. 當(dāng)發(fā)現(xiàn)某個(gè)服務(wù)消費(fèi)者出現(xiàn)了問(wèn)題,可以通過(guò)管理界面來(lái)屏蔽某個(gè)具體的消費(fèi)者.
另外我們?yōu)榱烁奖愕倪M(jìn)行查詢各個(gè)服務(wù)直接的關(guān)系,還提供了其他維度的查詢,比如: 應(yīng)用維度; 機(jī)器維度; 服務(wù)維度.
針對(duì)某一個(gè)生產(chǎn)者實(shí)例或者消費(fèi)者實(shí)例后面的總調(diào)用詳情,可以鏈接進(jìn)入具體的方法調(diào)用詳情,如下圖:
- 可以總覽消費(fèi)者或者生產(chǎn)者中所有方法執(zhí)行時(shí)長(zhǎng)的統(tǒng)計(jì), 如下圖:
-
服務(wù)監(jiān)控狀況統(tǒng)計(jì),可以查看所有服務(wù)里面具體方法最近十分鐘的健康情況,并可以獲取服務(wù)中各個(gè)方法的歷史調(diào)用情況
-
某個(gè)應(yīng)用實(shí)例日志檢索
-
提供獲取某個(gè)服務(wù)的某個(gè)方法的歷史調(diào)用曲線(精確到每分鐘的成功,失敗次數(shù))
-
收集Loom產(chǎn)生的RPC調(diào)用鏈信息,并給Loom提供該信息檢索(雙擊某行有問(wèn)題的Span記錄可以查看某次調(diào)用鏈詳情,了解具體的網(wǎng)絡(luò)情況和業(yè)務(wù)執(zhí)行情況)
loom常見(jiàn)問(wèn)題匯總
- Java提供服務(wù), ruby開(kāi)啟zipkin功能,則無(wú)法調(diào)用成功, 關(guān)閉則可以調(diào)用成功.
類似異常如下:WARNING: Unhandled exception in connection with /192.168.1.12:12343 , shutting down connection java.lang.NoSuchMethodError: org.apache.commons.codec.binary.Base64.encodeAsString([B)Ljava/lang/String; at com.twitter.util.Base64StringEncoder$class.encode(StringEncoder.scala:17) at com.twitter.util.Base64StringEncoder$.encode(StringEncoder.scala:25) at com.twitter.finagle.zipkin.thrift.RawZipkinTracer$$anonfun$createLogEntries$1.apply(RawZipkinTracer.scala:110) at com.twitter.finagle.zipkin.thrift.RawZipkinTracer$$anonfun$createLogEntries$1.apply(RawZipkinTracer.scala:106) at com.twitter.util.Try$.apply(Try.scala:13) at com.twitter.util.Future$.apply(Future.scala:79) at com.twitter.finagle.zipkin.thrift.RawZipkinTracer.createLogEntries(RawZipkinTracer.scala:106) at com.twitter.finagle.zipkin.thrift.RawZipkinTracer.logSpan(RawZipkinTracer.scala:118) at com.twitter.finagle.zipkin.thrift.RawZipkinTracer.mutate(RawZipkinTracer.scala:142) at com.twitter.finagle.zipkin.thrift.RawZipkinTracer.annotate(RawZipkinTracer.scala:234) at com.twitter.finagle.zipkin.thrift.RawZipkinTracer.record(RawZipkinTracer.scala:153) at com.twitter.finagle.zipkin.thrift.SamplingTracer.record(ZipkinTracer.scala:85) at com.twitter.finagle.tracing.Trace$$anonfun$uncheckedRecord$1.apply(Trace.scala:207) at com.twitter.finagle.tracing.Trace$$anonfun$uncheckedRecord$1.apply(Trace.scala:207) at scala.collection.LinearSeqOptimized$class.foreach(LinearSeqOptimized.scala:59) at scala.collection.immutable.List.foreach(List.scala:76) at com.twitter.finagle.tracing.Trace$.uncheckedRecord(Trace.scala:207) at com.twitter.finagle.tracing.Trace$.record(Trace.scala:248) at com.twitter.finagle.thrift.ThriftServerTracingFilter$$anonfun$apply$2$$anonfun$apply$4.apply(ThriftServerFramedCodec.scala:237) at com.twitter.finagle.thrift.ThriftServerTracingFilter$$anonfun$apply$2$$anonfun$apply$4.apply(ThriftServerFramedCodec.scala:236) at com.twitter.util.Future$$anonfun$map$1$$anonfun$apply$4.apply(Future.scala:823) at com.twitter.util.Try$.apply(Try.scala:13) at com.twitter.util.Future$.apply(Future.scala:79) at com.twitter.util.Future$$anonfun$map$1.apply(Future.scala:823) at com.twitter.util.Future$$anonfun$map$1.apply(Future.scala:823) at com.twitter.util.Future$$anonfun$flatMap$1.apply(Future.scala:786) at com.twitter.util.Future$$anonfun$flatMap$1.apply(Future.scala:785) at com.twitter.util.Promise$Transformer.liftedTree1$1(Promise.scala:95) at com.twitter.util.Promise$Transformer.k(Promise.scala:95) at com.twitter.util.Promise$Transformer.apply(Promise.scala:104) at com.twitter.util.Promise$Transformer.apply(Promise.scala:86) at com.twitter.util.Promise$$anon$2.run(Promise.scala:326) at com.twitter.concurrent.LocalScheduler$Activation.run(Scheduler.scala:186) at com.twitter.concurrent.LocalScheduler$Activation.submit(Scheduler.scala:157) at com.twitter.concurrent.LocalScheduler.submit(Scheduler.scala:212)
可能存在java中的codec包版本不兼容問(wèn)題, 解決辦法:
在java服務(wù)提供方程序中的pom.xml中引入:
<dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.8</version> </dependency>
- loom-admin 每隔6秒報(bào)類似decode錯(cuò)誤時(shí),類似如下:
Failed to retry subscribe {admin://192.168.1.100/h5wap-test?category=providers,consumers,routers,configurators&check=false&classifier=*&enabled=*&group=*&interface=h5wap-test&version=*=[com.xxx.loom.admin.model.RegistryServerSync@5fc625c3]}, waiting for again, cause: Failed to subscribe admin://192.168.1.100/h5wap-test?category=providers,consumers,routers,configurators&check=false&classifier=*&enabled=*&group=*&interface=h5wap-test&version=* to zookeeper zookeeper://192.168.10.66:2181?application=loom-admin&backup=192.168.10.57:2181,192.168.10.58:2181&timeout=20000, cause: URLDecoder: Illegal hex characters in escape (%) pattern - For input string: "co" #####[LOOM]##### - [com.xxx.loom.registry.zookeeper.ZookeeperRegistry]
com.xxx.loom.core.rpc.RpcException: Failed to subscribe admin://192.168.1.100/h5wap-test?category=providers,consumers,routers,configurators&check=false&classifier=*&enabled=*&group=*&interface=h5wap-test&version=* to zookeeper zookeeper://192.168.10.66:2181?application=loom-admin&backup=192.168.10.57:2181,192.168.10.58:2181&timeout=20000, cause: URLDecoder: Illegal hex characters in escape (%) pattern - For input string: "co"
請(qǐng)檢查相應(yīng)服務(wù)節(jié)點(diǎn)下的所有節(jié)點(diǎn)url進(jìn)行encode時(shí)是否正確.
如果服務(wù)消費(fèi)者,報(bào)類似: "服務(wù)消費(fèi)者, 通過(guò)服務(wù)名: [promotion-serv] 中的 thrift api [com.xxx.finagle.thrift.promotion.PromotionServ] 的代理調(diào)用失敗, 請(qǐng)確認(rèn)服務(wù)提供者是否啟動(dòng)成功 !!!!"
這可能是沒(méi)有服務(wù)提供者.
也可能是網(wǎng)絡(luò)問(wèn)題,導(dǎo)致消費(fèi)者不能訪問(wèn)到提供者.-
finagle在代理中使用aop的問(wèn)題
場(chǎng)景描述:
(1)同一個(gè)接口中存在兩個(gè)方法嘁锯,A和B宪祥。A方法有延遲聂薪,B方法正常。
(2)對(duì)該接口(返回值是future層)添加aop代理
(3)在aop中對(duì)返回值進(jìn)行處理蝗羊。
(4)調(diào)用A方法時(shí)正常藏澳,并發(fā)調(diào)用B方法時(shí),線程在10個(gè)左右的時(shí)候耀找,總會(huì)出現(xiàn)個(gè)別線程的返回值在等待A方法執(zhí)行完畢翔悠。問(wèn)題原因:
在對(duì)future坐Aop代理時(shí),方法返回值是future類型野芒。此類型是不堵塞線程的蓄愁。在aop代理類中認(rèn)為此線程已經(jīng)結(jié)束,實(shí)際上又用該線程等待future的返回值狞悲。等調(diào)用B的時(shí)候撮抓,有可能用重復(fù)使用該線程,導(dǎo)致堵塞摇锋。解決辦法:
(1)不要在future層做代理丹拯。應(yīng)該保持finagle的干凈性。如果有需求荸恕,建議在實(shí)現(xiàn)類中做代理咽笼。
(2)如果非要在future中加入代理,請(qǐng)使用future的回調(diào)函數(shù)addEventListener戚炫。
修改后剑刑,此現(xiàn)象消失。
-
spring版本問(wèn)題:
loom中使用的3.2.7双肤,我們大多數(shù)應(yīng)用使用的是3.2.1施掏, 還有少部分使用的更早的版本;
消息系統(tǒng)使用的是3.1.0茅糜,接入時(shí)七芭,啟動(dòng)spring, 報(bào)錯(cuò)<loom:registry id="zk01" protocol="zookeeper" address="192.168.101.194:2181" timeout="20000"/>
中timeout沒(méi)有g(shù)etter, setter升級(jí)spring版本后解決
service名稱匹配問(wèn)題
server端注冊(cè)service和客戶端引用service的名稱要一致:
- redis 版本沖突問(wèn)題
如果出現(xiàn)了redis的commons-pool問(wèn)題, 請(qǐng)升級(jí)redis的jedis的jar版本.
loom中默認(rèn)是支持redis-2.1.0 及以上版本.
如果你使用的是spring-data-redis ,請(qǐng)升級(jí)到如下版本
升級(jí)完之后, 配置參數(shù)少了 maxActive, maxWait 變成 maxWaitMillis<dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>1.4.2.RELEASE</version> </dependency>
如果你使用的是jedis, 請(qǐng)升級(jí)到redis-2.1.0及以上版本.
通過(guò)loom調(diào)用服務(wù), 參數(shù)不能傳null的問(wèn)題
請(qǐng)升級(jí)到1.1.3版本服務(wù)消費(fèi)端調(diào)用服務(wù)時(shí), 出現(xiàn)了Unknown異常
請(qǐng)升級(jí)到1.1.3版本
因?yàn)閒inagle中的服務(wù)調(diào)用是Funture, 異步調(diào)用, 在1.1.3之前的版本, 在調(diào)用服務(wù)的代理中, 我會(huì)主動(dòng)將target置為null, 如果服務(wù)調(diào)用過(guò)長(zhǎng), 異步返回時(shí)獲取不到target,會(huì)引起這個(gè)問(wèn)題-
服務(wù)啟動(dòng)不報(bào)錯(cuò), 也沒(méi)有打印成功啟動(dòng)日志
該服務(wù)的service id 名稱跟spring容器中其他類實(shí)例的名稱沖突, 導(dǎo)致服務(wù)實(shí)例被覆蓋.類似日志如下:
Overriding bean definition for bean 'expressWayBillStatService': replacing [Generic bean: class [com.xxx.loom.config.ServiceBean]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null] with [Generic bean: class [com.xxx.express.stat.service.impl.ExpressWayBillStatServiceImpl]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [/home/webuser/workarea/bill/target/classes/spring/ApplicationContext-dao.xml]]
-
finagle-thrift和finagle-zipkin版本不一致的問(wèn)題
項(xiàng)目用的finagle-thrift_2.9.2-6.10.0.jar和finagle-zipkin_2.9.2-6.8.1.jar包.
loom中引用的是finagle-thrift_2.9.2-6.20.0.jar和finagle-zipkin_2.9.2-6.20.0.jar包
在配置了loom后該項(xiàng)目去掉了對(duì)finagle-thrift_2.9.2-6.10.0.jar包的依賴,而沒(méi)有去掉對(duì)finagle-zipkin_2.9.2-6.8.1.jar的依賴
所以打包后出現(xiàn)finagle-thrift_2.9.2-6.20.0.jar和finagle-zipkin_2.9.2-6.8.1.jar版本不一致的問(wèn)題,導(dǎo)致下面異常.SEVERE: A server service threw an exception scala.MatchError: ServiceName(freightInsuranceServ) (of class com.twitter.finagle.tracing.Annotation$ServiceName) at com.twitter.finagle.zipkin.thrift.RawZipkinTracer.record(RawZipkinTracer.scala:142) at com.twitter.finagle.zipkin.thrift.SamplingTracer.record(ZipkinTracer.scala:85) at com.twitter.finagle.tracing.Trace$$anonfun$uncheckedRecord$1.apply(Trace.scala:234) at com.twitter.finagle.tracing.Trace$$anonfun$uncheckedRecord$1.apply(Trace.scala:234) at scala.collection.LinearSeqOptimized$class.foreach(LinearSeqOptimized.scala:59) at scala.collection.immutable.List.foreach(List.scala:76)
解決辦法:
在該系統(tǒng)中刪掉對(duì)finagle-zipkin_2.9.2-6.8.1.jar的依賴,全部用loom中對(duì)這兩個(gè)包的依賴.
- spring配置文件中, 通過(guò)imago獲取服務(wù)名時(shí), 在loo:service節(jié)點(diǎn)中的id屬性使用el表達(dá)式報(bào)錯(cuò)的問(wèn)題.
具體異常,類似如下:
Caused by: org.xml.sax.SAXParseException; lineNumber: 39; columnNumber: 22; cvc-datatype-valid.1.2.1: '#{configManager.getConfigValue('xxx_public_service_name','trade_subject_serv_2.0')}' 不是 'NCName' 的有效值。
at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:198)
at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.error(ErrorHandlerWrapper.java:134)
at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:437)
at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:368)
at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:325)
at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator$XSIErrorReporter.reportError(XMLSchemaValidator.java:458)
at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.reportSchemaError(XMLSchemaValidator.java:3237)
at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.processOneAttribute(XMLSchemaValidator.java:2832)
at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.processAttributes(XMLSchemaValidator.java:2769)
at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.handleStartElement(XMLSchemaValidator.java:2056)
at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.emptyElement(XMLSchemaValidator.java:766)
at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.scanStartElement(XMLNSDocumentScannerImpl.java:355)
如果有類似異常, 是說(shuō)xml驗(yàn)證時(shí), ID類型的屬性不支持特殊字符(el表達(dá)式是以"#"開(kāi)頭).
請(qǐng)升級(jí)到1.1.5-SNAPSHOT及以上版本
- 服務(wù)端拋出類似 InvocationTargetException 的異常.
在利用 Method 對(duì)象的 invoke 方法調(diào)用目標(biāo)對(duì)象的方法時(shí), 若在目標(biāo)對(duì)象的方法內(nèi)部拋出異常, 會(huì)拋出 InvocationTargetException 異常, 該異常包裝了目標(biāo)對(duì)象的方法內(nèi)部拋出異常.
這可能是服務(wù)端的業(yè)務(wù)方法往上拋出了異常, 這個(gè)異常被loom框架捕獲,但是沒(méi)有將異常堆棧詳情打印出來(lái), 在1.1.6-SNAPSHOT 及以上版本會(huì)針對(duì)該異常做出特殊處理, 會(huì)將targetException的詳細(xì)信息輸出.
- 用thrift短連接調(diào)用finagle出現(xiàn)客戶端接收不到返回值的bug
描述:服務(wù)端用loom提供服務(wù),客戶端用thrift短連接調(diào)用,調(diào)用一段時(shí)間(隨機(jī))會(huì)出現(xiàn)客戶端讀取不到返回值的情況
原因:loom里面用到的commons-codec包和commons-httpclient里面用到包版本不一致造成的.
解決辦法:將commons-httpclient里面的包排除掉commons-codec解決問(wèn)題.
## loom版本變更列表
1.2.1-SNAPSHOT
1. 統(tǒng)一方法調(diào)用統(tǒng)計(jì)信息格式為JSON格式
2. 修復(fù)統(tǒng)計(jì)信息中的bug
1.2.0-SNAPSHOT
1. 增加每個(gè)方法的調(diào)用總次數(shù),一分鐘生產(chǎn)力, 平均調(diào)用時(shí)長(zhǎng)的統(tǒng)計(jì)功能.
2. 增加消費(fèi)者維度,調(diào)用遠(yuǎn)程thrift接口的時(shí)長(zhǎng)統(tǒng)計(jì), 并將超過(guò)200ms的調(diào)用打印warn日志,可以結(jié)合eagleye進(jìn)行實(shí)時(shí)預(yù)警.
3. 增加服務(wù)提供者,每個(gè)api接口的調(diào)用時(shí)長(zhǎng)統(tǒng)計(jì), 并將超過(guò)200ms的調(diào)用打印warn日志,可以結(jié)合eagleye進(jìn)行實(shí)時(shí)預(yù)警.
4. 給application節(jié)點(diǎn)增加owner屬性, 可以簡(jiǎn)單標(biāo)識(shí)應(yīng)該的負(fù)責(zé)人, 同時(shí)可以在loom-admin中消費(fèi)者維度看到每個(gè)消費(fèi)者的負(fù)責(zé)人.
5. 在reference結(jié)點(diǎn)上添加owner屬性(只是為了配合agent使用,直接只用loom的用戶不需要關(guān)心).
6. 在消費(fèi)者啟動(dòng)邏輯中添加是否是agent判斷,如果為agent,則不管是否啟動(dòng)成功都進(jìn)行注冊(cè)(直接使用loom的用戶不需要關(guān)系該細(xì)節(jié))
7. 首頁(yè)關(guān)系圖展示, 添加直屬關(guān)系檢索
8. 增加服務(wù)端提供服務(wù)時(shí),服務(wù)超時(shí)時(shí)間, 默認(rèn)30秒
1.1.6-SNAPSHOT
1. 修復(fù)loom:reference節(jié)點(diǎn)中timeout無(wú)效的bug
2. 修復(fù)loom服務(wù)端業(yè)務(wù)方法拋出異常,只顯示InvocationTargetException,并沒(méi)有詳細(xì)信息的問(wèn)題
1.1.5-SNAPSHOT
1. 修復(fù)loom:service 的id不能使用el表達(dá)式的問(wèn)題
1.1.4-SNAPSHOT
1. 修復(fù)通過(guò)redis獲取心跳時(shí), 當(dāng)redis不穩(wěn)定時(shí),不能自動(dòng)回復(fù)的問(wèn)題
1.1.3
1. 修復(fù)服務(wù)調(diào)用時(shí)間過(guò)長(zhǎng)拋出Unknown錯(cuò)誤的問(wèn)題
1.1.2
1. 修復(fù)消費(fèi)者調(diào)用服務(wù)方法時(shí), 不能傳遞為null的參數(shù).
1.1.1
1. 應(yīng)用kill時(shí)主動(dòng)刪除zookeeper上的providers下的節(jié)點(diǎn), 提高其他watch該節(jié)點(diǎn)的響應(yīng).
2. 增加服務(wù)從啟動(dòng)開(kāi)始成功調(diào)用的總次數(shù)和失敗總次數(shù).
3. 如果調(diào)用失敗, 記錄具體請(qǐng)求哪個(gè)provider請(qǐng)求失敗.
4. 修復(fù)消費(fèi)者列表,提供者列表詳情×顯示X的bug.
---
文章題圖: 來(lái)自冒險(xiǎn)島數(shù)據(jù)庫(kù)系統(tǒng)