无题
GeoHash 是一种坐标编码算法,其目的是将一个经纬度坐标编码成一个 Base 32 字符串。
地球经度范围为东经 180° 到西经 180°,纬度范围是南纬 90° 到北纬 90°。在 GeoHash 算法中,将西经设置为负,东经设置为正,南纬设置为负,北纬设置正,则得到经度范围是 [-180,180],纬度范围是 [-90,90]。每个 GeoHash 都表示地球上的一块区域。例如,我们以赤道和本初子午线为边界,就可以将地球划分为 4 个区域,如下图所示,我们可以为每个区域设置一个二进制编码:
上图只将地球划分为四个区域,没有什么作用,因为粒度实在太大了,实现不了精确的定位和范围搜索功能。我们可以继续对整个地球进行更细致的划分,得到更精准的一个个小区域,如下图所示,然后将这些表示小块区域的二进制进行 Base32 编码得到的字符串,也就是 GeoHash 编码。
下面我们来看计算(40.085138, 116.327313)这个坐标的 GeoHash 的核心流程。
首先,将地区的经度分为 [-180, 0]、[0, 180],116° 位于右侧的 [0, 180] 区间 ...
无题
通过上一节的介绍,我们了解了 AOF 日志从生产到写入缓冲区的过程,也分析了 AOF 日志的格式,这里有两个额外的知识点需要补充说明一下。
第一个点是,当执行的是 EXPIRE 这种设置过期时间的命令,如果 AOF 是把 EXPIRE 指定的过期时间记录下来,在回放的时候,是不是就给 Key 设置了一个错误的过期时间呢?这个问题的答案需要小伙伴们回顾一下第 36 讲《命令解析篇:通用命令与 String 命令实现解析》中介绍的,expireGenericCommand() 函数中的一个细节,在设置完 Key 的过期时间之后,expireGenericCommand() 就已经将 client->argv 中记录的命令和参数进行了改写,它会将命令改成 PEXPIREAT 命令,并将过期时间归一化成毫秒级时间戳。同理, SET…EX|PX 这个带过期时间的复合命令,也会对命令进行改写,也是统一改写 SET…PXAT,过期时间归一化成毫秒级时间戳。
第二个点是,在介绍 Key 过期以及内存淘汰的时候,我们知道这两个功能是会将一部分 Key 删除掉的,这个时候需要产生 DEL 或者 UN ...
无题
通过前文的介绍我们知道,Redis 在开启 AOF 持久化功能之后,会将修改命令写入到磁盘上的 AOF 文件。随着 Redis 运行时间越久,就会有越来越多的命令追加 AOF 文件中,AOF 文件的大小也会不断膨胀,如果之后在某个时间点,要使用 AOF 文件进行恢复,就会读取很多无用的命令,导致耗时较长。
下图举了个例子,Redis 依次收到了 SET Key1 Value1 、SET Key1 Value2 、SET Key1 Value3 、SET Key1 Value4 这四条命令,相应地,在 AOF 文件中就会记录这 4 条命令,如下图最左边这一栏所示。在这 4 条命令的执行过程中,Redis 中 Key1 对应的 Value 值也在不断发生变化,如果下图红色那一栏所示。如果我们在 SET Key1 Value4 这条命令执行完之后,使用这个 AOF 文件进行数据恢复,这里的前三条 SET 命令其实都是无效的,因为执行或不执行这些语句,都不会影响最终的恢复结果。
为了解决这一问题,Redis 会定期对 AOF 进行压缩,这一操作被称为 AOF Rewrite,其核心原理是将 ...
无题
在大流量、高并发的场景中,我们一般不会只有一个单点作为存储,而是把存储做成一个分布式的系统,例如,我们常见的 MySQL 主从结构、Redis 主从结构、Redis Cluster、Memcache 集群或者 TiKV 这种基于 Raft 协议的集群。
Redis 有多种集群搭建方式,比如,主从模式、哨兵模式、Cluster 模式。本模块,我们就重点来介绍一下 Redis 主从模式的相关内容。
在使用 Redis 主从复制模式的时候,一般会搭建多个从库(Slave),从库只支持读请求的处理,用来分摊主库(Master)的读压力,主库(Master)只有一个,只专注于处理写请求,或者同时支持读写,这样就可以实现读写分离,降低主库的读压力,也实现了读请求在各个 Redis 节点之间的负载均衡。这样,我们就得到了 Redis 主从模式的核心架构,如下图所示:
正如前面所说,Redis 主从模式还解决了单点的问题。Redis 主库在进行修改操作的时候,会把相应的写入命令近乎实时地同步给从库,从库回放这些命令,就可以保证自己的数据与主库保持一致。那么,当主库发生宕机的时候,我们就可以将一个从库 ...
无题
介绍完主从复制的核心原理之后,从这节开始,我们将介绍 Redis 主从复制的核心实现。在上一节中提到,Redis 主从结构最开始,是由从库向主库发起建连请求的,所以这里就先以从库视角来看看主从复制的整个流程。
从库建连设置主库地址明确了从库是主从复制的主动发起方之后,我们再来看看从库是如何确认自己要连接哪个主库的,下面有两种设置主库的方式。
一种是从库在配置文件(或是启动参数)中添加了 replicaof 配置或者 slaveof 配置,replicaof 出现在 Redis 5.0 版本中,用于替换 slaveof 配置,slaveof 目前已经被标记为废弃 。在 Redis 从库启动过程中,loadServerConfigFromString() 函数中会解析 redis.conf 文件(以及启动参数)中的 replicaof(或 slaveof)配置,将主库的网络地址记录到 redisServer.masterhost 和 redisServer.masterport 中。
另一种是在从库启动之后,通过客户端向 Redis 服务发送 replicaof(或者 slaveof) ...
无题
通过上一节的分析我们知道,主从建连之后会一系列握手操作,这里面最关键的一步就是从库向主库发送 PSYNC 命令,其中会携带从库当前的 Replication ID 和 Replication Offset。这里紧接上文,继续介绍从库对 PSYNC 响应的处理。
当主库返回 +CONTINUE 响应的时候,表示进行部分同步,从库会直接进入 REPL_STATE_CONNECTED 状态,主从握手的流程也就结束了,后续会进入正常的主从复制流程。
当主库返回 +FULLRESYNC 响应时,从库就要准备与主库进行全量同步了,下面是从库需要做的准备工作。
从库首先会创建一个名为 temp-{秒级时间戳}.{进程ID}.rdb 的临时 RDB 文件,然后将这个文件名称以及对应的文件描述符记录到 redisServer.repl_transfer_tmpfile 字段和 repl_transfer_fd 字段中。
监听主从连接上的可读事件,等待主库发送 RDB 数据,相应的回调为 readSyncBulkPayload() 函数。
最后,从库会将 re ...
无题
在前面的章节中,我们已经详细介绍了 Redis 主从复制的核心原理,以及从库视角下主从复制的核心实现。从本节开始,我将和小伙伴们一起,分析一下主库视角下的主从复制,这样整个主从复制的实现就完整了。
在从库发起建连操作时,主库是无法立刻识别出该建连请求是来自从库的,会将其作为一个普通客户端进行处理,为其创建对应的 client 实例。这里注意 client 中的 replstate 字段,它记录了该从库状态的变更。在下图中,展示了主库与从库进行握手的核心流程,最右侧就是从库对应的 client->replstate 状态的变化流程:
在从库与主库建立连接之后,从库会向主库发送 PING 和 AUTH 命令进行探活和鉴权,此时从库在主库眼中与普通客户端无异,主库会正常地进行鉴权和响应。
接下来,从库会连续发送三条 REPLCONF 命令,将从库的 ip、port 以及从库支持的能力告知主库,在主库侧会根据 REPLCONF 命令的参数更新不同的字段,例如:
主库会将从库端口号记录到 client->slave_listening_port 字段中;
主库会将从库的 ip 记 ...
无题
在前面两节中,我们详细分析了全量同步和部分同步过程中,主库完成了哪些关键操作,以及 Redis 在不同版本中的各项优化。在这一节中,我们继续在主库视角下,分析一下客户端命令是如何从主库传播到从库的。
命令传播无论经过部分同步还是全量同步之后,主从的数据基本上是一致了,但是从库这个时候还是略微落后于主库,从库可以通过同步 backlog 里面的数据,进一步追平主库。这部分实现与主库正常执行一条命令并传播给从库的逻辑基本一致,所以我们将这两部分内容合并到这一节一起介绍。
写入共享缓冲区在前文介绍 AOF 持久化的时候提到,call() 函数不仅会执行客户端发来的命令,还会调用 alsoPropagate() 函数将命令写入 redisOpArray 队列中暂存,然后在 propagateNow() 中去读取 redisOpArray 队列,并写入 AOF 缓冲区,等待后续写入到 AOF 文件中。
如上图所示,propagateNow() 函数中还有另一个分支就是 replicationFeedSlaves() 函数,它是命令发送到从库的入口。
下面我们来看 replicationFeed ...
无题
在前面的章节中,我们已经详细分析了 Redis 主从复制的核心原理,分别用主库视角和从库视角分析了各自在主从复制方面的核心实现。在这个过程中,我们提到 Redis 主从复制的核心目的之一就是提高 Redis 服务的高可用性,主要是在主库出现故障时,我们可以将从库提升为主库,继续对外提供服务。
在绝大多数实际应用中,运维小伙伴希望系统能够在发生故障时,自动完成上述切换主库的操作,这个能力也就是我们常说的“故障转移”(failover)。要实现自动故障转移的能力,除了需要主从复制之外,需要一些额外监控和故障发现机制,其中一种实现方式,就是我们这一章要介绍的 Sentinel(哨兵机制) 。
Sentinel 概述Sentinel 是 Redis 提供的高可用解决方案之一。一个 Sentinel 服务进程其实本身就是 Redis 实例,只不过这个 Redis 服务实例是以 Sentinel 模式运行的,它不对外提供读写键值对的服务,而是监控其他 Redis 服务实例是否运行正常,有点类似现实生活中监工的感觉。
为了防止 Sentinel 本身出现单点问题,一般会将多个 Sentinel 实例 ...
无题
在上一节中,我们已经详细介绍了复制缓冲区的设计演进过程以及主库视角下部分同步的核心。在这一节,我们继续来分析一下主库视角下全量同步实现。
全量同步通过上一节的分析我们知道,如果主从之间能进行部分同步,需要检查 Replication ID、Replication Offset 等一系列条件是否成立,如果部分同步的条件不成立,就会进入全量同步的逻辑。在进行全量同步的时候,主库首先执行与部分同步类似的状态更新操作:
将 client->replstate 状态为 WAIT_BGSAVE_START,表示主库要为全量同步执行一次 RDB 持久化。
向 client->flags 中设置 CLIENT_SLAVE 标记,标识这是一个从库 client 实例。
将 client 添加到 server.slaves 列表中。
如果当前主库之前的 backlog 缓冲区一直为空,那从库必然只能进行全量同步,此时会初始化 replBacklog、在 redisServer.replid 字段中填充新生成的 Replication ID 、清空 Secondary ID 和 Second ...
