在微服务框架中,一个客户端请求,从发起到后端系统中,会经历多个不同的微服务结点的调用,每一个请求都会形成一条复杂的分布式调用链路,链路中任何一个服务出现故障或延时都会导致整个请求最终的失败。
Spring Cloud Sleuth提供了一套完整的服务监控跟踪解决方案,兼容支持Zipkin数据展现。
2、搭建链路监控步骤 2-1、安装ZipkinSpring 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。
到 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依赖。
cloud2020 com.king.springcloud 1.0-SNAPSHOT 4.0.0 cloud-provider-payment8001 org.springframework.cloud spring-cloud-starter-zipkin org.springframework.cloud spring-cloud-starter-netflix-eureka-client com.king.springcloud cloud-api-commons ${project.version} org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator org.mybatis.spring.boot mybatis-spring-boot-starter com.alibaba druid-spring-boot-starter mysql mysql-connector-java org.springframework.boot spring-boot-starter-jdbc org.springframework.boot spring-boot-devtools runtime true org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test
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 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 services = discoveryClient.getServices();
for (String element : services) {
log.info("------element------:" + element);
}
List 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;
}
@GetMapping("/payment/loadBalance")
public String getLoadBalancePort() {
return serverPort;
}
@GetMapping("/payment/feign/timeout")
public String paymentFeignTimeout() {
try {
// 睡眠3秒
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return serverPort;
}
@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中添加一个映射用于测试。
cloud2020 com.king.springcloud 1.0-SNAPSHOT 4.0.0 cloud-consumer-order80 org.springframework.cloud spring-cloud-starter-zipkin org.springframework.cloud spring-cloud-starter-netflix-eureka-client com.king.springcloud cloud-api-commons ${project.version} org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-devtools runtime true org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test
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 {
public static final String PAYMENT_URL = "http://CLOUD-PROVIDER-PAYMENT";
@Autowired
private RestTemplate restTemplate;
@Resource
private LoadBalancer loadBalancer;
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/consumer/payment/create")
public CommonResult create(Payment payment) {
// postForObject写操作,按照JSON数据格式
return restTemplate.postForObject(PAYMENT_URL + "/payment/createPayment", payment, CommonResult.class);
}
@GetMapping("/consumer/payment/create2")
public CommonResult create2(Payment payment) {
// postForEntity写操作,按照ResponseEntity数据格式
return restTemplate.postForEntity(PAYMENT_URL + "/payment/create", payment, CommonResult.class).getBody();
}
@GetMapping("/consumer/payment/get/{id}")
public CommonResult getPaymentById(@PathVariable("id") Long id) {
// getForObject读操作,返回JSON对象
return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
}
@GetMapping("/consumer/payment/getForEntity/{id}")
public CommonResult getPaymentById2(@PathVariable("id") Long id) {
// getForObject读操作,返回ResponseEntity对象包含了响应中的信息,比如响应头,响应状态码,响应体等
ResponseEntity 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, "查找失败");
}
}
// @GetMapping("/consumer/payment/loadBalance")
// public String getPaymentLoadBalance() {
// // 通过discoveryClient对象获取服务提供者对应的应用名称大写获取所有服务实例
// List 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);
// }
@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的管理后台,点击“查找”按钮,就可以看到刚刚请求,点进去请求,可以看到调用链路,依赖关系,调用耗时等等信息。



