image frame

新装Ubuntu之后设置SSH的root登录

1、确定一下我们的root用户存在

1
2
# 输入命令后会提示你输入密码,不用管密码对不对,随便输入,如果没有此root用户他会提示你
su root

2、修改root密码

1
2
# 他会让你输入新密码,然后就是让你再次输入新密码
sudo passwd root

3、安装SSH

1
sudo apt-get install openssh-server openssh-client;

4、允许root用户通过SSH连接

1
2
# 编辑配置文件
sudo vim /etc/ssh/sshd_config

     作出如下修改:

1
2
3
#PermitRootLogin prohibit-password
# 下面这是我们新添加的一行
PermitRootLogin yes

5、重启SSH服务

1
service sshd restart

一些JVM平台上的并发知识

1、处理器缓存设计

       处理器缓存通过减少写入延迟、批量刷新数据、限定缓存的局部可见性,并合理安排内存操作顺序,有效提升了处理器的运行效率和内存总线的利用率。

处理器缓存设计

2、重排序

       源代码从编译器到最终执行的指令序列的优化过程,通过编译器重排、指令级并行重排以及内存系统重排,逐步优化指令执行顺序,以提升处理器的执行效率。

重排序

3、happens-before

       “happens-before” 确保操作结果的可见性而不一定要求实际执行顺序。主要规则包括:程序顺序规则、监视器锁规则、volatile 变量规则和传递性规则,用于保证多线程环境下操作的有序性和一致性。

happens-before

4、数据依赖性

       数据依赖性在单个处理器和单线程操作中的重要性,强调了写后读、写后写和读后写等依赖类型。如果这些操作顺序被交换,执行结果会发生变化。然而,不同处理器或线程之间的依赖性不在编译器和处理器的考虑范围内。

数据依赖性

5、as-if-serial

       “as-if-serial” 语义,保证在单线程环境下操作的执行结果不受重排序的影响,无需担心内存可见性。代码执行顺序可以在不改变最终结果的前提下进行优化,例如图中 A、B、C 操作的顺序在编译器和处理器的重排序下仍保持一致的执行结果。

as-if-serial

6、程序顺序规则

       程序顺序规则中的传递性和可见性要求:如果操作 A happens-before B,且 B happens-before C,则 A happens-before C,但这并不强制要求执行顺序。只要B能够看到 A 的结果,即使 B 先于 A 执行也是合法的,JVM 允许这种非严格顺序的优化。

程序顺序规则

7、内存屏障

       下图介绍了内存屏障的不同类型及其作用,包括 LoadLoad、StoreStore、LoadStore 和 StoreLoad 屏障。每种屏障用于确保不同类型的内存操作顺序,从而在多处理器环境下保持数据一致性。其中,StoreLoad 屏障最强大且开销最高,用于在执行后续指令前确保当前处理器的所有写操作已刷新到内存。

内存屏障

8、重排序导致的问题

       在多线程环境下,线程 A 的写操作 a=1 和 flag=true 可能被重排序,线程 B 在读取 flag 后立即使用变量 a,但此时 a 可能尚未更新,导致不正确的结果。这种重排序问题会破坏程序的预期执行顺序,可能引发逻辑错误。

重排序导致的问题

9、数据竞争问题

       在多线程环境中,通过同步机制(如synchronized、volatile等)可以确保数据一致性和操作的原子性,避免数据竞争问题,但Java内存模型(JMM)不能对顺序一致性和原子性同时提供保证。

数据竞争问题

关于调优

     任何的调优手段,都是补救措施。调优再厉害,都只是补救。

     写出正规的代码,规范的研发流程,及时有效的运维与预警机制,胜过一切调优。

呃,多谢 ChatGPT 认可

用于实验的千万级数据库记录的创建脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
CREATE DATABASE `order_center` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
USE order_center;

-- 创建 users 表
CREATE TABLE users
(
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
email VARCHAR(150) NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_name_email (name, email), -- 索引覆盖场景
INDEX idx_created_at (created_at) -- 针对时间的查询
);

