当服务微服务进行管理后,服务模块之前的调用拓扑非常的复杂。并且当每一个模块又有多个分布式集群等复杂的情况时,一个请求可能会调用后端的N多台服务,那么在追查问题的时候是非常麻烦的。一般不同的小组会负责不同的服务模块,则跨团队的协作是非常麻烦的。比如电商平台中,当一个请求进入后,api网关会根据URI会分发到不同的服务模块,比如当前调用到了订单系统,订单系统可能会去查下商品系统。我们原来的商品系统会根据业务分为好几个子系统,有的子系统还有自己的服务模块,总共商品有上百台服务器。那么在问题追踪的时候可以想象。
那么实现链路追踪对于微服务治理来说至关重要。
Zipkin 是 Twitter 的一个开源项目,它基于 Google Dapper 实现,它致力于收集服务的定时数据,以解决微服务架构中的延迟问题,包括数据的收集、存储、查找和展现。 我们可以使用它来收集各个服务器上请求链路的跟踪数据,并通过它提供的 REST API 接口来辅助我们查询跟踪数据以实现对分布式系统的监控程序,从而及时地发现系统中出现的延迟升高问题并找出系统性能瓶颈的根源。除了面向开发的 API 接口之外,它也提供了方便的 UI 组件来帮助我们直观的搜索跟踪信息和分析请求链路明细,比如:可以查询某段时间内各用户请求的处理时间等。 Zipkin 提供了可插拔数据存储方式:In-Memory、MySql、Cassandra 以及 Elasticsearch。
Zipkin 的架构,主要由 4 个核心组件构成:
- Collector :收集器组件,它主要用于处理从外部系统发送过来的跟踪信息,将这些信息转换为Zipkin 内部处理的 Span 格式,以支持后续的存储、分析、展示等功能。
- Storage :存储组件,它主要对处理收集器接收到的跟踪信息,默认会将这些信息存储在内存中,我们也可以修改此存储策略,通过使用其他存储组件将跟踪信息存储到数据库中。
- RESTful API :API 组件,它主要用来提供外部访问接口。比如给客户端展示跟踪信息,或是外接系统访问以实现监控等。
- Web UI :UI 组件,基于 API 组件实现的上层应用。通过 UI 组件用户可以方便而有直观地查询和分析跟踪信息。
Zipkin分为两端,一个是 Zipkin 服务端,一个是 Zipkin 客户端,客户端也就是微服务的应用。客户端会配置服务端的 URL 地址,一旦发生服务间的调用的时候,会被配置在微服务里面的 Sleuth 的监听器监听,并生成相应的 Trace 和 Span 信息发送给服务端。发送的方式主要有两种,一种是 HTTP 报文的方式,还有一种是消息总线的方式如 RabbitMQ。
不论哪种方式,我们都需要:
- 一个服务注册中心,这里就用之前的 eureka 来当注册中心。
- 一个 Zipkin 服务端。
- 多个微服务,这些微服务中配置 Zipkin 客户端。
1、搭建 Zipkin 服务端
在 Spring Boot 2.0 版本之后,官方已不推荐自己搭建定制了,而是直接提供了编译好的 jar 包。详情可以查看官网:https://zipkin.io/pages/quickstart.html
我下载的是zipkin-server-2.23.2-exec.jar版本,下载之后启动
启动命令:java -jar zipkin-server-2.23.2-exec.jar
启动成功后访问:http://127.0.0.1:9411/
2、搭建 Zipkin 客户端
如上图,我们搭建了三个微服务,x-demo-springcloud-pay-service,x-demo-springcloud-user-service,x-demo-springcloud-order-service。在x-demo-springcloud-pay-service中调用user和order服务。
1、引入依赖(pay,user,order三个服务引入的依赖项目)
dependencies { compile("org.springframework.cloud:spring-cloud-starter-netflix-eureka-client") compile("org.springframework.cloud:spring-cloud-starter-zipkin") compile("org.springframework.cloud:spring-cloud-starter-netflix-ribbon") }
2、bootstrap.yml配置文件
x-demo-springcloud-pay-service,x-demo-springcloud-user-service,x-demo-springcloud-order-service三个服务的bootstrap.yml配置一样,只是应用名称和端口号不同。
第5行配置zipkin server地址。
1 spring: 2 application: 3 name: x-demo-springcloud-user-service 4 zipkin: 5 base-url: http://127.0.0.1:9411/ 6 sleuth: 7 sampler: 8 probability: 1.0 9 10 server: 11 port: 8086 12 13 eureka: 14 client: 15 service-url: 16 defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/,http://localhost:8763/eureka/ 17 logging: 18 level: 19 root: debug
4、用户微服务代码
为了省事,启动类上直接暴露了Rest接口
1 /** 2 * @author Leo 3 */ 4 @SpringBootApplication 5 @EnableEurekaClient 6 @RestController 7 public class UserServerApplication { 8 9 public static void main(String[] args) { 10 SpringApplication.run(UserServerApplication.class, args); 11 } 12 13 @RequestMapping(value = "user", method = RequestMethod.GET) 14 public String user() { 15 return "Leo"; 16 } 17 }
5、订单微服务代码
为了省事,启动类上直接暴露了Rest接口
1 /** 2 * @author Leo 3 */ 4 @SpringBootApplication 5 @EnableEurekaClient 6 @RestController 7 public class OrderServerApplication { 8 9 public static void main(String[] args) { 10 SpringApplication.run(OrderServerApplication.class, args); 11 } 12 13 @RequestMapping(value = "makeOrder", method = RequestMethod.GET) 14 public String makeOrder() { 15 return "下单成功"; 16 } 17 }
6、支付微服务代码
在pay方法中通过Ribbon方式调用x-demo-springcloud-user-service,x-demo-springcloud-order-service服务接口。
1 /** 2 * @author Leo 3 */ 4 @SpringBootApplication 5 @EnableEurekaClient 6 @RestController 7 public class PayServerApplication { 8 9 public static void main(String[] args) { 10 SpringApplication.run(PayServerApplication.class, args); 11 } 12 13 @Bean 14 @LoadBalanced 15 RestTemplate restTemplate() { 16 return new RestTemplate(); 17 } 18 19 @RequestMapping(value = "pay", method = RequestMethod.GET) 20 public String pay() { 21 String str1 = restTemplate().getForObject("http://x-demo-springcloud-user-service/user", String.class); 22 String str2 = restTemplate().getForObject("http://x-demo-springcloud-order-service/makeOrder", String.class); 23 return str1 + " " + str2; 24 } 25 }
7、启动Eureka,Zipkin Server,x-demo-springcloud-pay-service,x-demo-springcloud-user-service,x-demo-springcloud-order-service。
8、通过浏览器访问http://localhost:8087/pay
9、刷新Zipkin管理平台: http://localhost:9411/zipkin/
从下图可以看出pay-service调用了user-service及order-service服务。
还可以看到在一次请求中,总耗时以及调用其它服务耗时
截取部分后台日志:
2021-03-01 14:44:36.707 DEBUG [x-demo-springcloud-pay-service,db8d8d79c0da3e1d,c2ac26460fc33e48,true] 8504 2021-03-01 14:44:36.707 DEBUG [x-demo-springcloud-pay-service,db8d8d79c0da3e1d,c2ac26460fc33e48,true] 8504 2021-03-01 14:44:36.707 DEBUG [x-demo-springcloud-pay-service,db8d8d79c0da3e1d,c2ac26460fc33e48,true] 8504 2021-03-01 14:44:36.707 DEBUG [x-demo-springcloud-pay-service,db8d8d79c0da3e1d,c2ac26460fc33e48,true] 8504 2021-03-01 14:44:36.708 INFO [x-demo-springcloud-pay-service,db8d8d79c0da3e1d,c2ac26460fc33e48,true] 8504 2021-03-01 14:44:36.708 DEBUG [x-demo-springcloud-pay-service,db8d8d79c0da3e1d,c2ac26460fc33e48,true] 8504 2021-03-01 14:44:36.708 DEBUG [x-demo-springcloud-pay-service,db8d8d79c0da3e1d,c2ac26460fc33e48,true] 8504
这些元素正是实现分布式服务跟踪的重要组成部分,每个值的含义如下:
第一个值:x-demo-springcloud-pay-service,它记录了应用的名称
第二个值:db8d8d79c0da3e1d,是 Spring Cloud Sleuth 生成的一个 ID,称为 Trace ID,它用来标识一条请求链路。一条请求链路中包含一个 Trace ID,多个 Span ID。
第三个值:c2ac26460fc33e48,是 Spring Cloud Sleuth 生成的另外一个 ID,称为 Span ID,它表示一个基本的工作单元,比如发送一个 HTTP 请求。
第四个值:false,它表示是否要将该信息输出到 Zipkin Server 中来收集和展示。
Sleuth 默认采样算法的实现是 Reservoir sampling,具体的实现类是 PercentageBasedSampler,默认的采样比例为: 0.1,即 10%。我们可以通过 spring.sleuth.sampler.probability 来设置,所设置的值介于 0 到 1 之间,1 则表示全部采集。