在Redis的运维使用过程中你遇到过那些问题,又是如何解决的呢?本文收集了一些Redis的常见问题以及解决方案,与大家一同探讨。
你的Redis有bigkeys吗?
什么是bigkeys
bigkeys是指key不恰当设定,抑或是key对应的value值占用内存空间过大;具体表现为以下几种情形:
- key值不恰当设定(比较少见),key设定冗长
- String类型 value值长度过大
- Hash,List,Set,Zset 包含元素个数过多
bigkeys有什么危害
为什么我们必须警惕bigkey呢?其实bigkey主要有以下几个方面的危害:
- 内存使用不均匀,例如:在Redis-Cluster模式中,bigkey会造成节点内存使用不均匀。
- 超时阻塞,由于Redis是单线程架构,操作bigkey耗时较长,有可能造成Redis阻塞。
- 网络拥阻,例如:一个bigkey占用空间是1M,每秒访问1000次,将造成1000M的流量,可能造成打满机器带宽。
当然,如果bigkey访问频率不高,也仅会造成节点间内存使用不均;而当bigkey访问频繁时,其带来的影响是不可想象的,所以日常在开发运维的过程中应该警惕bigkey的存在。
如何找到bigkeys
了解到bigkey危害,我们该如何发现bigkeys呢?
Redis在设计之初就考虑到bigkeys的问题,我们可以使用 redis-cli --bigkeys 来发现bigkeys的分布情况;之后你如果想进一步了解bigkeys的具体情况可以使用 debug object <key> 来确定该key的具体信息。参考以下示例:
利用redis-cli --bigkeys找到bigkey,具体生产环境执行时强烈建议在从节点实行,如果担心OPS太高,可以使用 -i 0.1 ,表示没100条scan命令休眠0.1秒;其实该命令实现的原理就是利用我们常用的scan + type + strlen/hlen/llen/scard/zcard 命令实现的,具体可以从redis-cli.c的源码中探寻。
[root@VM_0_16_centos src]# redis-cli -p 6380 --bigkeys
# Scanning the entire keyspace to find biggest keys as well as
# average sizes per key type. You can use -i 0.1 to sleep 0.1 sec
# per 100 SCAN commands (not usually needed).
[00.00%] Biggest string found so far 'h' with 1 bytes
[00.00%] Biggest string found so far 'hello' with 105 bytes
[00.00%] Biggest string found so far 'heml' with 1434 bytes
-------- summary -------
Sampled 3 keys in the keyspace!
Total key length in bytes is 10 (avg len 3.33)
Biggest string found 'html' has 1434 bytes
0 lists with 0 items (00.00% of keys, avg size 0.00)
0 hashs with 0 fields (00.00% of keys, avg size 0.00)
3 strings with 1540 bytes (100.00% of keys, avg size 513.33)
0 streams with 0 entries (00.00% of keys, avg size 0.00)
0 sets with 0 members (00.00% of keys, avg size 0.00)
0 zsets with 0 members (00.00% of keys, avg size 0.00)
执行结果是发现String类型的"html"为bigkey,我们紧接着来了解"html"的具体信息;使用
debug object <key> 命令,还有如果是对于元素个数较多的数据结构,该命令可能会阻塞redis实例,所以强烈建议在从节点执行
[root@VM_0_16_centos src]# redis-cli -p 6380
127.0.0.1:6379> debug object html
Value at:0x7f0b13a665c0 refcount:1 encoding:raw serializedlength:251 lru:12181323 lru_seconds_idle:229
127.0.0.1:6379> strlen html
(integer) 1434
我们发现key为"html"的String类型的value长达1434个字节,以上便是演示查找bigkeys的过程;除了以上方式我们可以在bigkeys影响redis正常提供服务之前,通过 scan + debug object 对怀疑的bigkeys进行逐个检查。
当然你如果担心执行相关命令会对正式环境有一定的影响,你也可以通过对RDB进行备份,然后根据RDB文件的结构,对RDB中的数据进行逐个分析同样的可以找到bigkey,不过这种方式的开发成本会有些高;使用者可以依据自己的实际情况来酌情判断。
如何处理bigkeys
经过一番折腾,我们终于找到bigkeys了,那么我们应该如何处理它呢?
在Redis4.0之前版本,由于DEL命令是同步删除的,针对String类型的bigkeys确实可以使用DEL命令,删除速度相对较快,一般不会阻塞redis;然而对于元素个数较多的数据结构,使用DEL命令来删除可能会阻塞redis实例;针对 Hash 结构,我们可以利用 HSCAN + HDEL 删除元素的成员,成员删除之后再利用 DEL 删除key;其余数据类似都是渐进的方式先删除成员,再删除key。
Redis4.0版本之后则支持了Lazy Delete Free模式,你可以使用 UNLINK 命令来删除bigkeys,它的实现是异步的,具体可以从redis-cli.c的源码中探寻,你需要先确认打开了lazyfree相关配置。
bigkeys总结
bigkeys的表现形式是内存分配不均;频繁操作的实际影响是有可能造成超时阻塞,网络拥阻;解决思路是事前监控,事中找到bigkeys,并通过正确的方式删除bigkeys。
你的Redis有hotkeys吗?
什么是hotkeys?
hotkeys是在Redis实例中某些key的操作频次远高于其他key,那么这些被频繁操作的热点key我们就称之为hotkeys。
hotkeys有什么危害?
hotkeys有什么危害呢?以Redis-Cluster模式为例,存在hotkeys的节点,将面临以下挑战:
- 请求分配不均,存在hotkeys的节点面临较大的访问压力
- 缓存击穿,hotkeys过期时,大量请求将直接导向DB
- 缓存雪崩,击垮存在hotkeys的节点,导致不能正常提供服务
如何发现hotkeys呢?
Redis4.0之后客户端提供了hotkeys发现的相关命令,我们可以通过 redis-cli --hotkeys 来发现hotkeys;
Redis4.0之前我们也可以通过客户端,代理端,服务端,机器端等多个方面来发现hotkeys:
- 在客户端建立全局字典表,对key和调用次数进行统计;缺点:侵入客户端
- 如果你的集群是通过proxy + redis 的方式搭建的,那你可以很方便的从代理端对key和调用次数进行监控;缺点:限制代理模式的集群
- 在服务端可以利用 monitor,对服务端接收的请求进行监控;缺点:侵入服务端,在高并发情况下会使内存暴增,适合短时间使用
- 如果你不想侵入服务端与客户端,可以对服务端接收的请求进行抓包,分析以及监控;例如使用ELK(Elasticsearch + Logstach + kinbana)用packetbeat进行抓包。
如何处理hotkeys?
我们了解到hotkeys的危害,并可以通过技术手段找到hotkeys以后,我们该怎么对系统做优化呢?
首先针对hotkeys过期,面临的重建问题,可以使用以下有效手段来尽可能的减少key重建的过程:
- 设置互斥锁,保证由一个线程完成热点key的重建,避免大量的请求直接导向DB
- "永不过期",将hotkeys的过期时间设置较长的时间,或者永不过期;等待hotkeys触发的热点事件过去后再考虑过期。
针对Redis集群的优化,包括但不限于以下几种方式:
- hotkeys表现就是请求分配不均;我们可以以此为出发点,来想办法使请求尽可能的分布平均;例如:利用<hotkeys_n,value> ,n为随机数,尽可能的多个实例都有该数据,在访问时在n的范围内取随机数以此来分摊请求;此方式需要一定的代码改造;
- 本地缓存,此方式需要对热点信息有预知,例如:电商产品大促,热点产生在可以预知的范围内,便可以考虑此方式;
- 集群的热点数据的节点的扩容,此方式原理同第一种方式相同,不同点在于不需要对代码进行改造,而是直接增加hotkeys对应节点的数据副本,使多个节点都具备提供该数据的读取能力,以此来均衡请求。
hotkeys总结
hotkeys的表现形式是请求的分配不均,问题恶化将导致Redis集群请求倾斜,甚至集群雪崩,我们可以通过多种途径来均衡请求,避免单个节点过热;如果hotkeys的超时实现过短,可能会导致大量请求涌入到DB,并发重建key,可以通过合理的锁机制或者设置合理的超时时间来避免。
Redis缓存穿透
什么是缓存穿透
缓存穿透是大量请求的key在缓存中没有,直接请求到DB,使缓存失去保护数据库的作用;例如:黑客刻意构建大量缓存中没有的key,导致每次处理请求都需要去访问数据库。
正常缓存处理流程
正常缓存流程.png缓存穿透处理流程
缓存穿透.png缓存穿透的流程便是故意构建缓存中没有的key导致,所有的请求必须查库;缓存失去其保护数据库的意义。
解决缓存穿透
通过以上流程图我们对缓存穿透有了一定的了解,那该如何解决此类问题呢?通常解决的方式有两种:
(1) 对空值进行缓存,设置较短的失效时间;
缓存空结果.png分析:我们对null进行缓存,Redis需要更大的内存空间;此方案适用于请求key变化不频繁的情况;如何黑客恶意攻击,每次构建的不同的请求key,这种方案并不能从根本上解决此问题。
(2) 使用布隆过滤器,布隆过滤器优势在于检索一个元素是否在一个集合内;我们可以利用布隆过滤器来判断请求的key是否在合理的范围内,如果不存在,则直接过滤掉。
布隆过滤器.png分析:可以利用Redis的 BitMap来实现布隆过滤器,用其来缓存目标数据集变化不频繁,而请求key变化频繁的情况。
Redis缓存雪崩
什么是缓存雪崩
缓存雪崩是指由于缓存集中过期或者缓存不可用,导致大量请求直接导向数据库。在高并发的情况下,巨大的请求量有可能导致数据库的崩溃,甚至导致整个应用体系的全盘崩溃;缓存失去保护是数据库的作用,而巨大导致流量流向数据库就是缓存雪崩的表现形式。
如何预防及避免
- 设置合理的过期策略,避免缓存集中过期。
- hotkeys分片存储,避免请求数据的倾斜,导致缓存。
- hotkeys设置合理的过期时间或者“永不过期”。
Redis阻塞
我们知道Redis是单线程模型,如果线上Redis发生阻塞对整个应用将是毁灭性的;那什么原因会导致Redis阻塞呢?
API或数据结构使用不合理
常见的是在生产上执行时间复杂度高的命令如: KEYS,可以通过RENAME 方式将命令修改为不易猜测的,避免开发运维人员的不当执行。
数据结构的不合理,如存在频繁操作bigkeys,有可能造成阻塞,将bigkeys拆分成成员较小的key。
CPU饱和
Redis单实例OPS可以到达平均10W+左右,如果你的Redis实例OPS已经达到较高的数值,那你可以考虑集群的水平扩展,来降低实例的OPS;但是你的Redis实例OPS不高,CPU使用率较高,那你应该检查应用是否使用了时间复杂度较高的命令。
持久化阻塞
我们知道Redis可以进行持久化来防止数据的丢失;RDB方式,主进程会fork一个共享内存子进程来创建RDB文件,如果fork耗时过长,必然将阻塞主进程。
AOF刷盘,通常我们设置的刷盘策略是everysec,但由于磁盘压力过大,fsync有可能耗时较长,当时间大于1秒时,为了保证数据安全,下次fsync调用将阻塞知道上次调用结束。
其他原因
CPU竞争:Redis是CPU密集型应用,应避免跟其他CPU密集型应用部署在一起
内存交换:Redis由于从内存中直接读取,所以响应速度很快;当内存严重不足时,可能会存在内存交换,这将影响Redis的执行效率;通过cat /proc/$pid/smaps | grep Swap 来确认是否有频繁的内存交换。
网络问题:连接数限制或者网络延时等也有可能导致阻塞
总结
本文介绍了bigkeys,hotkeys,缓存穿透,缓存雪崩,阻塞等问题;然而我们在实际应用中难免会遇到各种各样的问题,本文难以一一列举;但是面对问题我们要沉着冷静,了解清楚问题的现象与本质,找到问题的症结所在,随后在对症下药便可以解决问题。
网友评论