
凌晨两点,手机疯狂震动。Redis CPU飙到95%,连接数爆满,大量请求超时——这是单机Redis的典型死法:要么被流量打死,要么被某个慢查询拖垮,要么主从切换失败整个服务不可用。
那天之后,我开始认真思考一个问题:Redis到底怎么做才能既扛住高并发,又能保证高可用?
高并发最直接的解法是把读流量分散。Redis主从复制(Master-Slave)天然支持读写分离:
主节点(Master):只负责写
从节点(Slave):负责读,可以挂多个配置非常简单,在从节点的redis.conf中加入:
replicaof 192.168.1.100 6379然后在代码中区分读写:
# 写操作走主节点
def set_key(key, value):
return master_conn.set(key, value)
# 读操作走从节点(轮询多个从节点)
def get_key(key):
slave = get_round_robin_slave()
return slave.get(key)这种架构能让读QPS轻松从几万提升到几十万。但问题也来了——如果主节点挂了,从节点不会自动变成主,需要人工介入或者引入Sentinel。
Sentinel(哨兵)是Redis官方提供的高可用解决方案。它干三件事:
配置一个最简单的Sentinel集群(至少3个节点防止误判):
# sentinel.conf
port 26379
sentinel monitor mymaster 192.168.1.100 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 150002表示需要2个Sentinel同时认为主挂了才会触发切换,避免了网络抖动引起的误切。
代码中客户端需要连接Sentinel而不是直接连Redis:
from redis.sentinel import Sentinel
sentinel = Sentinel([('192.168.1.101', 26379),
('192.168.1.102', 26379),
('192.168.1.103', 26379)],
socket_timeout=0.1)
# 获取主节点(写)
master = sentinel.master_for('mymaster')
master.set('key', 'value')
# 获取从节点(读)
slave = sentinel.slave_for('mymaster')
value = slave.get('key')但哨兵模式有一个硬伤:所有写流量仍然集中在一个主节点上。当写QPS超过单机上限(通常10万+/秒),就必须上集群了。
Redis Cluster是终极方案,它同时解决了两个问题:
搭建一个3主3从的集群(6个节点),关键配置:
# redis.conf
port 7000
cluster-enabled yes
cluster-config-file nodes-7000.conf
cluster-node-timeout 5000
appendonly yes启动6个节点后,用一条命令完成集群初始化:
redis-cli --cluster create \
192.168.1.101:7000 192.168.1.102:7001 192.168.1.103:7002 \
192.168.1.104:7003 192.168.1.105:7004 192.168.1.106:7005 \
--cluster-replicas 1--cluster-replicas 1表示每个主节点配1个从节点。
代码中使用Cluster客户端,完全不需要关心数据在哪个节点:
from redis.cluster import RedisCluster
rc = RedisCluster(host='192.168.1.101', port=7000)
# 和单机用法完全一样,客户端自动路由
rc.set('user:1001', '张三')
name = rc.get('user:1001')架构搭好了,但高并发下还有一些要命的细节:
1. 大Key问题
一个Key里塞了10MB的数据,一次请求拉取就能让网络带宽打满。解决方案:拆分,或者用UNLINK异步删除(别用DEL阻塞主线程)。
2. 热Key问题
某个明星开播,live:123:viewers这个Key被每秒数百万次访问。即使有集群,流量也集中在一个节点。
应对方案:
# 简单热Key应对:本地缓存+短过期时间
local_cache = {}
def get_hot_key(key):
if key in local_cache:
return local_cache[key]
value = redis.get(key)
local_cache[key] = value
# 1秒后失效,保证数据不太陈旧
threading.Timer(1.0, lambda: local_cache.pop(key, None)).start()
return value3. 连接池
高并发下每次新建连接是灾难。务必使用连接池:
import redis
pool = redis.ConnectionPool(
host='localhost',
port=6379,
max_connections=50, # 根据业务调整
socket_timeout=5
)
conn = redis.Redis(connection_pool=pool)从我那天凌晨踩坑到现在,这套架构经历了三个阶段:
阶段 | 架构 | 并发能力 | 可用性 |
|---|---|---|---|
初期 | 单机 | ~5万QPS | 单点故障 |
中期 | 主从+Sentinel | 读30万/写5万 | 自动切换(秒级) |
目前 | Cluster(3主3从) | 读60万/写30万 | 分片+自动切换 |
Redis的高并发和高可用,本质上是在做两件事:
高并发 = 把流量分散到更多节点(读写分离、分片) 高可用 = 每个节点都有备份(主从、哨兵、集群)
这两者互相叠加,就是现代Redis生产环境的全部秘密。
最后给一个实战建议:不要在应用层做分片逻辑(比如自己算hash取模),直接用Redis Cluster——它把分片、高可用、故障转移都内置了,省心得多。但要注意,Cluster不支持多Key事务和跨节点聚合操作,如果业务强依赖这些,就只能停留在哨兵模式并接受写单点的限制。
所有架构都是取舍。明白你的业务要什么,比知道所有技术选项更重要。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。