Welcome everyone

诡异的redis key 逐出

redis 汪明鑫 2639浏览 0评论

前言

相信大多数老哥和我抱有同样的想法,redis key被淘汰是因为key新增导致内存不足,然后触发redis淘汰策略,

然而真相并非仅仅如此,在一次线上事故中大量redis key被淘汰,由此领略到redis的骚操作,不讲武德 …

 

背景

数百个API实例去redis拉数据构建本地缓存

每天早上一个同步数据任务从hive表拉取30w个userid存储在redis

数据结构采用了set,30w个userid分了1000个set,大概每个set300个左右名单

 

现象

在同步任务数据跑完后,大量key淘汰

线上的key被干掉,导致数据不完整,已经是很严重的问题了

 

定位问题

redis key被逐出,要么集中过期淘汰,要么内存满了触发淘汰策略,

然后这些数据过期时间7天,排除掉,只能由于redis内存的原因触发了key淘汰

百度谷歌也都是千遍一律写操作导致key逐出,我们先从数据写入排查

数据同步任务,30w个userid每批pipline sadd 200个,跑了10几分钟

我寻思对redis没有一点压力啊。。。看了下内存使用率,连一小半都没用到!

那问题出在哪呢?

set编码格式有两种,一种是intset, 另一种是hash

 

set有2种编码格式,我写入redis的是userid,redis为了节省内存空间,使用intset编码格式

当set成员大于512时,才会升级为hash的编码格式

30w个数据分了1000个set,每个set也就300个左右,没达到512,因此会使用intset编码格式,inset插入操作是o(n)复杂度,可能会增加时间复杂度,耗内存

但是这是redis底层是统一的,也不至于intset sadd就内存打满吧

 

 

redis key被逐出是由于内存使用超过了设置的maxmemory

key 逐出时的redis内存使用峰值,1.07G,已经超过了maxmemory

当前集群的maxmemory只有500M

 

used_memory > maxmemory,触发key的淘汰,我们公司key的淘汰策略是allkeys-lru

maxmemory != redis节点物理内存,一般是物理内存的百分之多少,可以动态设置

used_memory 包括自身内存、对象内存、redis的缓冲区大小。

那究竟是什么命令把redis内存打爆了呢

10几分钟sadd 30w个数据不可能吧。。。

 

最后发现是由于redis缓冲区被打爆

缓冲区的功能其实很简单,主要就是用一块内存空间来暂时存放命令数据,以免出现因为数据和命令的处理速度慢于发送速度而导致的数据丢失和性能问题。但因为缓冲区的内存空间有限,如果往里面写入数据的速度持续地大于从里面读取数据的速度,就会导致缓冲区需要越来越多的内存来暂存数据。当缓冲区占用的内存超出了设定的上限阈值时,就会出现缓冲区溢出。

 

 

那我们分析一下哪里会有大量的命令发向redis,就是跑完同步数据,触发更新缓存的那一刻,

api集群有几百个实例,去拉取redis中的数据构建本地缓存

命令的写入大于从里面读取的速度,造成缓冲区被打满,触发了key被大量淘汰

涨姿势了,读操作尽然也会触发key被淘汰,以前潜意识任务只有写操作才会打满内存,导致key被淘汰

得到这样的结论,当然得去本地验证一波

 

本地验证

本地起一个redis实例

127.0.0.1:6668> info
# Memory  我们主要关注这一部分
used_memory:825312
used_memory_human:805.97K
used_memory_rss:2129920
used_memory_rss_human:2.03M
used_memory_peak:886064
used_memory_peak_human:865.30K
total_system_memory:1928933376
total_system_memory_human:1.80G
used_memory_lua:41984
used_memory_lua_human:41.00K
maxmemory:0
maxmemory_human:0B
maxmemory_policy:noeviction
mem_fragmentation_ratio:2.58
mem_allocator:jemalloc-4.0.3

 

设置一下 maxmemory、maxmemory_policy

127.0.0.1:6668> CONFIG GET maxmemory
1) "maxmemory"
2) "0"
127.0.0.1:6668> CONFIG SET maxmemory 1MB
OK
127.0.0.1:6668> config set maxmemory-policy allkeys-lru
OK

 

127.0.0.1:6668> keys *
 1) "str"2) "name"3) "1"4) "super carry"5) "tian"6) "msg"7) "haha"8) "city"

 

本地跑个测试,pipline读50w个key

/**
 * @author: wangmingxin03
 * @date: 2020-11-23
 */
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class RedisTest {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Test
    public void testQuery(){
        stringRedisTemplate.executePipelined(new SessionCallback<Object>() {
            @Overridepublic Object execute(RedisOperations operations) throws DataAccessException {
                for (int i = 0; i < 500000; i++) {
                    operations.opsForValue().get("name");
                }
                return null;
            }
        });

    }
}

 

再info一下

# Memory
used_memory:822048
used_memory_human:802.78K
used_memory_rss:3862528
used_memory_rss_human:3.68M
used_memory_peak:5550024
used_memory_peak_human:5.29M
total_system_memory:1928933376
total_system_memory_human:1.80G
used_memory_lua:41984
used_memory_lua_human:41.00K
maxmemory:1048576
maxmemory_human:1.00M
maxmemory_policy:allkeys-lru
mem_fragmentation_ratio:4.70
mem_allocator:jemalloc-4.0.3

 

used_memory_peak已经到5M了,redis使用内存峰值已经超过了maxmemory

看下我们的key是否都还健在?

127.0.0.1:6668> keys *
(empty list or set)

卧槽,全都被干没了。。。

验证了读操作也可能导致key被逐出,并不是只有写操作导致内存不足触发key逐出

通过redis扩容(maxmemory扩容 + proxy扩容)

 

redis timeout

通过扩容勉强解决上面的问题,又报了个redis 连接超时的问题

构建本地缓存时,redis 连接 timeout

通过监控我们发现proxy 流量进出飙升

 

proxy进出流量太大,说明redis 请求命令输入太多和数据返回太大

redis的long返回会转成string

 

我们暂时认为一个userid转成string平均占10个字节

400个实例 * 30w个string * 平均10字节

 

返回数据量太大,把proxy撑满,导致连接超时。

 

 

如果定位是redis可能出现问题,不要死扣代码和日志,多看redis的监控,可以快速定位一些问题。

比如内存使用占比,进出流量,key的数量,集群qps,error数量

 

优化

1、redis集群扩容(包括maxmemory、proxy)

2、本地构建缓存打散时间由 5s -> 1min (延迟打散时间)

3、一次pipeline请求1000个set -> 100个set (对pipeline再partition一下)

 

转载请注明:汪明鑫的个人博客 » 诡异的redis key 逐出

喜欢 (10)

说点什么

您将是第一位评论人!

提醒
avatar
wpDiscuz