redis笔记

redis笔记

Posted by WangZiTao on Tuesday, July 20, 2021

1 缓存异常

1.1 缓存雪崩

缓存雪崩是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。

解决方案 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。

一般并发量不是特别多的时候,使用最多的解决方案是加锁排队

给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新数据缓存。

1.2 缓存穿透

缓存穿透是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。

解决方案 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;

从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。
这样可以防止攻击用户反复用同一个id暴力攻击

采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力

1.3 缓存击穿

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。 和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

解决方案 设置热点数据永远不过期。 加互斥锁,互斥锁

1.4 缓存预热

缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!

2 redis除了做缓存,还可以用来做什么

分布式锁
防重提交
分布式限流
简易版消息队列
共享session
延时任务(redisson)

3 redis锁如何保证任何时刻有且只有一个线程持有这个锁

4 如何保证分布式锁不产生死锁

复现:设置key后系统挂了,锁无法释放
解决方案:使用命令 setnx key value ex seconds 设置key和它的过期时间,到了指定时间,锁自动释放

5 如何防止释放别的线程锁

复现:线程A 拿到锁并设置过期时间10秒,但是业务逻辑时间为12秒,这是线程B拿到锁执行业务,线程A的业务执行完后,释放锁,这时释放的事线程B的锁,线程B释放锁时失败

解决方案:根据线程id生成uuid,设置到redis key对应的value里面去, 释放锁时判断uuid是否相同,相等释放锁,不相等,直接结束。

6 分布式锁到期了,业务没有执行完,如何保证数据一致性

复现:线程A 拿到锁并设置过期时间10秒,但是业务逻辑时间为12秒,这是线程B拿到锁执行业务逻辑,会产生数据不一致

解决方案: 采用开门狗机制不断对锁续期。

开门狗机制: 获取锁后启动一个定时任务,每隔三分之一默认过期时间判断是否持有当前线程锁,持有的话续期,没有持有的话,结束定时任务
    1默认过期时间为30s
    2是一个定时任务,默认10s扫描一次
    3 业务没执行完,线程还持有锁,自动续期

看门狗的问题是:获取锁成功后需要创建一个监听器消耗了cpu资源,高并发场景下会影响性能,在分布式锁的上层加个本地锁,先用本地锁拦截

7 分布式锁如何实现可重入?

可重入锁的要点是对于同一线程可以多次获取锁,不同线程之间同一把锁不能重复获取。

如何保证线程可重入获取锁?
	获取锁时判断是否是当前线程,如果是当前线程时,锁重入次数+1,解锁时相反。

8 分布式锁如何解决redis主从节点不同步导致锁失效

复现:主节点没来得及把刚刚加锁的数据同步给从节点,主节点挂了,哨兵重新选举一个从节点为主节点,这是新的主节点没有锁的信息,其他线程过来可以加锁,导致锁失效。

采用红锁算法解决这个问题,红锁算法认为,只要(N/2)+1个节点加锁成功,那么久认为是加锁成功。生产不太推荐

如果真的需要这种强一致性的分布式锁的话,使用zk实现的分布式锁,性能比这个红锁的性能好。

9 分布式锁高并发下如何优化

复现:分布式锁业务逻辑时长为20ms,那么1秒只能执行五次

优化方案,借鉴ConcurrentHashMap的设计思想,采用分段锁,比如可以分成三段,每段都有三分之一的库存,然后hash用户id分别引流到不同段,性能提升三倍

10 不同场景如何选择分布式锁实现方案

  1. redis分布式锁 性能非常高,比较推荐
  2. zk实现分布式锁 可以选用ZOkkkeper作为分布式锁,采用cap理论中的cp模型保证高可用,性能不如redis,但是锁百分百可靠。
  3. mysql实现分布式锁

11 redis大key解决方案

redis大key经典问题生产问题分析 例如歌曲孤勇者,收藏接口,缓存是歌曲id为key,value是收藏的用户,如果收藏的用户过多,几万甚至更多,获取缓存时非常慢

11.1 redis 大key基本概念及常见场景:

所谓的redis大key问题是某个key对应的value比较大,所以本质还是打value问题。
key是开发过程中可以自行设置,可以控制大小,value往往不受程序控制跟业务场景有关系,因此可能导致value很大

11.2 基本概念:

在redis中,大key指的是key对应的value值所占用的空间比较大

  • value是string类型时,大小控制在10kb以内
  • value是hash,list,set,zset等集合类型时,元素数不要超过5000(或者1万,几万)

11.3 常见场景:

  • 大key的产生往往是业务方设计不合理,没有预见value的动态增长问题
  • 一直往value中塞数据,没有删除及过期机制,迟早要爆炸
  • 数据没有合理做分片,将大key变成一个个小key

11.4 场景:

  1. 社交类,某些明星或者d大V的粉丝列表
  2. 统计类,统计某游戏活动玩家榜单列表
  3. 缓存类,将数据从数据库获取出来序列化到redis里

