全链路监控
只要是微服务开发,没有全链路监控系统的有一个算一个全是坑。
维护的一个老项目里面日志打印格式那叫一个牛,还没有全链路监控,排查问题那叫一个爽,生怕你能定位到问题。
所以,痛定思痛赶紧把监控安排上
使用Spring提供的Spring-Cloud-Gateway-Sleuth
组件即可实现基础的链路追踪功能,Sleuth
提供基础的traceId和spanId,但是这两个对于一般的业务够用,但是稍微大点的系统是远远不够的。
比如现在我有一个需求:我需要知道当前请求的用户名、企业名、携带的参数,并且当这条链路请求完成后,我需要返回给调用方当前请求的traceId、spanId以及logid,可以参考微信公众平台、饿了么公众平台接口定义,如下图:
后面的1234533425就是用户Id,这里只是演示,更多字段可以根据需求来增加。
实现以上功能我们可以基于Sleuth
提供的Brave实现,我们直接找到Sleuth
的 官方文档 ,然后搜索cuctom定位到下面这张图,Sleuth
告诉我们如果需要自定义更高级的,不用自定义属性,使用@Bean来配置自定义字段
一共四段配置,直接上代码:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
false)
public class TracerConfig {
BaggageField requestRequestUri = BaggageField.create(ClassicConstants.REQUEST_REQUEST_URI);
BaggageField requestQueryString = BaggageField.create(ClassicConstants.REQUEST_QUERY_STRING);
BaggageField requestMethod = BaggageField.create(ClassicConstants.REQUEST_METHOD);
BaggageField userId = BaggageField.create("USER-ID");
/**
* 响应前响应后过滤器
*/
WebFilter tracerWebFilter(Optional<Tracer> tracer) {
return (exchange, chain) -> {
exchange.getResponse().beforeCommit(() -> {
Optional.ofNullable(exchange.<String>getAttribute(ServerWebExchange.LOG_ID_ATTRIBUTE)).ifPresent(logId -> exchange.getResponse().getHeaders().add("logId", logId));
return Mono.empty();
});
return chain.filter(exchange)
// 执行完请求后将相关traceId、spanID返回前端,方便排查
.doOnSubscribe(subscription -> tracer.ifPresent(t -> {
Optional.ofNullable(t.currentSpan()).map(Span::context).ifPresent(context -> exchange.getResponse().beforeCommit(() -> {
Optional.ofNullable(context.traceId()).ifPresent(traceId -> exchange.getResponse().getHeaders().add("traceId", traceId));
Optional.ofNullable(context.spanId()).ifPresent(spanId -> exchange.getResponse().getHeaders().add("spanId", spanId));
Optional.ofNullable(context.parentId()).ifPresent(parentId -> exchange.getResponse().getHeaders().add("parentId", parentId));
return Mono.empty();
}));
// 执行请求前传播字段
t.createBaggage(ClassicConstants.REQUEST_REQUEST_URI, exchange.getRequest().getURI().getPath());
t.createBaggage(ClassicConstants.REQUEST_QUERY_STRING, exchange.getRequest().getURI().getQuery());
t.createBaggage(ClassicConstants.REQUEST_METHOD, exchange.getRequest().getMethodValue());
String authorization = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
if (StringUtils.isNotEmpty(authorization) & StringUtils.startsWith(authorization,JWTAuthentication.BEARER)){
JSONObject jsonObject = JWTAuthentication.parseJwtToClaimsAsJSONObject(authorization);
t.createBaggage("USER-ID",jsonObject.getStr("user_name"));
}
}));
};
}
/**
* 传播字段定制器,SingleBaggageField.remote请求时携带请求头
*/
BaggagePropagationCustomizer baggagePropagationCustomizer() {
return builder -> builder
.add(BaggagePropagationConfig.SingleBaggageField.remote(requestRequestUri))
.add(BaggagePropagationConfig.SingleBaggageField.remote(requestQueryString))
.add(BaggagePropagationConfig.SingleBaggageField.remote(userId))
.add(BaggagePropagationConfig.SingleBaggageField.remote(requestMethod));
}
/**
* 相关范围定制器
*/
CorrelationScopeCustomizer correlationScopeCustomizer() {
return builder -> builder
.add(CorrelationScopeConfig.SingleCorrelationField.create(BaggageFields.PARENT_ID))
.add(CorrelationScopeConfig.SingleCorrelationField.create(BaggageFields.SAMPLED))
.add(CorrelationScopeConfig.SingleCorrelationField.newBuilder(requestRequestUri).flushOnUpdate().build())
.add(CorrelationScopeConfig.SingleCorrelationField.newBuilder(requestQueryString).flushOnUpdate().build())
.add(CorrelationScopeConfig.SingleCorrelationField.newBuilder(requestMethod).flushOnUpdate().build())
.add(CorrelationScopeConfig.SingleCorrelationField.newBuilder(userId).flushOnUpdate().build());
}
}
(proxyBeanMethods = 上面这段配置就完美实现了我们的需求,对于每一个请求进行全链路追踪且返回logId,对于问题排查效率大大增加。
Sleuth原理就是基于请求头,需要对feign进行处理,feign调用接口时需要将请求头也携带过去,具体可以参考 GitHub
最后
关于微服务我想说的几个点
- 团队太小了,后端团队都只有个位数,就不要玩微服务了,还不够人解决环境、部署、测试等问题的。
- 正在构建未经证实的项目,没人玩过,没有成功的案例参考。这种情况下谁都说不清楚后面会需要用到的哪些技术。
- 团队本身没有微服务开发经验,而且找不到有成功架构微服务经验的人做指导,那就老老实实用单体吧。