是谁的请求导致我的系统一直抛异常?
作者:屿山、十眠
在线上环境中,请求错综复杂,如果有某个请求出现了不符合预期的情况,我们往往会先需要确定这个请求在实际环境中是由哪个 Controller 来处理的。通常情况下,我们需要去查阅文档或是代码,这个过程往往比较繁琐,并且不一定是准确的,可能由于一些问题会导致我们的请求没有被预期的 Controller 处理。而借助微服务洞察的能力,能够快速地定位特定的请求在真实环境中是由哪个 Controller 处理的。
流程分析
本文的 demo 包含 log-demo-spring-cloud-zuul、log-demo-spring-cloud-a、log-demo-spring-cloud-b、log-demo-spring-cloud-c 四个应用,采用最简单的 Spring Cloud 标准用法依次调用,可以直接在项目上查看源码。https://github.com/aliyun/alibabacloud-microservice-demo/tree/master/mse-simple-demo
以 SpringCloud 为例,请求到达后会由 org.springframework.web.servlet.DispatcherServlet#doDispatch 方法处理请求的整体流程。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
……
// Actually invoke the handler.
……
}
}
}
可以看到决定由哪个 handler 来处理请求的功能由 org.springframework.web.servlet.DispatcherServlet#getHandler 方法负责。该方法所返回的 org.springframework.web.servlet.HandlerExecutionChain 类的实例,就是最后实际处理该请求的 handler。也就是说我们只需要借助微服务洞察的能力观测这个方法的返回值即可。
微服务洞察
首先,简单介绍一下要使用的微服洞察能力,该能力基于规则的模型,以动态增强的方式为我们获取真实的现场信息。它的规则模型如下图所示:
Target:
- ResourceTarget: 目标接口,支持 Web、Rpc、SQL 以及任意的自定义方法
- WorkloadTarget: 目标实例,可以选择所有机器或指定机器 IP
- TrafficCondition: 是否仅针对异常、慢调用、全链路灰度标签
Action:
- 相关上下文诊断信息的收集,参数、返回值、线程上下文、Target 对象、类加载器信息等
- 后续链路是否日志打印
- 流量染色、限流降级等治理动作
在本文的场景中,我们需要将 org.springframework.web.servlet.DispatcherServlet#getHandler 设为我们的 Target,目标实例为默认的全部,不添加流量过滤条件。
随后,我们选择想要打印的内容,在这个场景中我们所需要是该方法的返回值,其他的内容可以根据需要选择。
在开启规则之后,我们可以在控制台中看到如图的调用链展示,可以看到/a 这个请求在 log-demo-spring-cloud-a 中的调用栈在基础的框架记录中新增了我们所选择的方法 org.springframework.web.servlet.DispatcherServlet#getHandler 的相关记录,从右侧的 Attributes 中的 mse.return 字段中可以看到该请求会由 java.lang.String com.alibabacloud.mse.demo.AApplication$AController.a 来处理。
辅助异常分析
线上的请求出现了异常,在定位问题的过程中,我们往往会需要知道调用的堆栈信息,进而去排查堆栈上的方法。借助微服务洞察的能力,我们也可以很方便的进行这些操作。
本场景在前文的 Demo 中增加了对数据库的访问,使用了 Druid 作为连接池组件。
当发现某个 url 的请求部分报错,但我们并没有预先编写能够记录有效信息的日志,这时我们就可以通过一条规则来打印现场的堆栈信息,以获取我们需要排查的方法列表,再进一步对逐个方法进行分析。假设是/sql 的请求出现了部分报错,我们选择/sql 作为 Target,如果不知道具体的接口,也可以选择全部。
由于我们只需要分析错误的请求,所以在过滤规则条件中开启异常过滤,在打印内容中选中调用堆栈,其他的内容可以根据需要选择。
开启该规则后,可以在控制台看到堆栈信息。
at com.mysql.cj.jdbc.ClientPreparedStatement.executeQuery(ClientPreparedStatement.java:989)
at com.alibaba.druid.pool.DruidPooledPreparedStatement.executeQuery(DruidPooledPreparedStatement.java:213)
at com.alibabacloud.mse.demo.service.DruidCon.doCommond(DruidCon.java:57)
at com.alibabacloud.mse.demo.service.DruidService.query(DruidService.java:15)
at com.alibabacloud.mse.demo.BApplication$AController.sql(BApplication.java:89)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
截取其中一部分可以发现 com.alibabacloud.mse.demo.service.DruidCon.doCommond 以及 com.alibabacloud.mse.demo.service.DruidService.query 都是我们自身的业务逻辑方法,也是我们需要关注的方法,我们可以继续借助微服务洞察的能力,去获取这些方法的现场信息,比如参数、返回值、类加载器等等。
这边以获取 com.alibabacloud.mse.demo.service.DruidCon.doCommond 方法的参数信息为例。同样只需要一条规则,类似前文,将该方法设为 Target,打开异常过滤标签,在打印内容中选中请求参数,开启规则后,便可以在控制台看到该方法的参数信息。
以上只是简单的例子,但是能够由此发现,微服务洞察的能力能够让我们在 Java 方法任意点位收集信息,将排查工作变成零代码且动态的,由于不需要在测试环境中重复增加日志代码并不断重启应用,能够大大减小某些难以在测试环境中复现的问题的排查难度。
在非预期的情况下,工作在最优解
微服务洞察能力提供了 Java 方法任意点位的可观测能力,本文通过该能力演示了:观测请求的匹配、观测异常场景下请求的上下文等场景,可以帮助我们在系统出现异常时进行问题定位,除了本文的场景还有很多微服务的场景能够借助微服务洞察能力来观测,将原本复杂且交织的微服务场景清晰地展现在我们面前。微服务洞察能力我们还在持续地打磨与完善,而发现并定位问题并不是目的,对于系统稳定来说只是迈出了第一步,我们还需要进一步结合治理能力才能够真正让系统稳定运行,比如本文的场景中可以针对异常的请求进行限流,或是快速地隔离故障,或是针对不稳定的服务进行容错降级等等,从而保护我们的系统。
在真实环境中,系统的不稳定性不仅仅来自于我们自身的业务逻辑错误,还有很多可能比如突发的大流量,上下游的不稳定等等。而这些问题都可以交给 MSE,使系统在非预期的情况下仍能工作在最优解。