当前稍微上点体量的互联网公司已经逐渐采用微服务的开发模式,将之前早期的单体架构系统拆分为很多的子系统,子系统封装为微服务,彼此间通过HTTP协议RESET API的方式进行相互调用或者gRPC协议进行数据协作。
早期微服务只有几个的情况下,我们遇到问题可以直接简单、快速地通过采集日志进行分析,是A服务存在问题还是B服务存在问题,可以快速恢复服务。但是,如果微服务数量已经到达了几十个、甚至上百个,这些微服务之间的调用关系会变得错综复杂。如果某个API接口的业务逻辑很复杂、调用链路长,涉及的微服务较多,那么一旦其中一环出现问题,想要快速定位问题和解决问题,如果还是按照早期一一查看日志的方式进行排查,无疑对运维和开发人员是一场噩梦....., 等把问题定位出来再恢复,影响至少都是几个小时,几个小时对于大型互联网企业的损失不言而喻,可能今年部门年终奖都得凉凉~
所以作为开发以及运维团队, 我们急需一套APM(应用性能管理(Application Performance Management))系统,将我们业务开发与运维制定规范嵌入APM系统,通过可视化UI面板+告警就可以很快速地通过例如服务调用链的拓扑图进行问题定位,最后解决问题,实现服务的短时间恢复。
APM系统开源的其实有很多产品,例如韩国主导的Pinpoint、大众点评的CAT、推特的ZipKin、CNCF的Jaeger以及本文重点介绍的Apache SkyWalking. 感兴趣的同学都可以一一了解和使用。
APM重点关注三个指标或者说三个维度数据: 1、metrics 2、logging 3、tracing
metrics: 服务指标, 例如Prometheus暴露出来的metrics,我们可以知道服务运行状态、报错数量、异常数量等等
logging: 日志采集,这个也是APM中重要的维度信息,因为通过metrics我们只是知道某些服务出现了问题,但是具体问题详情,需要靠日志进一步分析具体原因
tracing: 调用链追踪,我们可以通过将这些服务之间的调用信息记录下来,最终形成调用链有向无循环图,方便我们查看是哪个链路上出现了问题以及也可以看到链路的性能情况
Aapache SkyWakling目前在tracing方面关注度和擅长度较高,其他2个维度也有在慢慢做起来。
SkyWalking 是基于 Apache 开源生态的分布式应用性能监控系统。它提供了面向云原生架构的 APM(Application Performance Management)解决方案,支持多种语言的应用和多种方式的部署,具有以下特点:
分布式追踪:支持多种语言、多种协议的应用追踪,可获取全链路的请求数据。
应用拓扑图:根据追踪数据自动生成应用程序拓扑,便于系统管理员快速定位故障。
监控告警:具有丰富的监控指标和告警规则,支持第三方告警接口。
插件化体系:可通过插件模块实现对不同类型服务的监控和数据收集。
SkyWalking 的设计理念是高度灵活和可扩展的,可以自定义仪表板、告警规则、数据接口等。它可以帮助用户诊断系统性能问题、提高系统的可用性和吞吐量,在企业级系统的性能管理、调优和问题排查中发挥重要作用。
SkyWalking 由中国华为和 Apache 开源社区共同开发,目前已成为 Apache 基金会下的顶级项目之一,作者吴晟。
SkyWalking 于2017年11月进入 Apache 孵化器(Apache Incubator),成为 Apache 软件基金会的一个开源项目。经过一段时间的发展和孵化,SkyWalking 在2019年2月毕业,成为 Apache 软件基金会的顶级项目。成为顶级项目意味着 SkyWalking 已经发展成熟并受到广泛认可,具有良好的社区治理和持续的技术发展。同时,作为顶级项目,SkyWalking 继续在 Apache 的指导下发展,并得到了更多的关注和支持。
项目Github地址: https://github.com/apache/skywalking
官网地址: https://skywalking.apache.org/
SkyWalking的主要特点和优点,我认为相对其他开源项目是:
SkyWalking Agent采集调用链信息的客户端对业务是无感的、非侵入式的。 那也就意味着,你的项目代码无须修改一行代码就能加入SkyWalking的采集、监控、调用链追踪。
这个特点真的很牛逼,那也就意味着你原来的项目接入SkyWalking很轻松、门槛很低。 底层大概得原理是, JAVA本身就提供JAVAAGENT的机制以及采用动态修改字节码技术的方式,Agent在JVM底层帮我们把调用链采集上报的过程透明化了,开发人员作为业务层写代码是无感的。
我们先记住这个大概的原理,下面我会在入门案例先也会简单做个初步分析和验证。
下载: SkyWalking的OAP压缩包
下载页面: Downloads | Apache SkyWalking
1、解压 apache-skywalking-apm-9.3.0.tar.gz
tar -zxvf apache-skywalking-apm-9.3.0.tar.gz
2、进入apache-skywalking-apm-9.3.0
进入bin目录有启动脚本,我们先启动oapService.sh(服务端)、再启动webappService.sh(UI客户端)
启动完毕,执行jps程序查看进程有没有正常拉起来:
正常会出现上面两个进程,如果没有拉起来在logs目录可以查看错误信息进行排查:
http://$ip:8080
目前刚开始服务列表这些是空的, SkyWalking支持多种数据存储介质例如默认内存、MySQL、ES等等,一般生产环境使用ES集群进行存储,我们测试直接不用改什么配置,默认采用内存的形式。那就意味着如果服务重启,则之前的测试数据会丢失
Trace调用链追踪,例如A->B->C->D 存在4个微服务的调用链关系。 假设我们通过HTTP协议进行交互,该怎么把调用链信息拿到呢?
Google的Dapper论文中提到了一种分布式调用链追踪的实现方式, 市面上的APM系统调用链追踪大部分是参考这篇论文实现的。 每个调用链可以使用TraceId进行标识,每个服务被调用称为一个Span会伴随一个SpanId, 每当父服务调用子服务的时候会将TraceId以及父层的SpanId往子服务传递,子服务收到后,记录父层传递的SpanId作为自己Span的父id, 最后2个服务都会把调用信息上报到APM的服务端,例如SkyWalking的某个端口.
最后SkyWalking因为采集到了这些Span的信息,Span信息记录着父子关系、调用时间信息、TraceId等等,经过UI绘制,就能把这一次调用的TraceId的所有Span进行串联,最终形成调用链拓扑图。
接下来我们部署一个SpringBoot项目和一个PHP项目,很简单的链路,SpringBoot调用PHP项目的HTTP接口,我们来看下SkyWalking的UI界面以及分析原理验证。
代码很简单,一个PHP文件即可, index.php。大家自行安装php环境
$headers,
'http_sw8_decode' => [
'HTTP_SW8_CORRELATION' => !empty($headers['HTTP_SW8_CORRELATION']) ? $headers['HTTP_SW8_CORRELATION'] : '',
'HTTP_SW8_X' => !empty($headers['HTTP_SW8_X']) ? $headers['HTTP_SW8_X'] : '',
'HTTP_SW8' => $vals
]
];
header("Content-type: application/json;charset=utf-8");
echo json_encode($data);
很简单,大家看下基本就知道,提供的这个index.php做了一件事就是从HTTP请求头拿到一些信息,针对这些信息做一个分析,最终返回给客户端仅此而已。
运行服务:
php -S 0.0.0.0:8090 index.php
浏览器访问服务看下是否正常: http://$ip:8090
写了一个简单的Controller, 就是调用PHP项目的index.php URL,拿到数据后返回给用户显示, controller代码如下:
package com.example.controllers;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping(path = "/user")
public class User {
@GetMapping(value = "/list")
public Map list() throws JsonProcessingException {
RestTemplate restTemplate = new RestTemplate();
String url = "http://192.168.30.15:8090/index.php";
ResponseEntity response = restTemplate.getForEntity(url, String.class);
String responseBody = response.getBody();
Map m = new HashMap<>();
ObjectMapper objectMapper = new ObjectMapper();
Map resp= objectMapper.readValue(responseBody, new TypeReference>() {});
m.put("resp", resp);
return m;
}
}
代码逻辑很简单
SpringBoot项目倒是部署好了,但是如果只是简单运行,那肯定和我们的SkyWalking还没进行结合。那怎么结合起来呢? 那就是通过运行的时候,设置参数,和SkyWalking Agent进行结合。
我们上面提到过,使用SkyWalking Agent最大的好处就是【无代码侵入】,那也就是意味着我们的源代码无须做任何变更, 只需要在运行java -jar JAR包的时候额外加入一下参数即可:
下载Agent:
解压skywalking-agent.jar:
找到skywalking-agent.jar所处的绝对路径, 记录一下.
运行SpringBoot项目需要加入参数如下:
-javaagent:/root/apache-skywalking-java-agent/skywalking-agent/skywalking-agent.jar
-DSW_AGENT_NAME=sw-springboot
-DSW_AGENT_COLLECTOR_BACKEND_SERVICES=192.168.30.15:11800
-javaagent: 这是 java运行命令可以添加的选项参数, :后面是agent jar包的绝对路径地址
-DSW_AGENT_NAME: 这是传递参数SW_AGENT_NAME, 可以理解为这个微服务的名称
-DSW_AGENT_COLLECTOR_BACKEND_SERVICES: 这个就是要将调用链信息上报的SkyWalking的服务端地址+端口
运行完毕之后,服务正常启动, 和正常的SpringBoot除了加上面的参数,服务启动没任何区别, 我们多次访问部署SpringBoot服务IP:8080端口:
可以看到 SpringBoot调用PHP的Web接口后返回了数据进行展示
服务列表信息已经有了数据:
查看调用service调用拓扑图:
查看Span调用链详细信息:
通过上面我们就能看到调用链信息,真的好神奇,没改一行业务的Java代码就能做到查询调用链的拓扑图。这是到底是怎么实现的?
我们没改一行代码,只是注入了javaagent和相关SW的参数,这个javaagent就是关键点。 Java本身就支持这种agent技术,可以在main()函数之前做一些钩子操作,例如对要加载到JVM的class字节码可以进行增强或者说修改吧,没错,就是对你的代码进行动态修改。 字节码都能修改了,Agent代码要做个线程或者进程把这些采集的数据进行上报那不是小case么.
例如可以在某个class的方法前记录调用使用,在方法后记录时间,两者一减,你的调用时间就出来了,然后再进行上报等等,我理解有点像反射技术哈,不用反射的原因我看资料反馈的是性能较差。
哈哈,这里就有点意思了。 非侵入式将信息上报给SkyWalking可以理解,那一个SpringBoot项目调用PHP项目,你咋知道这两个Span有啥关系? SpringBoot是怎么传递之前说的TraceId、SpanId传递到PHP服务的的? 毕竟这是在进程、跨服务器通信。
答案: 就是在传输协议HTTP上做了手脚。 我们之前说了Agent有动态修改字节码的能力,那这个太简单了,我直接在HTTP协议的底层类进行拦截,一旦你进行HTTP请求,我就在你的请求之前,请求头Header注入Span信息,如果对端服务(下游服务)也是用了SkyWalking的Agent,那么它也会从HTTP请求的Header中获取Span信息,从而进行处理,最后上报。
看下刚才调用SpringBoot的响应内容:
PHP解析HTTP Header有个规则,都以HTTP_开头, 小写转大写,_下划线转-:
所以Java SpringBoot那边在header里面传递的Header: HTTP_SW8, 实际是传的sw8作为key
项目地址: https://github.com/apache/skywalking-java
全文搜索一下关键词: sw8可以看到定义了这个header信息的类:
发现了关键字样,看起来是SW8携带数据Item class的定义. OK, 那我们再搜索关键词: HttpURLConnection, 为啥搜索这个词? 你上层HTTP封装再牛逼,也逃不了调用底层HTTP请求吧?
发现了关键代码, 嘿嘿:
咱们对这段代码不太懂是吧? ChatGPT给我上, 看下GPT是怎么理解这段代码的:
嗖嘎,和猜想的一模一样, Agent就是在底层HTTP请求的时候搞了钩子手脚,每次HTTP请求都是给我把SkyWalking的header加上,方便给下游服务传递Span和Trace上下文,搞定收工
经过上面的入门和简单分析验证:
1、我们知道了原来SkyWalking是通过动态修改字节码的方式来达到无侵入式的调用链追踪上报
2、并且通过分析各种协议例如GRPC、HTTP等常见协议, 通过底层设置钩子,如一旦发现HTTP请求,在Header上注入调用链Span上下文信息到下游服务,最后将这些Span信息上报到SkyWalking的server端,最后经过SkyWalking整理,通过UI界面就能查看到service的调用链拓扑图。