Spring Cloud Sleuth + Zipkin
一、分布式跟踪系统
在微服务架构中,众多的微服务之间互相调用,如何清晰地记录服务的调用链路是一个需要解决的问题。同时,由于各种原因,跨进程的服务调用失败时,运维人员希望能够通过查看日志和查看服务之间的调用关系来定位问题。
一个分布式服务跟踪系统主要由五部分构成:[ 数据采集、数据传输、数据存储 、数据分析、 数据可视化 ]
分布式服务跟踪系统设计理念:平台无关性、多语言支持、多中间件支持。
二、Sleuth
Spring Cloud Sleuth 是为了对微服务之间调用链路进行跟踪的一个组件,Sleuth主要是数据采集。
- Span:基本工作单元,例如,在一个新建的span中发送一个RPC等同于发送一个回应请求给RPC,span通过一个64位ID唯一表示,trace以另一个64位ID表示。span还有其他数据信息,比如摘要、时间戳事件、关键值注释(tags)、span的ID、以及进度ID(通常是IP地址) ,span在不断的启动和停止,同时记录了时间信息,当你创建了一个span,你必须在未来的某个时刻停止它;
- Trace:一系列spans组成的一个树状结构,例如,如果你正在跑一个分布式工程,你可能需要创建一个trace;
- Annotation:用来及时记录一个事件的存在,一些核心annotations用来定义一个请求的开始和结束。
- cs - Client Sent -客户端发起一个请求,这个annotion描述了这个span的开始;
- sr - Server Received -服务端获得请求并准备开始处理它,如果将其sr减去cs时间戳便可得到网络延迟;
- ss - Server Sent -注解表明请求处理的完成(当请求返回客户端),如果ss减去sr时间戳便可得到服务端需要的处理请求时间;
- cr - Client Received -表明span的结束,客户端成功接收到服务端的回复,如果cr减去cs时间戳便可得到客户端从服务端获取回复的所有所需时间。
链路日志由由 [appname,traceId,spanId,exportable] 组成的。
- appname:服务的名称,也就是 spring.application.name 的值。
- traceId:整个请求的唯一 ID,它标识整个请求的链路。
- spanId:基本的工作单元,发起一次远程调用就是一个 span。
- exportable:决定是否导入数据到 Zipkin 中。
2.1 抽样采集数据
如果服务的流量很大,全部采集对传输、存储压力比较大。这个时候可以设置采样率。
Sleuth 可以通过配置spring.sleuth.sampler.probability=X.Y (如配置为1.0,则采样率为100%,采集服务的全部追踪数据),
#zipkin 抽样比例
spring.sleuth.sampler.probability=1.0
若不配置默认采样率是0.1(即10%)。
也可以通过实现bean的方式来设置采样为全部采样(AlwaysSampler)或者不采样(NeverSampler):如
@Bean
public Sampler defaultSampler() {
return new AlwaysSampler();
}
Sleuth采样算法的实现是 Reservoir sampling(水塘抽样)。实现类是 PercentageBasedSampler。
附水塘抽样算法:https://www.cnblogs.com/krcys/p/9121487.html
2.2 使用
服务调用方
- 引入jar包
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-dependencies</artifactId>
<version>3.0.0</version>
</dependency>
</dependencyManagement>
- 修改yaml文件
server:
port: 6001
spring:
application:
name: SleuthZipkinServer01
sleuth:
trace-id128: true ## 允许打印日志
sampler:
probability: 1.0 ## 采样率
- 编写Controller
@RestController
public class DemoController {
Logger log = LoggerFactory.getLogger(DemoController.class);
@Autowired
private RestTemplate restTemplate;
@GetMapping("/test1")
public String test1() {
log.info("这里是SleuthZipkinServer01");
return restTemplate.getForObject("http://127.0.0.1:6002/test1", String.class);
}
}
- curl http://172.16.130.95:6001/test1
2021-05-22 13:38:44.664 INFO [SleuthZipkinServer01,60a898e4f1f46883ee833df45c8c21d4,ee833df45c8c21d4] 1240 --- [nio-6001-exec-8] com.zlf.controller.DemoController : 这里是SleuthZipkinServer01
服务被调用方
- 引入jar包
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-dependencies</artifactId>
<version>3.0.0</version>
</dependency>
</dependencyManagement>
- 修改yaml文件
server:
port: 6001
spring:
application:
name: SleuthZipkinServer02
sleuth:
trace-id128: true ## 允许打印日志
sampler:
probability: 1.0 ## 采样率
- 编写Controller
@RestController
public class DemoController {
Logger log = LoggerFactory.getLogger(DemoController.class);
@GetMapping("/test1")
public String test1() {
log.info("这里是SleuthZipkinServer02~~test1");
return "~~~~~~~server02~~test1~~~~~~~~~~~~~~~~~~";
}
}
- curl http://172.16.130.95:6001/test1
2021-05-22 13:38:44.668 INFO [SleuthZipkinServer02,60a898e4f1f46883ee833df45c8c21d4,382a1a74fda76399] 10044 --- [nio-6002-exec-5] com.zlf.controller.DemoController : 这里是SleuthZipkinServer02~~test1
三、Zipkin
Zipkin 是 Twitter 的一个开源项目,它基于 Google Dapper 实现,是一个致力于收集所有服务的监控数据的分布式跟踪系统,它提供了收集数据和查询数据两大接口服务。 我们可以使用它来收集各个服务器上请求链路的跟踪数据,并通过它提供的 REST API 接口来辅助我们查询跟踪数据以实现对分布式系统的监控程序,从而及时地发现系统中出现的延迟升高问题并找出系统性能瓶颈的根源。除了面向开发的 API 接口之外,它也提供了方便的 UI 组件来帮助我们直观的搜索跟踪信息和分析请求链路明细,比如:可以查询某段时间内各用户请求的处理时间等。
Zipkin 提供了可插拔数据存储方式:In-Memory、MySql、Cassandra 以及 Elasticsearch。接下来的测试为方便直接采用 In-Memory 方式进行存储,生产推荐 Elasticsearch。
3.1 环境搭建
- 下载zipkin jar包
curl -fL -o 'zipkin.jar' 'https://maven.aliyun.com/repository/central/io/zipkin/zipkin-server/2.23.2/zipkin-server-2.23.2-exec.jar'
- 启动Zipkin
java -jar zipkin.jar
启动成功:
页面访问:
3. Zipkin Mysql方式存储配置
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,
`remote_service_name` VARCHAR(255),
`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',
PRIMARY KEY (`trace_id_high`, `trace_id`, `id`)
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;
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(`remote_service_name`) COMMENT 'for getTraces and getRemoteServiceNames';
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 and autocomplete values';
ALTER TABLE zipkin_annotations ADD INDEX(`a_key`) COMMENT 'for getTraces and autocomplete values';
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,
`error_count` BIGINT,
PRIMARY KEY (`day`, `parent`, `child`)
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;
在zipkin.jar同级目录创建zipkin-server.properties文件:
zipkin.storage.type=mysql
zipkin.storage.mysql.host=localhost
zipkin.storage.mysql.port=3306
zipkin.storage.mysql.username=root
zipkin.storage.mysql.password=123
3.2 使用
服务调用方
- 引入jar包(在Sleuth基础上增加)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
- 修改yaml文件(在Sleuth基础上增加)
spring:
zipkin:
base-url: http://127.0.0.1:9411
- 编写Controller
@RestController
public class DemoController {
Logger log = LoggerFactory.getLogger(DemoController.class);
@Autowired
private RestTemplate restTemplate;
@GetMapping("/test2")
public String test2() {
log.info("这里是SleuthZipkinServer01");
return restTemplate.getForObject("http://127.0.0.1:6002/test2", String.class);
}
}
服务被调用方
- 引入jar包(在Sleuth基础上增加)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
<!-- 监控链路mysql数据库相关jar -->
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-instrumentation-mysql8</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
- 修改yaml文件(在Sleuth基础上增加)
主要是&queryInterceptors=brave.mysql8.TracingQueryInterceptor
&exceptionInterceptors=brave.mysql8.TracingExceptionInterceptor
&zipkinServiceName=myDatabaseService 配置
spring:
zipkin:
base-url: http://127.0.0.1:9411
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/test?useSSL=false&queryInterceptors=brave.mysql8.TracingQueryInterceptor&exceptionInterceptors=brave.mysql8.TracingExceptionInterceptor&zipkinServiceName=myDatabaseService&serverTimezone=UTC
username: root
password: 123
- 编写Controller
@RestController
public class DemoController {
Logger log = LoggerFactory.getLogger(DemoController.class);
@Autowired
private UserService userService;
@GetMapping("/test2")
public String test2() {
log.info("这里是SleuthZipkinServer02~~~test2");
return userService.getUser();
}
}
public interface UserService {
public String getUser();
}
@Service
public class UserServiceImpl implements UserService {
@Resource
private JdbcTemplate jdbcTemplate;
@Override
public String getUser() {
String sql = "select * from test_user";
List<UserEntity> userList = (List<UserEntity>) jdbcTemplate.query(sql, new RowMapper<UserEntity>() {
@Override
public UserEntity mapRow(ResultSet rs, int rowNum) throws SQLException {
UserEntity user = new UserEntity();
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
return user;
}
});
return userList.toString();
}
}
- 观察Zipkin客户端
四、常见的链路追踪技术
- cat
由大众点评开源,基于java开发的实时应用监控平台,包括实时应用监控,业务监控。继承方案是通过代码埋点的方式来实现监控,比如:拦截器,过滤器等。对代码的入侵性很大,集成成本较高,风险较大。 - zipkin
由Twitter公司开源,开放源代码分布式的跟踪系统,用于手机服务的定时数据,以解决微服务架构中的延迟问题,包括数据的收集、存储、查找和展现。该产品结合spring-cloud-sleuth使用较为简单,集成很方便,但功能较简单。 - pinpoint
由韩国人开源的基于字节码注入的调用链分析,以及应用监控分析工具,特点是支持多种插件,UI功能较强,接入端无代码入侵。 - skywalking
是本土开源的基于字节码注入的调用链分析,以及应用监控分析工具。特点和pinpoint差不多。
下载:http://skywalking.apache.org/
启动:java -javaagent:G:\github\incubator-skywalking\skywalking-agent\skywalking-agent.jar -Dskywalking.agent.service_name=test -jar app.jar