无题
Redis 作为一个基于内存的 NoSQL 数据库,在实践中最常作为缓存或是存储使用。除此之外,我们还可以将 Redis 作为一个消息通道,实现生产者发送数据、消息者消费数据的效果,这就有点类似于 Kafka、RabbitMQ 等消息中间件的功能。前面介绍的 List 结构,就可以用来实现简易版本的生产者消费者模式,优缺点在前面第 7 篇《实战应用篇:List 命令详解与实战(下)》中也有描述,小伙伴有遗忘的话,可以进行简单的回顾。
本节要介绍的 Pub/Sub,是一种发布订阅机制,也可以用来实现生产者消费者模式。当然 Redis 的 Pub/Sub 功能比较弱,远远没有那些成熟的消息中间件的功能完善,但是在实际应用中还是有很多应用场景的。
首先,我们先从整体上了解一下 Pub/Sub 的功能,如下图所示,Redis 中可以创建多个 Channel,一个 Channel 可以有多个 Client 订阅,当其他 Client 向 Channel 中发送消息的时候,订阅了该 Channel 的 Client 就能收到消息。
Pub/Sub命令核心实 ...
无题
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] 区间 ...
无题
通过前文的介绍我们知道,Redis 是使用单线程方式执行命令的,Redis 与客户端交互的模式是 Request-Response 模式,也就是:先由客户端发起请求,请求中包含一条 Redis 命令,Redis 执行完这条命令之后,给客户端返回对应的响应。
如果我们使用多条 Redis 命令组合,实现一个较为复杂的流程,在多个客户端同时执行的情况下,就可能会出现并发问题。
举个例子,我们在 Redis 中维护了一个商品的库存个数,现在进行秒杀活动,每个用户限只能下一个订单,每个订单最多可以购买 5 件商品,这里需要业务侧在每次减少库存值时,判断库存值是否已经到达 0 ,如果库存减到 0 了,就给用户返回“库存不足”的提示。如果下单的业务逻辑是先使用 GET 命令获取库存值,然后与订单购买的商品个数进行比较,在库存值大于购买个数的时候,才使用 SET 命令更新库存值的话,就会存在下表的并发问题,例如下表展示的这个并发执行顺序。
时间
Redis 客户端 A
Redis 客户端 B
T1
执行 GET 命令,获得库存量为 100
T2
执行 GET 命令,获得库存量为 ...
无题
上一节,我们详细介绍了 Redis 如何通过 Lua 脚本进行扩展,以及 Redis 底层是如何执行 Lua 脚本的。在 Redis 7 中,引入了 Functions 这种新的扩展方式,之所以引入 Functions 这种扩展方式,因为 Lua 脚本的的一些局限性,例如:
Lua 脚本在发到 Redis 服务端之后,只是暂存在内存中,不会进行持久化。当 Redis 重启或者出现主从切换,Lua 脚本就会丢失,需要客户端重新上传。这样的话,就需要所有的客户端都要保留一份 Lua 脚本,并实现一套上传 Lua 脚本的逻辑。
Lua 脚本进行代码更新的时候,也是同样的逻辑。除了要在 Redis 服务端进行更新,还需要同步全部客户端进行更新,否则,就会出现脚本代码不一致的情况。
另外,Lua 脚本之间是不能相互调用的,这就会造成许多代码重复,从开发和维护角度来说,都不是一件好事。
Functions 基本使用下面我们开始介绍一下 Redis Functions 的基本使用。
Redis Functions 目前只支持 Lua 脚本,所以我们先来定义 check_key()、my_hset ...
无题
小伙伴们,大家好,通过本小册的学习,相信小伙伴们已经对 Redis 的底层原理有非常全面、非常深刻的理解。这些知识非常重要,活学活用、与实战结合、最终服务业务,才是我们花费大力气来学习这些知识的最终目的,这才算真正点亮了 Redis 技能树。
在小册的最后,我就带领小伙伴们一起,来看一个我在实际工作中遇到的问题 —— Redis 热 Key 问题,以及解决这个问题的多种方案。当然,这个案例本身的价值有限,但是解决问题的思路,非常值得总结:
1分析问题本质 -> 如何感知/发现问题 -> 应用基础知识设计多套方案 -> 思考方案优劣势 -> 选择合适的方案
分析问题本质:热 Key 的场景介绍有的小伙伴可能会很疑惑,为什么 Redis 已经是纯内存的存储了,出现了热 Key 还扛不住吗?在解释这个问题之前,我们通过几个例子来说明热 Key 问题的本质。
假设我们有一个电商项目,用 Redis 缓存了商品的信息,然后在双十一大促的时候,会有很多商家各种限时抢购,开启抢购的一瞬间,就会有非常大的流量来查看某件促销产品的信息,流量会大到 Redis 扛不住,也就是我 ...
无题
在前面两节中,我们详细分析了全量同步和部分同步过程中,主库完成了哪些关键操作,以及 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 实例 ...
无题
上一节最后,我们介绍了 Sentinel 中相关的定时任务,其中最核心的逻辑就是检查线上 Redis 实例的状态,这里检查单个 Redis 实例的状态逻辑位于 sentinelHandleRedisInstance() 函数中,其流程如下图所示:
如上图所示,sentinelRedisInstance() 函数的核心逻辑分为监控、状态检查、故障转移三部分:
在监控部分的逻辑中,Sentinel 会通过发送各种命令来了解 Redis 集群的拓扑以及每个 Redis 实例的状态;
状态检查部分是 Sentinel 根据监控部分的结果,判断 Redis 实例是否进入主观下线或是客观下线状态;
最后的故障转移部分,是在发现有主库客观下线的时候,自动选出新的主库,继续对外提供服务的过程。
这一节,我们重点来关注监控部分的核心逻辑。
连接状态检查根据上面展示的 sentinelHandleRedisInstance() 函数流程图,首先会检查当前 Sentinel 与其他 Redis 实例之间的连接是否正常,如果连接不正常,则需要重新建连。
前面介绍 sentinelRedisInstanc ...
无题
在上一讲中,我们详细介绍了 Sentinel 监控 Redis 主从集群的核心思想,也详细分析了 Sentinel 判定 Redis 服务主观下线和客观下线状态的逻辑。那在 Sentinel 感知到 Master 节点发生客观下线之后,会做什么呢?
我可以先告诉你答案,Sentinel 会选择一个 Slave 节点提升为新 Master 节点,并对外继续提供服务,之后,其他 Slave 节点会与新提升的 Master 节点进行主从复制,这个过程就是我们所说的 “故障转移(failover)” 。
Redis 之所以引入 Sentinel,其最主要的目的就是实现“自动故障转移”,也就是说,无需人为干预,Sentinel 自动完成整个“故障转移(failover)”的流程,这就可以减少运维成本,提升了整个 Redis 服务的可用性。
failover 状态机在开始介绍 failover 操作之前,我们先来关注一下 Master 节点对应的 sentinelRedisInstance.failover_state 字段,它是用来控制 failover 执行流程的核心状态字段,整个 Senti ...
无题
在上一节中,我们阐述了常见的 Redis 分布式存储方案,了解了 Redis Cluster 的基本概念以及核心结构体的定义。在上一节最后,我们还分析了一个 Redis Cluster 节点启动时的关键流程,其中展开介绍了 nodes.conf 配置文件的加载和格式。
这一节,我们继续分析 Redis Cluster 初始化的另一个核心逻辑 —— 握手流程。
CLUSTER MEET 命令使用过 Redis Cluster 的小伙伴都知道,我们可以通过 CLUSTER NODES 命令查询节点能感知到的整个Cluster 的信息,该命令的返回与前文介绍的 nodes.conf 文件中的格式类似。
在 Redis Cluster 节点第一次启动的时候,它只能感知到自身的存在,我们可以手动执行 CLUSTER MEET 命令让当前节点感知到指定目标节点:
1CLUSTER MEET <ip> <port> [<cport>]
当 Redis 收到 CLUSTER MEET 命令之后,会调用 clusterStartHandshake() 函数创建目标节 ...
