700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > 基于docker部署的微服务架构(九): 分布式服务追踪 Spring Cloud Sleuth

基于docker部署的微服务架构(九): 分布式服务追踪 Spring Cloud Sleuth

时间:2020-05-27 11:27:32

相关推荐

基于docker部署的微服务架构(九): 分布式服务追踪 Spring Cloud Sleuth

为什么80%的码农都做不了架构师?>>>

前言

微服务架构中完成一项功能经常会在多个服务之间远程调用(RPC),形成调用链。每个服务节点可能在不同的机器上甚至是不同的集群上,需要能追踪整个调用链,以便在服务调用出错或延时较高时准确定位问题。

以下内容引用 Dapper,大规模分布式系统的跟踪系统 译文 ,介绍了分布式服务追踪的重要性以及设计原则:

当代的互联网的服务,通常都是用复杂的、大规模分布式集群来实现的。互联网应用构建在不同的软件模块集上,这些软件模块,有可能是由不同的团队开发、可能使用不同的编程语言来实现、有可能布在了几千台服务器,横跨多个不同的数据中心。因此,就需要一些可以帮助理解系统行为、用于分析性能问题的工具。

举一个跟搜索相关的例子,这个例子阐述了Dapper可以应对哪些挑战。比如一个前段服务可能对上百台查询服务器发起了一个Web查询,每一个查询都有自己的Index。这个查询可能会被发送到多个的子系统,这些子系统分别用来处理广告、进行拼写检查或是查找一些像图片、视频或新闻这样的特殊结果。根据每个子系统的查询结果进行筛选,得到最终结果,最后汇总到页面上。我们把这种搜索模型称为“全局搜索”(universal search)。总的来说,这一次全局搜索有可能调用上千台服务器,涉及各种服务。而且,用户对搜索的耗时是很敏感的,而任何一个子系统的低效都导致导致最终的搜索耗时。如果一个工程师只能知道这个查询耗时不正常,但是他无从知晓这个问题到底是由哪个服务调用造成的,或者为什么这个调用性能差强人意。首先,这个工程师可能无法准确的定位到这次全局搜索是调用了哪些服务,因为新的服务、乃至服务上的某个片段,都有可能在任何时间上过线或修改过,有可能是面向用户功能,也有可能是一些例如针对性能或安全认证方面的功能改进。其次,你不能苛求这个工程师对所有参与这次全局搜索的服务都了如指掌,每一个服务都有可能是由不同的团队开发或维护的。再次,这些暴露出来的服务或服务器有可能同时还被其他客户端使用着,所以这次全局搜索的性能问题甚至有可能是由其他应用造成的。举个例子,一个后台服务可能要应付各种各样的请求类型,而一个使用效率很高的存储系统,比如Bigtable,有可能正被反复读写着,因为上面跑着各种各样的应用。

上面这个案例中我们可以看到,对Dapper我们只有两点要求:无所不在的部署,持续的监控。无所不在的重要性不言而喻,因为在使用跟踪系统的进行监控时,即便只有一小部分没被监控到,那么人们对这个系统是不是值得信任都会产生巨大的质疑。另外,监控应该是7x24小时的,毕竟,系统异常或是那些重要的系统行为有可能出现过一次,就很难甚至不太可能重现。那么,根据这两个明确的需求,我们可以直接推出三个具体的设计目标:

低消耗:跟踪系统对在线服务的影响应该做到足够小。在一些高度优化过的服务,即使一点点损耗也会很容易察觉到,而且有可能迫使在线服务的部署团队不得不将跟踪系统关停。应用级的透明:对于应用的程序员来说,是不需要知道有跟踪系统这回事的。如果一个跟踪系统想生效,就必须需要依赖应用的开发者主动配合,那么这个跟踪系统也太脆弱了,往往由于跟踪系统在应用中植入代码的bug或疏忽导致应用出问题,这样才是无法满足对跟踪系统“无所不在的部署”这个需求。面对当下想Google这样的快节奏的开发环境来说,尤其重要。延展性:Google至少在未来几年的服务和集群的规模,监控系统都应该能完全把控住。一个额外的设计目标是为跟踪数据产生之后,进行分析的速度要快,理想情况是数据存入跟踪仓库后一分钟内就能统计出来。尽管跟踪系统对一小时前的旧数据进行统计也是相当有价值的,但如果跟踪系统能提供足够快的信息反馈,就可以对生产环境下的异常状况做出快速反应。

在spring cloud技术栈中,spring cloud Sleuth借鉴了Google Dapper的实现, 提供了分布式服务追踪的解决方案。

引入 Spring Cloud Sleuth 追踪系统

Spring Cloud Sleuth提供了两种追踪信息收集的方式,一种是通过http的方式,一种是通过异步消息的方式,这里以生产环境常用的异步消息的收集方式为例。

在之前创建的项目上做修改,增加Spring Cloud Sleuth分布式服务追踪功能。

修改add-service-demopom.xml文件,增加相关依赖:

<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-sleuth</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-sleuth-stream</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-stream-binder-rabbit</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-feign</artifactId></dependency>

