携手创作,共同生长!这是我参与「日新计划 8 月更文挑战」的第29天,点击检查活动概况

为何要进行服务链路追寻?

在一个微服务体系架构中,一个完整的恳求或许涉及到多个微服务的调用,这个调用构成一个链路。

比方,下单的恳求,需求经过网关去调用事务服务,事务服务去调用订单服务,而订单服务同步调用商品服务和用户服务,用户服务又去调用积分服务:

Spring Cloud Sleuth 整合 Zipkin 进行服务链路追踪

事务要求整个下单的恳求要在 1s 内完结,测试发现恳求下单接口耗时超越 2s ,这时咱们就需求去定位发现是调用链路上的哪个环节耗时异常,进而去解决问题。

Spring Cloud 就有这样一个组件专门做链路追寻,那便是 Spring Cloud Sleuth ,有如下功用:

  • 盯梢恳求的调用状况并将它发送到日志体系,然后能够从日志搜集其间看到相关的调用状况。
  • 检测恳求来自 Spring 应用程序的公共入口和出口点(servlet 过滤器、rest 模板、计划操作、消息通道、feign 客户端)。
  • 假如运用了 Zipkin 结合 Sleuth ,则应用程序将经过 HTTP 或者其他方法将 Sleuth 盯梢的恳求状况发送到 Zipkin

这儿提到的另一个组件 Zipkin 是一个能够搜集一切服务监控数据的盯梢体系。有了 Zipkin 咱们能够直观的检查调用链路,并且能够方便的看出服务之间的调用联系以及调用耗时。

Spring Cloud SleuthZipkin 的运用十分简略,官网上有很具体的文档:

Sleuth :spring.io/projects/sp…

Zipkin :zipkin.io/pages/quick…

下面咱们来实操一下。

微服务调用链路环境搭建

咱们以开篇举的例子来搭建这样一个环境:

Spring Cloud Sleuth 整合 Zipkin 进行服务链路追踪

仍是以本 Spring Cloud Alibaba 系列文章的代码 SpringCloudAlibabaDemo 为例,目前已有 gatwway-serviceorder-serviceuser-service ,咱们再创立两个微服务项目 product-serviceloyalty-service ,并构成一个调用链路。

完整代码仓库:github.com/ChenDapengJ… 。

为了展现,这儿贴出了调用逻辑上的要害代码。

product-service 查询商品信息:

@RestController
@RequestMapping("/product")
public class ProductController {
    @GetMapping("/price/{id}")
    public BigDecimal getPrice(@PathVariable("id") Long id) {
        if (id == 1) {
            return new BigDecimal("5899");
        }
        return new BigDecimal("5999");
    }
}

loyalty-service 积分服务中获取用户积分和添加积分的 API :

@RestController
@Slf4j
public class LoyaltyController {
    /**
     * 获取用户当时积分
     * @param id 用户id
     */
    @GetMapping("/score/{id}")
    public Integer getScore(@PathVariable("id") Long id) {
        log.info("获取用户 id={} 当时积分", id);
        return 1800;
    }
    /**
     * 为当时用户添加积分
     * @param id 用户id
     * @param lastScore 用户当时积分
     * @param addScore 要添加的积分
     */
    @GetMapping("/addScore")
    public Integer addScore(@RequestParam(value = "id") Long id,
                            @RequestParam(value = "lastScore") Integer lastScore,
                            @RequestParam(value = "addScore") Integer addScore) {
        log.info("用户 id={} 添加 {} 积分", id, addScore);
        return lastScore + addScore;
    }
}

user-service 经过 OpenFeign 调用积分服务:

FeignClient 类:

@Service
@FeignClient("loyalty-service")
public interface LoyaltyService {
    @GetMapping("/score/{id}")
    Integer getScore(@PathVariable("id") Long id);
    @GetMapping("/addScore")
    Integer addScore(@RequestParam(value = "id") Long id,
                     @RequestParam(value = "lastScore") Integer lastScore,
                     @RequestParam(value = "addScore") Integer addScore);
}

