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

问题描述

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

查找问题思路

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

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

记录一次并发情况下的redis导致服务假死的问题_第1张图片

发现很多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();
        }
    }
    
  • 分析一下这个代码,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);
            }
        }
    }
    

    这里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>

这里要注意一下的是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();
        }

    }

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

}

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

你可能感兴趣的:(spring,系列,redis,redis,java,缓存,并发)