-- 创建 orders 表
CREATE TABLE orders
(
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
order_date DATETIME DEFAULT CURRENT_TIMESTAMP,
status VARCHAR(50) NOT NULL,
total_amount DECIMAL(10, 2) NOT NULL,
INDEX idx_user_id (user_id), -- 最左前缀法则示范
INDEX idx_order_date_status (order_date, status) -- 查询组合索引
);

-- 创建 order_items 表
CREATE TABLE order_items
(
id BIGINT PRIMARY KEY AUTO_INCREMENT,
order_id BIGINT NOT NULL,
product_id BIGINT NOT NULL,
quantity INT NOT NULL,
price DECIMAL(10, 2) NOT NULL,
INDEX idx_order_product (order_id, product_id), -- 联表查询时提升性能
INDEX idx_price_quantity (price, quantity) -- 数量和价格查询优化
);

DELIMITER //
CREATE PROCEDURE GenerateData()
BEGIN
DECLARE i INT DEFAULT 0;
DECLARE user_id INT;
DECLARE order_id INT;
DECLARE num_orders INT;
DECLARE j INT;

WHILE i < 10000000 DO
-- 插入 user 数据
INSERT INTO users (name, email)
VALUES (CONCAT('user', FLOOR(RAND() * 1000000)), CONCAT(FLOOR(RAND() * 1000000), '@example.com'));

SET user_id = LAST_INSERT_ID();

-- 随机生成1到5个订单
SET num_orders = FLOOR(RAND() * 5) + 1;

SET j = 0;
WHILE j < num_orders DO
-- 插入 order 数据
INSERT INTO orders (user_id, status, total_amount)
VALUES (user_id, 'completed', RAND() * 1000);

SET order_id = LAST_INSERT_ID();

-- 插入 order_items 数据
INSERT INTO order_items (order_id, product_id, quantity, price)
VALUES (order_id,
FLOOR(RAND() * 10000), -- 随机生成 product_id
FLOOR(RAND() * 100), -- 随机生成数量
RAND() * 100); -- 随机生成价格

SET j = j + 1;
END WHILE;

SET i = i + 1;
END WHILE;
END
//
DELIMITER ;

-- 调用存储过程以生成数据
CALL GenerateData();

设计一个监控模块

1、监控模块设计

       要在开发平台层设计一个监控模块,提供平台级监控能力。几乎所有的监控系统都基于 CS 模式设计,监控模块也基于 CS 模式。但是开发平台层不需要自己完全实现完整的 Server 端和 Client 端。就这样设计了第一版监控模块。

第一版监控模块

  • server 端几乎无需自己实现,Client 只需要能以主动推送或者暴露接口的形式将监控数据提交至 server 端并展示即可。

  • client 端在逻辑上划分三层:数据源、采集层、接口层;

  • 数据层是指被采集对象的整体,监控数据源自于这里。监控数据可以是业务数据,可以是开发平台层数据,例如 rpc 调用信息,还可以直接是应用环境三方的,任何可以被应用程序获取的信息,例如:Spring Boot Actuator。

  • 采集层是真正需要开发平台层实现的逻辑,需要采集的各项指标待定.

  • 接口层负责将数据交付给展示层。

       UML 设计如下:

UML

2、为何 Server 端不采用 Spring Boot Admin

       Spring Boot Actuator + Spring Boot Admin 是 Spring 官方标配。但是 Spring Boot Admin 生产部署作为监控平台的很少见,并且 Spring Boot Admin 由于在数据交互上没有统一的数据规范,展示层必须针对特定数据进行前端开发(重新开发spring-boot-admin-server-ui )。目前 Spring Boot Admin 能展示的数据主要是 JVM 的进程信息以及依赖于 Spring 环境的相关信息(环境配置、beans、web)。

Spring Boot Admin

