• 记录一次并发情况下的redis导致服务假死的问题


    问题描述

    最近项目在做性能压测,框架使用的是 spring boot 2.1.2 + jedis 2.9.1,80个并发持续压测4-5分钟服务就假死,所有的请求就pending,查看服务日志没有任何异常,查看其它没有使用redis的接口都能正常请求。

    查找问题思路

    • 查看了一下redis的连接配置,都是正常够用的

    • 再使用jstack看一下堆栈信息

    在这里插入图片描述

    发现很多WAITING的线程,再往下看都是redis的getResource方法导致的等待。

    • 查看redis的源码Jedis.java

      @Override
      public void close() {
          if (dataSource != null) {//1
              if (client.isBroken()) {
                  this.dataSource.returnBrokenResource(this);
              } else {
                  this.dataSource.
                          returnResource(this); // 2
              }
              this.dataSource = null;   // 3
          } else {
              super.close();
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
    • 分析一下这个代码,client.isBroken()这里默认值是false,连接释放的时候先释放resource 然后再将dataSource置为null,那如果是并发的情况下话,那有可能再下一个线程进来的时候dataSource已经就是null了,在执行第2步的时候dataSource刚好被置为null,那这个时候就无法释放连接了。这个时候我们再看下获取连接的方法。JedisSentinelPool.java

      @Override
      public Jedis getResource() {
          while (true) {
              Jedis jedis = super.getResource();
              jedis.setDataSource(this);  // <-- This line 
      
              // get a reference because it can change concurrently
              final HostAndPort master = currentHostMaster;
              final HostAndPort connection = new HostAndPort(jedis.getClient().getHost(), jedis.getClient()
                      .getPort());
      
              if (master.equals(connection)) {
                  // connected to the correct master
                  return jedis;
              } else {
                  returnBrokenResource(jedis);
              }
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19

      这里jedis.setDataSource(this);设置的则为null。

    问题解决方案

    我查看了一下jedis的github,在issue上有找到有人曾经提出过这样的问题,https://github.com/redis/jedis/issues/1920 ,给出的解决方案是升级jedis的jar包到2.10.2版本以上,换成高版本的以后问题果然就解决了。

    <dependency>
        <groupId>redis.clientsgroupId>
        <artifactId>jedisartifactId>
        <version>2.10.2version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这里要注意一下的是jedis的版本跟spring-data-redis的版本是有一个对应关系的。

    • spring-data-redis 2.1.x 对应的jedis版本是2.x.x 版本

    • spring-data-redis 2.2.x 对应的jedis 版本就是3.x版本了

    分析升级版本以后的改动

    还是看那两个类,看看新版本做了什么改动

        public void close() {
            if (this.dataSource != null) {
                Pool<Jedis> pool = this.dataSource;
                this.dataSource = null;
                if (this.client.isBroken()) {
                    pool.returnBrokenResource(this);
                } else {
                    pool.returnResource(this);
                }
            } else {
                super.close();
            }
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    这里很明显的处理方式就是讲dataSource复制给pool,然后用pool去释放资源,这个时候设置dataSource与这个就没有关系了,就不存在释放资源释放不了的情况了。

  • 相关阅读:
    Python--入门
    c/c++字符串处理标准库 string 介绍
    如何设计一个高效可用的数据库
    React 使用echarts绘制滚动圆图,底部文字竖直放置
    虹科 | 解决方案 | 机械免拆压力测试方案
    Javascript知识【jQuery选择器】
    【社区投稿】给 NdArray 装上 CUDA 的轮子
    clock gating
    【Java面试题】DCL单例模式设计为什么需要volatile修饰实例对象
    【超好懂的比赛题解】2022 Jiangsu Collegiate Programming Contest 比赛题解
  • 原文地址:https://blog.csdn.net/wagnteng/article/details/126585769