分布式计算八大误区
- 网络可靠。
 - 延迟为零。
 - 带宽无限。
 - 网络绝对安全。
 - 网络拓扑不会改变。
 - 必须有一名管理员。
 - 传输成本为零。
 - 网络同质化。(操作系统,协议)
 
如果能跟踪每个请求,中间请求经过哪些微服务,请求耗时,网络延迟,业务逻辑耗时等。我们就能更好地分析系统瓶颈、解决系统问题。因此链路跟踪很重要。市面上链路追踪产品(zipkin、twitter、pinpoint、cat、EagleEye等)大部分基于google的Dapper论文。 https://bigbully.github.io/Dapper-translation/
链路追踪需要考虑的几个问题:
- 探针的性能消耗。尽量不影响 服务本尊。
 - 易用。开发可以很快接入,别浪费太多精力。
 - 
Sleuth
Sleuth是Spring cloud的分布式跟踪解决方案
 span(跨度),基本工作单元。一次链路调用,创建一个span,span用一个64位id唯一标识。
包括:id,描述,时间戳事件;spanId,span父id。
    span被启动和停止时,记录了时间信息,初始化span叫:root span,它的span id和trace id相等。 
- trace(跟踪),一组共享“root span”的span组成的树状结构 称为 trace,trace也有一个64位ID,trace中所有span共享一个trace id。类似于一颗 span 树。
 - 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依赖
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-sleuth</artifactId></dependency>
启动服务,可看到查看日志:
[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等存储。
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端口查看,可以查看微服务状态
配置邮件通知
<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
配置钉钉机器人通知
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();
        }
    }
}
 
 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);
    }
}