例子:遇到一个例子,一个用户将某明星一个专辑下所有视频信息都缓存到一个巨大的json中,造成这个json打到6Mb,后面用户查询此信息时,redis大key瞬间扛不住了。

11.5 redis大key造成的影响:

  1. 客户端超时阻塞:由于Redis 单线程的特性,操作 bigkey 的通常比较耗时,也就意味着阻塞 Redis 可能性越大,这样会造成客户端阻塞或者引起故障切换,会出现各种redis 慢查询中
  2. 内存空间不均匀:集群模式在 slot 分片均匀情况下,会出现数据和查询倾斜情况,部分有大 key 的Redis 节点占用内存多,QPS 高
  3. 引发网络阻塞:每次获取大 key 产生的网络流量较大,如果一个 key 的大小为 1MB,每秒访问量为 1000,那么每秒会产生 1000MB 的流量。这对于普通千兆网卡的服务器来说是灾难性的
  4. 阻塞工作线程:执行大key删除时,在低版本redis中可能阻塞线程。

11.6 redis大key如何检测:

  1. 改写 redis 客户端,在 sdk 中加入埋点,实时上报数据给 redis 大 key 检测平台、监控告警
  2. scan + debug object bigkey 命令。循环遍历 redis key 序列化后的长度。debug object bigkey可能会比较慢,它存在阻塞 Redis 的可能,建议在从节点执行该命令,官方不推荐
  3. scan + memory usage 。该命令是在 Redis 4.0+ 以后提供的,可以循环遍历统计计算每个键值的字节数。
  4. 通过 python 脚本迭代的 scan key,对每次 scan 的内容进行判断是否大 key。
  5. redis-cli –bigkeys 。可以找到某个 redis 实例 5 种数据类型(string、hash、list、set、zset)的最大 key。但如果 redis key 比较多,执行该命令会比较慢,建议在从节点执行该命令
  6. rdbtools 开源工具包。rdbtools 是 python 写的一个第三方开源工具,用来解析 Redis 快照文件,redis 实例上执行 bgsave,然后对 dump 出来的 rdb 文件进行分析,找到其中的大 key

11.7 redis如何删除:

如果对这类大 key直接使用del命令进行删除,会导致长时间阻塞,甚至崩溃。因为del命令在删除集合类型数据时,时间复杂度为 O(M),M是集合中元素的个数。Redis是单线程的,单个命令执行时间过长就会阻塞其他命令,容易引起雪崩。那我们怎么解决呢?

11.7.1 主动删除大 key

  1. 分批次渐进式删除
    一般来说,对于 string 数据类型使用 del 命令不会产生阻塞。其它数据类型分批删除,通过 scan 命令遍历大 key,每次取得少部分元素进行删除,然后再获取和删除下一批元素。对 Hash, Sorted Set, List,Set 分别处理,思路相同,先对 key 改名进行逻辑删除,使客户端无 法使用原 key,然后使用批量小步删除。

删除大 Hash 步骤:

  • key 改名,相当于逻辑上把这个 key 删除了,任何 redis 命令都访问不到这 key了
  • 小步多批次的删除
  1. 采用 unlink + bigkey 异步非阻塞删除。这个命令是在 redis 4.0+提供的代替 del 命令,不会阻塞主线程

11.7.2 被动删除大 key

被动删除是指利用 redis 自身的 key 清除策略,配置 lazyfree 惰性删除。但是参数默认是关闭的,可配置如下参数开启,如下所示:

lazyfree-lazy-expire on #过期惰性删除
lazyfree-lazy-eviction on #超过最大内存惰性删除
lazyfree-lazy-server-del on #服务端被动惰性删除

12 redis 大 key 如何设计与优化

主要针对以下两种经典场景进行优化:

12.1 单个 key 存储的 value 很大(超过 10kb)

  1. 从业务角度评估,value 中只存储有用的字段,尽量去掉无用的字段。
  2. 可以考虑在应用层先对 value 进行压缩,比如采用 LZ4/Snappy 之类的压缩算法,配合 redis客户端序列化配置,可以无侵入完成 value 的压缩。
  3. value设计的时候越小越好,关联的数据分不同的 key 进行存储。
  4. 大key分拆成几个 key-value, 使用multiGet 获取值,这样分拆的意义在于分拆单次操作的压力,将操作压力平摊到多个 redis 实例中,降低对单个 redis 的 IO 影响。
  5. 对redis集群进行扩容

12.2 集合数据类型

hash ,list,set ,sorted set 等存储过多的元素(超过 5000 个)

类似于场景一种的第一个做法,可以将这些元素分拆。

以 hash 为例,原先的正常存取流程是 hget(hashKey, field) ; hset(hashKey, field, value)
现在,我们可以分拆构建一个新的 newHashKey ,具体做法:固定一个桶的数量,比 10000,每次存取的时候,先在本地计算field的hash值,取模 10000,确定了该field落在哪个 newHashKey 上。

newHashKey = hashKey + (*hash*(field) % 10000);
hset (newHashKey, field, value) ;
hget(newHashKey, field) ;

set, sorted set, list 也可以类似上述做法.


comments powered by Disqus