3、各层的理想组合

       micrometer 的数据结构与普罗米修斯数据结构设计上一致,生产环境的监控平台 prometheus + grafana 几乎是最常见的,Sping Boot Actuator 对 prometheus 已经天然支持(spring-boot-actuator-autoconfigure @org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus.PrometheusMetricsExportAutoConfiguration)。因此将 Sping Boot Actuator 的 prometheus 端点作为接口层,将 prometheus + grafana 作为 Server 端,micrometer 作为采集层的采集工具,是最理想的组合。可行性验证效果展示:

prometheus + grafana

4、定制化监控系统的引入

       因为引入了一体化监控平台 XXX,开发平台要考虑将 XXX 作为 Server 端。Xxx 将监控信息抽象为 XxxBean 对象,并通过派生 XxxBean 对象得到三种监控场景。设计方案如图所示:

Xxx 设计

       除了支持上报 XxxBean 监控对象,Xxx 还支持基于日志的交易监控:

Xxx 设计

       其中需要特别说明:1)业务系统接入 Xxx 上报监控的能力,必须在业务逻辑中组装 XxxBean 模型(可用性模型、事件模型、调度模型);2)基于日志的交易监控,必须调整应用系统的日志输出格式。

5、基于度量指标的通用监控设计思路

       Xxx 从使用场景出发设计了监控平台,开发平台需要考虑更加通用开放的监控标准,基于开发的监控标准设计监控能力,再进一步将通用的监控能力运用于具体场景。

       在信息技术中,监控指的是对系统、网络、应用程序、基础设施等的持续观察、测量和分析,以确保其正常运行、性能优化和安全性。让我们回到监控这件事情本身来考虑,能否基于 V1 方案中 prometheus 协议进行设计,形成通用开发的监控能力,以应对类似于一体化监控平台等诸多展示层的接入需求。

       假设系统生成有度量指指标:demo_cpu_usage、demo_mem_usage、demo_transfer_amount。分别记作 a、b、c。

1
2
3
4
5
6
7
8
9
10
11
# prometheus 协议数据
<metric_name>{<label_name>=<label_value>, ...} <value> <timestamp>

a:
demo_cpu_usage{target="192.168.1.2"} 81 1694116786000

b:
demo_mem_uasge{target="192.168.1.2"} 81 1694116786000

c:
demo_transfer_amount{vip_user="000000123"} 20000000 1694116786000

       另外设:

       bool = g(x):x.value 必须小于 80,否则 g 函数返回 false。

       float = f(x):当 x.tag.vip_user in vip_user_list,x.value 如果大于一百万,f 则返回 x.value。

       以上 g 函数和 f 函数目的是分别通用的程序监控场景和业务场景,可以根据实际需要定义出更加复杂灵活的函数法则,函数法则施加于若干监控指标,从而得到一个具体的监控场景。以 g 函数和 f 函数为例:

       场景一:当 cpu 或者 mem 内存低于 80 的时候,触发可用性监控,伪代码如下:

1
2
3
if g(a) || g(b) 
# 组装可用性 XxxBean 模型
endif

       场景二:当 VIP 用户转账金额超过 100 万时,触发事件监控,伪代码如下:

1
2
3
if  f(c) > 1000000
# 组装事件 XxxBean 模型
endif

       基于以上具体案例的分析可以看出,监控模型的上报场景,完全是可以从一些自定义的监控指标中得出的,因此基于 prometheus 协议监控度量指标的封装,可以当做一种通用监控能力。基于这种能力,也可以很方便对交易进行监控,不仅是基于 Xxx 模型地监控,还是基于 prometheus 协议的监控,都有能力做到,此不赘述。

6、监控优化方案

       对 V1 方案优化如下:

       1)采集层基于 prometheus 协议设计出度量指标公共服务 MetricsService;

       2)在接口层引入度量指标汇聚层,用于汇聚各项指标数据,根据展示层实际的接入方式进行转换。

监控优化方案

