微服務環境,有A,B,C,D四個服務,調用關係爲:A->B->C->D。用戶在A的頁面選擇當前「語言」環境爲「英文」,在某些業務場景下,其它幾個服務需獲取到這個「語言」信息。java
這個需求仍是很簡單的,相似於「擊鼓傳花」:當前服務從上一個服務中獲取參數,並傳給下一個服務。我的感受基本上全部的RPC框架都會遇到這個問題,只是之前SOA架構下,服務層級比較少,將「語言」、「登錄」等附加信息放在參數列表中並不會帶來太多工做量,因此這個問題並非太突出。而引入了微服務架構思想後,服務調用層級急劇增加,這就須要一個更加優雅的方式來解決附加信息的傳遞問題。git
優勢:思路簡單,開發沒有學習成本github
缺點:spring
思考:微服務之間絕大多數狀況是經過HTTP調用的,HTTP的header中也能夠放參數信息。這樣,接口參數中就不用維護這些附加信了。緩存
實現:
1.自定義一個Filter,獲取Request中本身須要的附加信息,
2.將這些信息放入ThreadLocal中,
3.實現feign.Client(這裏先忽略RestTemplate)的execute()方法,將附件信息在調用下一層服務前塞入request的header中session
優勢:參數解耦多線程
缺點:若是B在獲取到附加信息後,新起了一個線程」T1「來調用服務C,這時T1就沒法從HhreaLocal拿到附加信息了架構
思考:app
- 若是我知道怎麼用無侵入的方式,在當前線程」T」建立子孫線程」T1」、」T1-1」時,將數據傳給後代,就能解決這個問題了
- 微服務調用鏈框架Sleuth的核心功能便是跟蹤一次請求從A到D的全過程,它確定支持多線程調用下的traceId的傳遞。所以,我能夠複用Sleuth的相關功能夾帶私貨
優勢:框架
思考:
目前獲取參數的問題解決了,用Filter,只剩下保存並傳給下一層的問題
既然Sleuth已經解決了多線程下traceId的傳遞問題,那我就直接用traceId來解決個人問題
實現:
優勢:擁有上述方案全部的優勢,解決上述方案全部缺點
缺點:看着很完美,可是你忽略了一件事:Sleuth要想傳遞本身的traceId,想必它已經重寫了execute()方法(確定的,那就是TraceFeignClient),你要想用,那就要想辦法在複用TraceFeignClient.execute()的同時,將本身的私貨帶進去
實現:有時候,改動源碼並不須要直接在原有包裏修改。好比:A->B->C->D,若是你要修改C的源碼,那就將AB源碼也copy出,做爲A1,B1,C#,而後重寫組件的入口,將組件加載順序變爲:A1->B1->C#->D,便可達到重寫源碼的目的。這時候注意的是,加載A1的條件必須跟加載A的相反。具體可參考我以前重寫Consul的入口例子,示例代碼以下
@ConditionalOnExpression("${spring.cloud.consul.ribbon.enabled:true}==false") public class MyRibbonConsulAutoConfiguration {} // 原有入口: @ConditionalOnProperty(value = "spring.cloud.consul.ribbon.enabled", matchIfMissing = true) public class RibbonConsulAutoConfiguration {}
綜上,能夠重寫TraceFeigClient的入口 TraceFeignClientAutoConfiguration->TraceFeignObjectWrapper>TraceFeignClient,便可達到本身的目的.
優勢:感受事兒基本就成了
缺點:配置爲false生效,使用者會以爲比較怪,Sleuth彷彿知作別人會這麼幹似的,它的類的訪問權限基本都是default,爲了copy過來的幾個類能正常編譯經過,你還要再copy九個它們的依賴類,程序太醜
思考:忽然想起來,還有一種改代碼的方式叫字節碼替換,若是我能在程序啓動的時,將個人execute()直接替換掉Sleuth的execute(),就一勞永逸了
優勢:高大上,不在源碼級替換,卻在字節碼級替換,虛虛實實
缺點:沒這麼幹過,總以爲說着容易作着難
思考:基本上以爲方案五已經能解決問題了。本着精益求精的態度,去技術羣裏問了下,很快有大神發來Demo,看過代碼後頓覺慚愧:我一直在想怎麼重寫TraceFeignClient的execute(),其實這個execute()真正作http請求時,調用的是feign.Client的另一個實現類,注意那句」this.delegate.execute」,只要想辦法用本身的Client替換掉delegate便可
private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); private final Client delegate; @Override public Response execute(Request request, Request.Options options) throws IOException { String spanName = getSpanName(request); Span span = getTracer().createSpan(spanName); if (log.isDebugEnabled()) { log.debug("Created new Feign span " + span); } try { AtomicReference<Request> feignRequest = new AtomicReference<>(request); spanInjector().inject(span, new FeignRequestTextMap(feignRequest)); span.logEvent(Span.CLIENT_SEND); addRequestTags(request); Request modifiedRequest = feignRequest.get(); if (log.isDebugEnabled()) { log.debug("The modified request equals " + modifiedRequest); } Response response = this.delegate.execute(modifiedRequest, options); logCr(); return response; } catch (RuntimeException | IOException e) { logCr(); logError(e); throw e; } finally { closeSpan(span); } }
實現:經過再次認真Debug源碼知道,TraceFeignClient默認會加載你的Client實現類做爲delegate(汗!),所以你只要直接實現feign.Client接口便可。我偷懶了一把,本身寫個實現類,直接複用了LoadBalancerFeignClient.execute()
優勢:基本什麼都有了吧
缺點:若是你覺得只是簡單地重寫個execute()就行,那就大錯特了。由於TraceFeignClient直接用了你的方法post過去,所以你要想辦法把ribbon手動集成進來。若是不以爲麻煩的話,能夠好好看下TraceFeignClient怎麼生成Client的實例:TraceFeignObjectWrapper.wrap(Object bean)
思考:既然你能夠在程序裏獲取到trace和span,那爲什麼不將你的信息放到span裏呢。若是span中能放點額外信息就行了,就不用本身寫這麼多東西。經大神提醒,Sleuth中有個baggage能夠試試
實現:獲取參數的方式不變,取得的參數放在baggage中
優勢:簡單,支持RestTemplate調用的狀況,跟其餘組件兼容性好
缺點:Sleuth的缺點
Github地址:https://github.com/bishion/sleuth-plugin
簡介:微服務下使用,調用過程當中用戶信息,頁面語言信息的透傳
使用方式
bizi: sleuth: config: headers: lang_info #若是由多個,逗號隔開.這裏配置從filter裏須要獲取的headerName
調用方式
@Service public class SessionInfoService { @Resource private SessionInfoOperator sessionInfoOperator; public String getLangInfo(){ return sessionInfoOperator.getSessionInfo("lang_info"); } public void setUserId(){ sessionInfoOperator.setSessionInfo("user_id","bishion"); } }
由於附加信息的傳遞在RPC中扮演了很重要的角色,我潛意識裏以爲,確定會有更加簡潔的方法或者框架我尚未了解到。但願各位各位讀者老師能不吝珠玉,批評指正