在微服务中,多个服务分布在不同物理机器上,各个服务之间相互调用。如何清晰地记录服务调用过程,并在出现问题的时候能够通过查看日志和服务之间的调用关系来定位问题,这样的系统就叫做链路跟踪系统。
分布式服务跟踪系统主要由五部分组成:
在微服务出现服务调用过程慢、服务无法调用成功等问题,都需要用到链路跟踪系统来进一步排查问题,否则就是盲人摸象,无从下手。
Spring Cloud Sleuth 是为了对微服务之间的调用链路进行跟踪的一个组件,提供了非常多的整合方案,比如可以整合zipkin
在了解sleth前,必须要掌握如下几个概念:
spring.sleuth.sampler.probability=1.0
,则表示采样率为100%(采集服务的全部追踪数据),默认值为0.1(10%)。AlwaysSampler
)或不采样(NeverSampler
)@Bean
public Sampler defaultSampler() {
return new AlwaysSampler();
}
了解了以上几个概念后,我们来看下具体的链路跟踪过程来加深下印象:
链路跟踪基本过程:
sleuth是spring cloud提供的平台无关性的链路跟踪组件,可以整合不同的实现,这里整合的就是zipkin。
zipkin是Twitter的一个开源项目,它致力于收集服务的定时数据,以解决微服务架构中的延迟问题,包括数据的收集、存储、查找和发现。
可以借助zipkin来收集各个服务器上请求链路的跟踪数据,并通过REST API接口来查询跟踪数据以实现对分布式系统的监控程序,从而及时地发现系统中出现的延迟升高、请求丢失等问题并找出系统的性能瓶颈。
除了API的方式,也提供了可视化组件来帮助我们直观的搜索跟踪信息和分析请求链路与花费时间。比如需要查询某段时间内各个用户请求的处理花费时间。
如图可知:
接下来我们通过示例来演示如何实现并使用链路跟踪系统。
此示例中,会搭建两个模块,一个是sale模块,一个是user模块,通过sale模块调用user模块,看看zipkin能不能跟踪到
安装jdk8、mysql
下载zipkin包:https://search.maven.org/remote_content?g=io.zipkin&a=zipkin-server&v=LATEST&c=exec
建立一个名为zipkin数据库,并初始化zipkin默认的几个表:
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-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=123456
java -jar zipkin.jar
开始之前,我们先新建一个user表,一起放在zipkin数据库中
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for tb_user
-- ----------------------------
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID ',
`name` varchar(255) NOT NULL COMMENT '用户名',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of tb_user
-- ----------------------------
BEGIN;
INSERT INTO `tb_user` VALUES (1, 'Kitty');
INSERT INTO `tb_user` VALUES (2, 'Mon');
COMMIT;
SET FOREIGN_KEY_CHECKS = 1;
创建一个名为sleuth-demo的maven项目,pom.xml如下:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>org.siqi.sleuthgroupId>
<artifactId>sleuth-demoartifactId>
<packaging>pompackaging>
<version>1.0-SNAPSHOTversion>
<modules>
<module>sleuth-salemodule>
<module>sleuth-usermodule>
modules>
<properties>
<maven.compiler.source>1.8maven.compiler.source>
<maven.compiler.target>1.8maven.compiler.target>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<spring-boot.version>2.3.4.RELEASEspring-boot.version>
<sleuth.version>2.2.6.RELEASEsleuth.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-sleuth-dependenciesartifactId>
<version>${sleuth.version}version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-dependenciesartifactId>
<version>${spring-boot.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
project>
继承两个父工程:spring boot和sleuth
创建sale子模块
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>sleuth-demoartifactId>
<groupId>org.siqi.sleuthgroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>saleartifactId>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-zipkinartifactId>
dependency>
dependencies>
project>
新建一个Sale实体
public class Sale {
/**
* ID
*/
private Long id;
/**
* 名称
*/
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
新建一个SaleController
@RestController
public class SaleController {
@Autowired
RestTemplate restTemplate;
/**
* 通过sale服务向user服务请求数据
* http://localhost:8081/sales/1
*
* @param id
* @return
*/
@RequestMapping(value = "/sales/{id}", method = GET, produces = {
APPLICATION_JSON_VALUE})
public Sale describeSale(@PathVariable(value = "id") Long id) {
// 向user服务请求数据
return restTemplate.getForObject("http://localhost:8091/users/{1}", Sale.class, id);
}
@Bean
RestTemplate restTemplate() {
return new RestTemplate();
}
}
模块配置为:
spring.application.name=sale
server.port=8081
#
#
logging.level.org.springframework.web=DEBUG
spring.sleuth.traceId128=true
spring.sleuth.sampler.probability=1.0
# Adds trace and span IDs to logs (when a trace is in progress)
logging.pattern.level=[%X{
traceId}/%X{
spanId}] %-5p [%t] %C{
2} - %m%n
# Propagates a field named 'user_name' downstream
# Note: In sleuth 3.x it is spring.sleuth.baggage.remote-fields=user_name
spring.sleuth.propagation-keys=user_name
新建一个user模块,供sale模块进行服务调用
pom.xml文件如下:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>sleuth-demoartifactId>
<groupId>org.siqi.sleuthgroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>userartifactId>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-zipkinartifactId>
dependency>
<dependency>
<groupId>io.zipkin.bravegroupId>
<artifactId>brave-instrumentation-mysql8artifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
dependency>
dependencies>
project>
新建一个User实体:
public class User {
/**
* ID
*/
private Long id;
/**
* 名称
*/
private String name;
public User() {
}
public User(Long id, String name) {
this.id = id;
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
新建一个UserRepository,来访问数据库获取用户数据:
@Repository
public class UserRepository {
private static final Logger logger = LoggerFactory.getLogger(UserRepository.class);
@Resource
private JdbcTemplate jdbcTemplate;
/**
* 根据用户ID查询用户信息
*
* @param id ID
* @return 用户信息
*/
public User queryById(Long id) {
logger.info("query user by id ");
// 由于是演示,所以使用最简单的方式查询数据库数据
return (User) jdbcTemplate.queryForObject("select id, name from tb_user where id = ?",
new Object[]{
id},
new BeanPropertyRowMapper(User.class));
}
}
新建一个UserController
@RestController
public class UserController {
@Resource
private UserRepository userRepository;
/**
* 查看用户信息
* http://localhost:8091/users/1
*
* @param id
* @return json格式
*/
@ResponseBody
@RequestMapping(value = "/users/{id}", method = GET, produces = {
APPLICATION_JSON_VALUE})
public User describeUser(@PathVariable(value = "id") Long id) {
if (id == null) {
throw new IllegalArgumentException("id can't be null");
}
return userRepository.queryById(id);
}
}
配置文件为:
spring.application.name=user
server.port=8091
# 日志等级
logging.level.org.springframework.web=DEBUG
logging.pattern.level=[%X{
traceId}/%X{
spanId}] %-5p [%t] %C{
2} - %m%n
#
# sleuth配置
spring.sleuth.traceId128=true
spring.sleuth.sampler.probability=1.0
# Adds trace and span IDs to logs (when a trace is in progress)
# Propagates a field named 'user_name' downstream
# Note: In sleuth 3.x it is spring.sleuth.baggage.remote-fields=user_name
spring.sleuth.propagation-keys=user_name
#
# 数据库配置
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/zipkin?\
useSSL=false\
&queryInterceptors=brave.mysql8.TracingQueryInterceptor\
&exceptionInterceptors=brave.mysql8.TracingExceptionInterceptor\
&zipkinServiceName=userDB\
&useUnicode=true&characterEncoding=utf8\
&allowMultiQueries=true\
&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
启动sale、user模块,并访问地址:http://localhost:8081/sales/1
这时我们发出了一个请求,通过浏览器-》sale-》user,然后返回数据
访问成功说明项目搭建没问题
我们来看看zipkin的跟踪情况,打开zipkin控制台:http://localhost:9411/
至此,我们完成了从示例构建到zipkin链路跟踪演示
cat
由大众点评开源,基于java开发的实时应用监控平台,包括实时应用监控,业务监控。继承方案是通过代码埋点的方式来实现监控,比如:拦截器,过滤器等。对代码的入侵性很大,集成成本较高,风险较大。
zipkin
由Twitter公司开源,开放源代码分布式的跟踪系统,用于手机服务的定时数据,以解决微服务架构中的延迟问题,包括数据的收集、存储、查找和展现。该产品结合spring-cloud-sleuth使用较为简单,集成很方便,但功能较简单。
pinpoint
由韩国人开源的基于字节码注入的调用链分析,以及应用监控分析工具,特点是支持多种插件,UI功能较强,接入端无代码入侵。
skywalking(推荐使用)
是本土开源的基于字节码注入的调用链分析,以及应用监控分析工具。特点和pinpoint差不多。
PercentageBasedSampler
。