7、知识补充一:prometheus

       Prometheus 是一个开源的监控系统,可以看做是一个时序数据库,它使用一种特定的数据格式来收集和存储时间序列数据。数据类型:

  • Counter(计数器):一个单调递增的度量值,通常用于记录事件发生的次数。例如, HTTP 请求的计数。

  • Gauge(量表):一个可以任意增减的度量值,通常用于记录某一时刻的状态或值。例如,当前的内存使用量、CPU 使用率等。

  • Histogram(直方图):用于测量样本的分布情况,比如请求延迟。它将数据样本划分到不同的桶(buckets)中,并记录每个桶中的样本数量。

  • Summary(摘要):类似于直方图,但会额外提供请求的总数和总和,通常用于计算百分位数等聚合度量。

       数据格式:

1
<metric_name>{<label_name>=<label_value>, ...} <value> <timestamp>
  • metric_name: 度量名称,必须符合正则表达式 [a-zA-Z_:][a-zA-Z0-9_:]*,例如:http_requests_total。

  • {<label_name>=<label_value>, …}: 可选的标签部分,用来区分不同的时间序列,例如:{method=“GET”, endpoint="/api"}。

  • value: 度量的值,是一个浮点数。

  • timestamp: 可选的 UNIX 时间戳,以毫秒为单位。

       数据样例:

1
2
3
4
# HELP http_requests_total Total number of HTTP requests.
# TYPE http_requests_total counter
http_requests_total{method="GET", handler="/home"} 1027 1694116786000
http_requests_total{method="POST", handler="/api"} 521 1694116786000

8、知识补充二:采集工具比较

采集工具比较

设计模式之模板方法模式和策略模式

     这篇博客的设计模式应用案例来自于这个仓库,完整代码可以参考本仓库:

     微信大模型接入

仓库主页

     简单介绍一下这个仓库:1)实现了微信接入;2)实现了大模型接入;3)将微信的提问发给大模型,将大模型的回答返回给微信(欢迎给个 star)。

1、背景分析

     1)我们预期接入的大模型肯定不止一种,现在市面上除了最牛的 GhatGPT,国内也陆续退出了豆包、文心一言、星火大模型等。为了获得良好的扩展性,我们可以基于策略模式对模型通讯模块进行封装,将不同的模型定义为一种通讯策略,程序中可以通过参数指定不同的模型工作;

     2)通讯的过程无非就是三个阶段:通信前参数组装、进行通讯、通讯完成处理结果,这里显然是可以通过模板方法进行封装的。结合策略模式,我们可以规定将来接入新模型的时候,有统一的代码组织形式和良好的扩展接口。

2、知识补充

     我们这里不再对设计模式本身进行专门的讲解。

     策略模式

     模板方法模式

3、代码分析

     如下图所示,DefaultHandler 是程序写给微信接入模块的一个回调(实现了消息处理接口 IMsgHandlerFace),当微信接入模块接收到微信消息,便会触发此回调,执行用户预定义行为。也就是在这个地方,我们接入了大模型,并将模型的问答结果返回给微信。

模型接入的地方

     一下三行代码的作用分别是:获取聊天模型的策略上下文(请参考上文中菜鸟教程——策略模式),返回的策略上下文会包含具体的执行策略,执行策略的选择是程序参数定义的。

1
2
3
4
5
6
// 聊天模型策略
StrategyContext context = getStrategyContext();
// 构建聊天请求
ChatRequest request = buildChatRequest(msg);
// 进行聊天
ChatResponse response = context.executeStrategy(request);

程序参数指定执行策略

3、策略模式实现

     首先,定义策略接口,策略接口中的 exec 方法是所有具体的策略类都需要实现的。

策略接口

     定义策略上下文,上下文是统一交给用户侧的一个”句柄”(可以参考上文 DefaultHandler 的代码,用户侧通过获取策略上下文来执行具体的策略实现的),用于持有具体的策略实现。

