各位大佬好,小编又来了。
只要页面出数据慢,那都是后端接口需要优化?
你是不是曾经也被测试以优化接口的名义拿捏,而无法自拔?但是真的是接口慢吗?有没有可能是前端渲染慢或者网络慢的问题?
这篇内容,可能就是你的救星!
请求耗时监控和traceId的内容,两个都比较简单,所以索性就放到一起了。其实和上一篇用到的东西都差不多,只不过上一篇有点长了, 所以分开来写的。
那么就直接开肝吧!
简单介绍
TraceId
关于traceId的内容,在微服务中是用得最多的,主要作用是把所有请求串联起来,当服务调用链路特别长的时候,日志的查询其实是非常困难的,因此traceId就相当于一个唯一id,存储在线程上下文,当你的log打印日志的时候就会把这个Id带上,方便你查询某次请求经过的所有服务打印的所有日志。
但是很抱歉,如果你想了解关于traceId的核心内容的话,这篇暂时还帮不到你,后面会解释(狡辩)的。
耗时监控
接口的耗时监控,其实也是一个蛮重要的问题,它可以直接的看出我们接口的一个性能情况,当页面出来的特别慢的时候,你可以很直观的看出来是接口响应慢,还是页面渲染慢,还是说网络传输慢。可以很直接的给到你一个优化的方向。
当然有朋友就有疑问了,浏览器在请求接口的时候,控制台不是也有一个监控了吗?这其实就涉及到刚提到的网络慢的问题了,浏览器拿到数据花了很长时间,但是这个时候是网络慢,还是接口慢,你是不是就分不清了。所以耗时监控还是有必要的。
开始
代码清单
RequestMonitor: 请求监控的一个接口
RequestMonitorContextHolder: 线程上下文对象持有器
TraceUtil: 处理traceId的工具类
RequestMonitorInterceptor: 拦截器
RequestMonitorResponseBodyAdvice: 对返回值处理,设置traceId和耗时
RequestMonitorConfiguration: 配置类
代码明细
RequestMonitor
public interface RequestMonitor {
void setTraceId(String traceId);
String getTraceId();
void setTimeUse(Long timeUse);
Long getTimeUse();
}
看起来像是一个getter/setter
,哈哈,没有错,就是!如果你的服务有统一的返回体,那么你其实可以不需要这个东西。我这里用这个东西的目的,就是我测试代码里面没有统一的返回体,所以给个超类,去设置对应的信息。
RequestMonitorContextHolder
public class RequestMonitorContextHolder {
private static final ThreadLocal<StopWatch> TL = ThreadLocal.withInitial(StopWatch::new);
public static StopWatch get() {
return TL.get();
}
public static void remove() {
TL.remove();
}
}
线程上下文持有器,持有StopWatch
,这玩意儿就是spring自带的,看过Springboot源码的朋友对这个应该不陌生了,就是监控耗时的一个工具。
TraceUtil
public class TraceUtil {
private static final Logger LOG = LoggerFactory.getLogger(TraceUtil.class);
public static final String TRACE_ID_KEY = "trace-id";
public static void setTraceId(String traceId) {
try {
// 设置traceId
MDC.put(TRACE_ID_KEY, traceId);
} catch (Exception e) {
LOG.error("set traceId exception.msg={}.", e.getMessage(), e);
}
}
public static String getTraceId() {
try {
// 获取traceId
return MDC.get(TRACE_ID_KEY);
} catch (Exception e) {
LOG.error("get traceId exception.msg={}.", e.getMessage(), e);
}
return null;
}
public static void clear() {
try {
// 清除traceId
MDC.clear();
} catch (Exception e) {
LOG.error("clear traceId exception.msg={}.", e.getMessage(), e);
}
}
}
组件必须要保证能力就是发生异常的时候不能影响主流程,所以在MDC
设置traceId这一块,每个方法都用try/catch
块包裹了一下。
RequestMonitorInterceptor
public class RequestMonitorInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 开启耗时监控监控
RequestMonitorContextHolder.get().start();
String traceId = request.getHeader("traceId");
if (traceId == null || traceId.isEmpty()) {
traceId = UUID.randomUUID().toString().replace("-", "");
}
TraceUtil.setTraceId(traceId);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
TraceUtil.clear();
RequestMonitorContextHolder.remove();
}
}
逻辑也很简单。
-
请求进来,开启耗时监控
-
对于traceId部分,如果上游服务有传递traceId过来,那么就直接用,否则就重新生产一个,然后往下游服务传递。
RequestMonitorResponseBodyAdvice
@ControllerAdvice
public class RequestMonitorResponseBodyAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
// 如果返回体是RequestMonitor的子类,就返回true
return RequestMonitor.class.isAssignableFrom(returnType.getMethod().getReturnType());
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
// 获取计时器
StopWatch watch = RequestMonitorContextHolder.get();
// 停掉耗时监控
watch.stop();
// 统一给返回体设置traceId和耗时
RequestMonitor rm = (RequestMonitor) body;
rm.setTraceId(TraceUtil.getTraceId());
rm.setTimeUse(watch.getTotalTimeMillis());
return body;
}
}
这个类也简单吧,如果是RequestMonitor的子类,那我们就统一给它的返回值设置traceId和耗时信息。
测试类
@RestController
@RequestMapping("/requestMonitor")
public class RequestMonitorTestController {
@GetMapping("/test")
public ResponseResult<Boolean> test() {
try {
// 随机模拟耗时操作
Thread.sleep(new Random().nextInt(5000));
} catch (InterruptedException e) {
e.printStackTrace();
}
return ResponseResult.success(Boolean.TRUE);
}
}
额。。。这个就介绍了吧!
见证奇迹的时候
GET http://localhost:8080/requestMonitor/test
Accept: */*
###
发起请求,看结果
人工图片文本智能识别(笑哭)开始…1s,2s,3s…..
识别成功,上图关键信息识别结果如下:
{
"code": 0,
"msg": null,
"data": true,
"traceId": "d5dfeb0fdbbf416f98d78c563fb4aaa8",
"timeUse": 2178
}
好了,主要内容就结束了,虽然设置了traceId,但是由于我这边并没有去配置对应的logback.xml
,所以请求日志是没有打印出traceId的,我也不准备去配了,有兴趣的可以自行去了解,traceID这个东西要深入了解其他内容的话还是有一点难度的。
解释(狡辩)一下
没有介绍traceId其他内容的主要原因并不是想水各位道友,因为这坨的内容和小编聊的内容不太相关,大家如果是慢慢一篇一篇看过来的话,其实应该了解,遇到能自定义的地方,小编都会插一脚。刚好由于最近不是聊了有关请求前后处理的一些内容嘛,所以小编这边主要是为了介绍有关请求前后处理的一些相关的自定义内容。而traceId的设置刚好有一点交集,所以就简单提及一下。
当然这篇的内容还有一些值得推敲的地方,我们接下来就简单聊一下其中的问题。
误差问题
traceId
关于TraceID的设置的位置,这个仁者见仁智者见智吧,我们都知道请求到达服务时,最先会走Filter
,所以在Filter
中去设置TraceID无疑是最好不过的。
由于我这边,对Filter用得非常少,基本上都是Interceptor,所以也没关系,大家就不要纠结是放哪里了,放哪里其实我感觉差别也不是特别大,就看各位喜欢放哪里吧(有的道友看到这句话,可能已经上高速了,哈哈哈【邪魅一笑】)!
有些朋友到这儿,就觉得有了traceId,其实也能把所有的请求日志串联起来,那上一篇的意义在哪里?为什么要搞个线程上下文存这些玩意儿。
说实话,这个问题我只能给你解释自己的理解了,怎么说呢,当你在追日志的时候,如果微服务服务特别多,你追出来一大堆的请求路径和请求参数的时候,你在那一堆日志里去,去找匹配请求路径和请求参数的对应关系的时候,可能还要你去翻翻代码才知道那个接口的参数是什么样子的,遇到请求参数贼长那种,你想想会不会稍微有些许的难受,我反正是吐了!
如果你觉得上面的解释有一点牵强,那就姑且认为我有强迫症吧(哈哈哈,确实也有,还有点严重),而且,我觉得放一起会更优雅一些!毕竟优雅,永不过时。
耗时监控
关于耗时监控,其实也和traceId有一样的问题,这个会有两个地方存在误差。
-
一个就是放哪里的问题。 -
细心的朋友其实发现了,我们的 ResponseBodyAdvice
其实也是无序的,所以有可能我的耗时监控解释后,还有advice在处理。
但是说实在的,我个人觉得这个误差是可以接受的,没有必要那么精确。
-
因为本身它给你的信息也只是给了你优化的方向,并不具体 -
我们耗时的地方往往是业务逻辑的代码,跟filter这些关系不大,说的简单点,你的非业务代码,在每个接口调用都会执行,所以其实并不需要关心这部分时间。 -
我们其实也不差这几ms的误差。
反正也是一个仁者见仁智者见智的地方吧,总有一些追求完美(强迫症特别明显)的朋友。对于这部分朋友呢,我的建议是,加油!
Good Luck…
原文始发于微信公众号(心猿易码):接口耗时监控组件-响应慢不慢,一看就知道,要不要优化不再是测试说了算!
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/133269.html