一、Redis核心特性与架构
1. Redis核心优势
- 内存存储:数据存储在内存中,读写性能极高(10万+ QPS)
- 丰富数据结构:支持String、Hash、List、Set、SortedSet等
- 持久化支持:RDB快照和AOF日志两种方式
- 高可用:支持主从复制、哨兵模式和集群模式
- 原子操作:单线程模型保证命令原子性
- 发布订阅:支持消息的发布/订阅模式
- Lua脚本:支持执行原子性Lua脚本
2. Redis架构模式对比
模式 | 特点 | 适用场景 |
---|---|---|
单机模式 | 简单部署,无高可用 | 开发测试环境 |
主从复制 | 读写分离,数据冗余 | 读多写少的业务场景 |
哨兵模式 | 自动故障转移 | 对可用性有要求的业务 |
Cluster集群 | 数据分片,水平扩展 | 大数据量高并发场景 |
二、Redis持久化机制
1. RDB(Redis Database)
原理:定时生成内存快照
# redis.conf配置示例
save 900 1 # 900秒内至少1个key变化
save 300 10 # 300秒内至少10个key变化
save 60 10000 # 60秒内至少10000个key变化
优点:
- 紧凑的二进制文件,恢复速度快
- 适合灾难恢复
- 最大化Redis性能
缺点:
- 可能丢失最后一次快照后的数据
- 大数据量时fork过程可能阻塞服务
2. AOF(Append Only File)
原理:记录所有写操作命令
# redis.conf配置示例
appendonly yes
appendfsync everysec # 每秒同步
# appendfsync always # 每个命令同步
# appendfsync no # 由操作系统决定
优点:
- 数据安全性更高(最多丢失1秒数据)
- AOF文件易于理解和解析
- 后台重写不会影响客户端请求
缺点:
- 文件体积通常比RDB大
- 恢复速度比RDB慢
3. 混合持久化(Redis 4.0+)
# redis.conf配置
aof-use-rdb-preamble yes
结合RDB和AOF优势,先使用RDB格式存储快照,后续增量使用AOF格式
三、Redis集群方案
1. 主从复制架构
特点:
- 读写分离:主节点写,从节点读
- 数据冗余:从节点备份数据
- 故障需手动切换
2. 哨兵模式(Sentinel)
# sentinel.conf配置示例
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
功能:
- 监控:持续检查主从节点状态
- 通知:通过API发送故障警报
- 自动故障转移:主节点故障时选举新主
- 配置提供者:客户端查询获取当前主节点地址
3. Cluster集群模式
数据分片:
-
16384个哈希槽(slot)
-
每个节点负责部分slot
-
支持自动重分片(resharding)
集群节点通信端口比数据端口大10000
redis-cli --cluster create 127.0.0.1:7000 127.0.0.1:7001
127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005
--cluster-replicas 1
优势:
- 自动数据分片
- 部分节点不可用时继续工作
- 支持线性扩展
四、Redis常见问题解决方案
1. 缓存雪崩
场景:大量key同时过期,请求直接打到数据库
解决方案:
// 1. 随机过期时间
jedis.setex("key", 3600 + new Random().nextInt(600), "value");
// 2. 多级缓存
// 本地缓存(Caffeine) -> Redis -> DB
LoadingCache<String, Object> localCache = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build(key -> getFromRedisOrDB(key));
// 3. 熔断降级
CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("redis");
Supplier<Object> supplier = CircuitBreaker.decorateSupplier(circuitBreaker,
() -> getFromRedis(key));
2. 缓存穿透
场景:大量查询不存在的数据
解决方案:
// 1. 布隆过滤器
BloomFilter<String> filter = BloomFilter.create(
Funnels.stringFunnel(Charset.defaultCharset()),
1000000,
0.01);
if(!filter.mightContain(key)) {
return null;
}
// 2. 缓存空值
Object value = jedis.get(key);
if(value == null) {
jedis.setex("null_"+key, 300, "");
return null;
}
3. 缓存击穿
场景:热点key突然失效,大量并发请求数据库
解决方案:
// 1. 互斥锁
String value = jedis.get(key);
if(value == null) {
if(jedis.setnx("lock_"+key, "1") == 1) {
jedis.expire("lock_"+key, 10);
try {
value = db.query(key);
jedis.setex(key, 3600, value);
} finally {
jedis.del("lock_"+key);
}
} else {
Thread.sleep(100);
return getFromCache(key);
}
}
// 2. 永不过期+后台刷新
jedis.set(key, value);
// 后台线程定期更新
scheduler.scheduleAtFixedRate(() -> {
String newValue = db.query(key);
jedis.set(key, newValue);
}, 30, 30, TimeUnit.MINUTES);
4. 数据一致性
场景:数据库更新后缓存未同步
解决方案:
// 1. 双写策略
@Transactional
public void updateUser(User user) {
userDao.update(user);
redisTemplate.opsForValue().set("user:"+user.getId(), user);
}
// 2. 延迟双删
public void updateProduct(Product product) {
redisTemplate.delete("product:"+product.getId());
productDao.update(product);
executor.schedule(() -> {
redisTemplate.delete("product:"+product.getId());
}, 1, TimeUnit.SECONDS);
}
// 3. Canal监听binlog
@CanalEventListener
public class RedisUpdateListener {
@UpdateListenPoint
public void onUpdate(User user) {
redisTemplate.opsForValue().set("user:"+user.getId(), user);
}
}
5. 大Key问题
场景:单个Key存储数据过大(>1MB)
解决方案:
// 1. 拆分大Key
// 原始大Hash -> 多个小Hash
Map<String, String> bigMap = new HashMap<>();
for(int i=0; i<10000; i++) {
bigMap.put("field"+i, "value"+i);
}
// 拆分为10个Hash
for(int i=0; i<10; i++) {
Map<String, String> part = new HashMap<>();
for(int j=0; j<1000; j++) {
part.put("field"+(i*1000+j), "value"+(i*1000+j));
}
redisTemplate.opsForHash().putAll("bigHash:part"+i, part);
}
// 2. 使用SCAN替代KEYS
String cursor = "0";
do {
ScanResult<String> scanResult = jedis.scan(cursor,
new ScanParams().match("user:*").count(100));
cursor = scanResult.getCursor();
List<String> keys = scanResult.getResult();
// 处理keys
} while(!cursor.equals("0"));
6. 热Key问题
场景:某些Key访问量远超其他Key
解决方案:
// 1. 本地缓存热Key
LoadingCache<String, Object> localCache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.SECONDS)
.build(key -> jedis.get(key));
// 2. Redis集群分片+副本
// 对热Key增加副本
jedis.set("hotKey:1", value);
jedis.set("hotKey:2", value); // 相同值的副本
// 3. 使用Redis代理中间件
// 如Twemproxy或Redis Cluster自动分片
五、Redis性能优化
1. 内存优化
# redis.conf关键配置
hash-max-ziplist-entries 512 # Hash元素数量≤512使用ziplist
hash-max-ziplist-value 64 # Hash元素值大小≤64字节使用ziplist
list-max-ziplist-entries 512
list-max-ziplist-value 64
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
2. 网络优化
# 1. 使用pipeline批量操作
Pipeline p = jedis.pipelined();
p.set("key1", "value1");
p.set("key2", "value2");
p.sync();
# 2. 避免大Value(超过1MB)
# 3. 使用连接池
JedisPool pool = new JedisPool(new JedisPoolConfig(), "localhost");
try (Jedis jedis = pool.getResource()) {
// 操作Redis
}
3. 命令优化
-
使用
MGET/MSET
替代多次GET/SET
-
使用
SCAN
替代KEYS
-
使用
DEL
异步删除大Key(Redis 4.0+)redis-cli --bigkeys # 查找大Key
redis-cli --memkeys # 分析内存使用
redis-cli --latency-history # 监控延迟
六、Redis监控与运维
1. 关键监控指标
指标 | 说明 | 健康值参考 |
---|---|---|
used_memory | 已用内存 | < 80% maxmemory |
mem_fragmentation_ratio | 内存碎片率 | 1.0-1.5 |
instantaneous_ops_per_sec | 每秒操作数 | 根据业务特点 |
keyspace_hits | 缓存命中数 | 越高越好 |
keyspace_misses | 缓存未命中数 | 越低越好 |
connected_clients | 客户端连接数 | < 10000 |
rejected_connections | 拒绝的连接数 | 0 |
2. 常用运维命令
# 内存分析
redis-cli info memory
redis-cli --bigkeys
# 性能测试
redis-benchmark -t set,get -n 100000 -q
# 慢查询分析
redis-cli slowlog get 10
config set slowlog-log-slower-than 10000 # 设置慢查询阈值(微秒)
# 持久化监控
redis-cli info persistence
七、Redis应用场景示例
1. 分布式锁
public boolean tryLock(String key, String value, long expireTime) {
return "OK".equals(jedis.set(key, value, "NX", "PX", expireTime));
}
public boolean unlock(String key, String value) {
String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(luaScript, Collections.singletonList(key),
Collections.singletonList(value));
return result.equals(1L);
}
2. 限流器
public boolean isAllowed(String key, int max, int intervalSec) {
String luaScript = "local current = redis.call('incr', KEYS[1])\n" +
"if current == 1 then\n" +
" redis.call('expire', KEYS[1], ARGV[1])\n" +
"end\n" +
"return current <= tonumber(ARGV[2])";
Object result = jedis.eval(luaScript, Collections.singletonList(key),
Arrays.asList(String.valueOf(intervalSec),
String.valueOf(max)));
return (Long)result <= max;
}
3. 排行榜
// 添加分数
jedis.zadd("leaderboard", 100, "user1");
jedis.zadd("leaderboard", 200, "user2");
// 获取排名
Set<Tuple> topUsers = jedis.zrevrangeWithScores("leaderboard", 0, 9);
八、Redis版本特性
Redis 6.0+
- 多线程I/O:提高网络IO性能(执行命令仍单线程)
- 客户端缓存:服务端辅助的客户端缓存
- ACL:更完善的访问控制
- SSL:支持加密连接
Redis 7.0+
- Function:支持服务端脚本(替代部分Lua使用场景)
- Multi-part AOF:AOF文件分块存储
- Sharded Pub/Sub:分片发布订阅
总结
Redis作为高性能的内存数据库,在缓存、会话存储、排行榜等场景有广泛应用。合理使用Redis需要:
- 根据业务特点选择合适的数据结构
- 设计合理的过期策略和缓存更新机制
- 针对雪崩、穿透、击穿等问题实施防御措施
- 做好监控和容量规划
- 及时升级版本获取新特性
通过以上最佳实践,可以充分发挥Redis的性能优势,同时避免常见的陷阱和问题。