策略上下文

     我们这里的策略实现类稍有不同,没有直接实现 exec 方法,也没有直接实现 IStrategy 接口。这涉及到另外一个设计模式——模板方法模式。

策略实现类

4、 模板方法模式

     抽象策略类定义了一个算法模板方法,这个模板方法规定了 exec 方法执行时发生的三个算法步骤:postChatRequest (执行前的参数处理)、doExec (执行通讯请求)、postChatResponse(通讯完成之后的响应报文处理)。但是我并没有对这三个步骤进行实现,他们都是抽象的,延迟到了将来的策略实现类去实现。

抽象策略类

     所有的策略实现类,实现的不是策略接口 IStrategy,而是继承抽象策略类 AbstractStrategy,也不再去实现 exec 方法,而是实现抽象类中算法模板规定的三个算法步骤。

实现抽象策略类中规定的三个既定步骤

5、 其他一些想法

     面向对象语言最重要的三个基本特性:封装、多态、继承,是软件工程七大原则开闭原则,里氏代换原则,依赖倒转原则,接口隔离原则,迪米特原则和合成复用原则的重要支撑点,设计模式是一种如何最大化发挥三个基本特性,从而能够遵循七大原则的一种编码层级上的技术,这也是 Java、C++ 等完美支持 OOP 编程范式语言,在面临庞大复杂工程时,总能将源代码组织得很好的原因之一吧。从这个角度出发,Go 语言在多态、继承的表现力上不足,也许是因为我还比较缺乏 Go 开发的实战经验,所以我不确定在面临复杂的建模场景的时候,Go 语言的编程方式还能不能进行有效表达。

实现一个健康检查模块

     参考 SpringBoot 的健康检查设计,在平台层实现一套健康检查机制。挺优雅。

1、健康检查器接口类设计

1
2
3
4
5
6
7
8
/**
* IHealthChecker
*
*/
public interface IHealthChecker {

Health doHealth();
}

2、健康检查器抽象实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* AbstractHealthChecker
*
* 有一些公共逻辑需要定义在这一层抽象层,所有的自检实现都必须具备,例如开关、
*
*/
public abstract class AbstractHealthChecker implements IHealthChecker {

/**
* 模板方法确保 Health 初始状态为 up
*
* @return
*/
public Health doHealth() {
Health.Builder builder = Health.up();
return health(builder);
}

public abstract Health health(Health.Builder builder);

// 自检开关
private boolean toggle = true;

// 严格模式,严格模式下的健康检查器状态,将影响最终健康状态
private boolean strict = true;

// 省略了 getter/setter
}

3、健康状态的枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/**
* 检查状态
*
*/
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public final class Status {

public static final Status UNKNOWN = new Status("UNKNOWN");

public static final Status UP = new Status("UP");


public static final Status DOWN = new Status("DOWN");


public static final Status OUT_OF_SERVICE = new Status("OUT_OF_SERVICE");

private final String code;

private final String description;

public Status(String code) {
this(code, "");
}


public Status(String code, String description) {
this.code = code;
this.description = description;
}

@JsonProperty("status")
public String getCode() {
return this.code;
}


@JsonInclude(JsonInclude.Include.NON_EMPTY)
public String getDescription() {
return this.description;
}

@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj instanceof Status) {
return ObjectUtils.nullSafeEquals(this.code, ((Status) obj).code);
}
return false;
}

@Override
public int hashCode() {
return this.code.hashCode();
}

@Override
public String toString() {
return this.code;
}

}

4、健康信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/**
* Health
*/
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public final class Health {

private final Status status;

private final Map<String, Object> details;

private Health(Builder builder) {
this.status = builder.status;
this.details = Collections.unmodifiableMap(builder.details);
}

@JsonUnwrapped
public Status getStatus() {
return this.status;
}

public Map<String, Object> getDetails() {
return this.details;
}

@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj instanceof Health) {
Health other = (Health) obj;
return this.status.equals(other.status) && this.details.equals(other.details);
}
return false;
}

