序
这段时间在准备软考高级架构师,已经有过一次失败经历的笔者发现只靠教材的内容并不能很好地应对第二大项的案例分析题,尤其是很久没有接触过大型网站的后端服务架构设计,因此想通过李智慧老师的《大型网站技术架构:核心原理与案例分析》 再好好复习一遍架构设计。刚好近期DeepSeek大火,通过与它交流阅读时遇到的问题和心得,来加深对架构设计的理解与记忆。
这一系列会按书实际的章节来组织,方便查询。
大型网站架构演化
分布式缓存 Redis的使用
在网站架构演化过程中,引入分布式缓存是提升系统性能和可扩展性的关键步骤。Redis 是目前最常用的分布式缓存技术之一,以下是其核心特性和使用方式:
Redis 核心特性
- 高性能:基于内存操作,支持每秒数十万级读写。
- 数据结构丰富:支持字符串(String)、哈希(Hash)、列表(List)、集合(Set)、有序集合(Sorted Set)等。
- 持久化:提供 RDB(快照)和 AOF(日志追加)两种数据持久化方案。
- 分布式支持:通过 Redis Cluster 实现分片存储和高可用,或通过 Sentinel 实现主从故障转移。
- 原子操作:支持事务和 Lua 脚本,保证复杂操作的原子性。
Redis 的典型使用方式
1. 基础部署
- 单节点模式(开发/测试):
# 启动 Redis 服务 redis-server
- 集群模式(生产环境):
# 配置 Redis Cluster(至少 3 主 3 从) redis-cli --cluster create 192.168.1.1:7000 192.168.1.2:7001 ... --cluster-replicas 1
2. 数据操作示例
-
缓存数据库查询结果(缓解数据库压力):
import redis # 连接 Redis r = redis.Redis(host='redis-host', port=6379) def get_user(user_id): # 先查缓存 user_data = r.get(f"user:{user_id}") if not user_data: # 缓存未命中,查数据库并写入缓存(设置过期时间) user_data = db.query("SELECT * FROM users WHERE id = ?", user_id) r.setex(f"user:{user_id}", 3600, user_data) # 缓存1小时 return user_data
-
分布式会话存储(替代单机 Session):
// Spring Boot 中配置 Redis 存储 Session @Configuration @EnableRedisHttpSession public class SessionConfig { @Bean public RedisConnectionFactory redisConnectionFactory() { return new LettuceConnectionFactory("redis-host", 6379); } }
3. 高级场景
- 缓存雪崩防护:为 Key 设置随机过期时间,避免同时失效。
- 热点数据预热:在流量高峰前提前加载高频数据到缓存。
- 分布式锁(如秒杀场景):
# 使用 SETNX 命令实现简单锁 lock_key = "product_lock:123" if r.set(lock_key, "locked", nx=True, ex=10): try: # 执行业务逻辑 finally: r.delete(lock_key)
Redis 在架构中的定位
- 位置:通常部署在应用服务器和数据库之间,作为缓存层。
- 作用:
- 减少数据库负载(缓存高频读、低频写数据)。
- 加速响应(内存访问速度远高于磁盘)。
- 解耦服务(如会话共享支持无状态应用)。
总结
Redis 凭借其高性能和丰富的功能,成为分布式缓存的首选。实际使用时需结合业务场景选择数据结构,并通过集群、持久化和监控(如 Redis Insight)保障稳定性。在架构演化中,Redis 可作为独立缓存层逐步引入,显著提升系统的吞吐能力和扩展性。
Redis 作为广泛使用的分布式缓存和内存数据库,其核心价值在于高性能和丰富的数据结构。以下是 Redis 的常用功能及对应命令,结合实际场景进行分类说明:
Redis常用功能命令
1. 基础数据操作
(1) 字符串(String)
- 功能:缓存简单值(如用户 Token、计数器)。
- 常用命令:
示例:SET key value [EX seconds] # 设置键值(带过期时间) GET key # 获取值 INCR key # 原子性自增(如秒杀库存计数) SETNX key value # 仅当键不存在时设置(分布式锁基础)
SET user:1001:token "abc123" EX 3600 # 缓存用户 Token,1 小时后过期 INCR page:views # 页面访问量统计
(2) 哈希(Hash)
- 功能:存储对象(如用户信息、商品详情)。
- 常用命令:
示例:HSET key field value # 设置单个字段 HGET key field # 获取字段值 HGETALL key # 获取所有字段和值 HMSET key field1 value1 ... # 批量设置字段(HMSET 已被 HSET 替代)
HSET user:1001 name "Alice" age 30 # 存储用户对象 HGET user:1001 age # 获取用户年龄
(3) 列表(List)
- 功能:实现队列、消息流(如任务队列、最新消息)。
- 常用命令:
示例:LPUSH key value1 value2 # 左侧插入元素 RPOP key # 右侧弹出元素 LRANGE key start stop # 获取列表范围(如最新 10 条消息)
LPUSH news:latest "news1" "news2" # 记录最新新闻 LRANGE news:latest 0 9 # 获取最近 10 条新闻
(4) 集合(Set)
- 功能:去重集合(如用户标签、抽奖去重)。
- 常用命令:
示例:SADD key member1 member2 # 添加元素 SISMEMBER key member # 检查元素是否存在 SMEMBERS key # 获取所有元素
SADD lottery:2023:users "user1001" # 记录参与抽奖用户 SISMEMBER lottery:2023:users "user1001" # 检查是否已参与
(5) 有序集合(Sorted Set)
- 功能:排行榜、延迟队列(如游戏积分排名)。
- 常用命令:
示例:ZADD key score member # 添加带分数的成员 ZRANGE key start stop [WITHSCORES] # 按分数升序获取成员 ZREVRANGE key start stop [WITHSCORES] # 按分数降序获取成员
ZADD game:ranking 1500 "PlayerA" 1000 "PlayerB" # 记录玩家积分 ZREVRANGE game:ranking 0 9 WITHSCORES # 获取 Top10 玩家
2. 高级功能
(1) 过期时间管理
- 功能:自动清理缓存,避免内存泄漏。
- 命令:
示例:EXPIRE key seconds # 设置过期时间(秒) TTL key # 查看剩余生存时间 PERSIST key # 取消过期时间
SET product:1001:info "{...}" EX 7200 # 缓存商品信息,2 小时后过期
(2) 发布订阅(Pub/Sub)
- 功能:实时消息通知(如订单状态更新)。
- 命令:
示例:PUBLISH channel message # 发布消息 SUBSCRIBE channel1 channel2 # 订阅频道
# 客户端A订阅订单频道 SUBSCRIBE order:status # 客户端B发布订单状态变更 PUBLISH order:status "Order123:shipped"
(3) 事务(Transaction)
- 功能:批量操作原子性执行。
- 命令:
示例:MULTI # 开启事务 SET key1 value1 INCR key2 EXEC # 提交事务
MULTI SET balance:user1001 500 INCR total_users EXEC # 原子性执行
(4) Lua 脚本
- 功能:复杂逻辑原子性执行(如分布式锁续期)。
- 命令:
示例(实现分布式锁续期):EVAL "script" numkeys key1 key2 ... arg1 arg2 ...
EVAL "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('EXPIRE', KEYS[1], ARGV[2]) else return 0 end" 1 lock_key client_id 30
3. 生产环境关键操作
(1) 持久化
- RDB 快照(全量备份):
SAVE # 阻塞式生成 RDB 文件 BGSAVE # 后台生成 RDB 文件
- AOF 日志(增量持久化):
CONFIG SET appendonly yes # 开启 AOF
(2) 集群管理
- Redis Cluster 命令:
CLUSTER INFO # 查看集群状态 CLUSTER NODES # 列出集群节点 CLUSTER KEYSLOT key_name # 查看 Key 所属的槽位
(3) 缓存问题解决方案
- 缓存雪崩:
SET key value EX 3600 RANDOM # 设置过期时间时添加随机偏移
- 缓存穿透(布隆过滤器):
BF.ADD visited_users user1001 # 使用 RedisBloom 模块 BF.EXISTS visited_users user1001
总结
- 选择数据结构:根据业务场景选择 String(简单缓存)、Hash(对象存储)、Sorted Set(排行榜)等。
- 内存管理:合理设置过期时间,避免内存溢出(可配置
maxmemory-policy
淘汰策略)。 - 持久化与高可用:生产环境建议开启 AOF + RDB,并通过 Redis Cluster 或 Sentinel 保障可用性。
- 监控工具:使用
redis-cli --stat
、INFO
命令或第三方工具(如 Prometheus + Grafana)监控性能。
对于分布式缓存服务器集群,新加入服务器可能会导致缓存路由失效,怎么改进缓存路由算法来保证缓存数据的可访问性?
在分布式缓存集群中,新增服务器节点时,传统的哈希算法(如简单取模或固定范围哈希)会导致大规模缓存失效(即缓存键的重新映射引发雪崩效应)。以下是通过改进路由算法解决这一问题的核心方案及实现思路:
1. 一致性哈希(Consistent Hashing)
核心原理
- 环形哈希空间:将哈希值范围构成一个环(例如 0 ~ 2^32-1),节点和缓存键均映射到环上。
- 数据定位规则:缓存键按顺时针方向找到最近的节点作为归属节点。
- 虚拟节点优化:为每个物理节点分配多个虚拟节点,均匀分布在环上,避免数据倾斜。
解决新增节点的问题
- 影响范围有限:仅需迁移新节点与环上相邻节点之间的数据(如节点 N+1 与节点 N 之间的键),而非全局数据。
- 示例流程:
- 新增节点:将新节点及其虚拟节点加入环。
- 数据迁移:仅需将原相邻节点中属于新节点范围的键迁移。
- 客户端路由:客户端通过一致性哈希算法自动定位到新节点。
代码示例(Python 伪代码)
from hashlib import md5
from bisect import bisect
class ConsistentHash:
def __init__(self, nodes, replicas=3):
self.ring = {}
self.sorted_keys = []
for node in nodes:
for i in range(replicas):
virtual_node = f"{node}#{i}"
hash_key = self._hash(virtual_node)
self.ring[hash_key] = node
self.sorted_keys.append(hash_key)
self.sorted_keys.sort()
def _hash(self, key):
return int(md5(key.encode()).hexdigest(), 16) % (2**32)
def get_node(self, key):
hash_key = self._hash(key)
idx = bisect(self.sorted_keys, hash_key) % len(self.sorted_keys)
return self.ring[self.sorted_keys[idx]]
# 初始化含 3 个节点的环
nodes = ["node1", "node2", "node3"]
ch = ConsistentHash(nodes)
# 新增节点 node4
ch.add_node("node4")
# 查询键的归属节点
print(ch.get_node("user:1001")) # 自动路由到新节点或相邻节点
2. 哈希槽分片(Hash Slot)
核心原理(如 Redis Cluster)
- 固定槽位数量:将整个键空间划分为固定数量的槽(如 Redis 的 16384 个槽)。
- 动态槽分配:每个节点负责一部分槽,新增节点时,从现有节点转移部分槽到新节点。
- 客户端重定向:客户端缓存槽-节点映射表,若请求的槽不在当前节点,返回
MOVED
重定向响应。
解决新增节点的问题
- 平滑迁移:
- 管理员将部分槽从现有节点迁移到新节点。
- 迁移过程中,新旧节点同时持有槽数据,直到迁移完成。
- 客户端通过
MOVED
或ASK
响应自动更新路由表。
- 示例命令:
# 将槽 1000-2000 从节点 A 迁移到新节点 B redis-cli --cluster reshard <host:port> --cluster-from <node-A-id> --cluster-to <node-B-id> --cluster-slots 1000
3. 客户端动态路由(如动态配置中心)
核心原理
- 集中式路由表:将节点路由信息存储在配置中心(如 ZooKeeper、Etcd)。
- 实时通知机制:客户端订阅路由表变更事件,节点增删时立即更新本地路由缓存。
实现步骤
- 新增节点:向配置中心注册新节点信息。
- 路由表更新:配置中心广播新路由表给所有客户端。
- 数据迁移:后台异步迁移数据,客户端根据新旧路由表双读确保访问成功。
4. 对比与选型
方案 | 一致性哈希 | 哈希槽分片 | 客户端动态路由 |
---|---|---|---|
数据迁移量 | 局部迁移(相邻节点) | 局部迁移(槽范围) | 依赖具体实现(通常需全量迁移) |
客户端复杂度 | 中等(需实现哈希环逻辑) | 低(依赖集群协议如 Redis) | 高(需集成配置中心) |
适用场景 | 通用缓存系统(如 Memcached) | 强一致性场景(如 Redis Cluster) | 多语言异构系统 |
5. 生产环境实践建议
- 预热新节点:在流量低峰期迁移数据,避免迁移期间性能抖动。
- 双写兼容:迁移过程中,短暂允许新旧节点同时写入,确保数据一致性。
- 监控与回滚:
- 监控缓存命中率、节点负载等指标。
- 准备快速回滚机制(如暂停新节点流量)。
- 自动化工具:使用 Redis Cluster 的
reshard
或第三方工具(如 Twitter 的Gizzard
)自动化迁移。
总结
通过 一致性哈希 或 哈希槽分片 算法,结合客户端动态路由机制,可显著降低新增节点时的缓存失效影响。在 Redis 生态中,优先采用原生的哈希槽分片(Redis Cluster)实现无缝扩容;若为自研缓存系统,可通过一致性哈希 + 虚拟节点优化平衡数据分布与迁移成本。