Skip to content

Commit 88cf8e4

Browse files
authored
Merge branch 'Snailclimb:main' into main
2 parents a352daa + f7060dd commit 88cf8e4

22 files changed

+87
-39
lines changed

docs/cs-basics/data-structure/linear-data-structure.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ tag:
9999

100100
![](https://oss.javaguide.cn/github/javaguide/cs-basics/data-structure/%E6%A0%88.png)
101101

102-
### 3.2. 栈的常见应用常见应用场景
102+
### 3.2. 栈的常见应用场景
103103

104104
当我们我们要处理的数据只涉及在一端插入和删除数据,并且满足 **后进先出(LIFO, Last In First Out)** 的特性时,我们就可以使用栈这个数据结构。
105105

@@ -154,7 +154,12 @@ public boolean isValid(String s){
154154

155155
#### 3.2.4. 维护函数调用
156156

157-
最后一个被调用的函数必须先完成执行,符合栈的 **后进先出(LIFO, Last In First Out)** 特性。
157+
最后一个被调用的函数必须先完成执行,符合栈的 **后进先出(LIFO, Last In First Out)** 特性。
158+
例如递归函数调用可以通过栈来实现,每次递归调用都会将参数和返回地址压栈。
159+
160+
#### 3.2.5 深度优先遍历(DFS)
161+
162+
在深度优先搜索过程中,栈被用来保存搜索路径,以便回溯到上一层。
158163

159164
### 3.3. 栈的实现
160165

@@ -316,13 +321,14 @@ myStack.pop();//报错:java.lang.IllegalArgumentException: Stack is empty.
316321

317322
虽然优先队列的底层并非严格的线性结构,但是在我们使用的过程中,我们是感知不到****的,从使用者的眼中优先队列可以被认为是一种线性的数据结构:一种会自动排序的线性队列。
318323

319-
### 4.3. 常见应用场景
324+
### 4.3. 队列的常见应用场景
320325

321326
当我们需要按照一定顺序来处理数据的时候可以考虑使用队列这个数据结构。
322327

323328
- **阻塞队列:** 阻塞队列可以看成在队列基础上加了阻塞操作的队列。当队列为空的时候,出队操作阻塞,当队列满的时候,入队操作阻塞。使用阻塞队列我们可以很容易实现“生产者 - 消费者“模型。
324329
- **线程池中的请求/任务队列:** 线程池中没有空闲线程时,新的任务请求线程资源时,线程池该如何处理呢?答案是将这些请求放在队列中,当有空闲线程的时候,会循环中反复从队列中获取任务来执行。队列分为无界队列(基于链表)和有界队列(基于数组)。无界队列的特点就是可以一直入列,除非系统资源耗尽,比如:`FixedThreadPool` 使用无界队列 `LinkedBlockingQueue`。但是有界队列就不一样了,当队列满的话后面再有任务/请求就会拒绝,在 Java 中的体现就是会抛出`java.util.concurrent.RejectedExecutionException` 异常。
325330
- 栈:双端队列天生便可以实现栈的全部功能(`push``pop``peek`),并且在 Deque 接口中已经实现了相关方法。Stack 类已经和 Vector 一样被遗弃,现在在 Java 中普遍使用双端队列(Deque)来实现栈。
331+
- 广度优先搜索(BFS),在图的广度优先搜索过程中,队列被用于存储待访问的节点,保证按照层次顺序遍历图的节点。
326332
- Linux 内核进程队列(按优先级排队)
327333
- 现实生活中的派对,播放器上的播放列表;
328334
- 消息队列

docs/cs-basics/network/nat.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,6 @@ SOHO 子网的“代理人”,也就是和外界的窗口,通常由路由器
5555
3. WAN 的 ISP 变更接口地址时,无需通告 LAN 内主机。
5656
4. LAN 主机对 WAN 不可见,不可直接寻址,可以保证一定程度的安全性。
5757

58-
然而,NAT 协议由于其独特性,存在着一些争议。比如,可能你已经注意到了,**NAT 协议在 LAN 以外,标识一个内部主机时,使用的是端口号,因为 IP 地址都是相同的。**这种将端口号作为主机寻址的行为,可能会引发一些误会。此外,路由器作为网络层的设备,修改了传输层的分组内容(修改了源 IP 地址和端口号),同样是不规范的行为。但是,尽管如此,NAT 协议作为 IPv4 时代的产物,极大地方便了一些本来棘手的问题,一直被沿用至今。
58+
然而,NAT 协议由于其独特性,存在着一些争议。比如,可能你已经注意到了,<b>NAT 协议在 LAN 以外,标识一个内部主机时,使用的是端口号,因为 IP 地址都是相同的。</b>这种将端口号作为主机寻址的行为,可能会引发一些误会。此外,路由器作为网络层的设备,修改了传输层的分组内容(修改了源 IP 地址和端口号),同样是不规范的行为。但是,尽管如此,NAT 协议作为 IPv4 时代的产物,极大地方便了一些本来棘手的问题,一直被沿用至今。
5959

6060
<!-- @include: @article-footer.snippet.md -->

docs/cs-basics/operating-system/operating-system-basic-questions-01.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ Thread[线程 2,5,main]waiting get resource1
402402

403403
上面提到的 **破坏** 死锁产生的四个必要条件之一就可以成功 **预防系统发生死锁** ,但是会导致 **低效的进程运行****资源使用率** 。而死锁的避免相反,它的角度是允许系统中**同时存在四个必要条件** ,只要掌握并发进程中与每个进程有关的资源动态申请情况,做出 **明智和合理的选择** ,仍然可以避免死锁,因为四大条件仅仅是产生死锁的必要条件。
404404

405-
我们将系统的状态分为 **安全状态****不安全状态**每当在未申请者分配资源前先测试系统状态,若把系统资源分配给申请者会产生死锁,则拒绝分配,否则接受申请,并为它分配资源。
405+
我们将系统的状态分为 **安全状态****不安全状态**每当在为申请者分配资源前先测试系统状态,若把系统资源分配给申请者会产生死锁,则拒绝分配,否则接受申请,并为它分配资源。
406406

407407
> 如果操作系统能够保证所有的进程在有限的时间内得到需要的全部资源,则称系统处于安全状态,否则说系统是不安全的。很显然,系统处于安全状态则不会发生死锁,系统若处于不安全状态则可能发生死锁。
408408

docs/database/mysql/how-sql-executed-in-mysql.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ update tb_student A set A.age='19' where A.name=' 张三 ';
100100

101101
我们来给张三修改下年龄,在实际数据库肯定不会设置年龄这个字段的,不然要被技术负责人打的。其实这条语句也基本上会沿着上一个查询的流程走,只不过执行更新的时候肯定要记录日志啦,这就会引入日志模块了,MySQL 自带的日志模块是 **binlog(归档日志)** ,所有的存储引擎都可以使用,我们常用的 InnoDB 引擎还自带了一个日志模块 **redo log(重做日志)**,我们就以 InnoDB 模式下来探讨这个语句的执行流程。流程如下:
102102

103-
- 先查询到张三这一条数据,如果有缓存,也是会用到缓存
103+
- 先查询到张三这一条数据,不会走查询缓存,因为更新语句会导致与该表相关的查询缓存失效
104104
- 然后拿到查询的语句,把 age 改为 19,然后调用引擎 API 接口,写入这一行数据,InnoDB 引擎把数据保存在内存中,同时记录 redo log,此时 redo log 进入 prepare 状态,然后告诉执行器,执行完成了,随时可以提交。
105105
- 执行器收到通知后记录 binlog,然后调用引擎接口,提交 redo log 为提交状态。
106106
- 更新完成。

docs/database/mysql/mysql-index.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,7 @@ PS: 不懂的同学可以暂存疑,慢慢往下看,后面会有答案的,
182182

183183
1. **唯一索引(Unique Key)**:唯一索引也是一种约束。唯一索引的属性列不能出现重复的数据,但是允许数据为 NULL,一张表允许创建多个唯一索引。 建立唯一索引的目的大部分时候都是为了该属性列的数据的唯一性,而不是为了查询效率。
184184
2. **普通索引(Index)**:普通索引的唯一作用就是为了快速查询数据,一张表允许创建多个普通索引,并允许数据重复和 NULL。
185-
3. **前缀索引(Prefix)**:前缀索引只适用于字符串类型的数据。前缀索引是对文本的前几个字符创建索引,相比普通索引建立的数据更小,
186-
因为只取前几个字符。
185+
3. **前缀索引(Prefix)**:前缀索引只适用于字符串类型的数据。前缀索引是对文本的前几个字符创建索引,相比普通索引建立的数据更小,因为只取前几个字符。
187186
4. **全文索引(Full Text)**:全文索引主要是为了检索大文本数据中的关键字的信息,是目前搜索引擎数据库使用的一种技术。Mysql5.6 之前只有 MYISAM 引擎支持全文索引,5.6 之后 InnoDB 也支持了全文索引。
188187

189188
二级索引:

docs/database/mysql/mysql-logs.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ InnoDB 将 redo log 刷到磁盘上有几种情况:
112112

113113
![](https://oss.javaguide.cn/github/javaguide/10.png)
114114

115-
在个**日志文件组**中还有两个重要的属性,分别是 `write pos、checkpoint`
115+
在这个**日志文件组**中还有两个重要的属性,分别是 `write pos、checkpoint`
116116

117117
- **write pos** 是当前记录的位置,一边写一边后移
118118
- **checkpoint** 是当前要擦除的位置,也是往后推移

docs/distributed-system/distributed-id.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ category: 分布式
99

1010
日常开发中,我们需要对系统中的各种数据使用 ID 唯一表示,比如用户 ID 对应且仅对应一个人,商品 ID 对应且仅对应一件商品,订单 ID 对应且仅对应一个订单。
1111

12-
我们现实生活中也有各种 ID,比如身份证 ID 对应且仅对应一个人、地址 ID 对应且仅对应
12+
我们现实生活中也有各种 ID,比如身份证 ID 对应且仅对应一个人、地址 ID 对应且仅对应一个地址。
1313

1414
简单来说,**ID 就是数据的唯一标识**
1515

docs/distributed-system/distributed-lock.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ category: 分布式
3030

3131
悲观锁总是假设最坏的情况,认为共享资源每次被访问的时候就会出现问题(比如共享数据被修改),所以每次在获取资源操作的时候都会上锁,这样其他线程想拿到这个资源就会阻塞直到锁被上一个持有者释放。也就是说,**共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程**
3232

33-
对于单机多线程来说,在 Java 中,我们通常使用 `ReetrantLock` 类、`synchronized` 关键字这类 JDK 自带的 **本地锁** 来控制一个 JVM 进程内的多个线程对本地共享资源的访问。
33+
对于单机多线程来说,在 Java 中,我们通常使用 `ReentrantLock` 类、`synchronized` 关键字这类 JDK 自带的 **本地锁** 来控制一个 JVM 进程内的多个线程对本地共享资源的访问。
3434

3535
下面是我对本地锁画的一张示意图。
3636

docs/high-performance/data-cold-hot-separation.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ head:
2020

2121
冷热数据到底如何区分呢?有两个常见的区分方法:
2222

23-
1. **时间维度区分**:按照数据的创建时间、更新时间、过期时间等,将一定时间段内的数据视为热数据,超过该时间段的数据视为冷数据。例如,订单系统可以将 1 年后的订单数据作为冷数据,1 年内的订单数据作为热数据。这种方法适用于数据的访问频率和时间有较强的相关性的场景。
24-
2. **访问评率区分**:将高频访问的数据视为热数据,低频访问的数据视为冷数据。例如,内容系统可以将浏览量非常低的文章作为冷数据,浏览量较高的文章作为热数据。这种方法需要记录数据的访问频率,成本较高,适合访问频率和数据本身有较强的相关性的场景。
23+
1. **时间维度区分**:按照数据的创建时间、更新时间、过期时间等,将一定时间段内的数据视为热数据,超过该时间段的数据视为冷数据。例如,订单系统可以将 1 年前的订单数据作为冷数据,1 年内的订单数据作为热数据。这种方法适用于数据的访问频率和时间有较强的相关性的场景。
24+
2. **访问频率区分**:将高频访问的数据视为热数据,低频访问的数据视为冷数据。例如,内容系统可以将浏览量非常低的文章作为冷数据,浏览量较高的文章作为热数据。这种方法需要记录数据的访问频率,成本较高,适合访问频率和数据本身有较强的相关性的场景。
2525

2626
几年前的数据并不一定都是热数据,例如一些优质文章发表几年后依然有很多人访问,大部分普通用户新发表的文章却基本没什么人访问。
2727

docs/high-performance/read-and-write-separation-and-library-subtable.md

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -196,16 +196,35 @@ MySQL 主从同步延时是指从库的数据落后于主库的数据,这种
196196
- 数据库中的数据占用的空间越来越大,备份时间越来越长。
197197
- 应用的并发量太大。
198198

199+
不过,分库分表的成本太高,如非必要尽量不要采用。而且,并不一定是单表千万级数据量就要分表,毕竟每张表包含的字段不同,它们在不错的性能下能够存放的数据量也不同,还是要具体情况具体分析。
200+
201+
之前看过一篇文章分析 “[InnoDB 中高度为 3 的 B+ 树最多可以存多少数据](https://juejin.cn/post/7165689453124517896)”,写的挺不错,感兴趣的可以看看。
202+
199203
### 常见的分片算法有哪些?
200204

201205
分片算法主要解决了数据被水平分片之后,数据究竟该存放在哪个表的问题。
202206

203-
- **哈希分片**:求指定 key(比如 id) 的哈希,然后根据哈希值确定数据应被放置在哪个表中。哈希分片比较适合随机读写的场景,不太适合经常需要范围查询的场景。
204-
- **范围分片**:按照特性的范围区间(比如时间区间、ID 区间)来分配数据,比如 将 `id``1~299999` 的记录分到第一个库, `300000~599999` 的分到第二个库。范围分片适合需要经常进行范围查找的场景,不太适合随机读写的场景(数据未被分散,容易出现热点数据的问题)。
207+
常见的分片算法有:
208+
209+
- **哈希分片**:求指定分片键的哈希,然后根据哈希值确定数据应被放置在哪个表中。哈希分片比较适合随机读写的场景,不太适合经常需要范围查询的场景。哈希分片可以使每个表的数据分布相对均匀,但对动态伸缩(例如新增一个表或者库)不友好。
210+
- **范围分片**:按照特定的范围区间(比如时间区间、ID 区间)来分配数据,比如 将 `id``1~299999` 的记录分到第一个表, `300000~599999` 的分到第二个表。范围分片适合需要经常进行范围查找且数据分布均匀的场景,不太适合随机读写的场景(数据未被分散,容易出现热点数据的问题)。
211+
- **映射表分片**:使用一个单独的表(称为映射表)来存储分片键和分片位置的对应关系。映射表分片策略可以支持任何类型的分片算法,如哈希分片、范围分片等。映射表分片策略是可以灵活地调整分片规则,不需要修改应用程序代码或重新分布数据。不过,这种方式需要维护额外的表,还增加了查询的开销和复杂度。
212+
- **一致性哈希分片**:将哈希空间组织成一个环形结构,将分片键和节点(数据库或表)都映射到这个环上,然后根据顺时针的规则确定数据或请求应该分配到哪个节点上,解决了传统哈希对动态伸缩不友好的问题。
205213
- **地理位置分片**:很多 NewSQL 数据库都支持地理位置分片算法,也就是根据地理位置(如城市、地域)来分配数据。
206-
- **融合算法**:灵活组合多种分片算法,比如将哈希分片和范围分片组合。
214+
- **融合算法分片**:灵活组合多种分片算法,比如将哈希分片和范围分片组合。
207215
- ……
208216

217+
### 分片键如何选择?
218+
219+
分片键(Sharding Key)是数据分片的关键字段。分片键的选择非常重要,它关系着数据的分布和查询效率。一般来说,分片键应该具备以下特点:
220+
221+
- 具有共性,即能够覆盖绝大多数的查询场景,尽量减少单次查询所涉及的分片数量,降低数据库压力;
222+
- 具有离散性,即能够将数据均匀地分散到各个分片上,避免数据倾斜和热点问题;
223+
- 具有稳定性,即分片键的值不会发生变化,避免数据迁移和一致性问题;
224+
- 具有扩展性,即能够支持分片的动态增加和减少,避免数据重新分片的开销。
225+
226+
实际项目中,分片键很难满足上面提到的所有特点,需要权衡一下。并且,分片键可以是表中多个字段的组合,例如取用户 ID 后四位作为订单 ID 后缀。
227+
209228
### 分库分表会带来什么问题呢?
210229

211230
记住,你在公司做的任何技术决策,不光是要考虑这个技术能不能满足我们的要求,是否适合当前业务场景,还要重点考虑其带来的成本。
@@ -214,7 +233,7 @@ MySQL 主从同步延时是指从库的数据落后于主库的数据,这种
214233

215234
- **join 操作**:同一个数据库中的表分布在了不同的数据库中,导致无法使用 join 操作。这样就导致我们需要手动进行数据的封装,比如你在一个数据库中查询到一个数据之后,再根据这个数据去另外一个数据库中找对应的数据。不过,很多大厂的资深 DBA 都是建议尽量不要使用 join 操作。因为 join 的效率低,并且会对分库分表造成影响。对于需要用到 join 操作的地方,可以采用多次查询业务层进行数据组装的方法。不过,这种方法需要考虑业务上多次查询的事务性的容忍度。
216235
- **事务问题**:同一个数据库中的表分布在了不同的数据库中,如果单个操作涉及到多个数据库,那么数据库自带的事务就无法满足我们的要求了。这个时候,我们就需要引入分布式事务了。关于分布式事务常见解决方案总结,网站上也有对应的总结:<https://javaguide.cn/distributed-system/distributed-transaction.html>
217-
- **分布式 ID**:分库之后, 数据遍布在不同服务器上的数据库,数据库的自增主键已经没办法满足生成的主键唯一了。我们如何为不同的数据节点生成全局唯一主键呢?这个时候,我们就需要为我们的系统引入分布式 ID 了。关于分布式 ID 的详细介绍&实现方案总结,网站上也有对应的总结:<https://javaguide.cn/distributed-system/distributed-id.html>
236+
- **分布式 ID**:分库之后, 数据遍布在不同服务器上的数据库,数据库的自增主键已经没办法满足生成的主键唯一了。我们如何为不同的数据节点生成全局唯一主键呢?这个时候,我们就需要为我们的系统引入分布式 ID 了。关于分布式 ID 的详细介绍&实现方案总结,可以看我写的这篇文章:[分布式 ID 介绍&实现方案总结](https://javaguide.cn/distributed-system/distributed-id.html)
218237
- **跨库聚合查询问题**:分库分表会导致常规聚合查询操作,如 group by,order by 等变得异常复杂。这是因为这些操作需要在多个分片上进行数据汇总和排序,而不是在单个数据库上进行。为了实现这些操作,需要编写复杂的业务代码,或者使用中间件来协调分片间的通信和数据传输。这样会增加开发和维护的成本,以及影响查询的性能和可扩展性。
219238
- ……
220239

0 commit comments

Comments
 (0)