@Override
public int hashCode() {
int hashCode = this.status.hashCode();
return 13 * hashCode + this.details.hashCode();
}

@Override
public String toString() {
return getStatus() + " " + getDetails();
}

public static Builder unknown() {
return status(Status.UNKNOWN);
}


public static Builder up() {
return status(Status.UP);
}

public static Builder down(Exception ex) {
return down().withException(ex);
}


public static Builder down() {
return status(Status.DOWN);
}


public static Builder outOfService() {
return status(Status.OUT_OF_SERVICE);
}


public static Builder status(String statusCode) {
return status(new Status(statusCode));
}

public static Builder status(Status status) {
return new Builder(status);
}

public static class Builder {

private Status status;

private final Map<String, Object> details;

public Builder() {
this.status = Status.UNKNOWN;
this.details = new LinkedHashMap<>();
}

public Builder(Status status) {
this.status = status;
this.details = new LinkedHashMap<>();
}

public Builder(Status status, Map<String, ?> details) {
this.status = status;
this.details = new LinkedHashMap<>(details);
}

public Builder withException(Throwable ex) {
return withDetail("error", ex.getClass().getName() + ": " + ex.getMessage());
}


public Builder withDetail(String key, Object value) {
this.details.put(key, value);
return this;
}

public Builder withDetails(Map<String, ?> details) {
this.details.putAll(details);
return this;
}

public Builder unknown() {
return status(Status.UNKNOWN);
}

public Builder up() {
return status(Status.UP);
}

public Builder down(Throwable ex) {
return down().withException(ex);
}

public Builder down() {
return status(Status.DOWN);
}


public Builder outOfService() {
return status(Status.OUT_OF_SERVICE);
}

public Builder status(String statusCode) {
return status(new Status(statusCode));
}

public Builder status(Status status) {
this.status = status;
return this;
}

public Health build() {
return new Health(this);
}

}

}

5、健康检查器注册中心

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/**
* HealthCheckerRegistry
*
*/
@Component
public class HealthCheckerRegistry {

/**
* 同步锁
*/
private final Object monitor = new Object();

/**
* 健康检查器集合
*/
private Map<String, AbstractHealthChecker> checkers = new LinkedHashMap<>();

@Autowired
public void setCheckers(Map<String, AbstractHealthChecker> checkers) {
this.checkers = checkers;
}


/**
* 注册检查者
*
* @param name
* @param checker
*/
public void register(String name, AbstractHealthChecker checker) {
synchronized (this.monitor) {
AbstractHealthChecker existing = this.checkers.putIfAbsent(name, checker);
if (existing != null) {
throw new AresRuntimeException("健康检查器{}已存在,请排查", name);
}
}

}

/**
* 卸载检查器
*
* @param name
* @return
*/
public AbstractHealthChecker unregister(String name) {
Assert.notNull(name, "检查器名称不能为空");
synchronized (this.monitor) {
return this.checkers.remove(name);
}
}

/**
* 获取所有的检查器
*
* @return
*/
public Map<String, AbstractHealthChecker> getAllCheckers() {
synchronized (this.monitor) {
return Collections.unmodifiableMap(new LinkedHashMap<>(this.checkers));
}
}
}

6、健康检查服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/**
* HealthCheckService 收集并组合所有的健康状态信息
*
*/
@Component
public class HealthCheckService {

@Resource
private HealthCheckerRegistry checkerRegistry;

/**
* 执行健康检查
*
* @return
*/
public Health doHealthCheck() {
return compositeHealth(checkerRegistry.getAllCheckers());
}

/**
* 聚合自检信息
*
* @param allCheckers
* @return
*/
public Health compositeHealth(Map<String, AbstractHealthChecker> allCheckers) {
Health.Builder builder = Health.up();
for (Map.Entry<String, AbstractHealthChecker> entry : allCheckers.entrySet()) {
String name = entry.getKey();
AbstractHealthChecker checker = entry.getValue();

// 如果当前检查器没有打开则跳过
if (!checker.isEnable()) {
continue;
}

// 执行自检逻辑
try {
Health health = checker.doHealth();
builder.withDetail(name, health);
if (health.getStatus() != Status.UP && checker.isStrict()) {
// 只要有一个健康检查器检查失败并且当前检查器处于严格模式,就设置为失败
builder.status(Status.DOWN);
}
} catch (Exception e) {
// 如果自检异常,则将该健康自检信息记录下来
builder.withDetail(name, Health.down(e).build());
// 如果检查器处于严格模式,DOWN之
if(checker.isStrict()){
// 并标记为 DOWN
builder.status(Status.DOWN);
}
}
}

return builder.build();
}

}

7、检查器实现案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
/**
* RedisHealthChecker
* <p>
* 缓存健康检查
*
*/
@Component
public class RedisHealthChecker extends AbstractHealthChecker {

@Value("${xxxx.cache.health.checker.enable:false}")
private boolean enable;

@Value("${xxxx.cache.health.checker.strict:true}")
private boolean strict;

// 数据缓存
@Resource
private RedisService cacheService;

private final static String PONG = "PONG";


@Override
public Health health(Health.Builder builder) {

// 数据缓存就绪性自检
if (null == cacheService) {
return builder.down().withDetail("redisHealthChecker", "cacheService not prepared").build();
}

RedisTemplate<String, Object> cacheTemplate = cacheService.getRedisTemplate();

if (null == cacheTemplate) {
return builder.down().withDetail("redisHealthChecker", "cacheTemplate not prepared").build();
}

RedisConnectionFactory cacheConnectionFactory = cacheTemplate.getConnectionFactory();
if (null == cacheConnectionFactory || cacheConnectionFactory.getConnection().isClosed()) {
return builder.down().withDetail("redisHealthChecker", "cache server is closed").build();
}

String cachePong = cacheConnectionFactory.getConnection().ping();

if (!StringUtil.isEmpty(cachePong) &&
PONG.equalsIgnoreCase(cachePong)) {
return builder.up().withDetail("redisHealthChecker", "session server is ready").build();
}

// 未知状态
return builder.up().withDetail("redisHealthChecker", "UNKNOWN").build();
}

@PostConstruct
public void init() {
// 检查开关
if (enable) {
enable();
} else {
disable();
}

// 严格模式
if (strict) {
enStrict();
} else {
deStrict();
}
}
}

8、Restful 方式接入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
*
*/
@RestController
public class HealthHandler {

// 聚合检查信息详情的服务
@Resource
private HealthCheckService healthCheckService;


ObjectMapper mapper = new ObjectMapper();

/**
* 存活性健康检查
*
* @return
*/
@RequestMapping(value = {"/liveness"}, produces = {"text/plain;charset=UTF-8"})
public String liveness() {
return "ok";
}

/**
* 就绪性健康检查
*
* @return
*/
@RequestMapping(value = {"/readiness"}, produces = {"text/plain;charset=UTF-8"})
public String readiness(HttpServletResponse response) {
Health health = healthCheckService.doHealthCheck();

// 如果就绪检查失败,则修改返回状态码
if(health.getStatus() == null || health.getStatus() != Status.UP){
response.setStatus(HttpStatus.SC_INTERNAL_SERVER_ERROR);
}

String jsonStr;
try {
jsonStr = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(health);
} catch (JsonProcessingException e) {
throw new AresRuntimeException("健康检查模块执行失败");
}

return jsonStr;
}
}

机房服务器网口

     服务器的网络端口分为两种:电口和光口,电口就是双绞线的水晶头接口,光口是光纤接口。光口的速率远远高于电口,因此实际生产机房中,光口一般用于数据通信,电口一般用于管理通信。

     下面视频中,绿色线是光纤线,白色线是双绞线:

  • Copyrights © 2017 - 2025 杨海波
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信