Redis在多线程情况下写入失败的问题及解决

 更新时间:2025年11月06日 09:03:36   作者:我惠依旧  
文章描述了在JRE 7 32位环境下使用Jedis客户端并发调用`set`方法时出现的问题,怀疑是多线程环境下获取连接导致的卡死,通过使用连接池和在JDK 8 64位环境下进行测试,问题得到解决

出现场景

同一时间多次调用jedis的set方法,出现:

redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketException: Socket is not connected: socket write error
    at redis.clients.jedis.Protocol.sendCommand(Protocol.java:98)
    at redis.clients.jedis.Protocol.sendCommand(Protocol.java:78)
    at redis.clients.jedis.Connection.sendCommand(Connection.java:101)
    at redis.clients.jedis.BinaryClient.set(BinaryClient.java:99)
    at redis.clients.jedis.Client.set(Client.java:29)
    at redis.clients.jedis.Jedis.set(Jedis.java:72)
    at com.castle.cache.JedisUtils.setSingle(JedisUtils.java:21)
    at com.castle.cache.JedisUtils$1.run(JedisUtils.java:36)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    at java.lang.Thread.run(Unknown Source)
Caused by: java.net.SocketException: Socket is not connected: socket write error
    at java.net.SocketOutputStream.socketWrite0(Native Method)
    at java.net.SocketOutputStream.socketWrite(Unknown Source)
    at java.net.SocketOutputStream.write(Unknown Source)
    at redis.clients.util.RedisOutputStream.flushBuffer(RedisOutputStream.java:31)
    at redis.clients.util.RedisOutputStream.write(RedisOutputStream.java:38)
    at redis.clients.jedis.Protocol.sendCommand(Protocol.java:84)
    ... 10 more

问题重现代码

环境:jre7 32bit

jedis:

        <dependency>
			<groupId>redis.clients</groupId>
			<artifactId>jedis</artifactId>
			<version>2.7.3</version>
			<type>jar</type>
			<scope>compile</scope>
		</dependency>
private static Jedis jedis = new Jedis("localhost",6379);;
	// 过期时间/生存时间
//    protected static int  expireTime = 60 * 60 *24;
	public static Jedis getInstance(){
		return jedis;
	}
	
	public static void setSingle(String key,String value){
		jedis.set(key, value);
//		jedis.expire(key, expireTime);
	}
	
	public static String getSingle(String key){
//		jedis.expire(key, expireTime);
		return jedis.get(key);
	}
	
	public static void main(String[] args) {
		ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 1; i <= 1000; i++) {
            final String ii = "TEST:TEST_NUM-"+i;
            cachedThreadPool.execute(new Thread(){
            	public void run(){
            		setSingle(ii, ii);
            	}
            });
        }
	}

运行代码然后就出现:

原因分析

猜测多线程获取连接,同一时间获取同一个连接导致卡死

解决方案

拟用连接池:

private static JedisPool pool;
	/**
     * 初始化Redis连接池
     */
    private static void initializePool() {
		//redisURL 与 redisPort 的配置文件
        JedisPoolConfig config = new JedisPoolConfig();
		//设置最大连接数(100个足够用了,没必要设置太大)
		config.setMaxTotal(20);
		//最大空闲连接数
		config.setMaxIdle(5);
		//获取Jedis连接的最大等待时间(50秒) 
		config.setMaxWaitMillis(50 * 1000);
		//在获取Jedis连接时,自动检验连接是否可用
		config.setTestOnBorrow(true);
		//在将连接放回池中前,自动检验连接是否有效
		config.setTestOnReturn(true);
		//自动测试池中的空闲连接是否都是可用连接
		config.setTestWhileIdle(true);
		//创建连接池
		pool = new JedisPool(config, "localhost",6379);
    }
 
    /**
     * 多线程环境同步初始化(保证项目中有且仅有一个连接池)
     */
    private static synchronized void poolInit() {
        if (null == pool) {
            initializePool();
        }
    }
	
	public static Jedis getJedis() {

	       if (pool == null) {
	           poolInit();
	       }
	       //如果没有以下代码会造成初始化的jedis拿不到 jedis对象
	       Jedis jedis = null;
	       try {
	           if (pool != null) {
	               jedis = pool.getResource();
	           }
	       }
	       catch (Exception e) {
	           e.printStackTrace();
	       }
	       return jedis;
	   }
	
	/**
     * 释放Jedis资源
     *
     * @param jedis
     */
    public static void returnResource(Jedis jedis) {
        if (null != jedis) {
            pool.returnResourceObject(jedis);
        }
    }
	
	//private static Jedis jedis = new Jedis("localhost",6379);;
