线上Redis高并发连接失败问题排查

项目背景

最近,做一个按优先级和时间先后排队的需求。用 Redis 的 sorted set 做排队队列。

主要使用的 Redis 命令有, zadd, zcount, zscore, zrange 等。

测试完毕后,发到线上,发现有大量接口请求返回超时熔断(超时时间为3s)。

Error日志打印的异常堆栈为:

redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool

Caused by: redis.clients.jedis.exceptions.JedisConnectionException: java.net.ConnectException: Connection timed out (Connection timed out)

Caused by: java.net.ConnectException: Connection timed out (Connection timed out)

且有一个怪异的现象,只有写库的逻辑报错,即 zadd 操作。像 zcount, zscore 这些操作全部能正常执行。

还有就是报错和正常执行交错持续。即假设每分钟有1000个 Redis 操作,其中900个正常,100个报错。而不是报错后,Redis 就不能正常使用了。

问题排查

1.连接池泄露?

从上面的现象基本可以排除连接池泄露的可能,如果连接未被释放,那么一旦开始报错,后面的 Redis 请求基本上都会失败。而不是有90%都可正常执行。

但 Jedis 客户端据说有高并发下连接池泄露的问题,所以为了排除一切可能,还是升级了 Jedis 版本,发布上线,发现没什么用。

2.硬件原因?

排查 Redis 客户端服务器性能指标,CPU利用率10%,内存利用率75%,磁盘利用率10%,网络I/O上行 1.12M/s,下行 2.07M/s。接口单实例QPS均值300左右,峰值600左右。

Redis 服务端连接总数徘徊在2000+,CPU利用率5.8%,内存使用率49%,QPS1500-2500。

硬件指标似乎也没什么问题。

3.Redis参数配置问题?

1 JedisPoolConfig config = new JedisPoolConfig();
 2 config.setMaxTotal (200);        // 最大连接数
 3 config.setMinIdle (5);           // 最小空闲连接数
 4 config.setMaxIdle (50);          // 最大空闲连接数
 5 config.setMaxWaitMillis (1000 * 1);    // 最长等待时间
 6 config.setTestOnReturn (false);
 7 config.setTestOnBorrow (false);
 8 config.setTestWhileIdle (true);
 9 config.setTimeBetweenEvictionRunsMillis (30 * 1000);
10 config.setNumTestsPerEvictionRun (50);

基本上大部分公司的配置包括网上博客提供的配置其实都和上面差不多,看不出有什么问题。

这里我尝试把最大连接数调整到500,发布到线上,并没什么卵用,报错数反而变多了。

4.连接数统计

在 Redis Master 库上执行命令:client list。打印出当前所有连接到服务器的客户端IP,并过滤出当前服务的IP地址的连接。

发现均未达到最大连接数,确实排除了连接泄露的可能。

线上Redis高并发连接失败问题排查

5.最大连接数调优和压测

既然连接远未打满,说明不需要设置那么大的连接数。而 Redis 服务端又是单线程读写。客户端创建过多连接,只会耗费资源,反而拖累性能。

线上Redis高并发连接失败问题排查

使用以上代码,在本机使用 JMeter 压测300个线程,连续请求30秒。

首先把最大连接数设为500,成功率:99.61%

请求成功:82004次,TP90耗时目测在50-80ms左右。

请求失败322次,全部为请求服务器超时:socket read timeout,耗时2s后,由 Jedis 自行熔断。

(这种情况造成数据不一致,实际上服务端已执行了命令,只是客户端读取返回结果超时)。

线上Redis高并发连接失败问题排查

线上Redis高并发连接失败问题排查

再把最大连接数设为20,成功率:98.62%(有一定几率100%成功)

请求成功:85788次,TP90耗时在10ms左右。

请求失败:1200次,全部为等待客户端连接超时:Caused by: java.util.NoSuchElementException: Timeout waiting for idle object,熔断时间为1秒。

线上Redis高并发连接失败问题排查

线上Redis高并发连接失败问题排查

再将最大连接数调整为50,成功率:100%

请求成功:85788次, TP90耗时10ms。

请求失败:0次。

线上Redis高并发连接失败问题排查

线上Redis高并发连接失败问题排查

综上,Redis 服务端单线程读写,连接数太多并没卵用,反而会消耗更多资源。最大连接数配置太小,不能满足并发需求,线程会因为拿不到空闲连接而超时退出。

