文章目录
Redis数据丢失场景
因为Redis是AP模型,所以它的数据是不可靠的,存在一些丢失数据的场景。
持久化丢失
- 采用RDB或者不持久化, 会有数据丢失,因为是手动或者配置以快照的形式来进行备份。
- 采用AOF默认会有1s的数据丢失。
解决方法
启用AOF,以命令追加的形式进行备份,但是默认也会有1s丢失,这是在性能与数据安全性中寻求的一个最适合的方案,如果为了保证数据一致性,可以将配置更改为always,但是性能很慢,一般不用。
# appendfsync always
主从切换
因为主从同步是异步的,主服务器以异步的方式同步给从服务器,这样做提升了性能,但是由于是异步同步到从。所以存在数据丢失的可能。
- master写入数据k1,由于是异步同步到slave,当master没有同步给slave的时候,master挂了。
- slave会成为新的master,并且没有同步k1。
- master重启,会成为新master的slave,同步数据会清空自己的数据,从新的master加载。
- 此时,数据k1丢失。
sentinel脑裂
发生脑裂的时候,会产生2个master,数据丢失过程如下。
-
假如有个sentinel集群与Redis集群,如图
-
当我的master机器(192.168.1.1)跟另外2台发生分区容错,网络断开。但192.168.1.1本身没有挂。
-
sentinel2跟sentinel3数量大于三分之二,满足故障转移条件(sentinel故障转移需要过半),这个时候会从2个slave中选出一个master,此时系统就会存在两个master。这里假设192.168.1.2是第二个master
-
客户端假如连接到sentinel1,数据会写入192.168.1.1;连接到sentinel2,数据就会写入192.168.1.2。同时有2个master写入数据。假如向192.168.1.1写了k1。
-
当网络恢复后,192.168.1.1会变成192.168.1.2的从节点,192.168.1.1的数据全部从192.168.1.2同步。
-
k1丢失。
解决方法
尽量减少主从切换跟sentinel数据丢失的解决办法:
min-replicas-to-write 1 //保证master至少有1个从节点。至少有1个从节点同步主节点的数据,但是由于是异步同步,所以是最终一致性 不会确保有数据写入
min-replicas-max-lag 10 //主从同步的延迟时间必须小于等于10s
Redis缓存跟DB的数据一致性问题
在并发环境下,可能会出现redis跟DB的数据不一致的情况,产生Redis缓存跟DB一致性问题。
怎么产生
- 查询缓存逻辑:
- 请求查询DB之前,先去查询Redis,如果Redis存在,直接返回;如果Redis不存在,从DB查询。
- 从DB查询后,回写到Redis。
- 修改数据的逻辑:
- 修改DB的数据,DB数据修改成功后,删除Redis的数据。
- 下一次查询该数据的时候,会先从DB查询,然后更新到Redis。
- 为什么不在修改的db的同时,修改Redis数据?因为Redis里可以存的是一个json化的数据,修改其中的一个字段的话,效率较低。
- 丢失场景:
- 线程A请求缓存,没有缓存,从DB拿到数据1。
- 线程B将数据1更改为2,并且删除Redis缓存,此时,DB的值为2。
- 线程A更新缓存,将数据1写入redis。
- 此时,redis数据为1 ,db数据为2,出现了数据一致性问题。
所以,数据一致性产生的根本问题,是查询DB 跟操作Redis不是原子性的,所以并发会导致数据一致性问题。
解决方法
强一致性方案(不可取)
- 采用锁机制,不让有并发。
- 在更新的时候,采取锁的机制,不让其他线程进行删除Redis数据的操作。即,更新数据A的时候,其他线程不能查询数据A。这个方法会拖慢整个性能,违背了Redis的初衷。
所以,我们只能采用最终一致性,不应该去保证强一致性
最终一致性方案
- 每个缓存数据(key)设置过期时间
- 设置过期时间,就算这条数据在Redis和DB的值不一致,也只是在有效时间内的不一致。
- Mysql canal等数据同步工具(用的比较少)
- 捕捉到DB的更改,同步到相关Redis,相对比较复杂,要知道每个数据对应的缓存。
Redis缓存雪崩、穿透、击穿问题
缓存雪崩
缓存雪崩就是Redis的大量热点数据同时过期(失效)(或者redis宕机了),因为设置了相同的过期时间,刚好这个时候Redis请求的并发量又很大,就会导致所有的请求落到数据库。
解决方法
- 保证Redis的高可用,(比如使用Redis主从、哨兵、Cluster等方法),防止由于Redis宕机导致数据请求全部落到DB。
- 通过加随机数,使key在不同的时间过期
- 加互斥锁或者使用队列,针对同一个key只允许一个线程到数据库查询。(一般不会用这个方法,会影响性能)。
- 缓存定时预先更新,避免同时失效。
缓存穿透
缓存穿透是指用户一直大量请求一个缓存和数据库中都没有的数据,因为redis没有这个数据,所以请求会全部落到DB。这时的用户很可能就是攻击者,恶意搞你们公司的,攻击会导致数据库压力过大。
解决方法
- 封攻击者的IP。
- 用布隆过滤器。
布隆过滤器
实现原理
redis的布隆过滤器可以用bitMap(位图)这样的数据类型实现。布隆过滤器保存一个bit数组,数组的每个元素只有0和1两种值,其中0代表不存在某个数据,1代表存在某个数据。
存入数据的过程
当一个元素加入布隆过滤器中的时会进行如下操作:
-
使用布隆过滤器中的hash函数对元素值进行计算,返回对应的hash值(一般有多个hash函数得到多个hash值);
-
根据返回的hash值映射对应的二进制集合的下标;
-
将下标对应的二进制数据改成1,如下图。假设要存入一个值“test”,用3个不同的hash函数分别计算出下标2、6、8,然后把数组中下标对应的元素组改为1。
判断某个数据是否存在
当我们需要判断一个元素是否存在于布隆过滤器的时候,会进行如下操作:
-
对给定元素再次用多个hash函数计算hash值;
-
根据返回的hash值判断位数组中对应的元素是否都为 1,如果值都为 1,那么说明这个值在布隆过滤器中,如果存在一个值不为 1,则说明该元素不在布隆过滤器中。如下图,可以看出值“test1”的对应的下标元素值不全为1,所以这个值不在集合中。
误判
- 由上可以看出,布隆过滤器是有可能误判的,因为两个不同的元素的hash计算结果有可能一样,发生冲突。所以,布隆过滤器能判断一定不存在,没法判断数据一定存在。
减少误判的方法
- 增加多种hash函数,对元素多次hash。
- 增大位图的数组的大小。
优点
- 读写速度快。布隆过滤器存储空间和插入/查询时间都是常数(即hash函数的个数)。
- 布隆过滤器不需要存储元素本身,在某些对保密要求非常严格的场合有优势。
- 节省空间。布隆过滤器可以用bitMap来实现。
缺点
- 存在误判。随着存入的元素数量增加,误判率随之增加(误判补救方法是:再建立一个小的白名单,存储那些可能被误判的信息)。但是如果元素数量太少,则使用散列表足矣。
- 不能删除其中的数据。因为一个位可能有其他的元素占有,如果把位图设为0,那么可能db有的数据你就查不到。
使用场景
- 大数据去重,比如判断一个数字是否存在于包含大量数字的数字集中(数字集很大,5 亿以上!);
- 垃圾邮件过滤,从数十亿个垃圾邮件列表中判断某邮箱是否垃圾邮箱;
- 缓存穿透,将已存在的缓存放到布隆过滤器中,当黑客频繁访问不存在的缓存时迅速返回避免缓存及数据库挂掉;
缓存击穿
单个key过期的时候,有大量并发请求这个key,请求落到db,造成压力。
解决方法
- 使用互斥锁,回写redis,并且采用双重检查锁来提升性能!减少对DB的访问。
慢查询分析
- 许多存储系统都会有慢日志查询,提供给开发跟运维来找到哪些指令是比较耗时的。比如Mysql。那么Redis中也会有慢日志查询。
- 但是,Redis的慢查时间只会去统计执行指令的时间,不会统计网络消耗时间。所以没有慢查不代表没有超时。
多慢才是慢查询?
慢查询的标准是可配的,配置如下:
# The following time is expressed in microseconds, so 1000000 is equivalent
# to one second. Note that a negative number disables the slow log, while
# a value of zero forces the logging of every command.
slowlog-log-slower-than 10000 //值以微秒为单位,即1000000us = 1s;如果值为0,就强制记录所有的命令。如果是负数,则禁用慢查询。默认值10ms,查询超过10ms就认为是慢查。
# There is no limit to this length. Just be aware that it will consume memory.
# You can reclaim memory used by the slow log with SLOWLOG RESET.
slowlog-max-len 128 //记录的最大长度,最多存储多少条慢查记录,超过这个值,最早的就会被覆盖。
查看慢查信息
127.0.0.1:6379> slowlog get 10 //查询慢查询记录,10为要查询的数量
1) 1) (integer) 2 //慢查询标志id
2) (integer) 1659806161 //发生的时间戳
3) (integer) 11025 //耗时 11.025ms
4) 1) "EVAL" //执行指令和参数
2) "local size = redis.call('hget', KEYS[1],'size');local hashIterations = redis.call('hget', KEYS[1],'hashIterations');assert(siz... (83 more bytes)"
3) "1"
4) "{product:bloom}:config"
5) "1459688"
6) "5"
5) "192.168.8.23:59236"
6) ""
2) 1) (integer) 1
2) (integer) 1659806161
3) (integer) 11019
4) 1) "hget"
2) "{product:bloom}:config"
3) "hashIterations"
5) "?:0"
6) ""
对慢查进行清理
127.0.0.1:6379> slowlog reset
发现慢查后如何处理
-
检查指令。尽量不使用hgetall, keys *等全量查询的指令。
-
尽量不要存大对象。
- 一般而言,value超过10k的就算大对象了,但是要根据实际业务来考虑。
- 可以把大对象拆分成多个子对象
-
查找大对象的指令
./redis-cli -p 6380 --bigkeys
Redis的阻塞分析
- 首先,业务记录好相关日志,通过降级、报警等系统能够知道redis是否发生了阻塞。
- 发生阻塞的原因主要有几点:
- 外部原因:网络阻塞、CPU竞争等
- 内部原因:(1)指令执行时间长,如keys *;(2)数据结构不合理,保存一些大key;(3)fork子进程阻塞,比如说aof刷盘阻塞。
参考资料
- 《咕泡云课堂》
- redis官网
https://redis.io/docs/stack/bloom/
- 布隆过滤器
https://mp.weixin.qq.com/s/t2GFWaFZtz6TtcTiCbe4lg