分布式计算八大误区

  • 网络可靠。
  • 延迟为零。
  • 带宽无限。
  • 网络绝对安全。
  • 网络拓扑不会改变。
  • 必须有一名管理员。
  • 传输成本为零。
  • 网络同质化。(操作系统,协议)

如果能跟踪每个请求,中间请求经过哪些微服务,请求耗时,网络延迟,业务逻辑耗时等。我们就能更好地分析系统瓶颈、解决系统问题。因此链路跟踪很重要。市面上链路追踪产品(zipkin、twitter、pinpoint、cat、EagleEye等)大部分基于google的Dapper论文。 https://bigbully.github.io/Dapper-translation/

链路追踪需要考虑的几个问题:

  1. 探针的性能消耗。尽量不影响 服务本尊。
  2. 易用。开发可以很快接入,别浪费太多精力。
  3. 数据分析。要实时分析。维度足够。

    Sleuth

    Sleuth是Spring cloud的分布式跟踪解决方案

  4. span(跨度),基本工作单元。一次链路调用,创建一个span,span用一个64位id唯一标识。

包括:id,描述,时间戳事件;spanId,span父id。
span被启动和停止时,记录了时间信息,初始化span叫:root span,它的span id和trace id相等。

  1. trace(跟踪),一组共享“root span”的span组成的树状结构 称为 trace,trace也有一个64位ID,trace中所有span共享一个trace id。类似于一颗 span 树。
  2. annotation(标签),annotation用来记录事件的存在,其中,核心annotation用来定义请求的开始和结束。
    • CS(Client Send客户端发起请求)。客户端发起请求描述了span开始。
    • SR(Server Received服务端接到请求)。服务端获得请求并准备处理它。SR-CS=网络延迟。
    • SS(Server Send服务器端处理完成,并将结果发送给客户端)。表示服务器完成请求处理,响应客户端时。SS-SR=服务器处理请求的时间。
    • CR(Client Received 客户端接受服务端信息)。span结束的标识。客户端接收到服务器的响应。CR-CS=客户端发出请求到服务器响应的总时间。

其实数据结构是一颗树,从root span 开始。

单独使用Sleuth

在需要监控的服务加入Sleuth依赖

  1. <dependency>
  2. <groupId>org.springframework.cloud</groupId>
  3. <artifactId>spring-cloud-starter-sleuth</artifactId>
  4. </dependency>

启动服务,可看到查看日志:
image.png
[zuul,9d077335e81fd9c5,9d077335e81fd9c5,true] ,zuul网关服务

[consumer-service,9d077335e81fd9c5,648eb10df890d3b4,true],consumer-service服务
各项代表意思:
[服务名称,traceId(一条请求调用链中 唯一ID),spanID(基本的工作单元,获取数据等),是否让zipkin收集和展示此信息]

zipkin

直接使用sleuth,日志显示调用链路,容易看错。因此引入zipkin,具有图形化界面。zipkin是twitter开源分布式跟踪系统。是通过收集系统时序数据,从而实现跟踪微服务架构中系统延时问题。

zipkin有4部分组成:

  • Collector 数据采集器
  • Storage 数据存储器
  • RESTful Api RESTful API
  • Web UI UI界面

sleuth收集跟踪信息通过http请求发送给zipkin server,zipkin将跟踪信息存储,以及提供RESTful API接口,zipkin ui通过调用api进行数据展示。默认内存存储,可以用mysql,ES等存储。
image.png
image.png

SpringCloud Admin健康检查

Spring Admin Server

<dependencies>
  <dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-starter-server</artifactId>
    <version>2.2.1</version>
  </dependency>
  <dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-server-ui</artifactId>
    <version>2.2.1</version>
  </dependency>
</dependencies>
server:
  port: 90
spring:
  application:
    name: admin
management:
  endpoint:
    health:
      show-details: always
  endpoints:
    web:
      exposure:
        include: '*'
@SpringBootApplication
@EnableAdminServer
public class AdminApplication {
    public static void main(String[] args) {
        SpringApplication.run(AdminApplication.class, args);
    }
}

需要监控的为服务端

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>de.codecentric</groupId>
        <artifactId>spring-boot-admin-starter-client</artifactId>
        <version>2.1.5</version>
    </dependency>
</dependencies>
spring:
  boot:
    admin:
      client:
        url: http://127.0.0.1:90   # 健康信息上报地址
management:
  endpoints:
    web:
      exposure:
        include: '*'   # 打开全部端口
  endpoint:
    health:
      show-details: always  # 设置详细信息

