Spring Cloud之-Spring Cloud Sleuth分布式请求链路跟踪- 16(个人笔记)

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.ymlcloud-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、启动服务,进行测试

依次启动Eureka7001Payment8001Order80模块,浏览器请求http://localhost//consumer/payment/zipkin,之后返回Zipkin的管理后台,点击“查找”按钮,就可以看到刚刚请求,点进去请求,可以看到调用链路,依赖关系,调用耗时等等信息。
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值