今日分享 – 分布式服务监控你真的会吗

背景

现在系统架构都朝着微服务化,云原生化发展,一个大型的系统光微服务数量有可能多达上百个,再加上多机分布式部署,服务异构等原因,导致系统调用链路只会越来越复杂。

一套大型微服务系统主要由以下几种功能组件构成:

  • API 网关,主要做服务接口接入,用户鉴权及认证,角色控制,主流组件有 kong 网关,zuul 网关
  • 服务发现注册中心,主要实现服务自动自动发现,服务路由信息维护更新,主流组件有 zookeeper、Eureka
  • 后台微服务框架,实现具体后台业务逻辑功能框架,有 spring-boot-web,spring-batch(批处理),golang Gin
  • 服务之间 RPC 调用,实现服务请求接口调用,主要有 grpc、http2
  • 服务故障熔断及降级,提高系统可用性,当接口请求达到一定阈值或者出现异常响应,在不影响系统主要功能时,可以实现服务熔断或者降级
  • 异步消息队列,实现请求削峰平滑,无关业务调用异步执行,避免服务请求过大造成请求积压,从而避免系统雪崩。主要的消息队列有 kafka、rabbitMQ

以简单的电商后台系统为例,用户请求经过 Nginx 转发到后端网关服务

  • user_center, 用户中心
  • deal_center, 订单中心
  • pay_center, 支付后台
  • register_center, 服务注册中心

简单电商后台架构

系统之间复杂地调用关系提高了我们排查异常的难度,当系统出现问题,可能需要从上到下,根据请求日志逐级排查,耗时耗力,

正因如此,APM(Application performance management) 也就显得越来越重要,在协助我们分析定位系统问题也是必不可少的。

服务监控介绍

在了解服务监控之前我们先了解下简单的监控概念

  • Liveness,存活探针,检测我们服务运行情况,通常为 http 接口,通过接口的返回码判断服务是否正常。
  • Span,一次调用我们称为一个 span,span 可以为 RPC 调用,Http 调用,数据库或者 redis 调用等,
    Span 通过一个 64 位 ID 唯一标识,Span 还有其他数据信息,比如摘要、时间戳事件、关键值注释 (Tags)、Span 的 ID、以及进度 ID (通常是 IP 地址)。
  • Trace,调用树,一系列 span 组成的调用链路,比如一个分布式系统,从用户请求经过各种微服务最终到达数据库,那么这一系列调用组成的链路就称为 trace 树,trace 采用与 span 区别的 64 位 ID 标识。
  • Kind,调用方,区别是客户端/服务端调用

监控系统组件及架构

我们从监控系统的数据流程可以将系统分为数据探针、数据采集上报、数据存储、数据分析聚合、数据可视化及监控告警。目前主流的开源系统监控组件主要有

  • zipkin 服务调用及链路追踪
  • skywalking apache 开源的服务性能监控组件
  • prometheus 监控数据采集及告警组件
  • grafana 数据可视化分析
  • elasticsearch 数据存储及聚合
prometheus 系统架构
zipkin系统架构

下面我们分别将这些系统组合起来,因篇幅有限,暂时不展开如何进行定制化开发和适配。

数据采集

首先,我们需要采集系统的原始监控数据。数据采集器主要有类似探针、agent,这里以 zipkin 链路监控为例

在 springboot 微服务中接入 zipkin 只需要引入相应的依赖包

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zipkin</artifactId>
    <version>2.2.4.RELEASE</version>
</dependency>

zipkin trace id的生成主要是构建 servlet filter 拦截器,判断是否是上游调用,上游调用就继续沿用trace id,作为客户端调用就继续生成新的span id.

数据上报及存储

为了监控链路的可用性,通常我们会将链路监控等数据写入 kafka,同时从 kafka 读取数据并写入到 elasticsearch 存储中。这样可以避免当我们监控服务异常时,不影响正常服务,同时监控数据还是写入成功,只是暂存在 kafka 等消息中间件中。幸运地是,zipkin 支持多种消息中间件 reporter。

image-20200923103957577.png

同时,你可以定制开发自己的 sender,在服务初始化时加载自定义的 reporter 组件,这里以数据写入 kafka 为例。

  • 添加 maven 依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zipkin</artifactId>
    <version>2.2.4.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.kafka</groupId>
    <artifactId>spring-kafka</artifactId>
</dependency>

  • 定制 sender,在配置类中初始化制定 sender 为 kafka sender
@Configuration
public class SenderCustomer {

    @Bean
    Sender sender(){
        return KafkaSender.newBuilder()
                .bootstrapServers("xxx.xxx.xxx.xxx:9092,xxx.xxx.xxx.xxx:9092")
                .encoding(Encoding.JSON)
                .topic("xxx")
                .build();

    }
}

在服务启动之后,我们调用测试接口,可以看到链路数据正确写入 Kafka 中

