Dubbo + Zipkin + Brave 实现全链路追踪
这两天看了好几篇帖子,写zipkin与dubbo整合的内容都不全面,忍不住亲自上手码一遍。
利用zipkin可以对dubbo进行调用链监控,可以查到调用链中的dubbo服务的性能,并且dubbo提供了SPI的接口,能很容易完成并自定义相应的filter去监控dubbo服务。
ZipKin介绍
Zipkin是一个致力于收集分布式服务的时间数据的分布式跟踪系统。
Zipkin 主要涉及四个组件:collector(数据采集),storage(数据存储),search(数据查询),UI(数据展示)。
github源码地址:https://github.com/openzipkin/zipkin。
Zipkin提供了可插拔数据存储方式:In-Memory,MySql, Cassandra, Elasticsearch
ZipKin部署与运行 (注意需要在linux下运行,JDK1.8)
1、下载zipkin
http://central.maven.org/maven2/io/zipkin/java/zipkin-server/2.11.7/zipkin-server-2.11.7-exec.jar
2、运行zipkin
(1)In-Memory方式
1
nohup java -jar zipkin-server-2.11.7-exec.jar &
注意:内存存储,zipkin重启后数据会丢失,建议测试环境使用
(2)MySql方式
目前只与MySQL的5.6-7。它的设计是易于理解,使用简单。但是,当数据量大时,查询很慢。性能不是很好。
创建数据库zipkin,建表 :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
CREATE TABLE IF NOT EXISTS zipkin_spans (
`trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit',
`trace_id` BIGINT NOT NULL,
`id` BIGINT NOT NULL,
`name` VARCHAR(255) NOT NULL,
`parent_id` BIGINT,
`debug` BIT(1),
`start_ts` BIGINT COMMENT 'Span.timestamp(): epoch micros used for endTs query and to implement TTL',
`duration` BIGINT COMMENT 'Span.duration(): micros used for minDuration and maxDuration query'
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;
ALTER TABLE zipkin_spans ADD UNIQUE KEY(`trace_id_high`, `trace_id`, `id`) COMMENT 'ignore insert on duplicate';
ALTER TABLE zipkin_spans ADD INDEX(`trace_id_high`, `trace_id`, `id`) COMMENT 'for joining with zipkin_annotations';
ALTER TABLE zipkin_spans ADD INDEX(`trace_id_high`, `trace_id`) COMMENT 'for getTracesByIds';
ALTER TABLE zipkin_spans ADD INDEX(`name`) COMMENT 'for getTraces and getSpanNames';
ALTER TABLE zipkin_spans ADD INDEX(`start_ts`) COMMENT 'for getTraces ordering and range';
CREATE TABLE IF NOT EXISTS zipkin_annotations (
`trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit',
`trace_id` BIGINT NOT NULL COMMENT 'coincides with zipkin_spans.trace_id',
`span_id` BIGINT NOT NULL COMMENT 'coincides with zipkin_spans.id',
`a_key` VARCHAR(255) NOT NULL COMMENT 'BinaryAnnotation.key or Annotation.value if type == -1',
`a_value` BLOB COMMENT 'BinaryAnnotation.value(), which must be smaller than 64KB',
`a_type` INT NOT NULL COMMENT 'BinaryAnnotation.type() or -1 if Annotation',
`a_timestamp` BIGINT COMMENT 'Used to implement TTL; Annotation.timestamp or zipkin_spans.timestamp',
`endpoint_ipv4` INT COMMENT 'Null when Binary/Annotation.endpoint is null',
`endpoint_ipv6` BINARY(16) COMMENT 'Null when Binary/Annotation.endpoint is null, or no IPv6 address',
`endpoint_port` SMALLINT COMMENT 'Null when Binary/Annotation.endpoint is null',
`endpoint_service_name` VARCHAR(255) COMMENT 'Null when Binary/Annotation.endpoint is null'
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;
ALTER TABLE zipkin_annotations ADD UNIQUE KEY(`trace_id_high`, `trace_id`, `span_id`, `a_key`, `a_timestamp`) COMMENT 'Ignore insert on duplicate';
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id_high`, `trace_id`, `span_id`) COMMENT 'for joining with zipkin_spans';
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id_high`, `trace_id`) COMMENT 'for getTraces/ByIds';
ALTER TABLE zipkin_annotations ADD INDEX(`endpoint_service_name`) COMMENT 'for getTraces and getServiceNames';
ALTER TABLE zipkin_annotations ADD INDEX(`a_type`) COMMENT 'for getTraces';
ALTER TABLE zipkin_annotations ADD INDEX(`a_key`) COMMENT 'for getTraces';
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id`, `span_id`, `a_key`) COMMENT 'for dependencies job';
CREATE TABLE IF NOT EXISTS zipkin_dependencies (
`day` DATE NOT NULL,
`parent` VARCHAR(255) NOT NULL,
`child` VARCHAR(255) NOT NULL,
`call_count` BIGINT
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;
ALTER TABLE zipkin_dependencies ADD UNIQUE KEY(`day`, `parent`, `child`);
启动zipkin命令
1
STORAGE_TYPE=mysql MYSQL_HOST=IP MYSQL_TCP_PORT=3306 MYSQL_DB=zipkin MYSQL_USER=username MYSQL_PASS=password nohup java -jar zipkin-server-2.11.7-exec.jar &
(3)Elasticsearch方式
创建elasticsearch用户,安装启动Elasticsearch服务
官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html
zipkin启动命令
1
STORAGE_TYPE=elasticsearch ES_HOSTS=http://IP:9200 nohup java -jar zipkin-server-2.11.7-exec.jar &
启动成功访问地址:http://192.168.20.15:9411/zipkin/(192.168.20.15替换为对应zipkin部署服务器地址)
效果图如下:
整合
简单的描述一下,同ServletFilter一样,在dubbo中利用Filter过滤请求,传递TraceId等参数,生成相应的span传递给zipkin服务器。引入brave-instrumentation-dubbo-rpc包,这是一个SPI的Filter包。
1
2
3
4
5
6
7
8
9
10
11
12
13
<dependencies>
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-sender-okhttp3</artifactId>
<version>2.7.10</version>
</dependency>
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-instrumentation-dubbo-rpc</artifactId>
<version>5.4.3</version>
</dependency>
</dependencies>
brave-instrumentation-dubbo-rpc包里面有一个TracingFilter的类,在其invoke方法中实现了对span的一些操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
if (tracer == null) return invoker.invoke(invocation);
RpcContext rpcContext = RpcContext.getContext();
Kind kind = rpcContext.isProviderSide() ? Kind.SERVER : Kind.CLIENT;
final Span span;
if (kind.equals(Kind.CLIENT)) {
span = tracer.nextSpan();
injector.inject(span.context(), invocation.getAttachments());
} else {
TraceContextOrSamplingFlags extracted = extractor.extract(invocation.getAttachments());
span = extracted.context() != null
? tracer.joinSpan(extracted.context())
: tracer.nextSpan(extracted);
}
if (!span.isNoop()) {
span.kind(kind);
String service = invoker.getInterface().getSimpleName();
String method = RpcUtils.getMethodName(invocation);
span.name(service + "/" + method);
parseRemoteAddress(rpcContext, span);
span.start();
}
boolean isOneway = false, deferFinish = false;
try (Tracer.SpanInScope scope = tracer.withSpanInScope(span)) {
Result result = invoker.invoke(invocation);
if (result.hasException()) {
onError(result.getException(), span);
}
isOneway = RpcUtils.isOneway(invoker.getUrl(), invocation);
Future<Object> future = rpcContext.getFuture(); // the case on async client invocation
if (future instanceof FutureAdapter) {
deferFinish = true;
((FutureAdapter) future).getFuture().setCallback(new FinishSpanCallback(span));
}
return result;
} catch (Error | RuntimeException e) {
onError(e, span);
throw e;
} finally {
if (isOneway) {
span.flush();
} else if (!deferFinish) {
span.finish();
}
}
}
该Filter是以SPI的方式引入dubbo的,在默认文件/META-INF/dubbo/com.alibaba.dubbo.rpc.Filter中,有这样的声明:
1
tracing=brave.dubbo.rpc.TracingFilter
然后在dubbo的配置中引入该Filter即可。
wholly-dubbo-provider.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!--指定Spring配置中用到的属性文件 -->
<bean id="propertyConfig"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:brave.properties</value>
</list>
</property>
</bean>
<bean id="zipkinProperties" class="com.whollyframework.dubbo.config.ZipkinProperties">
<property name="serviceName" value ="${brave.name}"/>
<property name="url" value ="${http.sender.address}"/>
<property name="connectTimeout" value ="${http.sender.connectTimeout}"/>
<property name="readTimeout" value ="${http.sender.readTimeout}"/>
</bean>
<!-- 提供方应用信息,用于计算依赖关系 -->
<dubbo:application name="demo-provider"/>
<!-- 使用multicast广播注册中心暴露服务地址 -->
<!-- <dubbo:registry address="multicast://224.5.6.7:1234"/> -->
<dubbo:registry address="zookeeper://localhost:2181"/>
<!-- 用dubbo协议在20880端口暴露服务 -->
<dubbo:protocol name="dubbo" port="20880"/>
<!-- 和本地bean一样实现服务 -->
<bean id="demoService" class="com.whollyframework.dubbo.provider.DemoServiceImpl"/>
<!-- 声明需要暴露的服务接口 -->
<dubbo:service interface="com.whollyframework.dubbo.api.DemoService" ref="demoService"/>
<context:component-scan base-package="com.whollyframework.dubbo" />
<dubbo:provider filter="tracing" />
</beans>
wholly-dubbo-consumer.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!--指定Spring配置中用到的属性文件 -->
<bean id="propertyConfig"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:brave.properties</value>
</list>
</property>
</bean>
<bean id="zipkinProperties" class="com.whollyframework.dubbo.config.ZipkinProperties">
<property name="serviceName" value ="${brave.name}"/>
<property name="url" value ="${http.sender.address}"/>
<property name="connectTimeout" value ="${http.sender.connectTimeout}"/>
<property name="readTimeout" value ="${http.sender.readTimeout}"/>
</bean>
<!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 -->
<dubbo:application name="demo-consumer"/>
<!-- 使用multicast广播注册中心暴露发现服务地址 -->
<!-- <dubbo:registry address="multicast://224.5.6.7:1234"/> -->
<dubbo:registry address="zookeeper://localhost:2181"/>
<!-- 生成远程服务代理,可以和本地bean一样使用demoService -->
<dubbo:reference id="demoService" check="false" interface="com.whollyframework.dubbo.api.DemoService"/>
<context:component-scan base-package="com.whollyframework.dubbo" />
<dubbo:consumer filter="tracing" />
</beans>
brave.properties
1
2
3
4
brave.name=provider
http.sender.address=http://192.168.20.15:9411/api/v2/spans
http.sender.connectTimeout=5000
http.sender.readTimeout=10000
然后还需要为该Filter注入一个Tracing实例,并且该实例名必须为tracing。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Configuration
public class ZipkinConfiguration {
@Autowired
private ZipkinProperties properties;
@Bean
public Tracing tracing(){
Sender sender = OkHttpSender.create(properties.getUrl());
AsyncReporter 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, "shiliew"))
.sampler(Sampler.ALWAYS_SAMPLE)
.spanReporter(reporter)
.build();
return tracing;
}
}
Tracing类的主要作用是针对span的操作做一些配置,并设置上传zipkin服务器。
效果
在这里不演示如何配置dubbo服务,之前的博文有相关介绍。效果图如下: