Skip to content

Commit 65add0f

Browse files
committed
2021-10-24
1 parent 21e349a commit 65add0f

8 files changed

Lines changed: 252 additions & 4 deletions

File tree

docs/1_消息队列.md

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# 消息队列
2+
3+
消息队列中间件是**分布式系统**中重要的组件,主要用于:异步处理,应用解耦,流量削锋,消息通讯等问题,实现高性能,高可用,可伸缩和最终一致性架构。目前使用较多的消息队列有 ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ。
4+
5+
## 消息模型
6+
7+
### 1. 点对点
8+
9+
消息生产者向消息队列中发送了一个消息之后,只能被一个消费者消费一次。
10+
11+
<div align="center"><img src="https://gitee.com/duhouan/ImagePro/raw/master/java-notes/systemDesign/685a692f-8f76-4cac-baac-b68e2df9a30f.jpg" width="500px"/></div>
12+
13+
### 2. 发布/订阅
14+
15+
消息生产者向频道发送一个消息之后,多个消费者可以从该频道订阅到这条消息并消费。
16+
17+
<div align="center"><img src="https://gitee.com/duhouan/ImagePro/raw/master/java-notes/systemDesign/ddb5ff4c-4ada-46aa-9bf1-140bdb5e4676.jpg" width="500px"/></div>
18+
19+
发布与订阅模式和观察者模式有以下不同:
20+
21+
- 观察者模式中,观察者和主题都知道对方的存在;而在发布与订阅模式中,发布者与订阅者不知道对方的存在,它们之间通过频道进行通信。
22+
- 观察者模式是同步的,当事件触发时,主题会调用观察者的方法,然后等待方法返回;而发布与订阅模式是异步的,发布者向频道发送一个消息之后,就不需要关心订阅者何时去订阅这个消息,可以立即返回。
23+
24+
<div align="center"><img src="https://gitee.com/duhouan/ImagePro/raw/master/java-notes/systemDesign/bee1ff1d-c80f-4b3c-b58c-7073a8896ab2.jpg" width="500px"/></div>
25+
26+
## 使用场景
27+
28+
### 1、异步处理
29+
30+
发送者将消息发送给消息队列之后,**不需要同步等待消息接收者处理完毕,而是立即返回进行其它操作**。消息接收者从消息队列中订阅消息之后异步处理。
31+
32+
比如:用户注册时,需要发注册邮件和注册短信。传统的做法有两种:
33+
34+
> 1、串行方式
35+
36+
将注册信息写入数据库成功后,发送注册邮件,再发送注册短信。以上三个任务全部完成后,返回给客户端。
37+
38+
![img](https://gitee.com/duhouan/ImagePro/raw/master/MessageQueuue/mq_4.png)
39+
40+
> 2、并行方式
41+
42+
将注册信息写入数据库成功后,发送注册邮件的同时,发送注册短信。以上三个任务完成后,返回给客户端。与串行的差别是,并行的方式可以减少处理的时间。
43+
44+
![img](https://gitee.com/duhouan/ImagePro/raw/master/MessageQueuue/mq_5.png)
45+
46+
引入消息队列,将不是必须的业务逻辑异步处理,注册邮件,发送短信写入消息队列后,直接返回,因此写入消息队列的速度很快,基本可以忽略,因此用户的响应时间可能是 50 ms。比串行提高了 3 倍,比并行提高了2 倍。
47+
48+
![img](https://gitee.com/duhouan/ImagePro/raw/master/MessageQueuue/mq_6.png)
49+
50+
只有在**业务流程允许异步处理的情况**下才能这么做,例如上面的注册流程中,如果要求用户对验证邮件进行点击之后才能完成注册的话,就不能再使用消息队列。
51+
52+
### 2、应用解耦
53+
54+
如果模块之间不直接进行调用,模块之间耦合度就会很低,那么修改一个模块或者新增一个模块对其它模块的影响会很小,从而实现可扩展性。
55+
56+
通过使用消息队列,一个模块只需要向消息队列中发送消息,其它模块可以**选择性地从消息队列中订阅消息**从而完成调用。
57+
58+
### 3、流量削峰
59+
60+
在高并发的场景下,如果短时间有大量的请求到达会压垮服务器。
61+
62+
可以将请求发送到消息队列中,服务器按照其处理能力从消息队列中订阅消息进行处理。
63+
64+
### 4、消息通讯
65+
66+
消息队列一般都内置了高效的通信机制,因此也可以用在纯消息通讯。比如实现点对点消息队列,或者聊天室等,也就是消息队列的两种消息模式:点对点或发布 / 订阅模式。
67+
68+
69+
70+
## 消费方式
71+
72+
在 JMS (Java Message Service,Java 消息服务) 中,消息的产生和消费都是异步的。对于消费来说,JMS 的消费者可以通过两种方式来消费消息:同步方式和异步方式。
73+
74+
### 1、同步方式
75+
76+
订阅者或消费者通过 receive() 方法来接收消息,receive() 方法在接收到消息之前(或超时之前)将一直阻塞。
77+
78+
### 2、异步方式
79+
80+
订阅者或消费者可以注册为一个消息监听器。当消息到达之后,系统自动调用监听器的 onMessage() 方法。
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+
### 1、系统可用性降低
106+
107+
系统可用性在某种程度上降低,为什么这样说呢?
108+
109+
在加入消息队列之前,不用考虑消息丢失或者说消息队列挂掉等等的情况,但是,引入消息队列之后你就需要去考虑了。
110+
111+
### 2、系统复杂性提高
112+
113+
加入消息队列之后,你需要保证消息没有被重复消费、处理消息丢失的情况、保证消息传递的顺序性等问题。
114+
115+
### 3、一致性问题
116+
117+
消息队列可以实现异步,消息队列带来的异步确实可以提高系统响应速度。但是,消息的真正消费者并没有正确消费消息,就会导致数据不一致的情况。

docs/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@
129129
# 🎓 系统设计
130130

131131
- [系统设计基础](1_基础.md)
132+
- [消息队列](1_消息队列.md)
132133

133134
## 安全性
134135

@@ -143,6 +144,17 @@
143144
- [分布式事务](distribution/4_分布式事务.md)
144145
- [Paxos 算法 & Raft 算法](distribution/5_Paxos算法.md)
145146

147+
## 集群
148+
149+
- [负载均衡](cluster/1_负载均衡.md)
150+
- [集群下的 Session 管理](cluster/2_集群下的Session管理.md)
151+
152+
## 缓存
153+
154+
- [缓存需要考虑的问题](cache/1_缓存需要考虑的问题.md)
155+
- [缓存常见问题](cache/2_缓存常见问题.md)
156+
- [数据分布](cache/3_数据分布.md)
157+
146158
# ☎️ 常用框架
147159

148160
## Spring
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# 缓存需要考虑的问题
2+
3+
## 缓存命中率
4+
5+
当某个请求能够通过访问缓存而得到响应时,称为缓存命中。缓存命中率越高,缓存的利用率也就越高。
6+
7+
## 最大空间
8+
9+
缓存通常位于**内存**中,内存的空间通常比磁盘空间小的多,因此缓存的最大空间不可能非常大。当缓存存放的数据量超过最大空间时,就需要**淘汰部分数据**来存放新到达的数据。常见的数据淘汰策略:
10+
11+
- FIFO(First In First Out)
12+
13+
FIFO 即先进先出策略,在实时性的场景下,需要经常访问最新的数据,那么就可以使用 FIFO,使得最先进入的数据(最晚的数据)被淘汰。
14+
15+
- LRU(Least Recently Used)
16+
17+
LRU 即最近最久未使用策略,优先淘汰最久未使用的数据,也就是上次被访问时间距离现在最久的数据。该策略可以保证内存中的数据都是热点数据,也就是经常被访问的数据,从而保证缓存命中率。
18+
19+
- LFU(Least Frequently Used)
20+
21+
LFU 即最不经常使用策略,优先淘汰一段时间内使用次数最少的数据。
22+
23+
## 缓存位置
24+
25+
缓存位置一般有:
26+
27+
- 浏览器
28+
29+
当 HTTP 响应允许进行缓存时,浏览器会将 HTML、CSS、JavaScript、图片等静态资源进行缓存。
30+
31+
- 反向代理
32+
33+
反向代理**位于服务器之前**,请求与响应都需要经过反向代理。通过将数据缓存在反向代理,在用户请求反向代理时就可以直接使用缓存进行响应。
34+
35+
- 本地缓存
36+
37+
使用 Guava Cache 将数据缓存在服务器本地内存中,服务器代码可以直接读取本地内存中的缓存,速度非常快。
38+
39+
- 分布式缓存
40+
41+
使用 **Redis、Memcache** 等分布式缓存将数据缓存在分布式缓存系统中。相对于本地缓存来说,分布式缓存单独部署,可以根据需求分配硬件资源。不仅如此,服务器集群都可以访问分布式缓存,而本地缓存需要在服务器集群之间进行同步,实现难度和性能开销上都非常大。
42+
43+
- 数据库缓存
44+
45+
MySQL 等数据库管理系统具有自己的查询缓存机制来提高查询效率。
46+
47+
- CPU 多级缓存
48+
49+
CPU 为了解决运算速度与主存 IO 速度不匹配的问题,引入了多级缓存结构,同时使用 MESI 等缓存一致性协议来解决多核 CPU 缓存数据一致性的问题。
50+
51+
- Java 内部的缓存
52+
53+
Java 为了优化空间,提高字符串、基本数据类型包装类的创建效率,设计了字符串常量池及 Byte、Short、Character、Integer、Long、Boolean 这六种包装类缓冲池。
54+

docs/cache/2_缓存常见问题.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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+
产生原因:缓存系统通常采用 hash 函数将 key 映射到对应的缓存节点,随着缓存节点数目的增加,键值分布到更多的节点上,导致客户端一次批量操作会涉及多次网络操作,这意味着批量操作的耗时会随着节点数目的增加而不断增大。此外,网络连接数变多,对节点的性能也有一定影响。
40+
41+
解决方案:
42+
43+
- 优化批量数据操作命令;
44+
- 减少网络通信次数;
45+
- 降低接入成本,使用长连接 / 连接池,NIO 等。

docs/cache/3_数据分布.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# 数据分布
2+
3+
## 哈希分布
4+
5+
哈希分布就是将数据计算哈希值之后,按照哈希值分配到不同的节点上。例如有 N 个节点,数据的主键为 key,则将该数据分配的节点序号为:hash(key)%N。
6+
7+
传统的哈希分布算法存在一个问题:当节点数量变化时,也就是 N 值变化,那么几乎所有的数据都需要重新分布,将导致大量的数据迁移。
8+
9+
## 顺序分布
10+
11+
将数据划分为多个连续的部分,按数据的 ID 或者时间分布到不同节点上。例如 User 表的 ID 范围为 1 ~ 7000,使用顺序分布可以将其划分成多个子表,对应的主键范围为 1 ~ 1000,1001 ~ 2000,...,6001 ~ 7000。
12+
13+
顺序分布相比于哈希分布的主要优点如下:
14+
15+
- 能保持数据原有的顺序;
16+
- 并且能够准确控制每台服务器存储的数据量,从而使得存储空间的利用率最大。

docs/cluster/1_负载均衡.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# 负载均衡
2+
3+
待补充。
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# 集群下的 Session 管理
2+

docs/distribution/5_Paxos算法.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
# 一、Paxos 算法
22

3-
待补充。
4-
5-
3+
- [分布式系列文章——Paxos算法原理与推导](https://www.cnblogs.com/linbingdong/p/6253479.html)
64

75
# 二、Raft 算法
86

9-
待补充。
7+
- [拜占庭将军问题](https://blog.csdn.net/bjweimengshu/article/details/80222416)
8+
- [Raft 算法动画详解](http://thesecretlivesofdata.com/raft/)

0 commit comments

Comments
 (0)