项目中Redis作为分布式缓存,或作为分布式锁都是会被优先考虑到的一种技术,今天记录一些Redis的基础知识点。
一、为啥要用缓存?
- 高性能
假设一个场景:有一个请求过来查询数据库表,耗时500ms。但是这个结果接下来一天可能都不会改变。
那么放在缓存中,下次再有请求过来,就不必再花费500ms去查数据库表,直接2ms搞定,性能提升百倍。 - 高并发
mysql这么重的数据库,压根儿设计不是让你玩儿高并发的,虽然也可以玩儿,但是天然支持不好。mysql单机支撑到2000qps也开始容易报警了。
如果有个系统一秒钟请求有1万,一个mysql单机绝对会挂掉。这时候就需要把数据放缓存中,单机支撑的并发量一秒钟轻松几万几十万,是mysql单机的几十倍。
二、Redis与memcached的区别
- 相比Memcached来说,Redis拥有更多的数据结构和并支持更丰富的数据操作。
- Redis比Memcached速度快。
在Memcached里,你需要将数据拿到客户端来进行类似的修改再set回去。这大大增加了网络IO的次数和数据体积。在Redis中,这些复杂的操作通常和一般的GET/SET一样高效。 - Redis可以持久化数据
- Redis原生支持集群模式
三、Redis的持久化
1. RDB持久化机制
Redis默认采用RDB方式。该方式的特点是,定时执行,保证了高性能。
RDB机制的优点:
- 持久化性能高
- 基于RDB快照文件,来重启恢复redis非常快速
- 缓存文件适合做冷备
RDB机制的缺点:
- 周期性得持久化数据,可能会导致数据的丢失
- 生成快照文件的时候,如果数据文件非常大,可能会导致服务暂停数毫秒甚至数秒。
2. AOF持久化机制
AOF方式,通过将写命令写入文件,使数据得到持久化,并提供了两个选项:同步写入和每秒异步写入。
同步写入,保证了数据写入文件后,再返回结果,避免了宕机丢失数据的问题。每秒异步写入,保证了一定的性能,也保证了最多只丢失一秒的数据。
AOF机制的优点:
- AOF可以更好的保护数据不丢失
- AOF日志文件以append-only模式写入,所以没有任何磁盘寻址的开销,写入性能非常高
AOF机制的缺点
- 对于同一份数据来说,AOF日志文件通常比RDB数据快照文件更大
RDB和AOF到底该如何选择
- 不要仅仅使用RDB,因为那样会导致丢失很多数据。
- 也不要仅仅使用AOF,通过AOF做冷备,没有RDB做冷备,来的恢复速度更快
- 综合使用AOF和RDB两种持久化机制,用AOF来保证数据不丢失,作为数据恢复的第一选择; 用RDB来做不同程度的冷备,在AOF文件都丢失或损坏不可用的时候,还可以使用RDB来进行快速的数据恢复
四、缓存雪崩、穿透、击穿
1. 缓存穿透
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据,导致数据库压力过大
解决方案:
- 采用布隆过滤器,使用一个足够大的bitmap,用于存储可能访问的key,不存在的key直接被过滤;
- 拦截器,id<=0的直接拦截
- 从cache和db都取不到,可以将key-value写为key-null,设置较短过期时间,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
2. 缓存击穿
一个热点数据再过期的一瞬间,有大量的请求打到数据库,导致数据库压力巨大。
解决方案:
- 设置热点数据永远不过期
- 使用setnx加互斥锁
3. 缓存雪崩
大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。
解决方案:
- 过期时间设置成随机
- 设置热点数据永不过期
- 如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中
五、缓存过期策略
- 定期删除:每隔100ms随机抽取一些key检查是否过期,过期了就进行删除
- 惰性删除:获取某个key的时候,redis会检查一下 ,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除,不会给你返回任何东西。
通过上述两种手段结合起来,确保过期的key一定会被干掉。但是实际上这还是有问题的,如果定期删除漏掉了很多过期key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?如果大量过期key堆积在内存里,导致redis内存块耗尽了,怎么办?
这时候就该使用内存淘汰机制,策略如下
- noeviction:当内存不足以容纳新写入数据时,新写入操作会报错,不推荐使用
- allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的)
- allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key,不推荐使用
- volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key
- volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除
六、缓存与数据库的更新顺序
1. Cache Aside Pattern
-
读的时候,先读缓存,缓存没有的话,那么就读数据库,然后取出数据后放入缓存,同时返回响应
-
更新的时候,先删除缓存,然后再更新数据库
2. 为什么是删除缓存,而不是更新缓存呢?
有些时候缓存可能并不是直接从数据库表取出来的值,可能需要经过夺标运算或一些复杂的查询,在这种情况下更新缓存的代价是很高的。
如果你频繁修改一个缓存涉及的多个表,那么这个缓存会被频繁的更新,频繁的更新缓存,但是问题在于,这个缓存到底会不会被频繁访问到?
举个例子,一个缓存涉及的表的字段,在1分钟内就修改了20次,那么缓存跟新20次; 但是这个缓存在1分钟内就被读取了1次,那么大量缓存更新的操作是无意义的。
实际上,如果你只是删除缓存的话,那么1分钟内,这个缓存不过就重新计算一次而已,开销大幅度降低。
删除缓存而不是更新缓存其实也是一种懒加载的思想,等到你去使用的时候再去加载。
七、 Redis为啥单线程效率高
- 纯内存操作
- 核心是基于非阻塞的IO多路复用机制
- 单线程反而避免了多线程的频繁上下文切换问题