Controller 调用:

@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
    private LoyaltyService loyaltyService;
    @GetMapping("/score/{id}")
    public Integer getScore(@PathVariable("id") Long id) {
        return loyaltyService.getScore(id);
    }
    @GetMapping("/addScore")
    public Integer addScore(@RequestParam Long id,
                            @RequestParam Integer lastScore,
                            @RequestParam Integer addScore) {
        return loyaltyService.addScore(id, lastScore, addScore);
    }
    @Autowired
    public void setLoyaltyService(LoyaltyService loyaltyService) {
        this.loyaltyService = loyaltyService;
    }
}

order-service 订单服务经过 OpenFeign 调用 user-serviceproduct-service

FeignClient 类

@Service
@FeignClient("product-service")
public interface ProductService {
    BigDecimal getPrice(@PathVariable("id") Long id);
}
@Service
@FeignClient("user-service")
public interface UserService {
    /**
     * 因为 user-service 运用了统一返回成果,所以此处的返回值是 ResponseResult
     * @param id 用户id
     * @return ResponseResult<Integer>
     */
    @GetMapping("/user/score/{id}")
    ResponseResult<Integer> getScore(@PathVariable("id") Long id);
    /**
     * 因为 user-service 运用了统一返回成果,所以此处的返回值是 ResponseResult
     */
    @GetMapping("/user/addScore")
    ResponseResult<Integer> addScore(@RequestParam(value = "id") Long id,
                     @RequestParam(value = "lastScore") Integer lastScore,
                     @RequestParam(value = "addScore") Integer addScore);
}

Controller 调用

@Slf4j
@RestController
@RequestMapping("/order")
public class OrderController {
    private UserService userService;
    private ProductService productService;
    @GetMapping("/create")
    public String createOrder(@RequestParam("userId") Long userId, @RequestParam("productId") Long productId) {
        log.info("创立订单参数,userId={}, productId={}", userId, productId);
        // 商品服务-获取价格
        BigDecimal price = productService.getPrice(productId);
        log.info("取得 price={}", price);
        // 用户服务-查询当时积分,添加积分
        Integer currentScore = userService.getScore(userId).getData();
        log.info("取得 currentScore={}", price);
        // 添加积分
        Integer addScore = price.intValue();
        Integer finalScore = userService.addScore(userId, currentScore, addScore).getData();
        log.info("下单成功,用户 id={} 终究积分:{}", userId, finalScore);
        return "下单成功,用户 id=" + userId + " 终究积分:" + finalScore;
    }
    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
    @Autowired
    public void setProductService(ProductService productService) {
        this.productService = productService;
    }
}