启动微服务和Sring-cloud-admin,在90端口查看,可以查看微服务状态
image.png
image.png

配置邮件通知

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
    <version>2.3.7.RELEASE</version>
</dependency>
server:
  port: 90
spring:
  application:
    name: admin
    # 邮件通知配置
  mail:
    host: smtp.163.com
    username: 17853142327@163.com  #用户
    password: PJJNDVSOBKZTNXFG  # 令牌
    properties:
      mail:
        smpt:
          auth: true
          starttls:
            enable: true
            required: true
  boot:
    admin:
      notify:
        mail:
          to: softlion18@gmail.com   # 收件邮箱
          from: 17853142327@163.com  # 发件邮箱

management:
  endpoint:
    health:
      show-details: always
  endpoints:
    web:
      exposure:
        include: '*'

此时启动报错(如下,原因是spring-bot-admin-starter版本与spring-boot 版本不匹配,提高admin 版本)

***************************
APPLICATION FAILED TO START
***************************

Description:

An attempt was made to call the method reactor.retry.Retry.retryMax(I)Lreactor/retry/Retry; but it does not exist. Its class, reactor.retry.Retry, is available from the following locations:

jar:file:/D:/mymaven/repository/io/projectreactor/addons/reactor-extra/3.2.0.RELEASE/reactor-extra-3.2.0.RELEASE.jar!/reactor/retry/Retry.class

It was loaded from the following location:

file:/D:/mymaven/repository/io/projectreactor/addons/reactor-extra/3.2.0.RELEASE/reactor-extra-3.2.0.RELEASE.jar


Action:

Correct the classpath of your application so that it contains a single, compatible version of reactor.retry.Retry

实验:宕掉一个服务,查看是否收到邮件
image.png

配置钉钉机器人通知

public class Message {
    private String msgtype;
    private MessageInfo text;

    public Message() {
    }

    public Message(String msgType, MessageInfo text) {
        this.msgtype = msgType;
        this.text = text;
    }

    public String getMsgtype() {
        return msgtype;
    }

    public void setMsgtype(String msgtype) {
        this.msgtype = msgtype;
    }

    public MessageInfo getText() {
        return text;
    }

    public void setText(MessageInfo text) {
        this.text = text;
    }
}
public class MessageInfo {
    private String content;

    public MessageInfo(String content) {
        this.content = content;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}
public class DingDingMessageUtil {

    private static String access_token = "d70f063f1bc6e47100e9f22beb28c4a791a670f33503abb6812173d1e139b43c";

    public static void sendTextMessage(String msg) {
        try {
            Message message = new Message();
            message.setMsgtype("text");
            message.setText(new MessageInfo(msg));
            URL url = new URL("https://oapi.dingtalk.com/robot/send?access_token=" + access_token);
            // 建立 http 连接
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setUseCaches(false);
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Charset", "UTF-8");
            conn.setRequestProperty("Content-Type", "application/Json; charset=UTF-8");
            conn.connect();
            OutputStream out = conn.getOutputStream();
            String textMessage = JSONObject.toJSONString(message);
            byte[] data = textMessage.getBytes();
            out.write(data);
            out.flush();
            out.close();
            InputStream in = conn.getInputStream();
            byte[] data1 = new byte[in.available()];
            in.read(data1);
            System.out.println(new String(data1));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

image.png
URL和access_token 由有图部分获取。同时消息里需包含关键字。

public class DingDingNotifier extends AbstractStatusChangeNotifier {
    public DingDingNotifier(InstanceRepository repository) {
        super(repository);
    }

    @Override
    protected Mono<Void> doNotify(InstanceEvent event, Instance instance) {
        String serviceName = instance.getRegistration().getName();
        String serviceUrl = instance.getRegistration().getServiceUrl();
        String status = instance.getStatusInfo().getStatus();
        Map<String, Object> details = instance.getStatusInfo().getDetails();
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("服务预警:【").append(serviceName).append("】\n");
        stringBuilder.append("【服务地址】:").append(serviceUrl).append("\n");
        stringBuilder.append("【服务当前状态】:").append(status).append("】\n");

        stringBuilder.append("【详情】").append(JSONObject.toJSONString(details)).append("】\n");


        return Mono.fromRunnable(() -> DingDingMessageUtil.sendTextMessage(stringBuilder.toString()));
    }
}
@Configuration
public class NotifierConfig {
    @Bean
    DingDingNotifier dingDingNotifier(InstanceRepository instanceRepository) {
        return new DingDingNotifier(instanceRepository);
    }
}

image.png