spring-cloud-starter-sleuth引入sleuth基础jar包,spring-cloud-sleuth-streamspring-cloud-stream-binder-rabbit引入通过异步消息收集追踪信息的相关jar包,spring-cloud-starter-feign引入了feign,用来远程调用别的服务(在 基于docker部署的微服务架构(二): 服务提供者和调用者 中有介绍),稍后会创建一个提供随机数的服务,用来展示服务调用链。

然后修改log4j2.xml配置文件, 修改日志格式为:

<Property name="PID">????</Property><Property name="LOG_EXCEPTION_CONVERSION_WORD">%xwEx</Property><Property name="LOG_LEVEL_PATTERN">%5p</Property><Property name="logFormat">%d{yyyy-MM-dd HH:mm:ss.SSS} ${LOG_LEVEL_PATTERN} [@project.artifactId@,%X{X-B3-TraceId},%X{X-B3-SpanId},%X{X-Span-Export}] ${sys:PID} --- [%15.15t] %-40.40c{1.} : %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD}</Property>

在日志信息中增加用来追踪的TraceId、SpanId,Export表示是否导出到zipkin

之前在 基于docker部署的微服务架构(四): 配置中心 的内容中已经配置了rabbitmq,用于spring cloud bus,所以这里就不用再配消息队列了,用之前配置的rabbitmq就可以了。

这时候启动add-service-demo工程,可以看到控制台输出的日志信息增加了TraceId、SpanId的相关信息,INFO [add-service-demo,,,] 18668,但是现在还没有具体的内容,因为没有发生服务调用。

创建一个新的工程random-service-demo,用来生成一个随机整数。新建maven项目,修改pom.xml文件,引入相关依赖:

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>1.4.2.RELEASE</version></parent><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-log4j2</artifactId></dependency><dependency><groupId>org.apache.kafka</groupId><artifactId>kafka-clients</artifactId><version>0.10.0.1</version></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-eureka</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-config</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-bus-amqp</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-sleuth</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-sleuth-stream</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-stream-binder-rabbit</artifactId></dependency></dependencies><dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>Camden.SR2</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><properties><!-- 指定java版本 --><java.version>1.8</java.version><!-- 镜像前缀,推送镜像到远程库时需要,这里配置了一个阿里云的私有库 --><docker.image.prefix>-/ztecs</docker.image.prefix><!-- docker镜像的tag --><docker.tag>demo</docker.tag><!-- 激活的profile --><activatedProperties></activatedProperties><kafka.bootstrap.servers>10.47.160.238:9092</kafka.bootstrap.servers></properties>

这里同样引入了Sleuth相关内容。

创建启动入口类RandomServiceApplication.java

@EnableDiscoveryClient@SpringBootApplicationpublic class RandomServiceApplication {public static void main(String[] args) {SpringApplication.run(RandomServiceApplication.class, args);}}

resources中的配置文件可以完全复用add-service-demo中的配置,因为最终的配置是从配置中心中拉取的,resources只需要配置config-server的相关内容即可。

在git仓库中增加random-service-demo-dev.yml配置文件,内容:

server:port: 8200spring:rabbitmq:host: 10.47.160.238port: 5673username: guestpassword: guest

配置了端口和消息队列。

创建一个RandomController.java对外提供随机数服务:

@RestController@RefreshScopepublic class RandomController {private static final Logger logger = LoggerFactory.getLogger(RandomController.class);@RequestMapping(value = "/random", method = RequestMethod.GET)public Integer random() {logger.info(" >>> random");Random random = new Random();return random.nextInt(10);}}

业务逻辑很简单,生成一个0 ~ 10的随机整数并返回。

接下来在add-service-demo工程中增加一个随机数相加的接口,调用random-service-demo生成随机数,并把随机数相加作为结果返回。

AddServiceApplication.java中增加@EnableFeignClients注解,开启feign客户端远程调用。

增加RandomService.java用来远程调用random-service-demo中的接口:

@FeignClient("RANDOM-SERVICE-DEMO")public interface RandomService {@RequestMapping(method = RequestMethod.GET, value = "/random")Integer random();}

AddController.java中增加randomAdd方法,并对外暴露接口。在方法中两次调用random-service-demo生成随机数的接口,把随机数相加作为结果返回:

@Autowiredprivate RandomService randomService;private static Logger logger = LoggerFactory.getLogger(AddController.class);@RequestMapping(value = "/randomAdd", method = RequestMethod.GET)public Map<String, Object> randomAdd() {logger.info(">>> randomAdd");Integer random1 = randomService.random();Integer random2 = randomService.random();Map<String, Object> returnMap = new HashMap<>();returnMap.put("code", 200);returnMap.put("msg", "操作成功");returnMap.put("result", random1 + random2);return returnMap;}

修改服务网关service-gateway-demo引入sleuth, 修改pom.xml引入依赖(参照add-service-demo),修改log4j2.xml中的日志格式(参照add-service-demo)。

启动add-service-demo、random-service-demo、service-gateway-demo,通过网关调用接口http://localhost/add-service/randomAdd。查看日志可以发现 从service-gateway-demo到add-service-demo再到random-service-demo中输出的日志信息,包含相同的TraceId,表明处于一个调用链。

