第一次接触分布式缓存是在2013年,当时我们电商平台的商品详情页接口响应时间已经突破800ms。引入Redis集群后,这个数字直接降到了120ms以下——这种性能提升的震撼感,至今记忆犹新。分布式缓存本质上是通过内存数据网格实现的读写加速层,它解决了单体应用最头疼的三个问题:数据库扛不住高并发查询、跨节点数据不一致、缓存雪崩引发的连锁故障。
现代分布式缓存系统通常具备几个关键特征:数据分片存储(比如Redis Cluster的16384个slot)、多副本高可用(主从切换+哨兵机制)、智能客户端路由(如Lettuce驱动的拓扑感知)。去年双十一,某头部电商的Redis集群峰值QPS达到420万/秒,这个数字是传统数据库根本无法企及的。
重要提示:分布式缓存不是银弹,它最适合存储高频访问的低变化率数据。比如电商系统的商品基础信息、社交平台的热门帖子、游戏服务器的玩家基础数据等。
去年给某金融客户做技术选型时,我们做了组对比测试:同样的服务器配置下,Redis 6.2的TPS是Memcached 1.6的3倍,但在纯KV场景下内存占用多15%。具体差异体现在:
| 特性 | Redis | Memcached |
|---|---|---|
| 数据结构 | 5种核心类型+模块扩展 | 纯String类型 |
| 持久化 | RDB+AOF混合持久化 | 无持久化 |
| 集群模式 | 原生Cluster方案 | 需要客户端分片 |
| 线程模型 | 单线程事件循环 | 多线程 |
| 典型适用场景 | 需要复杂操作的业务逻辑缓存 | 简单的会话缓存 |
实际项目中,如果要用到哈希存储、发布订阅、Lua脚本等高级功能,Redis是唯一选择。我们团队在物联网平台项目中就用Redis的Stream实现了设备消息队列,替代了原来的RabbitMQ方案。
去年帮一家初创公司做成本优化时,对比过三大云的缓存服务:
中小团队我建议直接用云服务,自建Redis Cluster光是处理节点故障转移就够喝一壶的。曾经有次凌晨3点被叫起来处理Redis脑裂问题,那滋味...
去年设计的票务系统架构中,我们实现了四级缓存体系:
关键技巧在于缓存穿透防护:对于不存在的票次ID,仍然在Redis存入空值标记(特殊字符串"NULL_FLAG"),避免恶意请求直接穿透到数据库。
用Java实现一致性哈希时有个坑:虚拟节点数不够会导致数据倾斜。我们通过以下配置解决:
java复制// 使用Redisson客户端
Config config = new Config();
config.useClusterServers()
.setNodeFilter(node -> !node.isMaster()) // 只连接从节点
.setLoadBalancer(new RoundRobinLoadBalancer())
.setRetryAttempts(3)
.setRetryInterval(1500);
实测发现当节点数超过50个时,虚拟节点数应该设置在200-300之间才能保证分片均匀。去年有个游戏项目就因为设了100个虚拟节点,导致30%的请求都集中到3个物理节点上。
最容易被忽视的是连接池配置,这里分享一组黄金参数:
yaml复制# Lettuce连接池配置
spring.redis.lettuce.pool:
max-active: 500 # 根据QPS估算:峰值QPS/(1000/平均RT)
max-idle: 50
min-idle: 10
max-wait: 1000ms # 超过这个时间直接熔断
曾经有次大促,因为max-wait设成了-1(无限等待),导致线程池积压拖垮整个系统。现在的原则是:宁可快速失败也不要阻塞。
去年处理过一个经典案例:某明星发布动态导致特定粉丝主页缓存击穿。最终解决方案组合拳:
--hotkeys参数识别热点关键是要在客户端实现多级探测,我们用的方案是基于Guava Cache的权重衰减算法:
java复制// 热点Key探测算法
LoadingCache<String, AtomicLong> counter = CacheBuilder.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(key -> new AtomicLong());
if(counter.get(key).incrementAndGet() > 1000) {
// 触发热点保护逻辑
}
最惨痛的一次教训是用JDK序列化存了1GB的HashMap,结果:
现在强制使用Protocol Buffers:
protobuf复制message CacheItem {
string key = 1;
bytes value = 2;
int64 expire_at = 3;
}
某次清理10GB的Hash Key时,直接执行DEL导致集群卡顿30秒。正确做法是:
bash复制# 使用scan+hdel渐进式删除
redis-cli --bigkeys -i 0.1 | grep '^Big' | awk '{print $3}' | while read key; do
redis-cli --scan --pattern "$key" | xargs -L 100 redis-cli unlink
sleep 1
done
血泪教训:超过1MB的Key就应该被监控,超过10MB必须立即处理。去年某P2P公司就因大Key导致主从同步失败,最终数据丢失。
现在最让我兴奋的是Redis 7.0的Function特性,它允许用JavaScript写服务端脚本。上周刚用这个特性实现了滑动窗口限流:
javascript复制#!js name=lib
redis.registerFunction('rate_limit', (client, key, window_ms, max_count) => {
const now = client.call('TIME')[0];
const clearBefore = now - window_ms/1000;
client.call('ZREMRANGEBYSCORE', key, 0, clearBefore);
const current = client.call('ZCARD', key);
if(current >= max_count) return 0;
client.call('ZADD', key, now, now+Math.random());
return 1;
});
调用时只需要:
bash复制TFCALL lib.rate_limit 1 my_rate_key 1000 10
这种将业务逻辑下沉到缓存层的模式,可能会彻底改变我们设计系统的思路。最近在测试的RedisGraph模块更是让我看到了用缓存做实时关系分析的潜力。