redis过期key监听产生大量线程,导致内存溢出

redis过期key监听产生大量线程,导致内存溢出

1.在说这个问题前,先了解redis通过editor进行注入的有趣实例
在这里插入图片描述
这种方式可以将StringRedisTemplate对象注入成valueOpeartions
通过spring源码在IOC初始化的时候会调用AbstractBeanFactory的doGetBean(),方法中调用了convertIfNecessary()方法.
在这里插入图片描述
通过调用链最终调用了TypeConverterDelegate的convertIfNecessary()方法,该方法中获取所有实现了PropertyEditor接口的类,并执行setValue()和getValue()方法.
在这里插入图片描述
而对于ValueOperations有个对应的ValueOpeationsEditor,该类正好是PropertyEditor的子类,并重写setValue()方法,完成类型转换的注入.
在这里插入图片描述

2.再聊聊redis中key的过期策略
2.1 redis默认的过期策略是惰性删除+定期删除
2.2.1惰性删除:
每次对redis进行任何操作之前,redis都会执行一个检查方法,该方法中有对过期key的处理策略,那就是如果key过期,那么将从redis中删除key.

过期键的惰性删除策略由expireIfNeeded函数实现,所有读写数据库的Redis命令在执行之前都会调用expireIfNeeded函数对输入键进行检查:

  • 如果输入键已经过期,那么将输入键从数据库中删除
  • 如果输入键未过期,那么不做任何处理

以上描述可以使用如下流程图表示:
在这里插入图片描述
实现原理(expireIfNeeded函数)
expireIfNeeded函数就像一个过滤器,它可以在命令真正执行之前,过滤掉过期的输入键,从而避免命令接触到过期键
惰性删除策略由db.c/expireIfNeeded函数实现,所有读写数据库的Redis命令在 执行之前都会调用expireIfNeeded函数对输入键进行检查:
如果输入键已经过期,那么expireIfNeeded函数将输入键从数据库中删除
如果输入键未过期,那么expireIfNeeded函数不做动作
在这里插入图片描述
因为每个被访问的键都可能因为过期而被expireIfNeeded函数删除,所以每个命令的实现函数都必须能同时处理键存在以及键不存在这两种情况:
当键存在时,命令按照键存在的情况执行
当键不存在或者键因为过期而被expireIfNeeded函数删除时,命令按照键不存在的情况 执行
举个例子,下图展示了GET命令的执行过程,在这个执行过程中,命令需要判断键是 否存在以及键是否过期,然后根据判断来执行合适的动作
在这里插入图片描述
2.2.2定期删除
在redis的配置文件中有个hz 10 ,databases 16 的默认配置, hz是redis检查过期key的频率,单位是ms,就是说redis每次会以hz 10检查16个数据库中的数据,加载20个数据,如果过期的key满足四分之一的比例,那么会继续检查,一直到过期key不满足这个条件.
综合以上来说,redis中已经过期的key如何不对其进行任何操作,那么key是有可能存在aof文件中的.当然,对该key执行get时会执行惰性删除.
2.2.3内存淘汰策略
Redis的LUR一共有6种
· noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。
· allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。
· allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。
· volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。
· volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。
· volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。
在redis的配置文件中默认值# maxmemory-policy noeviction
这个感兴趣的可以自己尝试修改配置进行测试,但个人认为redis的默认过期策略已经比较优秀了.
3.进入正题,剖析redis监听过期key产生jvm内存溢出的原因和解决方法.
3.1 打开源码在RedisHttpSessionConfiguration类中@Bean了RedisMessageListenerContainer对象.
同时也户注入bean的名称为springSessionRedisTaskExecutor的Executor taskExecutor
在这里插入图片描述
在这里插入图片描述
3.2 RedisMessageListenerContainer中判断如果Executor taskExecutor,为null,则注入默认的异步线程池,也即SimpleAsyncTaskExecutor. 该线程池的默认策略可根据源码中的execute()方法分析出,在没有开启限流的情况下,这个线程池是每次key过期都会开启一个新的线程,如果该异步方法存在阻塞的情况,过期的key又是大量的话,可能会导致JVM的内存溢出.
在这里插入图片描述
在这里插入图片描述

3.默认pringSessionRedisTaskExecutor线程池是没有注入的,所以使用的是默认线程池SimpleAsyncTaskExecutor.根据源码分析,如果想使用自定义的线程池,避免内存溢出的问题,则可以使用以下代码注入自定义线程池.
替换之前导入依赖,这个同时也是分布式session的一种解决方案.

org.springframework.session
spring-session-data-redis

@Bean(name = “springSessionRedisTaskExecutor”)
public ThreadPoolTaskExecutor springSessionRedisTaskExecutor(){
ThreadPoolTaskExecutor executor=new ThreadPoolTaskExecutor();
executor.setCorePoolSize(20);
executor.setMaxPoolSize(40);
executor.setKeepAliveSeconds(300);
executor.setQueueCapacity(50);
executor.setThreadNamePrefix("Spring session redis executor thread: ");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
executor.initialize();
return executor;
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值