APM框架系列:
相关概念:
- 架构组件:
- Instrumentation:探针,负责数据采集;
- Collector:ZipKin Server的数据接收器,接收方式支持HTTP、ActiveMQ、Kafka、RabbitMQ,启动ZipKin Server时配置指定,参考Zipkin Server README;
- Transport:数据传输方案,由Collector方案决定;
- Storage:数据存储方案,支持内存、Cassandra、ElasticSearch、MySQL,启动ZipKin Server时配置指定,参考Zipkin Server README;
- 项目组成:
- openzipkin/zipkin:ZipKin Server组件,包含Collector、Storage、API、UI;
- openzipkin/brave:Instrumentation探针项目,客户端引用;
- openzipkin/zipkin-reporter-java:Reporter指Instrumentation向Collector发送数据的方案,例如okhttp3、Kafka。首先Collector支持不同数据接收方式,Instrumentation发送方案也不一样;另外同一种Collector数据接收方案,也可以使用不同的客户端框架来完成,这些工作由reporter项目负责;
- 链路跟踪:
- TraceId:标记一次全链路调用,全局唯一;
- SpanId:标记一次RPC调用;
- Annotations:ZipKin跟踪RPC性能的方法,记录4个时间点来反映RPC调用性能细节:Client Start、Server Start、Server Finish、Client Finish,参考功界面功能 -> 链路跟踪详情;
- Propagation:在RPC调用过程中Inject封装、Extract解封跟踪数据,例如b3-propagation:
使用当前ZipKin Server最新版本2.19.2,使用my-demo作为演示项目。
ZipKin默认按Docker容器部署方式发布,使用Docker或Java快速部署都非常方便。
快速部署(内存存储、HTTP Collector):
curl -sSL https://zipkin.io/quickstart.sh | bash -s
java -jar zipkin.jar
存储到MySQL(只用到3个表):
- MySQL创建zipkin数据库,执行schema DDL建表;
- 启动ZipKin:
STORAGE_TYPE=mysql MYSQL_HOST=localhost MYSQL_TCP_PORT=3306 MYSQL_DB=zipkin MYSQL_USER=root MYSQL_PASS=dev java -jar zipkin.jar
访问http://localhost:9411查看UI。
PinPoint
:采用javaagent
方式,对应用完全无侵入,项目无需添加任何额外依赖项,无需修改代码,这点做得最好;SkyWalking
:采用javaagent
方式,普通功能对应用无侵入,以下两项功能需要应用稍作修改:- 中添加自定义Tag项:应用代码在
SkyWalking Span
中添加自定义Tag,可以按需输出方法参数值等关键信息,辅助应用排错和性能分析,PinPoint
和ZipKin
都不支持(可自行实现); - 日志中输出全局跟踪ID,需要添加
SkyWalking
依赖项;
- 中添加自定义Tag项:应用代码在
ZipKin
:没有采用javaagent
方式,应用强依赖ZipKin
,必须打包到应用中与应用一起运行,每个项目需要添加依赖项、配置,不同探针有不同的bean配置需求;
在my-demo中运行ZipKin演示:
- 使用
zipkin
参数编译打包(或者手工打包,使用maven profiledev,zipkin
):sh $PROJECT_HOME/package.sh -zipkin
- 按下面脚本顺序启动服务和应用:
java -jar item-service\target\item-service-0.0.1-SNAPSHOT.jar java -jar stock-service\target\stock-service-0.0.1-SNAPSHOT.jar java -jar user-service\target\user-service-0.0.1-SNAPSHOT.jar java -jar order-service\target\order-service-0.0.1-SNAPSHOT.jar java -jar shop-web\target\shop-web-0.0.1-SNAPSHOT.jar
- 访问http://localhost:8090/shop执行一些操作,即可在PinPoint界面查看结果;
对不同框架Instrumentation使用的拦截方案不同,具体使用方法参考各Instrumentation实现,下面是my-demo中用到的几种。
最简单方案是使用spring-cloud-sleuth(项目不需要是SpringCloud服务,普通SpringBoot Web项目添加spring-cloud-sleuth
依赖即可),my-demo中的shop-app
配置示例如下:
pom.xml
添加依赖项:<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-sleuth</artifactId> <version>2.2.0.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
application.yml
添加配置:spring: zipkin: # spring-cloud-sleuth配置 base-url: http://192.168.31.108:9411 # ZipKin Server地址 sleuth: sampler: percentage: 1.0 # 采样比例,1.0表示100%采样
底层使用Servlet的Filter实现HTTP拦截,ZipKin Instrumentation的设置工作都由spring-cloud-sleuth
完成,项目中无需额外处理。
使用Dubbo的Filter机制实现拦截,Alibaba Dubbo使用dubbo-rpc instrumentation,Apache Dubbo使用dubbo instrumentation。my-demo基于Alibaba Dubbo 2.6.7
、dubbo-spring-boot-starter
使用dubbo-rpc
,设置方法如下:
pom.xml
添加依赖项:<dependencies> <dependency> <groupId>io.zipkin.brave</groupId> <artifactId>brave-instrumentation-dubbo-rpc</artifactId> </dependency> <dependency> <groupId>io.zipkin.reporter2</groupId> <artifactId>zipkin-sender-okhttp3</artifactId> </dependency> </dependencies> <dependencyManagement> <dependency> <groupId>io.zipkin.brave</groupId> <artifactId>brave-bom</artifactId> <version>5.9.1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencyManagement>
- 实现
ZipkinProperties
和ZipkinConfiguration
:@ConfigurationProperties(prefix="zipkin") public class ZipkinProperties { @Value("${dubbo.application.name}") private String serviceName; private String server; private int connectTimeout; private int readTimeout; //getters and setters } @Configuration @EnableConfigurationProperties({ZipkinProperties.class}) public class ZipkinConfiguration { @Autowired ZipkinProperties properties; @Bean public Tracing tracing() { //名称必须为tracing Sender sender = OkHttpSender.create(properties.getServer()); AsyncReporter<Span> reporter = AsyncReporter.builder(sender) .closeTimeout(properties.getConnectTimeout(), TimeUnit.MILLISECONDS) .messageTimeout(properties.getReadTimeout(), TimeUnit.MILLISECONDS) .build(); Tracing tracing = Tracing.newBuilder() .localServiceName(properties.getServiceName()) .propagationFactory(ExtraFieldPropagation.newFactory(B3Propagation.FACTORY, "brave-trace")) .sampler(Sampler.ALWAYS_SAMPLE) .spanReporter(reporter) .build(); return tracing; } }
application.yml
添加配置:dubbo: # 省略了其它dubbo配置 provider: filter: tracing # 对服务提供者启用ZipKin监控 consumer: filter: tracing # 对服务消费者启用ZipKin监控 zipkin: server: http://192.168.31.108:9411/api/v2/spans connectTimeout: 2000 readTimeout: 2000
实现原理:
dubbo-rpc/META-INF/dubbo/com.alibaba.dubbo.rpc.Filter
内容如下:tracing=brave.dubbo.rpc.TracingFilter
provider
和consumer
上指定的filter: tracing
为这个SPI扩展的名称,Dubbo根据名称加载brave.dubbo.rpc.TracingFilter
扩展。TracingFilter
的注解说明该filter依赖一个名为tracing
的brave.Tracing
对象,即ZipkinConfiguration
提供的:Dubbo@Activate(group = {Constants.PROVIDER, Constants.CONSUMER}, value = "tracing")
ExtensionLoader
根据setters(setTracing(Tracing tracing)
、setRpcTracing(RpcTracing rpcTracing)
)为filter提供依赖注入。
dubbo-rpc 5.9.1
版本在处理一个新的调用链开始、结束方面有些问题。my-demo
中的shop-web
,之前并非是web项目,而是普通Java项目直接命令行运行,使用了SpringBoot,执行完runFullTestCase
后,记录下来的调用链是错乱的,改为web项目,前面使用了spring-cloud-sleuth
之后就正常了。
因为runFullTestCase
中触发了多个Dubbo服务调用,在第一次调用时整个应用还没有开启跟踪,没有trace上下文,应当开启一个新的跟踪,该方法调用结束时,结束这个跟踪,随后遇到其它Dubbo服务调用同样处理,这样记录下来的调用链就是合理的、结构正确的,这方面处理不当则会造成调用链错乱。
通过JDBC的interceptor机制实现,根据my-demo
使用的mysql-connector-java
版本选择brave-instrumentation-mysql8:
pom.xml
添加依赖项:<dependency> <groupId>io.zipkin.brave</groupId> <artifactId>brave-instrumentation-mysql8</artifactId> </dependency>
- JDBC url添加参数,指定interceptor:
?queryInterceptors=brave.mysql8.TracingQueryInterceptor&exceptionInterceptors=brave.mysql8.TracingExceptionInterceptor&zipkinServiceName=db-order
pom.xml
添加依赖项:<dependency> <groupId>io.zipkin.brave</groupId> <artifactId>brave-context-slf4j</artifactId> </dependency>
- 为
brave.Tracing
配置slf4j的MDCScopeDecorator
:Tracing tracing = Tracing.newBuilder() .localServiceName(properties.getServiceName()) .propagationFactory(ExtraFieldPropagation.newFactory(B3Propagation.FACTORY, "brave-trace")) .sampler(Sampler.ALWAYS_SAMPLE) .spanReporter(reporter) //配置slf4j的MDCScopeDecorator .currentTraceContext(ThreadLocalCurrentTraceContext.newBuilder().addScopeDecorator(MDCScopeDecorator.create()).build()) .build();
logback.xml
通过%X{traceId}
、%X{spanId}
输出链路跟踪信息:
ZipKin的界面功能最简单,只有依赖图、链路跟踪查询。
链路跟踪 - 详情(Dubbo服务调用,右边Annotations上的4个点依次代表Client Start、Server Start、Server Finish、Client Finish,可以清晰的看出服务端执行时间、客户端调用时间、请求和响应的网络传输时间):