redis命令大量超时 连接数突增

大家好,我是烤鸭:
     今天分享一个线上线上redis命令大量超时,连接数突增的问题。由于不是我这边的业务,只能根据事后的一些客观数据进行分析。

配置:

​ redis 4.0 3主3从,总内存36G。

​ 业务服务7台。

​ 框架 j2cache-2.8

现象

由于有一个需求需要向redis刷入大量数据,在内存刷到一定量时出的问题。

17:20左右 redis 内存达到95%后,触发内存报警。停写后key和内存下降,内存碎片升高。

在这里插入图片描述

在这里插入图片描述

18:20有两个redis节点连接数缓慢增加。

18:50 连接数增长明显,最高为原来的7倍,此时超时明显。

在这里插入图片描述

执行redis命令超时,一个get操作耗时几百耗时甚至几秒。

在这里插入图片描述

重启业务服务后连接数下降,业务恢复。

原因猜测

平时redis内存使用在70%以下,这次刷了很多数据,导致redis升至95%,在停写之后部分key过期或者可能触发了lru,产生了大量的内存碎片。

redis 碎片率记计算公式:

Redis 内存碎片率的计算公式:mem_fragmentation_ratio (内存碎片率)= used_memory_rss (操作系统实际分配给 Redis 的物理内存空间大小)/ used_memory(Redis 内存分配器为了存储数据实际申请使用的内存空间大小)
意思大概是 redis 总申请内存(包含连接等占用的) / redis 数据内存

所以会出现两种情况

  • 申请大内存时候 ,过期清理的内存不够支撑 会向系统申请新的内存 导致 总内存/数据内存=碎片率 升高
  • 数据过期后 内存正常情况下申请的内存空间是不会回收的, 导致数据内存变小,总内存不变, 所以 总内存/数据内存 =碎片率 升高

我们的系统redis使用是没有大key的,所以倾向于第二种情况。

内存碎片的产生时间和停大量写redis的时间一致,但是连接数大概40分钟后开始增长趋势。

后来就一直在想2个问题:

  • 连接数增长的原因?和内存碎片是否有关系?
  • 连接数达到峰值,大量的超时的真实原因是什么?重启业务服务后连接数下降,业务恢复。

问题分析

第一个问题,我们只能从业务源码上进行分析,最近提交的代码和操作。

  • 回源:在一些场景增加了回源写入redis的操作,原来是如果redis没有,就查库。现在是查库后多了一次redis写入,过期时间2小时。

  • 刷数据:有一些别的业务线刷入数据,过期时间30天。

在内存达到90%,刷数据停止写入。内存达标95%,回源停止写入。

项目也是几年没动过了,操作redis的框架:J2Cache,一款国产框架。

由于我们配置了一级缓存和二级缓存,一级本地和二级redis。

操作一级缓存(新增、修改、删除)的时候会通过redis的广播通知其他节点。

后来查了资料,看到有人提过类似issue。跟我们的现象很类似,https://gitee.com/ld/J2Cache/issues/I14IHH

还有这个,https://gitee.com/ld/J2Cache/issues/I566PG,恰好我

们也开启了拓扑刷新,而且时间更短。

分析到这,感觉连接数增加应该和内存碎片的产生没有关系。

第二个问题,连接数达到峰值,大量的超时的真实原因应该是redis端口号被耗尽,而大量的连接状态是time_waiting,恰好这个数据没有在promethus上采集。重启业务服务,原有连接强制断掉,一切恢复。

端口号被耗尽是怎么发生的。

再回顾一下,其中2个主节点发生连接激增,另一个主节点一点反应都没有。

业务服务有2台在18:08出现超时,其余正常,流量没有增加。后来陆续其他服务也有超时。

redis这边,2个主节点18:08以后连接数有所上升,命令耗时和QPS有所增加,19:10 连接数达到高点。

源码分析

先看下J2Cache的J2CacheBuilder.initFromConfig

一级缓存失效会发送pubsub消息通知redis,由于之前刷了很多短期缓存,导致大量的key短时间过期。

/**
 * 加载配置
 *
 * @return
 * @throws IOException
 */