服务启动日志
  • 启动 zipkin_server

确认数据正确推送到 Kafka,下面我们启动 zipkin_server,将数据从 Kafka 消费,并存储至 ES。

/**
 * @deprecated Custom servers are possible, but not supported by the community. Please use our
 * <a href="https://github.com/openzipkin/zipkin#quick-start">default server build</a> first.
 * If you find something missing, please <a href="https://gitter.im/openzipkin/zipkin">gitter</a> us
 * about it before making a custom server.
 *
 * <p>If you decide to make a custom server, you accept responsibility for troubleshooting your
 * build or configuration problems, even if such problems are a reaction to a change made by the
 * Zipkin maintainers. In other words, custom servers are possible, but not supported.
 */

从这段注释可以看出,在 zipkin2 后续的版本已经不再推荐自定义 zipkin_server,所以现在建议采用官方发布的 zipkin_server 或者自己 clone 源码本地编译后启动。采用 release zipkin_server 有官方教程 (https://zipkin.io/pages/quickstart),这里展示如何本地编译并启动

git clone https://github.com/openzipkin/zipkin.git
cd zipkin
mvn -DskipTests --also-make -pl zipkin-server clean install

STORAGE_TYPE=elasticsearch ES_SSL_NO_VERIFY=true ES_HOSTS=https://node1.elastic.com:9500,https://node2.elastic.com:9500 ES_HTTP_LOGGING=BASIC ES_USERNAME=elastic ES_PASSWORD=xxx java -jar zipkin-server-2.21.8-SNAPSHOT-exec.jar  --zipkin.collector.kafka.enabled=true --zipkin.collector.kafka.bootstrap-servers=xxx.xxx.xxx.xxx:9092 --zipkin.collector.kafka.topic=xxx

启动命令指定了 collector 类型为 Kafka,并且配置了相关的 Kafka servers 地址和 topic 值,storage 类型为 elasticsearch

我们可以访问 zipkin_server(http://localhost:9411) 地址,查看我们请求的调用链。

zipkin_server界面
请求调用信息

这里可以看到,服务展示了一次请求调用的路由,起始,终止时间,主/被调方,请求结果。

  • 查看 Elasticsearch 数据

在 zipkin_server 中我们查看了一次简单的 http 调用的展示界面,接着来看下存储在 ES 中的具体数据格式。一次简单调用的原始数据如下

elasticsearch数据展示
{
  "_index": "zipkin-span-2020-09-23",
  "_type": "_doc",
  "_id": "3ed3c7897c773c7c-00d8b820e72edda2216dc3328d19c661",
  "_version": 1,
  "_score": null,
  "_source": {
    "traceId": "3ed3c7897c773c7c",
    "duration": 16591,
    "remoteEndpoint": {
      "ipv4": "10.43.27.75",
      "port": 62422
    },
    "shared": true,
    "localEndpoint": {
      "serviceName": "user-info"
    },
    "timestamp_millis": 1600831775784,
    "kind": "SERVER",
    "name": "get /get-user-info",
    "id": "19ffb94ef8b93d5f",
    "parentId": "0e0811feba4614f4",
    "timestamp": 1600831775784046,
    "tags": {
      "http.method": "GET",
      "http.path": "/get-user-info",
      "mvc.controller.class": "GetUserInfo",
      "mvc.controller.method": "getUserInfo"
    }
  },
  "fields": {
    "timestamp_millis": [
      "2020-09-23T03:29:35.784Z"
    ]
  },
  "sort": [
    1600831775784
  ]
}

以上,已经验证了数据采集和上报 kafka 的链路。现在我们可以模拟链路之间的调用,这里启动几个简单的后台服务,并分别注册到 Eureka 注册中心,从注册中心可以看到我们的服务主要包括

  • gateway 网关
  • config server 配置中心
  • deal-info 订单中心
  • pay-center 支付中心
  • user-info 用户中心
服务注册中心

数据分析聚合

启动以上服务,通过接口调用,可以检查我们的服务调用关系,可以看到我们的支付中心需要调用订单中心和用户中心,校验用户状态和订单信息。

服务调用关系

数据可视化及监控告警

  • 建立监控视图
    因为数据主要存储在 elasticsearch 中,我们可以通过 kibana 为数据建立监控视图,这里主要创建两个聚合视图,按照请求耗时建立监控曲线,按照 traceId 分组统计请求量。
请求耗时平均值
trace ID 分组排序
  • 建立监控面板

建立好监控视图之后,我们统计监控面板

监控面板

通过以上监控面板,可以设置具体的平均请求耗时阈值,超过阈值发送短信邮件告警。

总结

以上,就是简单的系统调用链路监控。当然,我们可以继续搭建更多的监控指标和面板,同时还可以采用不同的监控组件。但是大体监控流程都是类似,主要是:

数据采集探针、数据上报、数据存储、监控视图、系统告警等。

【技术创作101训练营】

正文完