在满足并发的前提下,maxTotal连接数越小越好。在300线程并发下,最大连接数设为50,可以稳定运行。

基于以上结论,尝试调整 Redis 参数配置并发布上线,但以上实验只执行了 zadd 命令,仍未解决一个问题:为什么只有写库报错?

果然,发布上线后,接口超时次数有所减少,响应时间有所提升,但仍有报错,没能解决此问题。

6.插曲 – Redis锁

在优化此服务的同时,把同事使用的另一个 Redis 客户端一起优化了,结果同事的接口过了一天开始大面积报错,接口响应时间达到8个小时。

排查发现,同事的接口仅使用 Redis 作为分布式锁。而这个 RedisLock 类是从其他服务拿过来直接用的,自旋时间设置过长,这个接口又是超高并发。

最大连接数设为50后,锁资源竞争激烈,直接导致大部分线程自旋把连接池耗尽了。于是又紧急把最大连接池恢复到200,问题得以解决。

由此可见,在分布式锁的场景下,配置不能完全参考读写 Redis 操作的配置。

7.排查服务端持久化

在把客户端研究了好几遍之后,发现并没有什么可以优化的了,于是开始怀疑是服务端的问题。

持久化是一直没研究过的问题。在查阅了网上的一些博客,发现持久化确实有可能阻塞读写IO的。

“1) 对于没有持久化的方式,读写都在数据量达到800万的时候,性能下降几倍,此时正好是达到内存10G,Redis开始换出到磁盘的时候。并且从那以后再也没办法重新振作起来,性能比Mongodb还要差很多。

2) 对于AOF持久化的方式,总体性能并不会比不带持久化方式差太多,都是在到了千万数据量,内存占满之后读的性能只有几百。

3) 对于Dump持久化方式,读写性能波动都比较大,可能在那段时候正在Dump也有关系,并且在达到了1400万数据量之后,读写性能贴底了。在Dump的时候,不会进行换出,而且所有修改的数据还是创建的新页,内存占用比平时高不少,超过了15GB。而且Dump还会压缩,占用了大量的CPU。也就是说,在那个时候内存、磁盘和CPU的压力都接近极限,性能不差才怪。” —- 引用自lovecindywang 的博客园博客

内存越大,触发持久化的操作阻塞主线程的时间越长

Redis是单线程的内存数据库,在redis需要执行耗时的操作时,会fork一个新进程来做,比如bgsave,bgrewriteaof。 Fork新进程时,虽然可共享的数据内容不需要复制,但会复制之前进程空间的内存页表,这个复制是主线程来做的,会阻塞所有的读写操作,并且随着内存使用量越大耗时越长。例如:内存20G的redis,bgsave复制内存页表耗时约为750ms,redis主线程也会因为它阻塞750ms。” —- 引用自CSDN博客

而我们的Redis实例内存配额20G,已使用了50%,keys数量达4000w。

主从集群,从库不做持久化,主库使用RDB持久化。rdb的save参数是默认值。(这也恰好能解释通为什么写库报错,读库正常)

且此 Redis 已使用了几年,里面可能存在大量的key已经不使用了,但未设置过期时间。

然而,像 Redis、MySQL 这种都是由数据中台负责,我们并无权查看服务端日志,这个事情也不好推动,中台会说客户端使用的有问题,建议调整参数。

所以最佳解决方案可能是,重新申请 Redis 实例,逐步把项目中使用的 Redis 迁移到新实例,并注意设置过期时间。迁移完成后,把老的 Redis 实例废弃回收。

小结

1)如果简单的在网上搜索, Could not get a resource from the pool ,基本都是些连接未释放的问题。

然而很多原因可能导致 Jedis 报这个错,这条信息并不是异常堆栈的最顶层。

2)Redis其实只适合作为缓存,而不是数据库或是存储。它的持久化方式适用于救救急啥的,不太适合当作一个普通功能来用。

3)还是建议任何数据都设置过期时间,哪怕设1年呢。不然老的项目可能已经都废弃了,残留在 Redis 里的 key,其他人也不敢删。

4)不要存放垃圾数据到 Redis 中,及时清理无用数据。业务下线了,就把相关数据清理掉。

