微服务环境,有A,B,C,D四个服务,调用关系为:A->B->C->D。用户在A的页面选择当前“语言”环境为“英文”,在某些业务场景下,其它几个服务需获取到这个“语言”信息。
这个需求还是很简单的,类似于“击鼓传花”:当前服务从上一个服务中获取参数,并传给下一个服务。个人感觉基本上所有的RPC框架都会遇到这个问题,只是以前SOA架构下,服务层级比较少,将“语言”、“登陆”等附加信息放在参数列表中并不会带来太多工作量,所以这个问题并不是太突出。而引入了微服务架构思想后,服务调用层级急剧增长,这就需要一个更加优雅的方式来解决附加信息的传递问题。
思路简单,开发没有学习成本
微服务之间绝大多数情况是通过HTTP调用的,HTTP的header中也可以放参数信息。这样,接口参数中就不用维护这些附加信了。
参数解耦
如果B在获取到附加信息后,新起了一个线程”T1“来调用服务C,这时T1就无法从ThreadLocal拿到附加信息了
看着很完美,但是你忽略了一件事: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,即可达到自己的目的.
突然想起来,还有一种改代码的方式叫字节码替换,如果我能在程序启动的时,将我的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中
Sleuth的缺点
https://github.com/bishion/sleuth-plugin
因为附加信息的传递在RPC中扮演了很重要的角色,我潜意识里觉得,肯定会有更加简洁的方法或者框架我还没有了解到。希望各位各位读者老师能不吝珠玉,批评指正
admin
关注
发布 24 |
评论 2 |