高并发架构背后的秘密
从代码连接到企业级集群架构的演进之旅
如何用代码与 Redis 对话
(连接、连接池、数据操作)
解决单机瓶颈的第一步
(老板与助理的协作艺术)
让系统学会自我修复
(永不疲倦的监控保安)
突破内存极限的终极形态
(人多力量大的分工哲学)
Python 编程操作 Redis
让代码成为我们指挥 Redis 数据库的“指挥棒”
在之前的学习中,我们都是在黑漆漆的命令行里手敲命令(比如输入 SET key value)。
但在真实的企业开发中,Redis 是在后台默默服务的,我们必须通过程序代码来自动写入和读取数据。
我们要用 Python 操控 Redis,必须先引入一个概念:驱动(Driver)。
Python 只懂 Python 语法,Redis 只懂 Redis 协议。它们俩就像一个只懂中文,一个只懂英语,没法直接沟通。
驱动程序就是它们之间的翻译官,它负责把 Python 代码翻译成 Redis 能听懂的网络指令。
Python 程序
Redis 服务器
在 Python 的世界里,最官方、最常用的 Redis 驱动库叫做 redis-py。
我们使用 Python 的包管理工具 pip 来进行安装。在企业开发环境中,这通常是部署项目的第一步。
安装好驱动后,第一件事就是“打电话”给 Redis 服务器,建立连接。
在 redis-py 库中,推荐使用 StrictRedis 对象来连接。
因为它完全遵循官方 Redis 命令的语法和参数顺序,让我们写代码就像在命令行敲命令一样自然!
理论结合代码,我们来看看在 Python 中如何写入和读取一个字符串(String)。
提示:代码中的 decode_responses=True 非常关键,它能让返回的数据自动变成 Python 字符串,而不是难懂的字节码(bytes)。
上面那种每次要用就“创建连接”的方式,在学习时没问题,但在生产环境(真实应用中)会引发大灾难!
每次操作都新建连接,就像每次出门办事都要买一辆新自行车,办完事就把车扔了。非常浪费时间(建立连接耗时)和资源(内存)。
连接池(Connection Pool)就像是一个共享单车停放点:
在企业级开发中,我们必须使用连接池来管理 Redis 连接。
List 适合做消息队列、最新评论列表等。
Hash 非常适合存储对象结构,比如用户信息、商品属性。
网络通信随时可能中断(比如网线被老鼠咬断了,或者服务器停电了)。
在编写生产环境代码时,绝对不能假定网络永远通畅,必须加上异常捕获!
通常我们会捕获 redis.exceptions.RedisError(所有Redis错误的父类)。
Redis 主从复制模式
当一台服务器扛不住时,我们要开始找“小弟”了
在刚才的编程中,我们一直连的都是 127.0.0.1 这一台电脑。但真实世界里,只有一台 Redis 会面临三个致命问题:
一台机器的内存是有限的(比如最多128GB)。如果大数据业务有 500GB 的缓存数据,一台机器根本装不下!
虽然 Redis 很快(单机十万次并发),但在双十一大促时,如果有上百万的用户同时疯狂访问,单台机器 CPU 会被瞬间打满。
最可怕的危机!如果这唯一的服务器主板烧了,或者停电了,整个公司的缓存系统直接瘫痪,后果不堪设想。
为了解决单机问题,Redis 提供了主从复制(Master-Slave)机制。
你可以把 Master 节点看作是“老板”,把 Slave 节点看作是“助理”。
主从模式最重要的应用场景就是实现读写分离,极大地提升系统的并发能力。
禁忌:默认情况下,Slave 节点是“只读”的,如果你强行往 Slave 节点写数据,Redis 会直接报错拒绝!
为什么这很有用?
在真实的互联网应用中(比如刷微博、看淘宝),用户看(读)的操作往往是写(发)的操作的 10 倍甚至 100 倍!增加从节点就能轻松扛住巨大的访问量。
既然主节点和从节点分开了,那么主节点刚写入的数据,从节点是怎么知道的呢?
Redis 的同步机制分为两种:全量同步 和 增量同步。
当一个从节点刚刚加入(或断开很久重新连上)时触发。
代价高昂,比较慢。
当从节点完成全量同步后,进入日常工作状态触发。
轻量级,实时性高。
部署 Redis 主从模式非常简单,可以说是一键搞定。主节点不需要做任何配置,默认大家生下来都是主节点。
关键在于从节点,我们只需要告诉它:“去,给 XX 当小弟”。
在从节点的 Redis 配置文件(redis.conf)中,加入一行代码:
例如:replicaof 192.168.1.100 6379。重启从节点后,它就会自动去找主节点认大哥,并开始同步数据。
如果某天晚上,主节点(Master)的服务器突然宕机(死机)了,会发生什么?
结论:主从模式解决了性能瓶颈,但依然存在“单点故障”的隐患!
Redis 哨兵模式 (Sentinel)
给数据库请几位 24 小时巡逻的“智能保安”
为了解决主从模式下主节点挂掉无人接管的问题,Redis 官方引入了 哨兵 (Sentinel) 架构。
哨兵其实也是一个特殊的 Redis 节点,但它不存储业务数据(不存缓存)。
它的唯一任务就是:盯着主从集群看! 就像医院心电监护仪一样,时刻检测主节点的心跳。
Sentinel (哨兵集群)
监控哨兵会不断地检查主节点和从节点是否按预期正常工作。就像查寝的宿管阿姨。
当被监控的某个 Redis 节点出现问题时,哨兵可以通过 API 向管理员或其他应用程序发送通知。
最核心功能! 如果主节点宕机,哨兵会自动选出一个聪明的从节点,把它升职为新的主节点。
客户端(我们的 Python 代码)连接时,先问哨兵:“现在谁是老大?”哨兵会把最新主节点的 IP 告诉代码。
哨兵是怎么知道主节点死没死的呢?靠的是心跳包(PING指令)。
一个哨兵节点每秒钟向主节点发送 PING。如果主节点在设定的时间内心虚没回信(PONG),这个哨兵就会心里想:“哎呀,老板是不是跑路了?”
但这只是它个人的主观判断,万一是它自己的网线松了呢?
为了防止误判,哨兵们会互相拉个群开会(Gossip协议交流)。
当认为老板死掉的哨兵数量达到了法定人数(Quorum),大家就达成共识:“不是我的网络问题,是老板真挂了!”。此时正式宣布主节点客观下线。
当确认主节点彻底死亡后,需要执行故障转移。但这群哨兵里,谁去负责执行这个提拔任务呢?
如果大家都抢着去提拔自己的小弟,系统就乱套了。所以哨兵之间要先选出一个班长(Leader)。
这就是为什么在生产环境中,哨兵节点的数量必须是奇数(例如 3、5、7),以防投票平局死锁!
Leader 哨兵选出来后,它开始主持大局,执行以下三步操作:
从剩下的存活从节点中,挑选一个最优秀的。挑选标准包括:网络最稳的、数据同步最完整的、配置优先级最高的。
哨兵向选中的从节点发送命令 slaveof no one,让它立刻摆脱从属身份,成为新的真正的主节点。
让其他从节点认新的主节点做大哥;同时更新配置,告诉外面的 Python 代码新主节点的 IP;甚至如果老主节点诈尸复活了,也会被强行变成新主节点的小弟。
哨兵的部署需要独立的配置文件 sentinel.conf。
注:法定人数 2 意味着,至少需要 2 个哨兵都同意主节点挂了,才能执行故障转移。如果是 3 台哨兵的集群,设为 2 最合理。
有了哨兵,似乎高可用的问题(自动救火)解决了,但是...
在哨兵模式下,不论你有多少个从节点,真正能进行写操作的,永远只有那 1 台主节点!
同时,每一个节点内部都保存了 100% 的全量数据。如果公司有 1TB 的数据,由于单机内存限制根本装不下,哨兵模式依然束手无策。
Redis Cluster 集群模式
人多力量大,打破单机内存极限的终极武器
面对海量数据(比如微信上亿用户的登录状态),唯一的方法就是分而治之(分布式架构)。
在之前的模式里,不管怎样都有一个最高统治者(唯一的 Master)。
但在 Cluster 模式下,没有绝对的老大,大家都是平等的。有多台机器共同担任 Master 的角色,每个人只负责管理总体数据的一部分。
数据分片 (Sharding) 示意图
总容量 = A + B + C。无限水平扩展!
既然数据被分给了好几个主节点,那当我们想存一个叫做 student:name 的数据时,怎么知道该去哪台机器存呢?
Redis 引入了 哈希槽(Hash Slot) 的概念。
当 Python 客户端向集群发送指令 SET name 张三 时:
Redis 会把你的 key (也就是 'name') 放进一个叫 CRC16 的算法公式里,计算出一个整数结果。
把这个整数对 16384 求余数(% 16384)。得到的结果一定是 0 到 16383 之间的一个数字。这就是这个数据归属的槽位。
系统查看这个槽位当前被哪个主节点管辖,就把这条数据准确地保存到对应的服务器上。
集群里有这么多节点,它们怎么知道彼此的死活,怎么知道哪个槽归谁管呢?
它们使用的是 Gossip(八卦/流言)协议。
Gossip 协议就像村口大妈聊天一样。节点 A 随机挑几个邻居节点,把自己的近况和听来的八卦(集群状态)告诉他们。这些邻居听完后,再分别告诉他们的邻居...
结果:不出几秒钟,一传十,十传百,整个集群的所有节点都同步了最新的全局信息。非常高效,且不需要一个中央服务器来协调!
如果客户端不小心把读写请求发给了错误的主节点,会发生什么?
客户端:“节点A,我要获取 name 的值!”
节点A内部运算发现:“哎呀,name 经过计算属于 8800 号槽,这个槽目前归节点B管啊。”
节点A回复客户端:“MOVED 8800 192.168.1.101:6379” (拒绝执行,并告诉你去找B节点)。
客户端的驱动程序:默默擦干眼泪,自动带着请求去连接节点B重新拿数据(过程对我们写代码的人是透明的)。
虽然集群把数据分片了,解决了容量问题。但如果某个管着 33% 数据的主节点硬件坏了,那这部分数据岂不是全丢了?
所以,集群模式内嵌了主从复制和哨兵的功能!
虽然本节是理论课不要求实操,但大家可以看看现代 Redis 部署集群是多么的一键化:
回顾四大阶段的演进
架构没有最好,只有最适合当前业务的
| 部署模式 | 优点 | 致命缺点 | 适合场景 |
|---|---|---|---|
| 单机模式 | 最简单,零配置 | 无任何保障,容易死机 | 个人学习、早期测试 |
| 主从模式 | 读写分离,读性能翻倍 | 主节点挂了需要半夜人工抢修 | 读多写少的轻量级业务 |
| 哨兵模式 | 自动化故障转移,高可用 | 依然无法突破单台机器的内存上限 | 中型企业,对稳定性要求高 |
| 集群模式 (Cluster) | 容量和性能无限扩展,自带高可用 | 配置最复杂,维护成本高 | 海量大数据、超高并发的大厂核心业务 |
下节课我们将前往实训机房,亲手在 Linux 系统上敲入命令,将今天学到的架构一步步搭建出来!大家做好准备!
期待在下一节的实践操作课中与大家再见!