//	public static Jedis getInstance(){
//		return jedis;
//	}
	// 过期时间/生存时间
    protected static int  expireTime = 60 * 60 *24;
	
	public static void put(String key,String value){
//		jedis.set(key, value);
		Jedis jedis = getJedis();
        while (true) {
            if (null != jedis) {
                break;
            } else {
                jedis = getJedis();
            }
        }
        jedis.set(key, value);
        returnResource(jedis);
//		jedis.expire(key, expireTime);
	}
	
	public static String get(String key){
//		jedis.expire(key, expireTime);
//		return jedis.get(key);
		Jedis jedis = getJedis();
        while (true) {
            if (null != jedis) {
                break;
            } else {
                jedis = getJedis();
            }
        }
        String value = jedis.get(key);
        returnResource(jedis);
        return value;
	}
	
	public static Set<String> keys(String keyMatch){
//		jedis.expire(key, expireTime);
//		
		Jedis jedis = getJedis();
        while (true) {
            if (null != jedis) {
                break;
            } else {
                jedis = getJedis();
            }
        }
        Set<String> res = jedis.keys(keyMatch);
        returnResource(jedis);
        return res;
	}
	
	public static void remove(String key){
//		jedis.del(key);
		Jedis jedis = getJedis();
        while (true) {
            if (null != jedis) {
                break;
            } else {
                jedis = getJedis();
            }
        }
        jedis.del(key);
        returnResource(jedis);
	}

测试代码:

public static void main(String[] args) {
		ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 1; i <= 1000; i++) {
            final String ii = "TEST:TEST_NUM-"+i;
            cachedThreadPool.execute(new Thread(){
            	public void run(){
            		put(ii, ii);
            	}
            });
        }
	}

插入成功:

这是在jdk7 32位的情况下,我在jdk8 64位运行会报异常的代码仍然能插入。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Redis中三种特殊数据类型命令详解

    Redis中三种特殊数据类型命令详解

    Geospatial是地理位置类型,我们可以用来查询附近的人、计算两人之间的距离等,这篇文章主要介绍了Redis中三种特殊数据类型命令详解,需要的朋友可以参考下
    2024-05-05
  • Redis+Caffeine实现多级缓存的步骤

    Redis+Caffeine实现多级缓存的步骤

    随着不断的发展,这一架构也产生了改进,在一些场景下可能单纯使用Redis类的远程缓存已经不够了,还需要进一步配合本地缓存使用,例如Guava cache或Caffeine,从而再次提升程序的响应速度与服务性能,这篇文章主要介绍了Redis+Caffeine实现多级缓存,需要的朋友可以参考下
    2024-01-01
  • Redis Caffeine实现两级缓存的项目实践

    Redis Caffeine实现两级缓存的项目实践

    本文介绍了使用Redis和Caffeine实现两级缓存,以提高查询接口的性能,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-12-12
  • 利用Redis lua实现高效读写锁的代码实例

    利用Redis lua实现高效读写锁的代码实例

    这篇文章给大家介绍了如何利用Redis lua实现高效的读写锁,读写锁的好处就是能帮助客户读到的数据一定是最新的,写锁是排他锁,而读锁是一个共享锁,需要的朋友可以参考下
    2024-01-01
  • 解读redis slaveof命令执行后为什么需要清库重新同步

    解读redis slaveof命令执行后为什么需要清库重新同步

    这篇文章主要介绍了redis slaveof命令执行后为什么需要清库重新同步,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-04-04
  • K8S部署Redis(单机、集群)的超详细步骤

    K8S部署Redis(单机、集群)的超详细步骤

    redis是一款基于BSD协议,开源的非关系型数据库(nosql数据库)这篇文章主要给大家介绍了关于K8S部署Redis(单机、集群)的超详细步骤,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-05-05
  • Redis sentinel哨兵集群的实现步骤

    Redis sentinel哨兵集群的实现步骤

    本文主要介绍了Redis sentinel哨兵集群的实现步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-07-07
  • Redis如何存储对象

    Redis如何存储对象

    这篇文章主要介绍了Redis如何存储对象,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-06-06
  • Linux下Redis安装使用教程

    Linux下Redis安装使用教程

    这篇文章主要为大家详细介绍了Linux下Redis安装使用教程,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-09-09
  • Redis分布式限流的几种实现

    Redis分布式限流的几种实现

    分布式限流是指通过将限流策略嵌入到分布式系统中,以控制流量或保护服务,本文就来介绍一下Redis分布式限流的几种实现,感兴趣的可以了解一下
    2023-12-12

最新评论