private void initFromConfig(J2CacheConfig config) {
    SerializationUtils.init(config.getSerialization(), config.getSubProperties(config.getSerialization()));
    //初始化两级的缓存管理
    this.holder = CacheProviderHolder.init(config, (region, key) -> {
        //当一级缓存中的对象失效时,自动清除二级缓存中的数据
        Level2Cache level2 = this.holder.getLevel2Cache(region);
        level2.evict(key);
        if (!level2.supportTTL()) {
            //再一次清除一级缓存是为了避免缓存失效时再次从 L2 获取到值
            this.holder.getLevel1Cache(region).evict(key);
        }
        log.debug("Level 1 cache object expired, evict level 2 cache object [{},{}]", region, key);
        if (policy != null)
            policy.sendEvictCmd(region, key);
    });

    policy = ClusterPolicyFactory.init(holder, config.getBroadcast(), config.getBroadcastProperties());
    log.info("Using cluster policy : {}", policy.getClass().getName());
}

业务服务 18:05的内存释放很多
在这里插入图片描述
业务服务 18:00-19:00的内存波动频繁
在这里插入图片描述

项目中关于拓扑的配置,自适应刷新超时时间10s,每隔15s刷新。

还有个默认配置是如果拓扑失败,自动重连, refreshTriggersReconnectAttempts,默认是5。

@Primary
@Bean("j2CahceRedisConnectionFactory")
public LettuceConnectionFactory lettuceConnectionFactory(net.oschina.j2cache.J2CacheConfig j2CacheConfig) {
    //... 获取配置

    //开启 自适应集群拓扑刷新和周期拓扑刷新
    ClusterTopologyRefreshOptions clusterTopologyRefreshOptions = ClusterTopologyRefreshOptions.builder()
            // 开启全部自适应刷新
            .enableAllAdaptiveRefreshTriggers() // 开启自适应刷新,自适应刷新不开启,Redis集群变更时将会导致连接异常
            // 自适应刷新超时时间(默认30秒)
            .adaptiveRefreshTriggersTimeout(Duration.ofSeconds(10)) //默认关闭开启后时间为30秒
            // 开周期刷新
            // 默认关闭开启后时间为60秒
            //  ClusterTopologyRefreshOptions.DEFAULT_REFRESH_PERIOD 60
            // .enablePeriodicRefresh(Duration.ofSeconds(2))
            // .enablePeriodicRefresh().refreshPeriod(Duration.ofSeconds(2))
            .enablePeriodicRefresh(Duration.ofSeconds(15)) //每隔15秒回刷新
            .build();

    //https://github.com/lettuce-io/lettuce-core/wiki/Client-Options
    ClientOptions clientOptions = ClusterClientOptions.builder()
            .topologyRefreshOptions(clusterTopologyRefreshOptions)
            .build();


    LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder clientConfig = LettucePoolingClientConfiguration.builder();
    clientConfig.commandTimeout(Duration.ofMillis(CONNECT_TIMEOUT));
    clientConfig.poolConfig(getGenericRedisPool(l2CacheProperties, null));
    clientConfig.clientOptions(clientOptions);

	//... node相关信息

    connectionFactory = new LettuceConnectionFactory(clusterConfig, clientConfig.build());

    return connectionFactory;
}

有一些命令执行上的增长趋势。

在这里插入图片描述
del、cluster和publish执行增加。

在这里插入图片描述
原因主要是有2个叠加的:

  • 使用J2Cache框架,在短时间内写入了大量短期缓存数据,导致同时过期的数据增加,而一级缓存过期会发送pubsub消息。(pubilsh命令执行增加很少)
  • 由于redis性能问题,导致拓扑重试次数增加,从而导致后边的连接数激增。(cluster 命令执行次数倒是有所增加,不过很少,跟峰值比完全不是一个档次)

写完这俩结论自己都有点虚,数据上是没有体现的。但是从这出手优化是可以的。

改进:

  • 优化拓扑配置,增加超时时间和刷新周期时间。
  • J2Cache的广播方式由 lettuce改成 rabbitmq。

总结

这篇文章拖了3周没写出来,最后虽然给出了一个分析,但还是感觉不够说服力。毕竟出了问题、第一时间是解决问题,解决之后只能依靠历史的数据进行复盘、分析。一开始大家都说是由于redis内存刷到报警、内存碎片产生导致的,其实应该没啥关系。

由于是老项目,在框架使用上需要注意,可能用了很久的框架,不知道在一些场景上的性能怎么样。比如像这次大量刷入缓存,导致内存急速上、后来停刷之后的大量过期的场景并没有实际运行过。

还有就是出了问题,虽然第一时间找到运维那,不过大家也是一脸懵逼,后来查到有大量的time_waiting链接,ip是业务节点ip,重启业务服务后恢复。果然,出问题不要慌,找专业的人,重启大法牛的。

文章参考

https://blog.csdn.net/Zong_0915/article/details/126302182

https://juejin.cn/post/6844903967298682893

https://juejin.cn/post/6881470475395039239

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

烤鸭的世界我们不懂

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值