服務(wù)治理(LOOM)

概述

在介紹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)

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&timestamp=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&timestamp=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)求.
zipkin內(nèi)存溢出
  • 單服務(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é)果


    16小時(shí)壓測(cè)
  • 多服務(wù)壓力測(cè)試
    場(chǎng)景描述:
    (1) 服務(wù)調(diào)用鏈為: start=>a=>b=>c,d=>a, 具體如下圖:


    壓測(cè)調(diào)用關(guān)系圖

    (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è)調(diào)用關(guān)系圖
  • 壓測(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狀況如下圖:

壓測(cè)調(diào)用關(guān)系圖

壓測(cè)調(diào)用關(guān)系圖

對(duì)象回收正常, 老年代執(zhí)行了唯一一次回收, 還是我手動(dòng)進(jìn)行的GC.
下圖是loadrunner壓測(cè)8小時(shí)統(tǒng)計(jì)圖


壓測(cè)調(diào)用關(guān)系圖

下圖是loadrunner壓測(cè)14小時(shí)統(tǒng)計(jì)圖
壓測(cè)調(diào)用關(guān)系圖

下兩圖為loadrunner壓測(cè)48小時(shí)統(tǒng)計(jì)圖


壓測(cè)調(diào)用關(guān)系圖

壓測(cè)調(diào)用關(guān)系圖

結(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.01

    loom: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 例如:
    @Service
    public class MemberServiceImpl implements MemberServ.Iface
    
    此類名必須和loom:service節(jié)點(diǎn)里面的ref值保持一致。(ref值第一個(gè)字母要小寫(xiě))
  • 調(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)系展示
    應(yīng)用調(diào)用關(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)用詳情,如下圖:

調(diào)用詳情入口
方法級(jí)別的調(diào)用詳情
  • 可以總覽消費(fèi)者或者生產(chǎn)者中所有方法執(zhí)行時(shí)長(zhǎng)的統(tǒng)計(jì), 如下圖:
方法耗時(shí)總覽
  • 服務(wù)監(jiān)控狀況統(tǒng)計(jì),可以查看所有服務(wù)里面具體方法最近十分鐘的健康情況,并可以獲取服務(wù)中各個(gè)方法的歷史調(diào)用情況

    服務(wù)健康狀況統(tǒng)計(jì)
  • 某個(gè)應(yīng)用實(shí)例日志檢索


    loom日志

    loom日志檢索
  • 提供獲取某個(gè)服務(wù)的某個(gè)方法的歷史調(diào)用曲線(精確到每分鐘的成功,失敗次數(shù))


    loom調(diào)用

    loom調(diào)用歷史
  • 收集Loom產(chǎn)生的RPC調(diào)用鏈信息,并給Loom提供該信息檢索(雙擊某行有問(wèn)題的Span記錄可以查看某次調(diào)用鏈詳情,了解具體的網(wǎng)絡(luò)情況和業(yè)務(wù)執(zhí)行情況)


    rpc的span信息

    rpc調(diào)用鏈詳情

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í)到如下版本
      <dependency>
          <groupId>org.springframework.data</groupId>
          <artifactId>spring-data-redis</artifactId>
          <version>1.4.2.RELEASE</version>
      </dependency>
    
    升級(jí)完之后, 配置參數(shù)少了 maxActive, maxWait 變成 maxWaitMillis
    如果你使用的是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)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蔑赘,一起剝皮案震驚了整個(gè)濱河市狸驳,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌缩赛,老刑警劉巖耙箍,帶你破解...
    沈念sama閱讀 212,185評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異酥馍,居然都是意外死亡辩昆,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,445評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)旨袒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)汁针,“玉大人术辐,你說(shuō)我怎么就攤上這事∈┪蓿” “怎么了辉词?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,684評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)猾骡。 經(jīng)常有香客問(wèn)我瑞躺,道長(zhǎng),這世上最難降的妖魔是什么卓练? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,564評(píng)論 1 284
  • 正文 為了忘掉前任隘蝎,我火速辦了婚禮购啄,結(jié)果婚禮上襟企,老公的妹妹穿的比我還像新娘。我一直安慰自己狮含,他們只是感情好顽悼,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,681評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著几迄,像睡著了一般蔚龙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上映胁,一...
    開(kāi)封第一講書(shū)人閱讀 49,874評(píng)論 1 290
  • 那天木羹,我揣著相機(jī)與錄音,去河邊找鬼解孙。 笑死坑填,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的弛姜。 我是一名探鬼主播脐瑰,決...
    沈念sama閱讀 39,025評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼廷臼!你這毒婦竟也來(lái)了苍在?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,761評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤荠商,失蹤者是張志新(化名)和其女友劉穎寂恬,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體莱没,經(jīng)...
    沈念sama閱讀 44,217評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡掠剑,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,545評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了郊愧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片朴译。...
    茶點(diǎn)故事閱讀 38,694評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡井佑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出眠寿,到底是詐尸還是另有隱情躬翁,我是刑警寧澤,帶...
    沈念sama閱讀 34,351評(píng)論 4 332
  • 正文 年R本政府宣布盯拱,位于F島的核電站盒发,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏狡逢。R本人自食惡果不足惜宁舰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,988評(píng)論 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望奢浑。 院中可真熱鬧蛮艰,春花似錦、人聲如沸雀彼。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,778評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)徊哑。三九已至袜刷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間莺丑,已是汗流浹背著蟹。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,007評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留梢莽,地道東北人萧豆。 一個(gè)月前我還...
    沈念sama閱讀 46,427評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像蟹漓,于是被迫代替她去往敵國(guó)和親炕横。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,580評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容