1、概述
在微服务框架中,一个客户端请求,从发起到后端系统中,会经历多个不同的微服务结点的调用,每一个请求都会形成一条复杂的分布式调用链路,链路中任何一个服务出现故障或延时都会导致整个请求最终的失败
。
Spring Cloud Sleuth
提供了一套完整的服务监控跟踪解决方案
,兼容支持Zipkin数据展现
。
2、搭建链路监控步骤
2-1、安装Zipkin
Spring Cloud从F版起就不需要自己构建Zipkin Server了,只需要调用jar包即可,Java环境JRE 8起。
2-1-1、linux或者macOS
在终端执行:curl -sSL https://zipkin.io/quickstart.sh | bash -s
可以下载zipkin最新的jar包,下载后可执行java -jar zipkin.jar
直接运行,再访问http://localhost:9411/zipkin/
查看管理后台。
我这里自己改过存放路径zipkin.jar
包路径,所以启动时要先进入zipkin.jar
包根目录cd /Users/test/Documents/Maven_Repository/zipkin
下,再执行java -jar zipkin.jar
。
2-1-2、windows
到 https://zipkin.io/pages/quickstart.html
官网下载zipkin-server-2.12.0-exec.jar
包,在进入终端切换到包的根路执行 java -jar zipkin-server-2.12.0-exec.jar
2-1-3、docker
在终端执行 docker search openzipkin
搜索出很多个zipkin
的镜像,选第一个就行 openzipkin/zipkin
,然后 docker run -d -p 9411:9411 openzipkin/zipkin
就能在 http://localhost:9411/zipkin/
对zipkin
的控制台进行访问。
2-1-4、Zipkin简单介绍
上图可以看到一个请求链路中
,通过Trace Id作为唯一标识
,通过Span Id作为请求信息
,各个Span通过Parent Id关联起来
。
2-2、服务提供者
这里使用cloud-provider-payment8001
模块,修改pom.xml
,加入spring-cloud-starter-zipkin
依赖。
<?xml version="1.0" encoding="UTF-8"?>
<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>cloud2020</artifactId>
<groupId>com.king.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<!-- 子模块名称 -->
<artifactId>cloud-provider-payment8001</artifactId>
<dependencies>
<!-- 引用spring cloud sleuth(sleuth+zipkin)链路跟踪依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<!-- 引用eureka-client注册服务客户端依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 引用cloud-api-common公共模块 -->
<dependency>
<groupId>com.king.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!-- 引用父级spring boot的依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 引用父级的mybatis跟spring boot的依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!-- 引用父级的druid阿里巴巴连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<!-- 引用父级的mysql的依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- 配置热部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- 引用父级的lombok依赖 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
cloud-provider-payment8001
模块修改application.yml
文件:
# 配置服务端口号
server:
port: 8001
# 配置应用信息
spring:
application:
name: cloud-provider-payment # 配置应用名称
# 配置zipkin
zipkin:
base-url: http://localhost:9411 # zipkin监控后台地址
# 配置sleuth
sleuth:
sampler:
probability: 1 # 采样率介于0,1之间,1表示全部采集,一般使用0.5就够了
# 配置数据源
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 数据源类型
driver-class-name: com.mysql.cj.jdbc.Driver # 数据库驱动
url: jdbc:mysql://localhost:3306/cloud_DB_2020?useUnicode=true&charcaterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai # 数据库连接
username: root # 数据库用户名
password: rootroot # 数据库密码
# 配置eureka
eureka:
client:
register-with-eureka: true # 表示将自己注册进EurekaServer
# 表示是否从Eureka抓取已有的注册信息,默认为true,单点无所谓,集群时候,必须设置成true,才能配合Ribbon使用负载均衡
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka # 入驻的服务地址(单机模式)
# defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # 入驻的服务地址(集群模式)
instance:
instance-id: payment8001 # 指定服务实例id
prefer-ip-address: true # 访问路径可以显示IP地址
lease-renewal-interval-in-seconds: 1 # Eureka客户端向服务端发送心跳时间间隔,单位为秒,默认为30秒
lease-expiration-duration-in-seconds: 2 # Eureka服务端在接收到最后一次心跳后等待的时间上限,单位为秒,默认是90秒,超过后,微服务将被剔除
# mybatis配置
mybatis:
mapper-locations: classpath:mapper/*.xml # mapper文件的位置
type-aliases-package: com.king.springcloud.entities # 所有实体类所在(别名)包
cloud-provider-payment8001
模块修改控制层PaymentController.java
文件:
package com.king.springcloud.controller;
import com.king.springcloud.entities.CommonResult;
import com.king.springcloud.entities.Payment;
import com.king.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Slf4j
@RestController
public class PaymentController {
@Resource
private PaymentService paymentService;
/**
* 获取服务器端口号
*/
@Value("${server.port}")
private String serverPort;
/**
* 注入服务发现
*/
@Resource
private DiscoveryClient discoveryClient;
@PostMapping("/payment/createPayment")
public CommonResult createPayment(@RequestBody Payment payment){
int result = paymentService.createPayment(payment);
if (result > 0){
log.info("------payment控制层------createPayment方法执行成功");
return new CommonResult(200,"成功,执行服务器:" + serverPort, result);
}
log.info("------payment控制层------createPayment方法执行失败");
return new CommonResult(500,"失败,执行服务器:" + serverPort);
}
@GetMapping("/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {
Payment payment = paymentService.getPaymentById(id);
if (!StringUtils.isEmpty(payment)){
log.info("------payment控制层------getPaymentById方法执行成功.");
return new CommonResult(200, "成功,执行服务器:" + serverPort, payment);
}
log.info("------payment控制层------getPaymentById方法执行失败,查询ID:{id}",id);
return new CommonResult(500,"失败,执行服务器:" + serverPort);
}
@GetMapping("/payment/discovery")
public Object discovery(){
List<String> services = discoveryClient.getServices();
for (String element : services) {
log.info("------element------:" + element);
}
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PROVIDER-PAYMENT");
for (ServiceInstance instance : instances) {
log.info(instance.getServiceId() + "\t" + instance.getHost() + "\t" + instance.getPort() + "\t" + instance.getUri());
}
return this.discoveryClient;
}
/**
* 用于cloud-consumer-order80模块测试自定义轮询方法
* @return 返回当前服务器端口号
*/
@GetMapping("/payment/loadBalance")
public String getLoadBalancePort() {
return serverPort;
}
/**
* 用于cloud-consumer-feign-order80模块测试openFeign超时控制
* @return 返回当前服务器端口号
*/
@GetMapping("/payment/feign/timeout")
public String paymentFeignTimeout() {
try {
// 睡眠3秒
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return serverPort;
}
/**
* 使用cloud-consumer-order80模块此方法,形式链路,然后查看Zipkin
* @return
*/
@GetMapping("/payment/zipkin")
public String paymentZipkin() {
return "this is zipkin feedback";
}
}
2-3、服务消费者
这里使用cloud-consumer-order80
模块,修改pom.xml
,同上加入spring-cloud-starter-zipkin
依赖,application.yml
和cloud-provider-payment8001
修改方法一样。在cloud-consumer-order80
模块OrderController
中添加一个映射用于测试。
<?xml version="1.0" encoding="UTF-8"?>
<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>cloud2020</artifactId>
<groupId>com.king.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<!-- 子模块名称 -->
<artifactId>cloud-consumer-order80</artifactId>
<dependencies>
<!-- 引用spring cloud sleuth(sleuth+zipkin)链路跟踪依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<!-- 引用eureka-client注册服务客户端依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 引用cloud-api-common公共模块 -->
<dependency>
<groupId>com.king.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!-- 引用父级spring boot的依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 配置热部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- 引用父级的lombok依赖 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
cloud-consumer-order80
模块的application.yml
文件:
# 配置服务端口号
server:
port: 80
# 配置应用信息
spring:
application:
name: cloud-consumer-order # 配置应用名称
# 配置zipkin
zipkin:
base-url: http://localhost:9411 # zipkin监控后台地址
# 配置sleuth
sleuth:
sampler:
probability: 1 # 采样率介于0,1之间,1表示全部采集,一般使用0.5就够了
# 配置eureka
eureka:
client:
register-with-eureka: true # 表示将自己注册进EurekaServer
# 表示是否从Eureka抓取已有的注册信息,默认为true,单点无所谓,集群时候,必须设置成true,才能配合Ribbon使用负载均衡
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka # 入驻的服务地址(单机模式)
# defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # 入驻的服务地址(集群模式)
instance:
instance-id: consumer-order80 # 指定服务实例id
prefer-ip-address: true # 访问路径可以显示IP地址
cloud-consumer-order80
模块的控制层OrderController.java
文件:
package com.king.springcloud.controller;
import com.king.springcloud.balance.LoadBalancer;
import com.king.springcloud.entities.CommonResult;
import com.king.springcloud.entities.Payment;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.net.URI;
import java.util.List;
@Slf4j
@RestController
public class OrderController {
/**
* Eureka注册中心的提供服务模块的应用名称
*/
public static final String PAYMENT_URL = "http://CLOUD-PROVIDER-PAYMENT";
@Autowired
private RestTemplate restTemplate;
@Resource
private LoadBalancer loadBalancer;
@Autowired
private DiscoveryClient discoveryClient;
/**
* 客户端调用服务端创建方法
* @param payment
* @return
*/
@GetMapping("/consumer/payment/create")
public CommonResult create(Payment payment) {
// postForObject写操作,按照JSON数据格式
return restTemplate.postForObject(PAYMENT_URL + "/payment/createPayment", payment, CommonResult.class);
}
/**
* 客户端调用服务端创建方法
* @param payment
* @return
*/
@GetMapping("/consumer/payment/create2")
public CommonResult create2(Payment payment) {
// postForEntity写操作,按照ResponseEntity数据格式
return restTemplate.postForEntity(PAYMENT_URL + "/payment/create", payment, CommonResult.class).getBody();
}
/**
* 客户端调用服务端查询方法
* @param id
* @return
*/
@GetMapping("/consumer/payment/get/{id}")
public CommonResult getPaymentById(@PathVariable("id") Long id) {
// getForObject读操作,返回JSON对象
return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
}
/**
* 客户端调用服务端查询方法
* @param id
* @return
*/
@GetMapping("/consumer/payment/getForEntity/{id}")
public CommonResult getPaymentById2(@PathVariable("id") Long id) {
// getForObject读操作,返回ResponseEntity对象包含了响应中的信息,比如响应头,响应状态码,响应体等
ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
System.out.println("status code=" + entity.getStatusCode());
System.out.println("headers=" + entity.getHeaders());
// 判断请求状态是2xx
if (entity.getStatusCode().is2xxSuccessful()) {
return entity.getBody();
} else {
return new CommonResult(404, "查找失败");
}
}
/**
* 自定义轮询算法
* @return
*/
// @GetMapping("/consumer/payment/loadBalance")
// public String getPaymentLoadBalance() {
// // 通过discoveryClient对象获取服务提供者对应的应用名称大写获取所有服务实例
// List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PROVIDER-PAYMENT");
//
// // 判断这个服务实例对象等于null或者个数等于0时直接返回null
// if (instances == null || instances.size() == 0) {
// return null;
// }
//
// // 自定义的instances()方法拿到所有的服务实例,使用访问次数%服务实例数量求得目标服务的下标,返回下标对应的服务实例
// ServiceInstance instance = loadBalancer.instances(instances);
// URI uri = instance.getUri();// 获取这个实例的uri
// // /payment/loadBalance请求对应服务提供者controller中新加的映射方法,返回当前服务提供者的serverPort的值
// return restTemplate.getForObject(uri + "/payment/loadBalance", String.class);
// }
/**
* 上面的getPaymentLoadBalance()要注释掉,要么就把它放到所有方法的最后面,不然会报找不到CLOUD-PROVIDER-PAYMENT服务,因为被拦截了。
* 调用cloud-provider-payment8001模块的paymentZipkin()方法,形成链路再查看Zipkin
* @return
*/
@GetMapping(value = "/consumer/payment/zipkin")
public String paymentZipkin() {
// getForObject读操作,返回JSON对象
return restTemplate.getForObject(PAYMENT_URL + "/payment/zipkin", String.class);
}
}
2-4、启动服务,进行测试
依次启动Eureka7001
,Payment8001
,Order80
模块,浏览器请求http://localhost//consumer/payment/zipkin
,之后返回Zipkin的管理后台
,点击“查找”按钮,就可以看到刚刚请求,点进去请求,可以看到调用链路,依赖关系,调用耗时等等信息。