一、背景
在灰度部署、A/B测试、单元化部署等场景下,微服务服务之间的调用,要求我们对上游服务给过来的数据进行透传至下游服务。
如果是灰度部署,需要对http请求进行染色,http header头部增加灰度标识,然后传递给下游服务。这个传递就跟击鼓传花一样,谁都不能丢弃掉这个灰度标识。
可现实是,我们的服务在执行的过程中,极容易把这个灰度标识丢掉了。(当然不是故意的)
如果程序的执行顺序都是串行的,那当然不会丢了上下文中的数据。现实中的程序执行,为了高性能和解耦,我们就会使用线程池、本地异步事件、mq异步、redis数据订阅与发布等技术,使得上下文的数据丢掉了,无法继续透传下去了。
为了做到数据透传,我们一般有两种做法,一是java agent技术,另外就是在服务中,在拦截器中增加数据的赋值。
验证数据透传的结果,一般的做法是输入日志,通过唯一标识去跟踪数据透传到哪一步。比如http feign调用框架,你就可以使用wireshark抓包来验证。(本文就使用后者来验证feign框架的数据透传)
二、目标
- 支持feign调用的数据透传
- 支持父子线程和线程池等的数据透传
- 能够验证数据透传的结果
三、边界
服务之间的数据透传,不包括rabbitmq、redis等第三方中间件的透传。
四、关键思路
-
父子线程的上下文传递,包括线程池等并发工具类,可以使用阿里云的开源框架transmittable-thread-local
-
抓http协议的包,你可以使用charles,也可以使用强大的抓包工具:wireshark(说后者强大,是因为它还可以抓websocket等协议的包)
-
feign调用的参数传递,使用RequestInterceptor拦截器,在头部增加你的参数进行调用
五、feign拦截器
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
public class CustomFeignInterceptor implements RequestInterceptor {
private static final Logger log = LoggerFactory.getLogger(CustomFeignInterceptor.class);
/**
* FeignClient调用时传递的用户id
*/
public static String XX_USER_ID = "XX_USER_ID";
/**
* FeignClient调用时传递的学校id
*/
public static String XX_SCHOOL_ID = "XX_SCHOOL_ID";
@Autowired
@Qualifier(ITransmittableThreadLocal.TransmittableThreadLocal_BEAN_NAME)
ITransmittableThreadLocal<TransmittedUserInfo> transmittableThreadLocal;
@Override
public void apply(RequestTemplate template) {
TransmittedUserInfo userInfo = transmittableThreadLocal.get();
if (userInfo != null) {
if (StringUtils.isNotEmpty(userInfo.getUserId())){
template.header(CustomFeignInterceptor.XX_USER_ID, userInfo.getUserId());
}
if (StringUtils.isNotEmpty(userInfo.getSchoolId())){
template.header(CustomFeignInterceptor.XX_SCHOOL_ID, userInfo.getSchoolId());
}
}
}
}
六、把要透传的数据传递至上下文
String authUserId = request.getHeader(JwtAuthHeaders.AUTH_USER_ID);
String authSchoolId = request.getHeader(JwtAuthHeaders.AUTH_SCHOOL_ID);
if (StringUtils.isNotBlank(authUserId)) {
TransmittedUserInfo userInfo = new TransmittedUserInfo(authUserId, authSchoolId);
ITransmittableThreadLocal<TransmittedUserInfo> transmittableThreadLocal = (ITransmittableThreadLocal<TransmittedUserInfo>) ApplicationContextProvider
.getApplicationContext()
.getBean(ITransmittableThreadLocal.TransmittableThreadLocal_BEAN_NAME);
transmittableThreadLocal.set(userInfo);
}
七、wireshark抓包
开启抓包
# 这里输入过滤条件
ip.addr == 192.168.5.70 and tcp.port == 7584
使用Postman调用接口,开始测试
找到http协议的报文,见"Hypertext Transfer Protocol"部分:
如果你想要去看那些tcp协议的报文,可以进一步帅选:
ip.addr == 192.168.5.70 and tcp.port == 7584 and http