一、redis的过期策略
redis并不会主动去监控每个key,到时候把它删除
- 惰性删除:单某个key过期了,并不立即处理,而是当查询到这个key的时候,看看它有没有过期,如果过期就清除掉
- 定期删除:redis会运行一个后台线程,随机读取20个key,把里面过期的key删除掉,同时计算过期key的比例,如果超过25%,则会继续进行这一步骤,直到比例低于25%为止
如果达到最大内存以后,可以配置不同的清理策略:
noevation:不清理
volatile-lru:清理所有过期的key,一直到空间足够,如果还是不够,就回退到noevication
allkeys-random:随机清理任何key
volatile-random:随机删除过期的key
volatile-ttl:清理马上就要过期的key
常用的策略是volatile-random
二:基于冷热分离优化redis内存使用
思考题1:一百万云店如何优化redis内存使用
- 热数据:从redis里面读取数据的时候,判断一下,ttl剩余存活时间是多少,如果剩余时间不多了,可以做一个延期,expire time,设置的长一些,这样,经常读取和访问的数据,会一直留在内存里面
- 冷数据:因为不经常读取,所以到期就会自动过期
三、缓存数据一致性问题和分析
目前系统中使用的是数据库和缓存双写的方案,这种方案的优点是实现比较简单,可以显著提高并发性能;但是在并发场景下会存在数据不一致的问题,在需要保证数据一致性的场景下不建议使用。
举例分析:例如一个促销活动信息,在缓存中刚好过期了,此时后管人员去修改活动信息,正常情况下是先将数据库中的数据修改之后存入缓存中,如果此时刚好前端访问了这条数据,在DB修改数据之前读取到了信息,这个访问线程也会将读取到的数据存入缓存中,那么缓存有可能还是旧的数据。
时间 | 前端线程 | 后管线程 | DB | 缓存 |
1 | 缓存失效 | v1 | null | |
2 | 从DB读取v1版本 | v1 | null | |
3 | 更新DB为v2 | v2 | null | |
4 | 写入缓存 | v2 | v2 | |
5 | 写入缓存 | v2 | v1 |
有意见说在后管修改的方法里面加上事务,可以保证一致性,经验证这个是行不通的,事务默认可重复读的话,两个线程并发,读到的数据是一样的,后管线程执行完,前端线程卡住,那么就会产生数据不一致的情况。
一致性保证分为两种:
强一致性:只要写了数据,立马就可以从DB+缓存中读取到正确的数据
最终一致性:写成功了,此时保证DB中可以读取到正确的数据,但是缓存中可能需要过一点时间(几十毫秒或者几秒内)和DB保持一致
思考题2:如何保证一致性
方案1:
时间 | 前端线程 | 后管线程 | DB | 缓存 |
1 | 缓存失效 | v1 | null | |
2 | 从DB读取v1版本 | v1 | null | |
3 | 更新DB为v2 | v2 | null | |
4 | 写入缓存 | v2 | v2 | |
5 | 尝试写入缓存,比较版本号之后丢弃旧数据 | v2 | v2 |
方案2:
如果是对于主要是读,修改很少的场景,可以在写缓存的步骤增加分布式锁
时间 | 前端线程 | 后管线程 | DB | 缓存 |
1 | 缓存失效,读取缓存失败 | v1 | null | |
2 | 获取分布式锁成功,再次读取缓存失败,读DB,再存入缓存 | v1 | v1 | |
3 | 获取分布式锁,双写DB和缓存 | v2 | v2 |
时间 | 前端线程 | 后管线程 | DB | 缓存 |
1 | 缓存失效,读取缓存失败 | v1 | null | |
2 | 获取分布式锁,双写DB和缓存 | v2 | v2 | |
2 | 获取分布式锁成功,再次读取缓存成功,返回缓存v2 | v2 | v2 |
思考题3:方案2还有没有缺点?
这个方案可以解决数据不一致的问题,但是增加了分布式锁,带来另一个问题,如果是高并发的场景下,一次修改操作,会使很多线程都进入等待锁的队列中,所以还需要进一步优化
优化方案是,前端线程获取锁的时候应该加一个超时时间,获取锁超时之后,串行转并发,再次读取缓存,如果读取成功则返回,读取失败则抛出错误即可。
四:Redis节约内存使用的优化技巧和方法总结
1、key尽量使用缩写
2、同类型的、数量有限的key保存到hash里面