Redis相关总结
一、缓存雪崩、缓存穿透
现象:缓存雪崩:大量缓存同时过期、缓存中间件宕机
缓存穿透:访问不存在key、缓存过期
解决:缓存雪崩:设置不同过期时间
缓存穿透:不存在key也存入缓存、使用布隆过滤器、使用分布式锁
二、Redis集群
主从复制:支持redis读写分离,不支持故障恢复
哨兵集群 :在主从复制模式下增加哨兵监控,当主节点故障,会选取一个新的主节点(无法扩容)
Cluster模式:分片模式,slot槽,可以扩容
三、RDB、AOF
RDB:快照模式
文件:dump.rdb
配置参数:save x y(x秒内有y个写入)
可以配置多个规则
//RDB模式默认是开启的 save 900 1 save 300 10 save 60 10000
触发:手动执行bgsave命令触发异步快照、执行save命令触发同步快照,根据redis.conf文件里的配置,自动触发bgsave,主从复制时触发
流程图:
AOF:日志模式
文件:appendfsync.aof
配置参数:
//AOF持久化,默认是关闭的。 打开 redis.conf, 找到如下配置 appendonly no # 改为 yes 就开启了 AOF appendfilename "appendonly.aof" # aof 文件名 # appendfsync always #每执行一个命令保存一次 appendfsync everysec # 默认,每一秒保存一次 # appendfsync no # 不主动执行保存,由操作系统决定
当执行指令时会将执行命令追加到缓冲区,再通过子线程把缓冲区数据写入到aof文件
由于aof文件会很大,aof文件到达一定大小会有一个重写机制
aof重写机制:对相同key的指令只保留最新的指令,然后将过滤后的指令写入到新的aof文件,然后删除旧的aof文件
在重写过程中,新的指令会存入到重写缓冲区中,重写完成后再将重写缓冲区内的指令写入到新的aof文件
流程图:
四:Redis存在线程安全问题吗?
redis是线程安全的,redis多线程模型只是针对IO网络。
执行多个指令无法保证原子性会出现线程安全问题,可以通过事务和lua脚本、锁实现
事务和lua脚本无法保证完全的原子性,因为不存在回滚机制
事务:MULTI 开启事务
指令1
指令2
EXEC 执行事务
#region Redis事务 StackChange.Redis if (RedisTranBool) { var db = RedisHelper1.One.CacheRedis; if (db.KeyExists("Count") && (int)db.StringGet("Count") == 0) { db.KeyDelete("Count"); db.KeyDelete("Index"); db.KeyDelete("Record"); } Console.WriteLine("删除成功"); if (!db.KeyExists("Count")) { db.StringSet("Count", 10000); } if (!db.KeyExists("Index")) { db.StringSet("Index", 0); } Console.WriteLine("初始化成功"); await Task.Run(async () => { try { while (true) { var Count = (int)db.StringGet("Count"); var Index = (int)db.StringGet("Index"); var tran = db.CreateTransaction(); tran.AddCondition(Condition.StringEqual("Count", Count)); tran.AddCondition(Condition.StringEqual("Index", Index)); if (Count > 0) { tran.StringGetAsync("Count"); tran.StringDecrementAsync("Count", 1); tran.StringIncrementAsync("Index", 1); tran.SortedSetAddAsync("Record", JsonConvert.SerializeObject(new { index = Index, id = Guid.NewGuid(), time = DateTime.Now.ToString("YYYY-MM-DD HH:mm:ss") }), Index); } bool IsResult = await tran.ExecuteAsync(); if (IsResult) { Console.Clear(); var _count = db.StringGet("Count").ToString(); Console.WriteLine(_count); var _index = db.StringGet("Index").ToString(); Console.WriteLine(_index); var record = db.SortedSetScan("Record")? .OrderBy(c => c.Score) .Select(c => JsonConvert.DeserializeObject(c.Element))? //.OrderBy(c =>c?.GetType().GetProperty("index")) .ToList(); record?.ForEach((r) => { Console.WriteLine(r); }); } Thread.Sleep(5000); } } catch (Exception ex) { Console.WriteLine(ex.ToString()); } }); } #endregion
原子性指令
1. SET key value:将字符串值 value 关联到 key。
SETEX key seconds value 设置键值对和过期时间
2. GET key:返回 key 所关联的字符串值。
3. INCR key:将 key 中存储的数字值增一。
4. DECR key:将 key 中储存的值减一。
5. APPEND key value:如果 key 已经存在并且是一个字符串, APPEND 命令将 value 追加到 key 原来的值的末尾。
6. HSET key field value:将哈希表 key 中的字段 field 的值设为 value。
7. HGET key field:返回哈希表 key 中给定字段 field 的值。
8. SADD key member:将 member 元素加入到集合 key 当中。
9. SPOP key:移除并返回集合中的一个随机元素。
10.SETNX
五:内存淘汰机制
触发淘汰:当到达了设置的最大内存
1、Random,随机淘汰算法
2、TTL ,在设置了过期时间的key中找到最早过期的key进行移除
3、LRU,移除最近很少使用的key
4、LFU,访问频次和上次访问时间
六:key过期了为啥没有释放内存空间
redis并不是实时删除过期数据,而且通过定期删除和过惰性删除,
定期删除是一定时间间隔会随机选取一批数据来判断是否过期,如果过期则删除
惰性删除是当一个key被查询时,会去检测这个key是否过期,如果过期则删除
七:使用keys指令可能会有什么问题
1、keys指令需要遍历整个redis的键空间,如果redis的key非常庞大会非常耗时影响性能
2、阻塞问题:keys指令是一阻塞命令,在keys执行完成之前,其他客户端无法访问redis
八:redis中大key怎么处理
定义:key对应的value数据很大
九:redis实现分布式锁,及相关问题
首先redis实现分布式锁使用的是setNX指令,表示插入一个key,能插入成功就获得锁
问题一(死锁):集群环境下如果一个节点获得了锁,然后这个节点挂掉了,锁就无法释放会发生死锁,这时可以引入锁过期机制,
通过lua脚本保证setNX和设置key过期指令原子性
问题二(锁续期):引入过期机制以后,如果业务没有处理完,锁就过期了。这时可以增加一个线程(类似看门狗),如果当前获得锁的进程还存在,则自动增加过期时间(锁续期问题)
问题三(锁释放):在问题二的基础上,假如线程A执行业务没有完成,锁提前过期了,这时线程B拿到锁,执行业务逻辑。这时线程A业务逻辑完成,释放了锁。此时释放的是线程B拿到的锁。
要解决这个问题,可以在加锁时,每个线程将value值设置成guid,释放锁时先判断value是否为当前线程。