|
5 | 5 | - [Redis 有什么数据类型?分别用于什么场景?](#redis-有什么数据类型分别用于什么场景)
|
6 | 6 | - [Redis 的主从复制是如何实现的?](#redis-的主从复制是如何实现的)
|
7 | 7 | - [Redis 的 key 是如何寻址的?](#redis-的-key-是如何寻址的)
|
| 8 | + - [背景](#背景) |
| 9 | + - [寻址 key 的步骤](#寻址-key-的步骤) |
8 | 10 | - [Redis 的集群模式是如何实现的?](#redis-的集群模式是如何实现的)
|
| 11 | + - [Redis Cluster 节点分配](#redis-cluster-节点分配) |
| 12 | + - [Redis Cluster 主从模式](#redis-cluster-主从模式) |
| 13 | + - [Redis Sentinel](#redis-sentinel) |
9 | 14 | - [Redis 如何实现分布式锁?ZooKeeper 如何实现分布式锁?比较二者优劣?](#redis-如何实现分布式锁zookeeper-如何实现分布式锁比较二者优劣)
|
| 15 | + - [数据库实现](#数据库实现) |
| 16 | + - [Redis 实现](#redis-实现) |
| 17 | + - [ZooKeeper 实现](#zookeeper-实现) |
| 18 | + - [实现对比](#实现对比) |
10 | 19 | - [Redis 的持久化方式?有什么优缺点?持久化实现原理?](#redis-的持久化方式有什么优缺点持久化实现原理)
|
11 | 20 | - [RDB 快照(snapshot)](#rdb-快照snapshot)
|
12 | 21 | - [AOF](#aof)
|
|
33 | 42 |
|
34 | 43 | ## Redis 的主从复制是如何实现的?
|
35 | 44 |
|
| 45 | +1. 从服务器连接主服务器,发送 SYNC 命令; |
| 46 | +2. 主服务器接收到 SYNC 命名后,开始执行 BGSAVE 命令生成 RDB 文件并使用缓冲区记录此后执行的所有写命令; |
| 47 | +3. 主服务器 BGSAVE 执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令; |
| 48 | +4. 从服务器收到快照文件后丢弃所有旧数据,载入收到的快照; |
| 49 | +5. 主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令; |
| 50 | +6. 从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令; |
| 51 | + |
36 | 52 | ## Redis 的 key 是如何寻址的?
|
37 | 53 |
|
| 54 | +### 背景 |
| 55 | + |
| 56 | +(1)redis 中的每一个数据库,都由一个 redisDb 的结构存储。其中: |
| 57 | + |
| 58 | +- redisDb.id 存储着 redis 数据库以整数表示的号码。 |
| 59 | +- redisDb.dict 存储着该库所有的键值对数据。 |
| 60 | +- redisDb.expires 保存着每一个键的过期时间。 |
| 61 | + |
| 62 | +(2)当 redis 服务器初始化时,会预先分配 16 个数据库(该数量可以通过配置文件配置),所有数据库保存到结构 redisServer 的一个成员 redisServer.db 数组中。当我们选择数据库 select number 时,程序直接通过 redisServer.db[number] 来切换数据库。有时候当程序需要知道自己是在哪个数据库时,直接读取 redisDb.id 即可。 |
| 63 | + |
| 64 | +(3)redis 的字典使用哈希表作为其底层实现。dict 类型使用的两个指向哈希表的指针,其中 0 号哈希表(ht[0])主要用于存储数据库的所有键值,而 1 号哈希表主要用于程序对 0 号哈希表进行 rehash 时使用,rehash 一般是在添加新值时会触发,这里不做过多的赘述。所以 redis 中查找一个 key,其实就是对进行该 dict 结构中的 ht[0] 进行查找操作。 |
| 65 | + |
| 66 | +(4)既然是哈希,那么我们知道就会有哈希碰撞,那么当多个键哈希之后为同一个值怎么办呢?redis 采取链表的方式来存储多个哈希碰撞的键。也就是说,当根据 key 的哈希值找到该列表后,如果列表的长度大于 1,那么我们需要遍历该链表来找到我们所查找的 key。当然,一般情况下链表长度都为是 1,所以时间复杂度可看作 o(1)。 |
| 67 | + |
| 68 | +### 寻址 key 的步骤 |
| 69 | + |
| 70 | +1. 当拿到一个 key 后,redis 先判断当前库的 0 号哈希表是否为空,即:if (dict->ht[0].size == 0)。如果为 true 直接返回 NULL。 |
| 71 | +2. 判断该 0 号哈希表是否需要 rehash,因为如果在进行 rehash,那么两个表中者有可能存储该 key。如果正在进行 rehash,将调用一次\_dictRehashStep 方法,\_dictRehashStep 用于对数据库字典、以及哈希键的字典进行被动 rehash,这里不作赘述。 |
| 72 | +3. 计算哈希表,根据当前字典与 key 进行哈希值的计算。 |
| 73 | +4. 根据哈希值与当前字典计算哈希表的索引值。 |
| 74 | +5. 根据索引值在哈希表中取出链表,遍历该链表找到 key 的位置。一般情况,该链表长度为 1。 |
| 75 | +6. 当 ht[0] 查找完了之后,再进行了次 rehash 判断,如果未在 rehashing,则直接结束,否则对 ht[1]重复 345 步骤。 |
| 76 | + |
38 | 77 | ## Redis 的集群模式是如何实现的?
|
39 | 78 |
|
| 79 | +Redis Cluster 是 Redis 的分布式解决方案,在 Redis 3.0 版本正式推出的。 |
| 80 | + |
| 81 | +Redis Cluster 去中心化,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。 |
| 82 | + |
| 83 | +### Redis Cluster 节点分配 |
| 84 | + |
| 85 | +Redis Cluster 特点: |
| 86 | + |
| 87 | +1. 所有的 redis 节点彼此互联(PING-PONG 机制),内部使用二进制协议优化传输速度和带宽。 |
| 88 | +2. 节点的 fail 是通过集群中超过半数的节点检测失效时才生效。 |
| 89 | +3. 客户端与 redis 节点直连,不需要中间 proxy 层。客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。 |
| 90 | +4. redis-cluster 把所有的物理节点映射到[0-16383] 哈希槽 (hash slot)上(不一定是平均分配),cluster 负责维护 node<->slot<->value。 |
| 91 | +5. Redis 集群预分好 16384 个桶,当需要在 Redis 集群中放置一个 key-value 时,根据 CRC16(key) mod 16384 的值,决定将一个 key 放到哪个桶中。 |
| 92 | + |
| 93 | +### Redis Cluster 主从模式 |
| 94 | + |
| 95 | +Redis Cluster 为了保证数据的高可用性,加入了主从模式。 |
| 96 | + |
| 97 | +一个主节点对应一个或多个从节点,主节点提供数据存取,从节点则是从主节点拉取数据备份。当这个主节点挂掉后,就会有这个从节点选取一个来充当主节点,从而保证集群不会挂掉。所以,在集群建立的时候,一定要为每个主节点都添加了从节点。 |
| 98 | + |
| 99 | +### Redis Sentinel |
| 100 | + |
| 101 | +Redis Sentinel 用于管理多个 Redis 服务器,它有三个功能: |
| 102 | + |
| 103 | +- **监控(Monitoring)** - Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。 |
| 104 | +- **提醒(Notification)** - 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。 |
| 105 | +- **自动故障迁移(Automatic failover)** - 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会将失效主服务器的其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器; 当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器。 |
| 106 | + |
| 107 | +Redis 集群中应该有奇数个节点,所以至少有三个节点。 |
| 108 | + |
| 109 | +哨兵监控集群中的主服务器出现故障时,需要根据 quorum 选举出一个哨兵来执行故障转移。选举需要 majority,即大多数哨兵是运行的(2 个哨兵的 majority=2,3 个哨兵的 majority=2,5 个哨兵的 majority=3,4 个哨兵的 majority=2)。 |
| 110 | + |
| 111 | +假设集群仅仅部署 2 个节点 |
| 112 | + |
| 113 | +``` |
| 114 | ++----+ +----+ |
| 115 | +| M1 |---------| R1 | |
| 116 | +| S1 | | S2 | |
| 117 | ++----+ +----+ |
| 118 | +``` |
| 119 | + |
| 120 | +如果 M1 和 S1 所在服务器宕机,则哨兵只有 1 个,无法满足 majority 来进行选举,就不能执行故障转移。 |
| 121 | + |
40 | 122 | ## Redis 如何实现分布式锁?ZooKeeper 如何实现分布式锁?比较二者优劣?
|
41 | 123 |
|
| 124 | +分布式锁的三种实现: |
| 125 | + |
| 126 | +- 基于数据库实现分布式锁; |
| 127 | +- 基于缓存(Redis 等)实现分布式锁; |
| 128 | +- 基于 Zookeeper 实现分布式锁; |
| 129 | + |
| 130 | +### 数据库实现 |
| 131 | + |
| 132 | +### Redis 实现 |
| 133 | + |
| 134 | +1. 获取锁的时候,使用 setnx 加锁,并使用 expire 命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的 value 值为一个随机生成的 UUID,通过此在释放锁的时候进行判断。 |
| 135 | +2. 获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。 |
| 136 | +3. 释放锁的时候,通过 UUID 判断是不是该锁,若是该锁,则执行 delete 进行锁释放。 |
| 137 | + |
| 138 | +### ZooKeeper 实现 |
| 139 | + |
| 140 | +1. 创建一个目录 mylock; |
| 141 | +2. 线程 A 想获取锁就在 mylock 目录下创建临时顺序节点; |
| 142 | +3. 获取 mylock 目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁; |
| 143 | +4. 线程 B 获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点; |
| 144 | +5. 线程 A 处理完,删除自己的节点,线程 B 监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。 |
| 145 | + |
| 146 | +### 实现对比 |
| 147 | + |
| 148 | +ZooKeeper 具备高可用、可重入、阻塞锁特性,可解决失效死锁问题。 |
| 149 | +但 ZooKeeper 因为需要频繁的创建和删除节点,性能上不如 Redis 方式。 |
| 150 | + |
42 | 151 | ## Redis 的持久化方式?有什么优缺点?持久化实现原理?
|
43 | 152 |
|
44 | 153 | ### RDB 快照(snapshot)
|
|
0 commit comments