当前位置: 首页 > news >正文

Redis缓存详解及常见问题解决方案

一、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 {@UpdateListenPointpublic 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需要:

  1. 根据业务特点选择合适的数据结构
  2. 设计合理的过期策略和缓存更新机制
  3. 针对雪崩、穿透、击穿等问题实施防御措施
  4. 做好监控和容量规划
  5. 及时升级版本获取新特性

通过以上最佳实践,可以充分发挥Redis的性能优势,同时避免常见的陷阱和问题。

http://www.lryc.cn/news/610437.html

相关文章:

  • MySQL 基本操作入门指南
  • MCP进阶:工业协议与AI智能体的融合革命
  • 使用 SecureCRT 连接华为 eNSP 模拟器的方法
  • typeof和instanceof区别
  • Linux学习记录(八)文件共享
  • 认识pytorch与pytorch lightning
  • BackgroundTasks 如何巧妙驾驭多任务并发?
  • 我的创作纪念日____在 CSDN一年来的成长历程和收获
  • openvela之内存管理
  • Linux 磁盘管理与分区配置
  • VUE+SPRINGBOOT从0-1打造前后端-前后台系统-注册实现
  • 向量魔法:Embedding如何赋能大模型理解世界
  • Go语言select
  • Git基础玩法简单描述
  • 【LeetCode刷题集】--排序(一)
  • ICCV2025 Tracking相关paper汇总和解读(19篇)
  • ubuntu 20.04 C和C++的标准头文件都放在哪个目录?
  • windows双系统下ubuntu20.04安装教程
  • HTTPS有哪些优点
  • Jeston + TensorRT + Realsense D435i + ROS noetic + Yolo11 各版本模型目标检测
  • Flink CDC 介绍
  • Field and wave electromagnetics 复习
  • 正点原子阿波罗STM32F429IGT6移植zephyr rtos(四)---在独立的应用工程里使用MPU6050
  • 【Java】一篇详解HashMap的扩容机制!!
  • SparkSQL—sequence 函数用法详解
  • 四、Linux 的实用操作
  • wpf Image 转 90 度
  • 华为OD机考2025C卷 - 分配土地 (Java Python JS C++ C )
  • 复合机器人抓取精度怎么测量?
  • Tableau筛选器所有值与总和的差异:同一度量,两重世界