使用zipkin收集追踪信息并展现

通过上边的配置,在服务调用的过程中spring cloud sleuth自动帮我们添加了TraceId、SpanId等服务追踪需要的内容。现在还需要集中收集这些信息,并提供可视化界面把这些信息展示出来。

Zipkin是Twitter的一个开源项目,允许开发者收集各个服务上的监控数据,并提供查询接口。spring cloud sleuth对zipkin做了封装,提供了两种数据保存方式:内存和mysql,这里以生产环境中使用的mysql持久化方式为例。

创建一个maven工程zipkin-server-demo,修改pom.xml文件增加相关依赖:

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>1.4.2.RELEASE</version></parent><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-log4j2</artifactId></dependency><dependency><groupId>org.apache.kafka</groupId><artifactId>kafka-clients</artifactId><version>0.10.0.1</version></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-sleuth-zipkin-stream</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-sleuth</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-stream-binder-rabbit</artifactId></dependency><dependency><groupId>io.zipkin.java</groupId><artifactId>zipkin-autoconfigure-ui</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency></dependencies><dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>Camden.SR2</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><properties><!-- 指定java版本 --><java.version>1.8</java.version><!-- 镜像前缀,推送镜像到远程库时需要,这里配置了一个阿里云的私有库 --><docker.image.prefix>-/ztecs</docker.image.prefix><!-- docker镜像的tag --><docker.tag>demo</docker.tag><!-- 激活的profile --><activatedProperties></activatedProperties><kafka.bootstrap.servers>10.47.160.238:9092</kafka.bootstrap.servers></properties>

简单说明下引入的依赖:spring-cloud-sleuth-zipkin-stream引入了通过消息驱动的方式收集追踪信息所需要的zipkin依赖,spring-cloud-starter-sleuthspring-cloud-stream-binder-rabbit,这两个和之前项目中引入的一样,都是消息驱动的sleuth相关依赖。zipkin-autoconfigure-ui引入了zipkin相关依赖,最后引入了mysql和jdbc的依赖,用于保存追踪数据。

在resources目录中新建配置文件application.yml:

server:port: 9411spring:profiles:active: @activatedProperties@rabbitmq:host: 10.47.160.114port: 5673username: guestpassword: guestdatasource:schema: classpath:/mysql.sqlurl: jdbc:mysql://10.47.160.114:3306/sleuth_logusername: soapassword: 123456initialize: truecontinueOnError: truesleuth:enabled: falseoutput:ansi:enabled: ALWAYSzipkin:storage:type: mysql

配置了zipkinweb页面的端口9411,配置mysql和初始化脚本, 并指定zipkin.storage.type为mysql。

在resources目录中创建mysql初始化脚本mysql.sql

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';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保存追踪数据需要的表。

新建log4j2.xml配置文件,可以把其他项目中的复制过来(add-service-demo等),内容都是一样的。

创建启动入口类ZipkinServerApplication.java

@SpringBootApplication@EnableZipkinStreamServerpublic class ZipkinServerApplication {public static void main(String[] args) {SpringApplication.run(ZipkinServerApplication.class, args);}}

运行main方法启动zipkin,访问http://localhost:9411打开页面。

有可能在zipkin中查询不到数据,这是因为sleuth有一个采样率的概念,并不会发送所有的数据,可以通过配置spring.sleuth.sampler.percentage指定数据采样的百分比。

重复多次访问http://localhost/add-service/randomAdd调用接口,就能在zipkin中查询到数据了。

还可以查看服务间的调用链:

使用docker-maven-plugin打包并生成docker镜像

这部分内容和前面几篇文章基本相同,都是把容器间的访问地址和--link参数对应,不再赘述。

demo源码 spring-cloud-4.0目录

grok插件解析日志内容

如果使用ELK进行日志分析的话,可以使用grok插件解析spring cloud sleuth追踪系统的日志信息(关于ELK系统的部署,可以参阅 基于docker部署的微服务架构(七): 部署ELK日志统计分析系统 )。

修改logstash的配置文件,增加grokfilter:

filter {grok {match => { "message" => "%{TIMESTAMP_ISO8601:timestamp}\s+%{LOGLEVEL:severity}\s+\[%{DATA:service},%{DATA:trace},%{DATA:span},%{DATA:exportable}\]\s+%{DATA:pid}---\s+\[%{DATA:thread}\]\s+%{DATA:class}\s+:\s+%{GREEDYDATA:rest}" }}}

这样就可以解析日志信息了。

最后

分布式服务追踪在微服务架构中是非常重要的一部分,在发生异常时需要通过追踪系统来定位问题。Spring Cloud Sleuth基于Google Dapper提供了一个简单易用的分布式追踪系统。

在生产环境中,只有追踪系统还不够,在服务调用发生错误时,比如:网络延时、资源繁忙等,这种错误往往会造成阻塞,造成后续访问困难,在高并发情况下,调用服务失败时如果没有隔离措施,会波及到整个服务端,进而使整个服务端崩溃。

所以还需要一个熔断系统,对服务依赖做隔离和容错。下一篇将会介绍hystrix熔断系统。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。