Original: https://www.cnblogs.com/lyosaki88/p/13745721.html
Author: 丶谦信
Title: 线上Redis高并发连接失败问题排查

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/573410/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Spring Boot + Spring Cloud 实现权限管理系统 后端篇(十九):服务消费(Ribbon、Feign)

    技术背景 上一篇教程中,我们利用Consul注册中心,实现了服务的注册和发现功能,这一篇我们来聊聊服务的调用。单体应用中,代码可以直接依赖,在代码中直接调用即可,但在微服务架构是分…

    Java 2023年5月30日
    079
  • Java AbstractQueuedSynchronizer(AQS)

    AbstractQueuedSynchronizer 为 java.util.concurrent.locks 包下的一个抽象类,简称 AQS( 抽象队列同步器)。 并发包(JUC…

    Java 2023年5月29日
    0100
  • Springcloud学习笔记49–Springboot读取外部配置文件,避免更新jar包内配置文件重新打包部署

    1 Springboot读取外部配置文件优先级 如果springBoot项目与配置文件不分离,那么每次修改配置文件都需要重新重新打包部署应用,十分麻烦。解决方法是让springbo…

    Java 2023年5月30日
    0101
  • idea集成maven插件和使用骨架创建maven的java工程

    idea集成maven插件 打开idea点击配置搜索maven 配置自己的maven路径和仓库位置 使用骨架创建maven的java工程 项目结构: Original: https…

    Java 2023年6月6日
    083
  • 多表头定位

    (function (cols) {//1.横向处理 设置每一行Xfor (var row = 0; row < cols.length; row++) {var curro…

    Java 2023年6月13日
    059
  • JAVA设计模式-原型模式

    JAVA设计模式-原型模式 介绍 原型模式是一种创建型模式,用于创建重复的对象,并且保证性能。原型模式创建的对象是由原型对象自身创建的,是原型对象的一个克隆,和原型对象具有相同的结…

    Java 2023年6月15日
    088
  • spring-retry使用

    Spring Retry提供了自动重新调用失败的操作的功能。这在错误可能是暂时性的(例如瞬时网络故障)的情况下很有用。Spring Retry提供对流程和基于策略的行为的声明式控制…

    Java 2023年5月30日
    096
  • Linux下搭建maven(maven3.6+nexus3.2)私服

    准备maven和nexus安装包,nexus安装包好像要FQ,不然下载不到! 链接:https://pan.baidu.com/s/1bVMadGoTAK9pSLW6yBNOCg提…

    Java 2023年6月8日
    082
  • 构建系统概念

    什么是构建系统 构建系统的 第一要务 是将系统源代码编译成可执行的文件。 在这基础之上,它允许通过机器自动创建 build,如提交代码到 GitHub 后自动触发构建。 javac…

    Java 2023年6月5日
    068
  • nginx的请求限制

    一、http协议的连接与请求 总结: HTTP请求是建立在一次TCP连接的基础之上。 一次TCP请求至少产生一次HTTP请求。 二、连接限制 limit_conn_module 配…

    Java 2023年5月30日
    078
  • 数据结构与算法之随机快速排序

    快速随机排序的思路是从一个数组中随机选择一个主元,然后将这个主元放到数组的最后.循环数组时,先定义一个指针,发现了比主元小的元素,如果指针和循环下标相同 则只是把指针自增,如果发现…

    Java 2023年6月8日
    068
  • poi导出excel工具类+注解

    导出excel作为很多页面的常用功能,但是不同页面导出的数据内容和字段不一,不方便操作,本文通过注解和工具类来解决excel通用导出问题 1.注解 通过注解来定义excel字段的名…

    Java 2023年6月9日
    079
  • QThread停止线程

    1 强制停止线程,停止使用run函数启动的线程。 if (m_td != NULL){m_td->terminate();m_td->wait(); // 调用wait…

    Java 2023年5月30日
    086
  • 报错One record is expected, but the query result is multiple records

    总结:出现这种情况,显而易见,就是查询的数据在数据库中不止一条,而我调用的selectOne方法,返回值是一个User对象,导致报错 点击查看错误代码 LambdaQueryWra…

    Java 2023年6月15日
    068
  • Pycharm 运行和终端都无法走代理

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    Java 2023年6月8日
    068
  • SpringCloud(二):nacos作为配置中心

    访问nacos服务:http://localhost:8848/nacos/#默认账户、密码:nacos 1、创建namespace 2、在’配置管理’-》…

    Java 2023年5月30日
    090
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球