redis 的使用很普遍,如果你没有使用过 redis,都不好意思说自己是开发界的老司机。今天又到了知识普及时间。
聊到 redis,首先想到的是它的老表--Memcached。那么相比较它的老表,redis 有哪些闪光点呢?
1、持久化机制,可以定期将内存中的数据持久化到硬盘上。
2、binlog功能,可以将所有操作写入日志,当redis出现故障,可依照binlog进行数据恢复。
3、支持virtual memory,可以限定内存使用大小,当数据超过阈值,则通过类似LRU的算法把内存中的最不常用数据保存到硬盘的页面文件中。
4、支持的数据类型更多,使用的想象空间更大。
5、支持实物。
当然,这些也可以看做 redis 的优点。除了这些,被大家熟知的就是--快。对滴,就是快,redis 是一个快男,但是有时候快并不一定坏事。
redis 优点
1、速度快。
2、支持丰富数据类型: String ,List,Set,Sorted Set,Hash 。
3、丰富的特性。如订阅发布 Pub / Sub 功能、key 过期策略等。
4、持久化存储。
redis 缺点
1、过于依赖内存。
2、redis是单线程的,单台服务器无法充分利用多核服务器的CPU。
redis 是如何让自己变成快男的呢?
1、纯内存操作。
2、单线程,避免频繁上下文切换。
3、采用了非阻塞I/O多路复用机制。
4、使用底层模型不同,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。(有钱真的可以为所欲为,自给自足。)
redis 使用久了,不可避免的会暴露出一些问题。小场面,不要慌,一切困难都是纸老虎,干就完了。
1、缓存穿透
- 恶意攻击,查询数据库中不存在的数据(未记录缓存)
- 方案:
1、利用互斥锁,缓存失效的时候,先去获得锁,得到锁了,再去请求数据库。没得到锁,则休眠一段时间重试。
2、用异步更新策略,无论key是否取到值,都直接返回。value值中维护一个缓存失效时间,缓存如果过期,异步起一个线程去读数据库,更新缓存。需要做缓存预热(项目启动前,先加载缓存)操作。
3、提供一个能迅速判断请求是否有效的拦截机制,比如,利用布隆过滤器,内部维护一系列合法有效的key。迅速判断出,请求所携带的Key是否合法有效。如果不合法,则直接返回。
2、缓存雪崩
- 缓存同一时间大面积的失效,这个时候又来了一波请求,结果请求都怼到数据库上,从而导致数据库连接异常。
- 方案:
1、缓存时间加随机数,设置不同有效期,避免同时失效。
2、分布式部署,数据分散在不同的redis和数据库。
3、永不过期(不建议)
3、缓存击穿
- 单一 key 非常热点,失效后直接请求数据库。
- 方案:
1、永不过期
2、互斥锁
4、redis 和数据库双写一致性问题
- 分布式常见问题。数据库和缓存双写,就必然会存在不一致的问题。
- 方案:首先,采取正确更新策略,先更新数据库,再删缓存。其次,因为可能存在删除缓存失败的问题,提供一个补偿措施即可,例如利用消息队列。
5、redis 并发竞争 key 问题。
- 同时有多个子系统去set一个key。
- 方案:
1、redis 事物机制,不适用 redis 集群(可能多个 key 不存储在一个 redis-server 上)。
2、(1)如果对这个key操作,不要求顺序
这种情况下,准备一个分布式锁,大家去抢锁,抢到锁就做set操作即可,比较简单。
(2)如果对这个key操作,要求顺序
假设有一个key1,系统A需要将key1设置为valueA,系统B需要将key1设置为valueB,系统C需要将key1设置为valueC.
期望按照key1的value值按照 valueA-->valueB-->valueC的顺序变化。这种时候我们在数据写入数据库的时候,需要保存一个时间戳。假设时间戳如下
系统A key 1 {valueA 3:00}
系统B key 1 {valueB 3:05}
系统C key 1 {valueC 3:10}
那么,假设这会系统B先抢到锁,将key1设置为{valueB 3:05}。接下来系统A抢到锁,发现自己的valueA的时间戳早于缓存中的时间戳,那就不做set操作了。以此类推。
其他方法,比如利用队列,将set方法变成串行访问也可以。总之,灵活变通。
redis 过期策略及内存淘汰机制
以下完全复制来的,博主写的已经非常好了,不需要再修饰了,哈哈哈。
分析:
这个问题其实相当重要,到底redis有没用到家,这个问题就可以看出来。比如你redis只能存5G数据,可是你写了10G,那会删5G的数据。怎么删的,这个问题思考过么?还有,你的数据已经设置了过期时间,但是时间到了,内存占用率还是比较高,有思考过原因么?
回答:
redis采用的是定期删除+惰性删除策略。
为什么不用定时删除策略?
定时删除,用一个定时器来负责监视key,过期则自动删除。虽然内存及时释放,但是十分消耗CPU资源。在大并发请求下,CPU要将时间应用在处理请求,而不是删除key,因此没有采用这一策略.。
定期删除+惰性删除是如何工作的呢?
定期删除,redis默认每个100ms检查,是否有过期的key,有过期key则删除。需要说明的是,redis不是每个100ms将所有的key检查一次,而是随机抽取进行检查(如果每隔100ms,全部key进行检查,redis岂不是卡死)。因此,如果只采用定期删除策略,会导致很多key到时间没有删除。
于是,惰性删除派上用场。也就是说在你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除。
采用定期删除+惰性删除就没其他问题了么?
不是的,如果定期删除没删除key。然后你也没即时去请求key,也就是说惰性删除也没生效。这样,redis的内存会越来越高。那么就应该采用内存淘汰机制。
在redis.conf中有一行配置
# maxmemory-policy volatile-lru
该配置就是配内存淘汰策略的(什么,你没配过?好好反省一下自己)
1)noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。应该没人用吧。
2)allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。推荐使用,目前项目在用这种。
3)allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。应该也没人用吧,你不删最少使用Key,去随机删。
4)volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。这种情况一般是把redis既当缓存,又做持久化存储的时候才用。不推荐
5)volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。依然不推荐
6)volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。不推荐
ps:如果没有设置 expire 的key, 不满足先决条件(prerequisites); 那么 volatile-lru, volatile-random 和 volatile-ttl 策略的行为, 和 noeviction(不删除) 基本上一致。
老规矩,遇到好的文章要分享。
https://mp.weixin.qq.com/s/gEU8HtsQNPXY8bzkK-Qllg