网关 gateway-service 装备 Nacos 注册中心和路由:

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.242.112:81
    gateway:
      routes:
        - id: order-service
          uri: lb://order-service
          predicates:
            - Path=/order/**

发动网关以及其他四个服务,

Spring Cloud Sleuth 整合 Zipkin 进行服务链路追踪

然后能够在 Nacos 中看到注册进来的实例:

Spring Cloud Sleuth 整合 Zipkin 进行服务链路追踪

一切服务发动成功之后,经过网关调用下单 API :

Spring Cloud Sleuth 整合 Zipkin 进行服务链路追踪

整个调用链路没有问题。

Spring Cloud Sleuth 的运用

要想运用 Sleuth ,只需简略几个操作即可。

除了 gateway-service 网关服务,其他四个服务均执行以下过程:

1, 导入 spring-cloud-starter-sleuth 依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>

2, org.springframework.web.servlet.DispatcherServlet 日志级别调整为 DEBUG

logging:
  level:
    org.springframework.web.servlet.DispatcherServlet: DEBUG

然后重启这四个服务,再次经过网关拜访下单 API ,看到每个服务都打印了这样的日志:

user-service

Spring Cloud Sleuth 整合 Zipkin 进行服务链路追踪

product-service

Spring Cloud Sleuth 整合 Zipkin 进行服务链路追踪

loyalty-service

Spring Cloud Sleuth 整合 Zipkin 进行服务链路追踪

order-service

Spring Cloud Sleuth 整合 Zipkin 进行服务链路追踪

这样形式的日志:

# [服务名,总链路ID,子链路ID]
[order-service,5eda5d7bdcca0118,5eda5d7bdcca0118]

便是全体的一个调用链路信息。

Zipkin 服务布置与运用

布置 Zipkin 服务

简略来说, Zipkin 是用来图形化展现 Sleuth 搜集来的信息的。

Zipkin 需求单独装置,它是一个 Java 编写的 Web 项目,咱们运用 Docker Compose 进行布置装置 Zipkin

Tip: 我已经十分关心的把 Docker Compose 的运用共享了,详见:用 docker-compose 布置服务真是好用,底子停不下来! 。

布置过程:

1, 创立 /usr/local/zipkin 目录,进入到该目录:

mkdir /usr/local/zipkin
cd /usr/local/zipkin

2, 创立 docker-compose.yml 文件,文件内容如下:

version: "3"
services:
  zipkin:
   image: openzipkin/zipkin
   restart: always
   container_name: zipkin
   ports:
     - 9411:9411

这是简化版的 docker-compose.yml 装备文件,这样的装备就能发动 Zipkin 。更多的装备详见:github.com/openzipkin-… 。

3, 运用 docker-compose up -d 命令(-d 表示后台发动)发动:

Spring Cloud Sleuth 整合 Zipkin 进行服务链路追踪

布置成功后,拜访 Zipkin ,端口为 9411 ,拜访地址:http://192.168.242.112:9411/zipkin/

Spring Cloud Sleuth 整合 Zipkin 进行服务链路追踪

这样,一个 Zipkin 服务就布置完结了。

将 Sleuth 搜集到的日志信息发送到 Zipkin

首要,仍是需求在微服务项目中导入 spring-cloud-sleuth-zipkin 的依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>

然后,添加一些装备,让 Sleuth 搜集的信息发送到 Zipkin 服务上:

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.242.112:81
  sleuth:
    enabled: true
    sampler:
      # 设置 Sleuth 搜集信息的百分比,一般状况下,10%就够用了,这儿设置100%调查
      rate: 100
  zipkin:
    sender:
      type: web
    base-url: http://192.168.242.112:9411/

好了,再来发动每个服务,然后拜访下单接口再看下 Zipkin 的面板。拜访 http://192.168.242.112:9411/zipkin/

Spring Cloud Sleuth 整合 Zipkin 进行服务链路追踪

能够看到有一个恳求出来了,点击 SHOW 检查概况:

Spring Cloud Sleuth 整合 Zipkin 进行服务链路追踪

能够清楚地看到调用链路上每一步的耗时。

小结

Spring Cloud Sleuth 结合 Zipkin 能够对每个微服务进行链路追寻,然后帮助咱们剖析服务间调用联系以及调用耗费的时刻。

本文只简略介绍了经过 web 方法(装备项:spring.zipkin.sender.type=web):

Spring Cloud Sleuth 整合 Zipkin 进行服务链路追踪

也便是经过 HTTP 的方法发送数据到 Zipkin ,假如恳求量比较大,这种方法其实功能是比较低的,一般状况下咱们都是经过消息中间件来发送,比方 RabbitMQ

假如日志数据量比较大,一般推荐具有更高吞吐量的 Kafka 来进行日志推送。

这种方法便是让服务将 Sleuth 搜集的日志推给 MQ ,让 Zipkin 去监控 MQ 的信息,经过 MQ 的队列获取到服务的信息。这样就提高了功能。

而日志的存储则能够选用 Elasticsearch 对数据进行持久化,这样能够保证 Zipkin 重启后,链路信息不会丢掉。

下次有机会再共享一下如何经过 MQ 发送调用链数据信息以及运用 Elasticsearch 持久化数据,今日就到这儿了。