分布式计算八大误区
- 网络可靠。
- 延迟为零。
- 带宽无限。
- 网络绝对安全。
- 网络拓扑不会改变。
- 必须有一名管理员。
- 传输成本为零。
- 网络同质化。(操作系统,协议)
如果能跟踪每个请求,中间请求经过哪些微服务,请求耗时,网络延迟,业务逻辑耗时等。我们就能更好地分析系统瓶颈、解决系统问题。因此链路跟踪很重要。市面上链路追踪产品(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);
}
}