对于一个大型的几十个、几百个微服务构成的微服务架构系统,通常会遇到下面一些问题,比如:
如何串联整个调用链路,快速定位问题?
如何理清各个微服务之间的依赖关系?
如何进行各个微服务接口的性能分折?
如何跟踪整个业务流程的调用处理顺序?
skywalking 是一个国产开源框架,2015 年由吴晟开源 , 2017 年加入 Apache 孵化器。skywalking 是分布式系统的应用程序性能监视工具,专为微服务、云原生架构和基于容器(Docker、K8s、Mesos)架构而设计。SkyWalking 是观察性分析平台和应用性能管理系统,提供分布式追踪、服务网格遥测分析、度量聚合和可视化一体化解决方案。
官网:http://skywalking.apache.org/
下载:http://skywalking.apache.org/downloads/
调用链选型
Zipkin 是 Twitter 开源的调用链分析工具,目前基于 springcloud sleuth 得到了广泛的使用,特点是轻量,使用部署简单。
Pinpoint 是韩国人开源的基于字节码注入的调用链分析,以及应用监控分析工具。特点是支持多种插件,UI 功能强大,接入端无代码侵入。
SkyWalking 是本土开源的基于字节码注入的调用链分析,以及应用监控分析工具。特点是支持多种插件,UI 功能较强,接入端无代码侵入。目前已加入 Apache 孵化器。
CAT 是大众点评开源的基于编码和配置的调用链分析,应用监控分析,日志采集,监控报警等一系列的监控平台工具。
探针性能对比
模拟了三种并发用户:500,750,1000。使用 jmeter 测试,每个线程发送 30 个请求,设置思考时间为 10ms。使用的采样率为 1,即 100%,这边与生产可能有差别。pinpoint 默认的采样率为 20,即 50%,通过设置 agent 的配置文件改为 100%。zipkin 默认也是 1。组合起来,一共有 12 种。下面看下汇总表:
从上表可以看出,在三种链路监控组件中,skywalking 的探针对吞吐量的影响最小,zipkin 的吞吐量居中。pinpoint 的探针对吞吐量的影响较为明显,在 500 并发用户时,测试服务的吞吐量从 1385 降低到 774,影响很大。然后再看下 CPU 和 memory 的影响,在内部服务器进行的压测,对 CPU 和 memory 的影响都差不多在 10%之内。
整个架构分成四部分:
SkyWalking 支持三种探针:
Agent – 基于 ByteBuddy 字节码增强技术实现,通过 jvm 的 agent 参数加载,并在程序启动时拦截指定的方法来收集数据。
SDK – 程序中显式调用 SkyWalking 提供的 SDK 来收集数据,对应用有侵入。
Service Mesh – 通过 Service mesh 的网络代理来收集数据。
后端(Backend)
接受探针发送过来的数据,进行度量分析,调用链分析和存储。后端主要分为两部分:
OAP(Observability Analysis Platform)- 进行度量分析和调用链分析的后端平台,并支持将数据存储到各种数据库中,如:ElasticSearch,MySQL,InfluxDB 等。
OAL(Observability Analysis Language)- 用来进行度量分析的 DSL,类似于 SQL,用于查询度量分析结果和警报。
界面(UI)
RocketBot UI – SkyWalking 7.0.0 的默认 web UI
CLI – 命令行界面
这三个模块的交互流程:
skywalking agent 和业务系统绑定在一起,负责收集各种监控数据
Skywalking oapservice 是负责处理监控数据的,比如接受 skywalking agent 的监控数据,并存储在数据库中;接受 skywalking webapp 的前端请求,从数据库查询数据,并返回数据给前端。Skywalking oapservice 通常以集群的形式存在。
skywalking webapp,前端界面,用于展示数据。
用于存储监控数据的数据库,比如 mysql、elasticsearch 等。
下载:http://skywalking.apache.org/downloads/
目录结构
先使用默认的 H2 数据库存储,不用修改配置
config/application.yml
启动成功后会启动两个服务,一个是 skywalking-oap-server,一个是 skywalking-web-ui
skywalking-oap-server 服务启动后会暴露 11800 和 12800 两个端口,分别为收集监控数据的端口 11800 和接受前端请求的端口 12800,修改端口可以修改 config/applicaiton.yml
博主采用docker-compose一件部署,连接mysql(官方推荐ES)
version: '3' services: m_oap: image: apache/skywalking-oap-server:8.9.0 container_name: m_oap ports: - 11800:11800 # agent 上报数据的端口,这是 gRPC 端口 - 12800:12800 # ui 读取数据的端口, 这是 http 端口 environment: - TZ=Asia/Shanghai - SW_STORAGE=mysql - SW_JDBC_URL="jdbc:mysql://192.168.189.128:3306/skywalking" #连接的数据库 - SW_DATA_SOURCE_USER=root - SW_DATA_SOURCE_PASSWORD=ck010316 volumes: - /opt/soft/mysql-connector-java-8.0.30.jar:/skywalking/oap-libs/mysql-connector-java-8.0.30.jar # 连接数据库需要的jar包,需要提前准备好 m_skywaling-ui: image: apache/skywalking-ui:8.9.1 container_name: ms_ui depends_on: - m_oap links: - m_oap ports: - 8088:8080 environment: - SW_OAP_ADDRESS=http://192.168.189.128:12800 - TZ=Asia/Shanghai
服务(Service) :表示对请求提供相同行为的一系列或一组工作负载,在使用 Agent 时,可以定义服务的名字;
服务实例(Service Instance) :上述的一组工作负载中的每一个工作负载称为一个实例, 一个服务实例实际就是操作系统上的一个真实进程;
端点(Endpoint) :对于特定服务所接收的请求路径, 如 HTTP 的 URI 路径和 gRPC 服务的类名 + 方法签名;
需要引入的pom依赖
<skywalking.version>8.12.0</skywalking.version> <opentracing.version>0.33.0</opentracing.version> <!-- Skywalking分布式链路追踪--> <dependency> <groupId>org.apache.skywalking</groupId> <artifactId>apm-toolkit-trace</artifactId> <version>${skywalking.version}</version> </dependency> <dependency> <groupId>org.apache.skywalking</groupId> <artifactId>apm-toolkit-logback-1.x</artifactId> <version>${skywalking.version}</version> </dependency> <dependency> <groupId>org.apache.skywalking</groupId> <artifactId>apm-toolkit-opentracing</artifactId> <version>${skywalking.version}</version> </dependency> <!--opentracing-api定义和实现分布式追踪的核心接口和规范--> <!--它使不同的分布式追踪工具(如Jaeger、Zipkin等)可以以相同的API进行操作,从而方便开发人员在不同工具之间切换--> <dependency> <groupId>io.opentracing</groupId> <artifactId>opentracing-api</artifactId> <version>${opentracing.version}</version> </dependency> <dependency> <groupId>io.opentracing</groupId> <artifactId>opentracing-util</artifactId> <version>${opentracing.version}</version> </dependency> <dependency> <groupId>io.opentracing</groupId> <artifactId>opentracing-noop</artifactId> <version>${opentracing.version}</version> </dependency>
日志xml配置logback-spring.xml(按需修改)
<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 --> <!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true --> <!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 --> <!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 --> <configuration> <!-- 引用 Spring Boot 的 logback 基础配置 --> <include resource="org/springframework/boot/logging/logback/defaults.xml" /> <!-- 变量 chengke.info.base-package,基础业务包 --> <springProperty scope="context" name="chengke.info.base-package" source="chengke.info.base-package"/> <!-- 格式化输出:%d 表示日期,%X{tid} SkWalking 链路追踪编号,%thread 表示线程名,%-5level:级别从左显示 5 个字符宽度,%msg:日志消息,%n是换行符 --> <property name="PATTERN_DEFAULT" value="%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}} | %highlight(${LOG_LEVEL_PATTERN:-%5p} ${PID:- }) | %boldYellow(%thread [%tid]) | %boldGreen(%-40.40logger{39}) | %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/> <!-- 控制台 Appender --> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder"> <layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout"> <pattern>${PATTERN_DEFAULT}</pattern> </layout> </encoder> </appender> <!-- 文件 Appender --> <!-- 参考 Spring Boot 的 file-appender.xml 编写 --> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder"> <layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout"> <pattern>${PATTERN_DEFAULT}</pattern> <!-- 日志格式中添加 %tid 即可输出 trace id --> <!-- <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %tid %logger{50} - %msg%n</pattern>--> <!--<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %tid %t %logger{50}: %msg%n</pattern>--> </layout> </encoder> <!-- 日志文件名 --> <file>${LOG_FILE}</file> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <!-- 滚动后的日志文件名 --> <fileNamePattern>${LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN:-${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz}</fileNamePattern> <!-- 启动服务时,是否清理历史日志,一般不建议清理 --> <cleanHistoryOnStart>${LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START:-false}</cleanHistoryOnStart> <!-- 日志文件,到达多少容量,进行滚动 --> <maxFileSize>${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB}</maxFileSize> <!-- 日志文件的总大小,0 表示不限制 --> <totalSizeCap>${LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0}</totalSizeCap> <!-- 日志文件的保留天数 --> <maxHistory>${LOGBACK_ROLLINGPOLICY_MAX_HISTORY:-30}</maxHistory> </rollingPolicy> </appender> <!-- 异步写入日志,提升性能 --> <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender"> <!-- 不丢失日志。默认的,如果队列的 80% 已满,则会丢弃 TRACT、DEBUG、INFO 级别的日志 --> <discardingThreshold>0</discardingThreshold> <!-- 更改默认的队列的深度,该值会影响性能。默认值为 256 --> <queueSize>256</queueSize> <appender-ref ref="FILE"/> </appender> <!-- SkyWalking GRPC 日志收集,实现日志中心。注意:SkyWalking 8.4.0 版本开始支持 --> <appender name="GRPC" class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.log.GRPCLogClientAppender"> <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder"> <layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout"> <pattern>${PATTERN_DEFAULT}</pattern> </layout> </encoder> </appender> <!-- 本地环境 --> <springProfile name="local"> <root level="INFO"> <appender-ref ref="STDOUT"/> <appender-ref ref="GRPC"/> <!-- 本地环境下,如果不想接入 SkyWalking 日志服务,可以注释掉本行 --> <appender-ref ref="ASYNC"/> <!-- 本地环境下,如果不想打印日志,可以注释掉本行 --> </root> </springProfile> <springProfile name="dev"> <root level="DEBUG"> <appender-ref ref="STDOUT"/> <appender-ref ref="ASYNC"/> <appender-ref ref="GRPC"/> </root> </springProfile> <!-- 其它环境 --> <springProfile name="test,stage,prod,default"> <root level="INFO"> <appender-ref ref="STDOUT"/> <appender-ref ref="ASYNC"/> <appender-ref ref="GRPC"/> </root> </springProfile> </configuration>
一般是生产环境使用:
准备一个 springboot 程序,打成可执行 jar 包,写一个 shell 脚本,在启动项目的 Shell 脚本上,通过 -javaagent 参数进行配置 SkyWalking Agent 来跟踪微服务;
#!/bin/sh #进入执行文件文件 cd /www/wwwroot/demo.com/service RESOURCE_NAME=demo.jar PORT=48083 # SkyWalking Agent配置 export SW_AGENT_NAME=tiaodaike-prod #Agent名字,一般使用`spring.application.name` export SW_AGENT_COLLECTOR_BACKEND_SERVICES=10.118.26.236:11800 #配置 Collector 地址。 export SW_AGENT_SPAN_LIMIT=2000 #配置链路的最大Span数量,默认为 300。 export JAVA_AGENT=-javaagent:/opt/skywalking-agent/skywalking-agent.jar # 停止进程 tpid=`lsof -i :$PORT -s TCP:LISTEN -t` if [ -n "$tpid" ]; then echo "停止进程开始" kill -15 $tpid sleep 15 fi # 再次检查进程是否停止,如果未停止则强制杀死进程 tpid=`lsof -i :$PORT -s TCP:LISTEN -t` if [ -n "$tpid" ]; then echo "Kill Process!" kill -9 $tpid sleep 10 echo "进程杀死,项目停止" else echo "进程停止,项目终止" fi #启动执行命令 nohup /usr/local/java8/bin/java \ -Xms512m \ -Xmx1500m \ -Dspring.config.location=./application.yml \ -Dserver.port=$PORT \ -XX:+PrintGCDetails \ -XX:+PrintGCDateStamps \ -Xloggc:./gc.log \ -XX:+HeapDumpOnOutOfMemoryError \ -XX:HeapDumpPath=./heapdump.hprof \ # 加入Skywalking参数 $JAVA_AGENT \ -jar $RESOURCE_NAME >> ./manage.log 2>&1 & echo "项目启动成功"
拿着tracId去查找对应的服务调用链路
示例:登录功能链路调用,gateway==》auth==》tenant==》rbac
在运行的程序配置 jvm 参数,如下图所示:
Skywalking 跨多个微服务跟踪,只需要每个微服务启动时添加 javaagent 参数即可。
skywalking 告警的核心由一组规则驱动,这些规则定义在 config/alarm-settings.yml 文件中,告警规则的定义分为三部分:
1、告警规则:它们定义了应该如何触发度量警报,应该考虑什么条件;
2、网络钩子(Webhook}:当警告触发时,哪些服务终端需要被通知;
3、gRPC 钩子:远程 gRPC 方法的主机和端口,告警触发后调用;
为了方便,skywalking 发行版中提供了默认的 alarm-setting.yml 文件,包括一些规则,每个规则有英文注释,可以根据注释得知每个规则的作用:
在最近 10 分钟的 3 分钟内服务平均响应时间超过 1000ms
最近 10 分钟内,服务成功率在 2 分钟内低于 80%
服务实例的响应时间在过去 10 分钟的 2 分钟内超过 1000ms
数据库访问{name}的响应时间在过去 10 分钟的 2 分钟内超过 1000ms
只要我们的服务请求符合 alarm-setting.yml 文件中的某一条规则就会触发告警。
比如 service_resp_time_rule 规则:
该规则表示服务{name}的响应时间在最近 10 分钟的 3 分钟内超过 1000ms;
测试:
编写接口,模拟慢查询:
@RequestMapping("/info/{id}") public User info(@PathVariable("id") Integer id){ try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } return userService.getById(id); }
回调接口
@RequestMapping("/notify") public String notify(@RequestBody Object obj){ //TODO 告警信息,给技术负责人发短信,钉钉消息,邮件,微信通知等 System.err.println(obj.toString()); return "notify successfully"; }
在 config/alarm-settings.yml 中配置回调接口,并重启 skywalking 服务
测试访问:http://localhost:8000/user/info/1,满足告警规则后,控制台输出告警信息,SkyWalking UI 显示告警信息
参考:https://github.com/apache/skywalking/blob/master/docs/en/setup/backend/backend-alarm.md
对接钉钉:
Webhook 回调通知
SkyWalking 告警 Webhook 回调要求接收方是一个 Web 容器(比如 tomcat 服务),告警的消息会通过 HTTP 请求进行发送, 请求方法为 POST, Content-Type 为 application/json, JSON 格式基于 List<org.apache.skywalking.oap.server.core.alarm.AlarmMessage>的集合对象数据, 集合中的每个 AlarmMessage 包含以下信息:
scopeId. 所有可用的 Scope,参考:org.apache.skywalking.oap.server.core.source.DefaultScopeDefine;
name. 目标 Scope 的实体名称;
id0. Scope 实体的 ID;
id1. 未使用;
ruleName. 在 alarm-settings.yml 中配置的规则名;
alarmMessage. 报警消息内容;
startTime. 告警时间, 位于当前时间与 UTC 1970/1/1 之间;
[{ scopeId = 2, scope = SERVICE_INSTANCE, name = 98e1839 a6fdf48b0aedb0ecabb8ea5f7 @192 .168 .233 .1 of springboot - skywalking - demo, id0 = c3ByaW5nYm9vdC1za3l3YWxraW5nLWRlbW8 = .1 _OThlMTgzOWE2ZmRmNDhiMGFlZGIwZWNhYmI4ZWE1ZjdAMTkyLjE2OC4yMzMuMQ == , id1 = , ruleName = service_instance_resp_time_rule, alarmMessage = Response time of service instance 98e1839 a6fdf48b0aedb0ecabb8ea5f7 @192 .168 .233 .1 of springboot - skywalking - demo is more than 1000 ms in 2 minutes of last 10 minutes, startTime = 1613913565462 }, { scopeId = 6, scope = ENDPOINT_RELATION, name = User in User to / user / info / { id } in springboot - skywalking - demo, id0 = VXNlcg == .0 _VXNlcg == , id1 = c3ByaW5nYm9vdC1za3l3YWxraW5nLWRlbW8 = .1 _L3VzZXIvaW5mby97aWR9, ruleName = endpoint_relation_resp_time_rule, alarmMessage = Response time of endpoint relation User in User to / user / info / { id } in springboot - skywalking - demo is more than 1000 ms in 2 minutes of last 10 minutes, startTime = 1613913565462 }]
docker-compose已经实现,容器启动即可,不坐过多赘述。
查看 对应的数据库,可以看到生成了很多表。
说明启动成功了,打开配置对应的地址http://192.168.3.100:8080/,可以看到 skywalking 的 web 界面。
不做过多赘述。
如果我们希望对项目中的业务方法,实现链路追踪,方便我们排查问题,可以使用如下的代码
在业务方法中可以 TraceContext 获取到 traceId
@RequestMapping("/list") public List<User> list(){ //TraceContext可以绑定key-value TraceContext.putCorrelation("name", "fox"); Optional<String> op = TraceContext.getCorrelation("name"); log.info("name = {} ", op.get()); //获取跟踪的traceId String traceId = TraceContext.traceId(); log.info("traceId = {} ", traceId); return userService.list(); }
如果一个业务方法想在 ui 界面的跟踪链路上显示出来,只需要在业务方法上加上 @Trace 注解即可
我们还可以为追踪链路增加其他额外的信息,比如记录参数和返回信息。实现方式:在方法上增加 @Tag 或者 @Tags。
@Trace @Tag(key = "list", value = "returnedObj") public List<User> list(){ return userMapper.list(); } @Trace @Tags({@Tag(key = "param", value = "arg[0]"), @Tag(key = "user", value = "returnedObj")}) public User getById(Integer id){ return userMapper.getById(id); }
2.5 Skywalking 集成日志框架
logback官方配置
log4j官方配置
log4j2j官方配置
添加 logback-spring.xml 文件,并配置 %tid 占位符
Skywalking 通过 grpc 上报日志 (需要 >=v8.4.0)
gRPC 报告程序可以将收集到的日志转发到 SkyWalking OAP 服务器上
logback-spring.xml 中添加
<!-- v8.4.0提供 --> <appender name="grpc-log" class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.log.GRPCLogClientAppender"/> <root level="info"> <appender-ref ref="grpc-log" /> </root>
打开 agent/config/agent.config 配置文件,添加如下配置信息:
plugin.toolkit.log.grpc.reporter.server_host=${SW_GRPC_LOG_SERVER_HOST:192.168.3.100} plugin.toolkit.log.grpc.reporter.server_port=${SW_GRPC_LOG_SERVER_PORT:11800} plugin.toolkit.log.grpc.reporter.max_message_size=${SW_GRPC_LOG_MAX_MESSAGE_SIZE:10485760} plugin.toolkit.log.grpc.reporter.upstream_timeout=${SW_GRPC_LOG_GRPC_UPSTREAM_TIMEOUT:30}
以上配置是默认配置信息,agent 与 oap 在本地的可以不配
2.6 Skywalking 集群部署
Skywalking 集群是将 skywalking oap 作为一个服务注册到 nacos 上,只要 skywalking oap 服务没有全部宕机,保证有一个 skywalking oap 在运行,就能进行跟踪。
搭建一个 skywalking oap 集群需要:
(1)至少一个 Nacos(也可以是 nacos 集群)
(2)至少一个 ElasticSearch(也可以是 es 集群)
(3)至少 2 个 skywalking oap 服务;
(4)至少 1 个 UI(UI 也可以集群多个,用 Nginx 代理统一入口)
修改 config/application.yml 文件
使用 nacos 作为注册中心
修改 nacos 配置
可以选择性修改监听端口
修改存储策略,使用 elasticsearch7 作为 storage
2. 配置 ui 服务 webapp.yml 文件的 listOfServers,写两个地址
3.启动服务测试
启动 Skywalking 服务,指定 springboot 应用的 jvm 参数
-DSW_AGENT_COLLECTOR_BACKEND_SERVICES=192.168.3.10:11800,192.168.3.12:11800