分布式的共识算法
分布式系统的共识算法是指在分布式环境中多个节点就某些信息达成一致的算法。这些算法在分布式系统中至关重要,尤其是在容错和保证一致性方面。
Paxos 算法
Paxos 算法是一种经典的分布式共识算法,由 Leslie Lamport 在 1990 年代提出。它旨在解决分布式系统中多个节点就某些信息达成一致的问题,尤其是在存在网络分区或节点故障的情况下。Paxos 算法特别强调强一致性,即所有参与的节点最终都会同意同一个值。
Paxos 算法包括两个主要的角色:Proposer 和 Acceptor。此外,还有一种角色叫做 Learner,用于监听并记录共识的结果。以下是 Paxos 算法的基本步骤:
- Prepare 阶段:
- Proposer 选择一个提案编号 n,并向一组 Acceptor 发送一个 Prepare(n) 消息。
- Acceptor 收到 Prepare(n) 消息后,如果它尚未对编号大于等于 n 的提案做出承诺,它会回复一个 Promise 消息,其中包含它先前承诺过的最大编号的提案值(如果有的话)。
- Proposer 收集大多数 Acceptor 的 Promise 消息。
- Accept 阶段:
- Proposer 选择一个值 v(通常是它收到的 Promise 消息中最大的提案值,如果没有则可以自由选择),并发送一个 Accept(n, v) 消息给所有 Acceptor。
- Acceptor 收到 Accept(n, v) 消息后,如果它已经回复了 Prepare(n) 的 Promise 消息,它就会接受该提案并记录提案值 v。
- Proposer 收集大多数 Acceptor 的 Accept 消息。
- Learn 阶段:
- Learner 收集大多数 Acceptor 的 Accept 消息后,它会学习到提案值 v,并将其传播给所有参与者。
Raft 算法是一种分布式共识算法,由 Diego Ongaro 和 John Ousterhout 在 2014 年提出。它旨在简化分布式系统中多个节点就某些信息达成一致的过程,并且相比 Paxos 算法更加直观易懂。Raft 算法特别强调通过选举领导者来简化共识过程,并且易于理解和实现。
Raft 算法的基本原理
Raft 算法的核心在于通过选举领导者来简化共识过程。它包括三种节点状态:Follower、Candidate 和 Leader。以下是 Raft 算法的基本步骤:
- Nacos中选举领导者:
- 当前领导者挂掉了 某个Follower 由于长时间没有收到当前领导者的心跳 所以将自己的状态改为Candidata 然后更换自己的任期编号后 向集群中的所有节点发送投票请求 并给自己投一票。
- 集群中的Follower 节点在收到投票请求后,会判断自己的任期编号和发起请求的Candidata 节点的任期编号,如果比发起请求的节点的任期编号小并且自己在当前任期并未给其他节点投票的话,那么当前节点会投票给发起请求的节点,并返回给发起请求的Candidata 节点
- 如果 Candidate 收集到大多数节点的投票,它将成为 Leader。
- Nacos中日志复制:
- 当 Leader 接收到服务的注册或发现请求时,它会将此操作作为一条日志条目写入自己的日志。
- Leader 然后向集群中的所有 Follower 节点发送这条日志条目的复制请求。
- Follower 节点接收到日志条目后,会检查其任期编号是否与自己的任期编号匹配。如果匹配,则将日志条目写入自己的日志,并回复确认消息。
- 当 Leader 收到多数 Follower 返回的确认消息后,它会认为这条日志条目已经被复制成功。
- Leader 这时才会应用这条日志条目,执行相应的服务注册或发现操作,并更新自己的状态机。
- Leader 会继续向集群中的所有节点发送心跳消息,以维持自己的领导地位。
- Nacos中容错机制:
- 当 Leader 长时间没有收到某个 Follower 的心跳消息时,它会认为该 Follower 已经失效。
- Leader 会尝试联系该 Follower,看看能否恢复它的连接。如果恢复成功, Leader 节点会将最新的日志同步给恢复成功的节点,使其能够重新加入集群 。
- 如果多次尝试都无法恢复与该 Follower 的连接,那么 Leader 会认为该 Follower 长久失效,并将其标记为不可用。
- 在某些情况下,如集群规模较小或配置文件允许的情况下,Leader 可能会尝试自动替换掉失效的 Follower,以便保持集群的可用性。
- 如果集群中有足够多的健康节点,那么系统仍然可以正常运行;否则,可能会出现性能下降或功能受限的情况。
Nacos中使用了Raft算法来保证服务注册与发现的一致性。
ZAB 算法
ZAB 算法是 专为 ZooKeeper 设计的一种分布式共识协议
ZAB的选举机制的大致过程:
- 初始化: 当 ZooKeeper 集群启动时,每个节点都会初始化自己的轮次号为 0。
- 选举开始:
- 当前领导者失败或无法响应客户端请求。
- 所有节点检测到领导者失败,并进入
LOOKING
状态。并将自己的轮次号增加1。
- 发送选举请求:
- 每个处于
LOOKING
状态的节点都会向集群中的所有节点发送一个选举请求消息。 - 消息包含自己的节点ID和轮次号。
- 节点在发送选举请求时,会将自己的轮次号加 1,例如从 0 加到 1。
- 每个处于
- 投票:
- 每个节点收到选举请求后,会检查自己的投票记录。
- 如果还没有投票或者认为当前的候选者更好,则投票给这个候选者,并将投票信息发送回候选者。
- 节点在投票时,会比较候选者的轮次号和自己记录的轮次号。如果候选者的轮次号更高,节点会投票给这个候选者;如果轮次号相同,则根据节点ID来决定。(会投票给节点ID更低的节点,节点ID一般是按照节点的加入顺序分配的,越早加入的节点会获得更低的节点ID)
- 成为领导者:
- 当一个候选者获得集群中超过半数节点的投票时,它成为新的领导者。
- 成为领导者后,该节点会将自己的状态设置为
LEADING
,并且将自己的轮次号作为当前的领导者轮次号。
- 通知其他节点:
- 领导者向集群中的所有其他节点发送消息,通知它们自己已成为领导者。
- 其他节点收到消息后,将自己的状态设置为
FOLLOWING
。 - 其他节点也会更新自己的轮次号为领导者轮次号。
redission做分布式锁:
redission的看门狗机制
简化来说就是后台有一个线程定期的去检测锁的过期时间,去持续的续费锁的过期时间。这个机制使得Redission实现的分布式锁是可以自动续期的。
看门狗机制的默认是在锁的有效时间剩余1/3的时候触发。
两种获取锁的方式,第一种是通过trylock()方法,此方法是非阻塞的,如果在指定时间内没有获取到锁,则立即返回。 该方法会返回一个布尔值,指示是否成功获取锁。
trylock代码如下:
public boolean tryLock(long waitTime, TimeUnit unit) throws InterruptedException {
return tryLock(waitTime, -1, unit);
}
1. waitTime:获取锁的最大等待时间(没有传默认为-1)
2. leaseTime:锁自动释放的时间(没有传的话默认-1)
3. unit:时间的单位(等待时间和锁自动释放的时间单位)
一般加锁和释放锁的操作包含在try{}finally{}代码块中执行,防止以为异常退出导致锁没有正常释放的情况。在finally代码块中释放锁之前,还需要判断一下锁是否是被当前线程锁持有,以避免释放不属于当前线程的锁。
另外一种是lock()
lock.lock()
方法试图获取锁,如果没有获取到锁,它将阻塞当前线程,直到成功获取锁为止。这意味着如果锁被其他客户端持有,调用 lock.lock()
的线程将等待锁被释放。
trylock示例代码如下:
lock.lock();
try {
// 执行关键操作
System.out.println("Processing with lock");
// 模拟一些业务逻辑
Thread.sleep(10000); // 模拟业务逻辑执行耗时
} finally {
// 释放锁
lock.unlock();
}
mysql的锁有几种
1. 行级锁 (Row-Level Locks)
行级锁是最细粒度的锁,它只锁定被操作的具体行。这种锁可以进一步分为共享锁(S锁)和排他锁(X锁)。
- 共享锁 (S Lock):
- 共享锁允许多个事务读取同一行数据,但不允许写入。
- 当一个事务请求对某一行数据加共享锁时,其他事务可以同时对该行数据加共享锁,但不能加排他锁。
- 适用于 SELECT 语句。
- 排他锁 (X Lock):
- 排他锁阻止其他事务对加锁的行进行任何读写操作。
- 当一个事务请求对某一行数据加排他锁时,其他事务既不能对该行数据加共享锁也不能加排他锁。
- 适用于 INSERT、UPDATE、DELETE 语句。
2. 表级锁 (Table-Level Locks)
表级锁是一种较粗粒度的锁,它锁定整个表。这种锁在早期的 MySQL 存储引擎中较为常见,如 MyISAM。
- 读锁 (Read Lock):
- 读锁允许其他事务读取表中的数据,但不允许写入。
- 适用于 SELECT 语句。
- 写锁 (Write Lock):
- 写锁阻止其他事务对表进行任何读写操作。
- 适用于 INSERT、UPDATE、DELETE 语句。
3. 页级锁 (Page-Level Locks)
页级锁是一种介于行级锁和表级锁之间的锁,它锁定的是表的一部分,而不是整个表。InnoDB 存储引擎使用页级锁。
4. 意向锁 (Intention Locks)
意向锁不是直接由用户设置的,而是由 InnoDB 存储引擎在需要时自动添加的。意向锁分为意向共享锁(IS)和意向排他锁(IX)。
- 意向共享锁 (IS Lock):
- 表示事务想要获得表上所有行的共享锁。
- 意向排他锁 (IX Lock):
- 表示事务想要获得表上所有行的排他锁。
5. 其他锁类型
除了上述常见的锁类型外,还有一些特殊的锁:
- 间隙锁 (Gap Lock):
- 间隙锁锁定的是索引项之间的“间隙”,它是为了防止其他事务插入行到锁定行之间。
- 仅在 InnoDB 存储引擎中使用。
- 记录锁 (Record Lock):
- 记录锁是对索引项上的记录进行锁定。
- 插入意向锁 (Insert Intention Lock):
- 插入意向锁是一种特殊的排他锁,用于在插入新行前锁定相邻记录的间隙。
Mysql的事务隔离级别
-
读未提交(RU):
可以读取到其他事物未提交的数据,可能会出现幻读,不可重复读,脏读的情况
-
读已提交(RC):
读取其他事物已经提交的数据。可能会出现幻读和不可重复读的情况。(在每次读取同一行数据的时候都会生成新的快照读)
-
可重复读(RR):
读取的数据都是已经提交的。解决了不可重复读的问题(在一个事务中 出现多次读取一行数据的情况,只会在第一次生成快照,意思是在这个事务中,不管读取几次这行数据 读取的都是undo log中的同一个版本的数据),但是会出现幻读
-
串行化(S):
事务串行执行,不会出现幻读、脏读、不可重复读,是最高的隔离级别,但同时也是并发性能最低的一个隔离级别。
MVCC是什么?
MVCC 是一种用于支持读取操作与事务并发的技术,它通过保留多个版本的数据来避免读取操作与未提交的更新操作之间产生冲突。这种机制使得读取操作不会阻塞写入操作,从而提高了系统的并发性。
MVCC在MySQL中的实现
MySQL 的 InnoDB 存储引擎实现了 MVCC,它利用了行记录中的隐藏字段来管理不同版本的数据。
主要实现是依靠mysql的undo log日志,他会在undo log中保存一些旧版本的数据,通过mysql的隐藏列的指针,形成一个数据链,其中事务未提交的数据也会保存在其中。当有事务执行读取操作的时候,首先会生成一个快照,快照里面有三个数据 一个是当前我这个执行查询操作的事务ID 另外一个是当前已经开始但是未提交的最小事务ID 还有一个是未开始的事务ID也就是最大事务ID 然后通过这个快照信息去判断 到底应该选择的是那个版本的数据,(简而言之就是选择最近一次提交的数据 )
读已提交和可重复读中使用MVCC是不同的
读已提交
在一个事务中 如果我连续两次 读取同一行的操作 他每次都会去生成一个快照
可重复读
只有第一次会生成快照 第二次直接使用第一次生成的快照
MVCC不能解决幻读的问题
mysql的索引类型
-
单列索引 (Single Column Index)
-
定义:
- 单列索引是针对表中单个列建立的索引。
- 这是最常见的索引类型。
-
示例:
CREATE INDEX idx_column_name ON table_name(column_name);
2**. 复合索引 (Composite Index / Multiple Column Index)**
-
定义:
- 复合索引是在表的多个列上建立的索引。
- 可以提高涉及多个列的查询的性能。
-
示例:
CREATE INDEX idx_columns ON table_name(column1, column2, column3);
3. 唯一索引 (Unique Index)
-
定义:
- 唯一索引用于确保表中某列或某几列的值是唯一的。
- 可以用作主键索引的替代品。
-
示例:
CREATE UNIQUE INDEX idx_unique ON table_name(column_name);
- 主键索引 (Primary Key Index)
-
定义:
- 主键索引是一种唯一索引,用于标识表中的每一行。
- 每个表只能有一个主键。
-
示例:
ALTER TABLE table_name ADD PRIMARY KEY(column_name);
5. 全文索引 (Full-text Index)
-
定义:
- 全文索引用于全文搜索,可以快速查找包含特定单词或短语的行。
- 适用于包含大量文本的列。
-
示例:
CREATE FULLTEXT INDEX idx_fulltext ON table_name(column_name);
6. 空间索引 (Spatial Index / R-tree Index)
-
定义:
- 空间索引用于地理空间数据,可以快速查找位于特定地理区域内的对象。
- 使用 R-tree 索引来组织空间数据。
-
示例:
CREATE SPATIAL INDEX idx_spatial ON table_name(column_name);
7. 聚簇索引 (Clustered Index)
-
定义:
- 聚簇索引决定了表中行的物理存储顺序。
- InnoDB 存储引擎使用主键作为聚簇索引,如果没有定义主键,则使用一个隐藏的聚簇索引来存储行。
-
示例:
CREATE TABLE table_name ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) ) ENGINE=InnoDB;
8. 函数索引 (Function-based Index)
-
定义:
- 函数索引是在表达式或函数计算结果的基础上建立的索引。
- 可以用于优化涉及表达式的查询。
-
示例:
CREATE INDEX idx_function ON table_name(LENGTH(column_name));
- 虚拟列索引 (Virtual Column Index)
-
定义:
- 虚拟列索引是在虚拟列上建立的索引。
- 虚拟列是基于其他列计算得出的列,不会实际存储在表中。
-
示例:
CREATE TABLE table_name ( col1 INT, col2 INT AS (col1 * 2), INDEX idx_virtual(col2) ) ENGINE=InnoDB;
10. 隐式索引 (Implicit Index)
- 定义
- 隐式索引是 MySQL 自动为某些情况创建的索引,如主键索引。
- 无需手动创建。
11. 显式索引 (Explicit Index)
- 定义
- 显式索引是用户手动创建的索引。
- 用于提高查询性能。
-
mysql的索引结构
InnoDB引擎中,使用的B+树,在myISAM引擎中使用的是B树。
1. InnoDB 存储引擎的索引结构
InnoDB 存储引擎使用 B+ 树作为其索引结构的基础。B+ 树是一种自平衡树,它能够保证在索引中查找数据的时间复杂度为 O(log n)。以下是 InnoDB 中索引的一些关键特性:
B+ 树索引
- 叶子节点:
- 包含实际的数据行。
- 叶子节点之间通过双向链表相互连接。
- 叶子节点按关键字排序。
- 非叶子节点:
- 存储索引关键字和指向子节点的指针。
- 非叶子节点用于加速查找过程。
- 聚簇索引 (Clustered Index):
- InnoDB 使用主键作为聚簇索引。
- 如果没有定义主键,则使用一个隐藏的聚簇索引来存储行。
- 聚簇索引决定了表中行的物理存储顺序。
- 二级索引 (Secondary Index):
- 除了聚簇索引之外的所有索引都是二级索引。
- 二级索引包含一个索引键和一个指向前述聚簇索引中的行的指针。
- 二级索引可以是单列索引或多列索引。
2. MyISAM 存储引擎的索引结构
MyISAM 存储引擎也使用 B+ 树作为索引结构的基础。MyISAM 的索引结构与 InnoDB 类似,但有一些不同之处:
B+ 树索引
- 叶子节点:
- 包含实际的数据行。
- 叶子节点之间通过双向链表相互连接。
- 叶子节点按关键字排序。
- 非叶子节点:
- 存储索引关键字和指向子节点的指针。
- 非叶子节点用于加速查找过程。
- 非聚簇索引:
- MyISAM 使用非聚簇索引。
- 索引文件与数据文件分开存储。
- 索引包含一个索引键和一个指向数据文件中行的偏移量。
索引结构的示例
InnoDB 示例
假设我们有一个名为 employees
的表,包含以下字段:
- id: 主键,整数类型。
- first_name: 名字,字符串类型。
- last_name: 姓氏,字符串类型。
- email: 邮箱,字符串类型。
聚簇索引
- 聚簇索引:
- 使用
id
作为聚簇索引。 - 行数据按照
id
的值进行排序。
- 使用
二级索引
- 二级索引:
- 创建一个基于
first_name
的二级索引。 - 二级索引包含
first_name
的值和指向聚簇索引中相应行的指针。
- 创建一个基于
MyISAM 示例
假设我们有同样的 employees
表。
索引
- 索引:
- 创建一个基于
first_name
的索引。 - 索引包含
first_name
的值和指向数据文件中行的偏移量。
- 创建一个基于
总结
- InnoDB:
- 使用聚簇索引和二级索引。
- 聚簇索引决定了行的物理存储顺序。
- 二级索引包含指向聚簇索引中行的指针。
- MyISAM:
- 使用非聚簇索引。
- 索引文件与数据文件分开存储。
- 索引包含指向数据文件中行的偏移量。
mysql索引失效的场景
MySQL 中索引失效的场景是指在查询过程中,MySQL 优化器决定不使用索引,转而使用全表扫描或其他方式来执行查询的情况。了解索引失效的原因可以帮助你更好地设计和优化查询。下面列举了一些常见的索引失效场景:
1. 使用非等值比较操作符
-
原因:
- 当使用
>
、<
、>=
、<=
、!=
、<>
等非等值比较操作符时,索引可能无法被充分利用。 - 这些操作符通常会导致全表扫描。
- 当使用
-
示例:
SELECT * FROM table_name WHERE column_name > 10;
2. 使用 LIKE
操作符匹配前缀模糊查询
-
原因:
- 当使用
LIKE
操作符进行前缀模糊查询(如%abc%
或abc%
)时,索引可能无法被充分利用。 - 前缀模糊查询会导致全表扫描。
- 当使用
-
示例:
SELECT * FROM table_name WHERE column_name LIKE '%abc%';
3. 使用函数或表达式
-
原因:
- 当在 WHERE 子句中使用函数或表达式时,索引可能无法被充分利用。
- MySQL 优化器可能无法有效地使用索引。
-
示例:
SELECT * FROM table_name WHERE LENGTH(column_name) > 5;
4. 使用 OR
操作符连接非等值条件
-
原因:
- 当使用
OR
操作符连接非等值条件时,索引可能无法被充分利用。 OR
操作符可能会导致全表扫描。
- 当使用
-
示例:
SELECT * FROM table_name WHERE column_name1 = 'value1' OR column_name2 > 10;
5. 使用 NOT IN
或 NOT EXISTS
-
原因:
- 使用
NOT IN
或NOT EXISTS
时,索引可能无法被充分利用。 - 这些操作符可能会导致性能下降。
- 使用
-
示例:
SELECT * FROM table_name WHERE column_name NOT IN (select column_name from other_table);
6. 使用 IN
操作符与非常量列表
-
原因:
- 当使用
IN
操作符与非常量列表(如子查询结果)时,索引可能无法被充分利用。 - 这种情况下,MySQL 可能会选择全表扫描。
- 当使用
-
示例:
SELECT * FROM table_name WHERE column_name IN (select column_name from other_table);
7. 使用 ORDER BY
或 GROUP BY
与非索引列
-
原因:
- 当使用
ORDER BY
或GROUP BY
与非索引列时,索引可能无法被充分利用。 - 这种情况下,MySQL 可能会选择全表扫描。
- 当使用
-
示例:
SELECT * FROM table_name ORDER BY non_index_column;
8. 使用 LIMIT
与非索引列
-
原因:
- 当使用
LIMIT
与非索引列时,索引可能无法被充分利用。 - 这种情况下,MySQL 可能会选择全表扫描。
- 当使用
-
示例:
SELECT * FROM table_name WHERE non_index_column = 'value' LIMIT 10;
9. 使用 JOIN
操作符与非等值条件
-
原因:
- 当使用
JOIN
操作符与非等值条件时,索引可能无法被充分利用。 - 这种情况下,MySQL 可能会选择全表扫描。
- 当使用
-
示例:
SELECT * FROM table1 JOIN table2 ON table1.column_name > table2.column_name;
10. 使用 BETWEEN
操作符与非等值条件
-
原因:
- 当使用
BETWEEN
操作符与非等值条件时,索引可能无法被充分利用。 - 这种情况下,MySQL 可能会选择全表扫描。
- 当使用
-
示例:
SELECT * FROM table_name WHERE column_name BETWEEN 1 AND 100;
EXPLAIN 的使用
EXPLAIN
是 MySQL 中的一个非常有用的命令,用于分析查询的执行计划。它可以帮助你理解 MySQL 如何执行查询,包括使用了哪些索引、表的扫描方式等。通过 EXPLAIN
,你可以诊断查询性能问题并优化查询。
EXPLAIN 的语法
EXPLAIN
命令的基本语法如下:
EXPLAIN [extended] [verbose] SELECT 查询语句;
extended
:- 使用扩展输出格式显示更多的信息。
- 例如,可以显示索引是否被使用。
verbose
:- 显示更详细的输出,包括分区信息等。
EXPLAIN 输出的列
EXPLAIN
命令的输出通常包含以下列:
id
:- 查询块的标识符。
- 数字越小优先级越高,数字相同则顺序执行。
select_type
:- 查询块的类型。
- 例如,
SIMPLE
(简单查询)、PRIMARY
(主查询)、DEPENDENT SUBQUERY
(依赖子查询)等。
table
:- 表名。
type
:- 表的访问类型。
- 例如,
ALL
(全表扫描)、index
(索引扫描)、range
(范围扫描)、ref
(使用索引列)、eq_ref
(使用索引列并且是等值比较)等。
possible_keys
:- 可能使用的索引列表。
key
:- 实际使用的索引名称。
key_len
:- 使用的索引的长度。
ref
:- 使用的索引列或常量。
rows
:- MySQL 估计需要检查的行数。
Extra
:- 额外的信息。
- 例如,
Using where
(使用了WHERE条件)、Using index
(使用了索引)、Using temporary
(使用了临时表)等。
示例
假设我们有一个名为 employees
的表,包含以下字段:
- id: 主键,整数类型。
- first_name: 名字,字符串类型。
- last_name: 姓氏,字符串类型。
- email: 邮箱,字符串类型。
我们想查看以下查询的执行计划:
1SELECT * FROM employees WHERE first_name = 'John';
使用 EXPLAIN
EXPLAIN SELECT * FROM employees WHERE first_name = 'John';
输出解释
以下是可能的 EXPLAIN
输出:
+----+-------------+----------+--------+-------------------+---------+---------+-------+------+---------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+----------+--------+-------------------+---------+---------+-------+------+---------------------------------+
| 1 | SIMPLE | employees| range | first_name | first_name| 100 | const | 1 | Using where; Using index |
+----+-------------+----------+--------+-------------------+---------+---------+-------+------+---------------------------------+
输出解释
id
: 1,表示这是一个单一的查询块。select_type
:SIMPLE
,表示这是一个简单的查询。table
:employees
,表示查询的表。type
:range
,表示使用了范围扫描。possible_keys
:first_name
,表示可能使用的索引。key
:first_name
,表示实际使用的索引。key_len
: 100,表示使用的索引长度(这里假设first_name
的索引长度为 100 字节)。ref
:const
,表示使用了常量作为索引查找的条件。rows
: 1,表示 MySQL 估计需要检查的行数为 1。Extra
:Using where
(使用了WHERE条件);Using index
(使用了索引)。
总结
通过使用 EXPLAIN
,你可以了解 MySQL 如何执行查询,并据此优化查询性能。如果你还有其他问题或需要进一步的解释,请随时告诉我!### 分布式的共识算法
分布式系统的共识算法是指在分布式环境中多个节点就某些信息达成一致的算法。这些算法在分布式系统中至关重要,尤其是在容错和保证一致性方面。
Paxos 算法
Paxos 算法是一种经典的分布式共识算法,由 Leslie Lamport 在 1990 年代提出。它旨在解决分布式系统中多个节点就某些信息达成一致的问题,尤其是在存在网络分区或节点故障的情况下。Paxos 算法特别强调强一致性,即所有参与的节点最终都会同意同一个值。
Paxos 算法包括两个主要的角色:Proposer 和 Acceptor。此外,还有一种角色叫做 Learner,用于监听并记录共识的结果。以下是 Paxos 算法的基本步骤:
- Prepare 阶段:
- Proposer 选择一个提案编号 n,并向一组 Acceptor 发送一个 Prepare(n) 消息。
- Acceptor 收到 Prepare(n) 消息后,如果它尚未对编号大于等于 n 的提案做出承诺,它会回复一个 Promise 消息,其中包含它先前承诺过的最大编号的提案值(如果有的话)。
- Proposer 收集大多数 Acceptor 的 Promise 消息。
- Accept 阶段:
- Proposer 选择一个值 v(通常是它收到的 Promise 消息中最大的提案值,如果没有则可以自由选择),并发送一个 Accept(n, v) 消息给所有 Acceptor。
- Acceptor 收到 Accept(n, v) 消息后,如果它已经回复了 Prepare(n) 的 Promise 消息,它就会接受该提案并记录提案值 v。
- Proposer 收集大多数 Acceptor 的 Accept 消息。
- Learn 阶段:
- Learner 收集大多数 Acceptor 的 Accept 消息后,它会学习到提案值 v,并将其传播给所有参与者。
Raft 算法是一种分布式共识算法,由 Diego Ongaro 和 John Ousterhout 在 2014 年提出。它旨在简化分布式系统中多个节点就某些信息达成一致的过程,并且相比 Paxos 算法更加直观易懂。Raft 算法特别强调通过选举领导者来简化共识过程,并且易于理解和实现。
Raft 算法的基本原理
Raft 算法的核心在于通过选举领导者来简化共识过程。它包括三种节点状态:Follower、Candidate 和 Leader。以下是 Raft 算法的基本步骤:
- Nacos中选举领导者:
- 当前领导者挂掉了 某个Follower 由于长时间没有收到当前领导者的心跳 所以将自己的状态改为Candidata 然后更换自己的任期编号后 向集群中的所有节点发送投票请求 并给自己投一票。
- 集群中的Follower 节点在收到投票请求后,会判断自己的任期编号和发起请求的Candidata 节点的任期编号,如果比发起请求的节点的任期编号小并且自己在当前任期并未给其他节点投票的话,那么当前节点会投票给发起请求的节点,并返回给发起请求的Candidata 节点
- 如果 Candidate 收集到大多数节点的投票,它将成为 Leader。
- Nacos中日志复制:
- 当 Leader 接收到服务的注册或发现请求时,它会将此操作作为一条日志条目写入自己的日志。
- Leader 然后向集群中的所有 Follower 节点发送这条日志条目的复制请求。
- Follower 节点接收到日志条目后,会检查其任期编号是否与自己的任期编号匹配。如果匹配,则将日志条目写入自己的日志,并回复确认消息。
- 当 Leader 收到多数 Follower 返回的确认消息后,它会认为这条日志条目已经被复制成功。
- Leader 这时才会应用这条日志条目,执行相应的服务注册或发现操作,并更新自己的状态机。
- Leader 会继续向集群中的所有节点发送心跳消息,以维持自己的领导地位。
- Nacos中容错机制:
- 当 Leader 长时间没有收到某个 Follower 的心跳消息时,它会认为该 Follower 已经失效。
- Leader 会尝试联系该 Follower,看看能否恢复它的连接。如果恢复成功, Leader 节点会将最新的日志同步给恢复成功的节点,使其能够重新加入集群 。
- 如果多次尝试都无法恢复与该 Follower 的连接,那么 Leader 会认为该 Follower 长久失效,并将其标记为不可用。
- 在某些情况下,如集群规模较小或配置文件允许的情况下,Leader 可能会尝试自动替换掉失效的 Follower,以便保持集群的可用性。
- 如果集群中有足够多的健康节点,那么系统仍然可以正常运行;否则,可能会出现性能下降或功能受限的情况。
Nacos中使用了Raft算法来保证服务注册与发现的一致性。
ZAB 算法
ZAB 算法是 专为 ZooKeeper 设计的一种分布式共识协议
ZAB的选举机制的大致过程:
- 初始化: 当 ZooKeeper 集群启动时,每个节点都会初始化自己的轮次号为 0。
- 选举开始:
- 当前领导者失败或无法响应客户端请求。
- 所有节点检测到领导者失败,并进入
LOOKING
状态。并将自己的轮次号增加1。
- 发送选举请求:
- 每个处于
LOOKING
状态的节点都会向集群中的所有节点发送一个选举请求消息。 - 消息包含自己的节点ID和轮次号。
- 节点在发送选举请求时,会将自己的轮次号加 1,例如从 0 加到 1。
- 每个处于
- 投票:
- 每个节点收到选举请求后,会检查自己的投票记录。
- 如果还没有投票或者认为当前的候选者更好,则投票给这个候选者,并将投票信息发送回候选者。
- 节点在投票时,会比较候选者的轮次号和自己记录的轮次号。如果候选者的轮次号更高,节点会投票给这个候选者;如果轮次号相同,则根据节点ID来决定。(会投票给节点ID更低的节点,节点ID一般是按照节点的加入顺序分配的,越早加入的节点会获得更低的节点ID)
- 成为领导者:
- 当一个候选者获得集群中超过半数节点的投票时,它成为新的领导者。
- 成为领导者后,该节点会将自己的状态设置为
LEADING
,并且将自己的轮次号作为当前的领导者轮次号。
- 通知其他节点:
- 领导者向集群中的所有其他节点发送消息,通知它们自己已成为领导者。
- 其他节点收到消息后,将自己的状态设置为
FOLLOWING
。 - 其他节点也会更新自己的轮次号为领导者轮次号。
redission做分布式锁:
redission的看门狗机制
简化来说就是后台有一个线程定期的去检测锁的过期时间,去持续的续费锁的过期时间。这个机制使得Redission实现的分布式锁是可以自动续期的。
看门狗机制的默认是在锁的有效时间剩余1/3的时候触发。
两种获取锁的方式,第一种是通过trylock()方法,此方法是非阻塞的,如果在指定时间内没有获取到锁,则立即返回。 该方法会返回一个布尔值,指示是否成功获取锁。
trylock代码如下:
public boolean tryLock(long waitTime, TimeUnit unit) throws InterruptedException {
return tryLock(waitTime, -1, unit);
}
1. waitTime:获取锁的最大等待时间(没有传默认为-1)
2. leaseTime:锁自动释放的时间(没有传的话默认-1)
3. unit:时间的单位(等待时间和锁自动释放的时间单位)
一般加锁和释放锁的操作包含在try{}finally{}代码块中执行,防止以为异常退出导致锁没有正常释放的情况。在finally代码块中释放锁之前,还需要判断一下锁是否是被当前线程锁持有,以避免释放不属于当前线程的锁。
另外一种是lock()
lock.lock()
方法试图获取锁,如果没有获取到锁,它将阻塞当前线程,直到成功获取锁为止。这意味着如果锁被其他客户端持有,调用 lock.lock()
的线程将等待锁被释放。
trylock示例代码如下:
lock.lock();
try {
// 执行关键操作
System.out.println("Processing with lock");
// 模拟一些业务逻辑
Thread.sleep(10000); // 模拟业务逻辑执行耗时
} finally {
// 释放锁
lock.unlock();
}
mysql的锁有几种
1. 行级锁 (Row-Level Locks)
行级锁是最细粒度的锁,它只锁定被操作的具体行。这种锁可以进一步分为共享锁(S锁)和排他锁(X锁)。
- 共享锁 (S Lock):
- 共享锁允许多个事务读取同一行数据,但不允许写入。
- 当一个事务请求对某一行数据加共享锁时,其他事务可以同时对该行数据加共享锁,但不能加排他锁。
- 适用于 SELECT 语句。
- 排他锁 (X Lock):
- 排他锁阻止其他事务对加锁的行进行任何读写操作。
- 当一个事务请求对某一行数据加排他锁时,其他事务既不能对该行数据加共享锁也不能加排他锁。
- 适用于 INSERT、UPDATE、DELETE 语句。
2. 表级锁 (Table-Level Locks)
表级锁是一种较粗粒度的锁,它锁定整个表。这种锁在早期的 MySQL 存储引擎中较为常见,如 MyISAM。
- 读锁 (Read Lock):
- 读锁允许其他事务读取表中的数据,但不允许写入。
- 适用于 SELECT 语句。
- 写锁 (Write Lock):
- 写锁阻止其他事务对表进行任何读写操作。
- 适用于 INSERT、UPDATE、DELETE 语句。
3. 页级锁 (Page-Level Locks)
页级锁是一种介于行级锁和表级锁之间的锁,它锁定的是表的一部分,而不是整个表。InnoDB 存储引擎使用页级锁。
4. 意向锁 (Intention Locks)
意向锁不是直接由用户设置的,而是由 InnoDB 存储引擎在需要时自动添加的。意向锁分为意向共享锁(IS)和意向排他锁(IX)。
- 意向共享锁 (IS Lock):
- 表示事务想要获得表上所有行的共享锁。
- 意向排他锁 (IX Lock):
- 表示事务想要获得表上所有行的排他锁。
5. 其他锁类型
除了上述常见的锁类型外,还有一些特殊的锁:
- 间隙锁 (Gap Lock):
- 间隙锁锁定的是索引项之间的“间隙”,它是为了防止其他事务插入行到锁定行之间。
- 仅在 InnoDB 存储引擎中使用。
- 记录锁 (Record Lock):
- 记录锁是对索引项上的记录进行锁定。
- 插入意向锁 (Insert Intention Lock):
- 插入意向锁是一种特殊的排他锁,用于在插入新行前锁定相邻记录的间隙。
Mysql的事务隔离级别
-
读未提交(RU):
可以读取到其他事物未提交的数据,可能会出现幻读,不可重复读,脏读的情况
-
读已提交(RC):
读取其他事物已经提交的数据。可能会出现幻读和不可重复读的情况。(在每次读取同一行数据的时候都会生成新的快照读)
-
可重复读(RR):
读取的数据都是已经提交的。解决了不可重复读的问题(在一个事务中 出现多次读取一行数据的情况,只会在第一次生成快照,意思是在这个事务中,不管读取几次这行数据 读取的都是undo log中的同一个版本的数据),但是会出现幻读
-
串行化(S):
事务串行执行,不会出现幻读、脏读、不可重复读,是最高的隔离级别,但同时也是并发性能最低的一个隔离级别。
MVCC是什么?
MVCC 是一种用于支持读取操作与事务并发的技术,它通过保留多个版本的数据来避免读取操作与未提交的更新操作之间产生冲突。这种机制使得读取操作不会阻塞写入操作,从而提高了系统的并发性。
MVCC在MySQL中的实现
MySQL 的 InnoDB 存储引擎实现了 MVCC,它利用了行记录中的隐藏字段来管理不同版本的数据。
主要实现是依靠mysql的undo log日志,他会在undo log中保存一些旧版本的数据,通过mysql的隐藏列的指针,形成一个数据链,其中事务未提交的数据也会保存在其中。当有事务执行读取操作的时候,首先会生成一个快照,快照里面有三个数据 一个是当前我这个执行查询操作的事务ID 另外一个是当前已经开始但是未提交的最小事务ID 还有一个是未开始的事务ID也就是最大事务ID 然后通过这个快照信息去判断 到底应该选择的是那个版本的数据,(简而言之就是选择最近一次提交的数据 )
读已提交和可重复读中使用MVCC是不同的
读已提交
在一个事务中 如果我连续两次 读取同一行的操作 他每次都会去生成一个快照
可重复读
只有第一次会生成快照 第二次直接使用第一次生成的快照
MVCC不能解决幻读的问题
mysql的索引类型
-
单列索引 (Single Column Index)
-
定义:
- 单列索引是针对表中单个列建立的索引。
- 这是最常见的索引类型。
-
示例:
CREATE INDEX idx_column_name ON table_name(column_name);
2**. 复合索引 (Composite Index / Multiple Column Index)**
-
定义:
- 复合索引是在表的多个列上建立的索引。
- 可以提高涉及多个列的查询的性能。
-
示例:
CREATE INDEX idx_columns ON table_name(column1, column2, column3);
3. 唯一索引 (Unique Index)
-
定义:
- 唯一索引用于确保表中某列或某几列的值是唯一的。
- 可以用作主键索引的替代品。
-
示例:
CREATE UNIQUE INDEX idx_unique ON table_name(column_name);
- 主键索引 (Primary Key Index)
-
定义:
- 主键索引是一种唯一索引,用于标识表中的每一行。
- 每个表只能有一个主键。
-
示例:
ALTER TABLE table_name ADD PRIMARY KEY(column_name);
5. 全文索引 (Full-text Index)
-
定义:
- 全文索引用于全文搜索,可以快速查找包含特定单词或短语的行。
- 适用于包含大量文本的列。
-
示例:
CREATE FULLTEXT INDEX idx_fulltext ON table_name(column_name);
6. 空间索引 (Spatial Index / R-tree Index)
-
定义:
- 空间索引用于地理空间数据,可以快速查找位于特定地理区域内的对象。
- 使用 R-tree 索引来组织空间数据。
-
示例:
CREATE SPATIAL INDEX idx_spatial ON table_name(column_name);
7. 聚簇索引 (Clustered Index)
-
定义:
- 聚簇索引决定了表中行的物理存储顺序。
- InnoDB 存储引擎使用主键作为聚簇索引,如果没有定义主键,则使用一个隐藏的聚簇索引来存储行。
-
示例:
CREATE TABLE table_name ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) ) ENGINE=InnoDB;
8. 函数索引 (Function-based Index)
-
定义:
- 函数索引是在表达式或函数计算结果的基础上建立的索引。
- 可以用于优化涉及表达式的查询。
-
示例:
CREATE INDEX idx_function ON table_name(LENGTH(column_name));
- 虚拟列索引 (Virtual Column Index)
-
定义:
- 虚拟列索引是在虚拟列上建立的索引。
- 虚拟列是基于其他列计算得出的列,不会实际存储在表中。
-
示例:
CREATE TABLE table_name ( col1 INT, col2 INT AS (col1 * 2), INDEX idx_virtual(col2) ) ENGINE=InnoDB;
10. 隐式索引 (Implicit Index)
- 定义
- 隐式索引是 MySQL 自动为某些情况创建的索引,如主键索引。
- 无需手动创建。
11. 显式索引 (Explicit Index)
- 定义
- 显式索引是用户手动创建的索引。
- 用于提高查询性能。
-
mysql的索引结构
InnoDB引擎中,使用的B+树,在myISAM引擎中使用的是B树。
1. InnoDB 存储引擎的索引结构
InnoDB 存储引擎使用 B+ 树作为其索引结构的基础。B+ 树是一种自平衡树,它能够保证在索引中查找数据的时间复杂度为 O(log n)。以下是 InnoDB 中索引的一些关键特性:
B+ 树索引
- 叶子节点:
- 包含实际的数据行。
- 叶子节点之间通过双向链表相互连接。
- 叶子节点按关键字排序。
- 非叶子节点:
- 存储索引关键字和指向子节点的指针。
- 非叶子节点用于加速查找过程。
- 聚簇索引 (Clustered Index):
- InnoDB 使用主键作为聚簇索引。
- 如果没有定义主键,则使用一个隐藏的聚簇索引来存储行。
- 聚簇索引决定了表中行的物理存储顺序。
- 二级索引 (Secondary Index):
- 除了聚簇索引之外的所有索引都是二级索引。
- 二级索引包含一个索引键和一个指向前述聚簇索引中的行的指针。
- 二级索引可以是单列索引或多列索引。
2. MyISAM 存储引擎的索引结构
MyISAM 存储引擎也使用 B+ 树作为索引结构的基础。MyISAM 的索引结构与 InnoDB 类似,但有一些不同之处:
B+ 树索引
- 叶子节点:
- 包含实际的数据行。
- 叶子节点之间通过双向链表相互连接。
- 叶子节点按关键字排序。
- 非叶子节点:
- 存储索引关键字和指向子节点的指针。
- 非叶子节点用于加速查找过程。
- 非聚簇索引:
- MyISAM 使用非聚簇索引。
- 索引文件与数据文件分开存储。
- 索引包含一个索引键和一个指向数据文件中行的偏移量。
索引结构的示例
InnoDB 示例
假设我们有一个名为 employees
的表,包含以下字段:
- id: 主键,整数类型。
- first_name: 名字,字符串类型。
- last_name: 姓氏,字符串类型。
- email: 邮箱,字符串类型。
聚簇索引
- 聚簇索引:
- 使用
id
作为聚簇索引。 - 行数据按照
id
的值进行排序。
- 使用
二级索引
- 二级索引:
- 创建一个基于
first_name
的二级索引。 - 二级索引包含
first_name
的值和指向聚簇索引中相应行的指针。
- 创建一个基于
MyISAM 示例
假设我们有同样的 employees
表。
索引
- 索引:
- 创建一个基于
first_name
的索引。 - 索引包含
first_name
的值和指向数据文件中行的偏移量。
- 创建一个基于
总结
- InnoDB:
- 使用聚簇索引和二级索引。
- 聚簇索引决定了行的物理存储顺序。
- 二级索引包含指向聚簇索引中行的指针。
- MyISAM:
- 使用非聚簇索引。
- 索引文件与数据文件分开存储。
- 索引包含指向数据文件中行的偏移量。
mysql索引失效的场景
MySQL 中索引失效的场景是指在查询过程中,MySQL 优化器决定不使用索引,转而使用全表扫描或其他方式来执行查询的情况。了解索引失效的原因可以帮助你更好地设计和优化查询。下面列举了一些常见的索引失效场景:
1. 使用非等值比较操作符
-
原因:
- 当使用
>
、<
、>=
、<=
、!=
、<>
等非等值比较操作符时,索引可能无法被充分利用。 - 这些操作符通常会导致全表扫描。
- 当使用
-
示例:
SELECT * FROM table_name WHERE column_name > 10;
2. 使用 LIKE
操作符匹配前缀模糊查询
-
原因:
- 当使用
LIKE
操作符进行前缀模糊查询(如%abc%
或abc%
)时,索引可能无法被充分利用。 - 前缀模糊查询会导致全表扫描。
- 当使用
-
示例:
SELECT * FROM table_name WHERE column_name LIKE '%abc%';
3. 使用函数或表达式
-
原因:
- 当在 WHERE 子句中使用函数或表达式时,索引可能无法被充分利用。
- MySQL 优化器可能无法有效地使用索引。
-
示例:
SELECT * FROM table_name WHERE LENGTH(column_name) > 5;
4. 使用 OR
操作符连接非等值条件
-
原因:
- 当使用
OR
操作符连接非等值条件时,索引可能无法被充分利用。 OR
操作符可能会导致全表扫描。
- 当使用
-
示例:
SELECT * FROM table_name WHERE column_name1 = 'value1' OR column_name2 > 10;
5. 使用 NOT IN
或 NOT EXISTS
-
原因:
- 使用
NOT IN
或NOT EXISTS
时,索引可能无法被充分利用。 - 这些操作符可能会导致性能下降。
- 使用
-
示例:
SELECT * FROM table_name WHERE column_name NOT IN (select column_name from other_table);
6. 使用 IN
操作符与非常量列表
-
原因:
- 当使用
IN
操作符与非常量列表(如子查询结果)时,索引可能无法被充分利用。 - 这种情况下,MySQL 可能会选择全表扫描。
- 当使用
-
示例:
SELECT * FROM table_name WHERE column_name IN (select column_name from other_table);
7. 使用 ORDER BY
或 GROUP BY
与非索引列
-
原因:
- 当使用
ORDER BY
或GROUP BY
与非索引列时,索引可能无法被充分利用。 - 这种情况下,MySQL 可能会选择全表扫描。
- 当使用
-
示例:
SELECT * FROM table_name ORDER BY non_index_column;
8. 使用 LIMIT
与非索引列
-
原因:
- 当使用
LIMIT
与非索引列时,索引可能无法被充分利用。 - 这种情况下,MySQL 可能会选择全表扫描。
- 当使用
-
示例:
SELECT * FROM table_name WHERE non_index_column = 'value' LIMIT 10;
9. 使用 JOIN
操作符与非等值条件
-
原因:
- 当使用
JOIN
操作符与非等值条件时,索引可能无法被充分利用。 - 这种情况下,MySQL 可能会选择全表扫描。
- 当使用
-
示例:
SELECT * FROM table1 JOIN table2 ON table1.column_name > table2.column_name;
10. 使用 BETWEEN
操作符与非等值条件
-
原因:
- 当使用
BETWEEN
操作符与非等值条件时,索引可能无法被充分利用。 - 这种情况下,MySQL 可能会选择全表扫描。
- 当使用
-
示例:
SELECT * FROM table_name WHERE column_name BETWEEN 1 AND 100;
EXPLAIN 的使用
EXPLAIN
是 MySQL 中的一个非常有用的命令,用于分析查询的执行计划。它可以帮助你理解 MySQL 如何执行查询,包括使用了哪些索引、表的扫描方式等。通过 EXPLAIN
,你可以诊断查询性能问题并优化查询。
EXPLAIN 的语法
EXPLAIN
命令的基本语法如下:
EXPLAIN [extended] [verbose] SELECT 查询语句;
extended
:- 使用扩展输出格式显示更多的信息。
- 例如,可以显示索引是否被使用。
verbose
:- 显示更详细的输出,包括分区信息等。
EXPLAIN 输出的列
EXPLAIN
命令的输出通常包含以下列:
id
:- 查询块的标识符。
- 数字越小优先级越高,数字相同则顺序执行。
select_type
:- 查询块的类型。
- 例如,
SIMPLE
(简单查询)、PRIMARY
(主查询)、DEPENDENT SUBQUERY
(依赖子查询)等。
table
:- 表名。
type
:- 表的访问类型。
- 例如,
ALL
(全表扫描)、index
(索引扫描)、range
(范围扫描)、ref
(使用索引列)、eq_ref
(使用索引列并且是等值比较)等。
possible_keys
:- 可能使用的索引列表。
key
:- 实际使用的索引名称。
key_len
:- 使用的索引的长度。
ref
:- 使用的索引列或常量。
rows
:- MySQL 估计需要检查的行数。
Extra
:- 额外的信息。
- 例如,
Using where
(使用了WHERE条件)、Using index
(使用了索引)、Using temporary
(使用了临时表)等。
示例
假设我们有一个名为 employees
的表,包含以下字段:
- id: 主键,整数类型。
- first_name: 名字,字符串类型。
- last_name: 姓氏,字符串类型。
- email: 邮箱,字符串类型。
我们想查看以下查询的执行计划:
1SELECT * FROM employees WHERE first_name = 'John';
使用 EXPLAIN
EXPLAIN SELECT * FROM employees WHERE first_name = 'John';
输出解释
以下是可能的 EXPLAIN
输出:
+----+-------------+----------+--------+-------------------+---------+---------+-------+------+---------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+----------+--------+-------------------+---------+---------+-------+------+---------------------------------+
| 1 | SIMPLE | employees| range | first_name | first_name| 100 | const | 1 | Using where; Using index |
+----+-------------+----------+--------+-------------------+---------+---------+-------+------+---------------------------------+
输出解释
id
: 1,表示这是一个单一的查询块。select_type
:SIMPLE
,表示这是一个简单的查询。table
:employees
,表示查询的表。type
:range
,表示使用了范围扫描。possible_keys
:first_name
,表示可能使用的索引。key
:first_name
,表示实际使用的索引。key_len
: 100,表示使用的索引长度(这里假设first_name
的索引长度为 100 字节)。ref
:const
,表示使用了常量作为索引查找的条件。rows
: 1,表示 MySQL 估计需要检查的行数为 1。Extra
:Using where
(使用了WHERE条件);Using index
(使用了索引)。
总结
通过使用 EXPLAIN
,你可以了解 MySQL 如何执行查询,并据此优化查询性能。