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过期,那么在get key时会删除key. 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; }
