`

[FAQ] Jedis使用过程中踩过的那些坑

 
阅读更多

4. 一个大坑若实例化 JedisShardInfo 时不设置节点名称(name属性),那么当Redis节点列表的顺序发生变化时,会发生键 rehash 现象

 

使用BTrace追踪redis.clients.util.Sharded的实时状态,验证“Jedis分片机制的一致性哈希算法”实现;

发现一个致命坑:若JedisShardInfo不设置节点名称(name属性),那么当Redis节点列表的顺序发生变化时,会发生“键 rehash 现象”。见Sharded的initialize(...)方法实现:

(I) this.algo.hash("SHARD-" + i + "-NODE-" + n)

【缺点】 大坑:将节点的顺序索引i作为hash的一部分! 当节点顺序被无意识地调整了,会触发”键 rehash 现象”,那就杯具啦!("因节点顺序调整而引发rehash"的问题)

(II) this.algo.hash(shardInfo.getName() + "*" + shardInfo.getWeight() + n)

【优点】 这样设计避免了上面"因节点顺序调整而引发rehash"的问题。

【缺点】 坑:"节点名称+权重"必须是唯一的,否则节点会出现重叠覆盖! 同时,"节点名称+权重"必须不能被中途改变!

(III) 节点IP:端口号+编号

Memcached Java Client,就是采用这种策略。

【缺点】 因机房迁移等原因,可能导致节点IP发生改变!

(IIII) 唯一节点名称+编号

较好地一致性hash策略是:唯一节点名称+编号,不要考虑权重因素!

long hash = algo.hash(shardInfo.getName() + "*" + n)

 

所以,在配置Redis服务列表时,必须要设置节点逻辑名称(name属性)

 

redis.server.list=192.168.6.35:6379:Shard-01,192.168.6.36:6379:Shard-02,192.168.6.37:6379:Shard-03,192.168.6.38:6379:Shard-04

 

相关代码如下所示:

 

 

public class Sharded<R, S extends ShardInfo<R>> {

  public static final int DEFAULT_WEIGHT = 1;
  private TreeMap<Long, S> nodes;
  private final Hashing algo;
  private final Map<ShardInfo<R>, R> resources = new LinkedHashMap<ShardInfo<R>, R>();

  public Sharded(List<S> shards) {
    this(shards, Hashing.MURMUR_HASH); // MD5 is really not good as we works with 64-bits not 128
  }

  public Sharded(List<S> shards, Hashing algo) {
    this.algo = algo;
    initialize(shards);
  }

  private void initialize(List<S> shards) {
    nodes = new TreeMap<Long, S>();

    for (int i = 0; i != shards.size(); ++i) {
      final S shardInfo = shards.get(i);
      if (shardInfo.getName() == null) for (int n = 0; n < 160 * shardInfo.getWeight(); n++) {
        nodes.put(this.algo.hash("SHARD-" + i + "-NODE-" + n), shardInfo);
      }
      else for (int n = 0; n < 160 * shardInfo.getWeight(); n++) {
        nodes.put(this.algo.hash(shardInfo.getName() + "*" + shardInfo.getWeight() + n), shardInfo);
      }
      resources.put(shardInfo, shardInfo.createResource());
    }
  }

  ...

}

 

3. "Redis客户端连接数一直降不下来"的问题

 

这个问题发生有两方面的原因:

  1. 未正确使用对象池的空闲队列行为LIFO“后进先出”栈方式)
  2. 关闭集群链接时异常导致连接泄漏”问题(见本文的第一个问题)

 

具体分析过程,详见《[线上问题] "Redis客户端连接数一直降不下来"的问题解决》。

 

2. Jedis “Socket读取超时”导致“返回值类型错误

 

异常信息如下所示:

 

[2015-02-07 09:17:47] WARN  c.f.f.b.s.r.i.CustomShardedJedisFactory -quit jedis connection for server fail: xxx.xxx.xxx.xxx:xxx
java.lang.ClassCastException: java.lang.Long cannot be cast to [B   (强制类型转换异常)
	at redis.clients.jedis.Connection.getStatusCodeReply(Connection.java:181) ~[jedis-2.6.2.jar:na]
	at redis.clients.jedis.BinaryJedis.quit(BinaryJedis.java:136) ~[jedis-2.6.2.jar:na]
	at cn.fraudmetrix.forseti.biz.service.redis.impl.CustomShardedJedisFactory.destroyObject(CustomShardedJedisFactory.java:116) ~[forseti-biz-service-1.0-SNAPSHOT.jar:na]
	at org.apache.commons.pool2.impl.GenericObjectPool.destroy(GenericObjectPool.java:848) [commons-pool2-2.0.jar:2.0]
	at org.apache.commons.pool2.impl.GenericObjectPool.invalidateObject(GenericObjectPool.java:626) [commons-pool2-2.0.jar:2.0]
	at redis.clients.util.Pool.returnBrokenResourceObject(Pool.java:83) [jedis-2.6.2.jar:na]
	at cn.fraudmetrix.forseti.biz.service.redis.impl.CustomShardedJedisPool.returnBrokenResource(CustomShardedJedisPool.java:121) [forseti-biz-service-1.0-SNAPSHOT.jar:na]
	at cn.fraudmetrix.forseti.biz.service.redis.impl.RedisServiceImpl.zadd(RedisServiceImpl.java:337) [forseti-biz-service-1.0-SNAPSHOT.jar:na]
	at cn.fraudmetrix.forseti.biz.service.redis.impl.RedisServiceImpl.zadd(RedisServiceImpl.java:319) [forseti-biz-service-1.0-SNAPSHOT.jar:na]
	...
[2015-02-07 09:17:47] ERROR c.f.f.b.s.r.i.RedisServiceImpl -'zadd' key fail, key: xxx, score: xxx, member: xxx

[2015-02-07 09:17:47] ERROR c.f.f.b.s.r.i.RedisServiceImpl -java.net.SocketTimeoutException: Read timed out

redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: Read timed out    (Socket读取超时异常)
	at redis.clients.util.RedisInputStream.ensureFill(RedisInputStream.java:201) ~[jedis-2.6.2.jar:na]     ('limit = in.read(buf);' at java.io.InputStream.read(InputStream.java:100) - 这里出现阻塞导致"Socket读取超时"!)
	at redis.clients.util.RedisInputStream.readByte(RedisInputStream.java:40) ~[jedis-2.6.2.jar:na]
	at redis.clients.jedis.Protocol.process(Protocol.java:128) ~[jedis-2.6.2.jar:na]
	at redis.clients.jedis.Protocol.read(Protocol.java:192) ~[jedis-2.6.2.jar:na]
	at redis.clients.jedis.Connection.readProtocolWithCheckingBroken(Connection.java:282) ~[jedis-2.6.2.jar:na]
	at redis.clients.jedis.Connection.getIntegerReply(Connection.java:207) ~[jedis-2.6.2.jar:na]
	at redis.clients.jedis.Jedis.zadd(Jedis.java:1293) ~[jedis-2.6.2.jar:na]
	at redis.clients.jedis.ShardedJedis.zadd(ShardedJedis.java:364) ~[jedis-2.6.2.jar:na]
	at cn.fraudmetrix.forseti.biz.service.redis.impl.RedisServiceImpl.zadd(RedisServiceImpl.java:328) [forseti-biz-service-1.0-SNAPSHOT.jar:na]
	at cn.fraudmetrix.forseti.biz.service.redis.impl.RedisServiceImpl.zadd(RedisServiceImpl.java:319) [forseti-biz-service-1.0-SNAPSHOT.jar:na]
	...
Caused by: java.net.SocketTimeoutException: Read timed out
	at java.net.SocketInputStream.socketRead0(Native Method) ~[na:1.7.0_51]
	at java.net.SocketInputStream.read(SocketInputStream.java:152) ~[na:1.7.0_51]
	at java.net.SocketInputStream.read(SocketInputStream.java:122) ~[na:1.7.0_51]
	at java.net.SocketInputStream.read(SocketInputStream.java:108) ~[na:1.7.0_51]
	at redis.clients.util.RedisInputStream.ensureFill(RedisInputStream.java:195) ~[jedis-2.6.2.jar:na]
	... 38 common frames omitted
 

 

从异常信息来看,首先是在'zadd'操作时出现"Socket读取超时异常",具体异常信息"JedisConnectionException: java.net.SocketTimeoutException: Read timed out"。

出现异常后,会销毁这个阻塞的Jedis连接池对象(CustomShardedJedisPool.returnBrokenResource(CustomShardedJedisPool.java:121)),但在请求Redis服务端关闭连接时,出现"强制类型转换异常",具体异常信息"ClassCastException: java.lang.Long cannot be cast to [B"。

 

这个问题已经有前辈遇到过了,其解释:

查看 Jedis 源码发现它的Connection中对网络输出流做了一个封装(RedisInputStream),其中自建了一个buffer。当发生异常的时候,这个buffer里还残存着上次没有发送或者发送不完整的命令。这个时候没有做处理,直接将该连接返回到连接池,那么重用该连接执行下次命令的时候,就会将上次没有发送的命令一起发送过去,所以才会出现上面的错误“返回值类型不对”

所以,正确的写法应该是:在发送异常的时候,销毁这个连接,不能再重用! 

 

参考自:

 

1. CustomShardedJedisFactory.destroyObject(PooledObject<ShardedJedis> pooledShardedJedis) 存在“客户端连接泄露”问题

 

异常信息如下所示:

 

[2015-01-28 15:33:51] ERROR c.f.f.b.s.r.i.RedisServiceImpl -ShardedJedis close fail   

redis.clients.jedis.exceptions.JedisException: Could not return the resource to the pool 
at redis.clients.util.Pool.returnBrokenResourceObject(Pool.java:85) ~[jedis-2.6.2.jar:na]       
at cn.fraudmetrix.forseti.biz.service.redis.impl.CustomShardedJedisPool.returnBrokenResource(CustomShardedJedisPool.java:120) ~[forseti-biz-service-1.0-SNAPSHOT.jar:na]       
at cn.fraudmetrix.forseti.biz.service.redis.impl.CustomShardedJedisPool.returnBrokenResource(CustomShardedJedisPool.java:26) ~[forseti-biz-service-1.0-SNAPSHOT.jar:na]       
at redis.clients.jedis.ShardedJedis.close(ShardedJedis.java:638) ~[jedis-2.6.2.jar:na]       
at cn.fraudmetrix.forseti.biz.service.redis.impl.RedisServiceImpl.close(RedisServiceImpl.java:90) [forseti-biz-service-1.0-SNAPSHOT.jar:na]       
at cn.fraudmetrix.forseti.biz.service.redis.impl.RedisServiceImpl.zadd(RedisServiceImpl.java:380) [forseti-biz-service-1.0-SNAPSHOT.jar:na]       
at cn.fraudmetrix.forseti.biz.service.redis.impl.RedisServiceImpl.zadd(RedisServiceImpl.java:346) [forseti-biz-service-1.0-SNAPSHOT.jar:na]       
...    
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471) [na:1.7.0_51]       
at java.util.concurrent.FutureTask.run(FutureTask.java:262) [na:1.7.0_51]       
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) [na:1.7.0_51]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) [na:1.7.0_51]
at java.lang.Thread.run(Thread.java:744) [na:1.7.0_51]       
Caused by: java.lang.ClassCastException: java.lang.Long cannot be cast to [B 
at redis.clients.jedis.Connection.getStatusCodeReply(Connection.java:181) ~[jedis-2.6.2.jar:na]       
at redis.clients.jedis.BinaryJedis.quit(BinaryJedis.java:136) ~[jedis-2.6.2.jar:na]       
at redis.clients.jedis.BinaryShardedJedis.disconnect(BinaryShardedJedis.java:35) ~[jedis-2.6.2.jar:na]
at cn.fraudmetrix.forseti.biz.service.redis.impl.CustomShardedJedisFactory.destroyObject(CustomShardedJedisFactory.java:106) ~[forseti-biz-service-1.0-SNAPSHOT.jar:na]       
at org.apache.commons.pool2.impl.GenericObjectPool.destroy(GenericObjectPool.java:848) ~[commons-pool2-2.0.jar:2.0]       
at org.apache.commons.pool2.impl.GenericObjectPool.invalidateObject(GenericObjectPool.java:626) ~[commons-pool2-2.0.jar:2.0]       
at redis.clients.util.Pool.returnBrokenResourceObject(Pool.java:83) ~[jedis-2.6.2.jar:na]       
... 37 common frames omitted

 

 

从异常信息来看,是由于应用程序无法捕获运行时的强制类型转换异常(“java.lang.ClassCastException: java.lang.Long cannot be cast to [B”)导致关闭操作异常中断,问题的根源代码位于“BinaryShardedJedis.disconnect(BinaryShardedJedis.java:35)

CustomShardedJedisFactory.destroyObject(CustomShardedJedisFactory.java:106)”。

 

原实现代码只捕获了 JedisConnectionException 异常,如下所示:

 

 

    public void destroyObject(PooledObject<ShardedJedis> pooledShardedJedis) throws Exception {
        final ShardedJedis shardedJedis = pooledShardedJedis.getObject();

        shardedJedis.disconnect(); // "链接资源"无法被释放,存在泄露
    }

 

 

  public void disconnect() {
    for (Jedis jedis : getAllShards()) {
      try {
        jedis.quit();
      } catch (JedisConnectionException e) {
        // ignore the exception node, so that all other normal nodes can release all connections.
      }
      try {
        jedis.disconnect();
      } catch (JedisConnectionException e) {
        // ignore the exception node, so that all other normal nodes can release all connections.
      }
    }
  }

 

修复后代码捕获了所有的 Exception,就不存在释放链接时由于异常未捕获而导致链接释放中断。如下所示:

 

    public void destroyObject(PooledObject<ShardedJedis> pooledShardedJedis) throws Exception {
        final ShardedJedis shardedJedis = pooledShardedJedis.getObject();

        // shardedJedis.disconnect(); // "链接资源"无法被释放,存在泄露
        for (Jedis jedis : shardedJedis.getAllShards()) {
            try {
                // 1. 请求服务端关闭连接
                jedis.quit();
            } catch (Exception e) {
                // ignore the exception node, so that all other normal nodes can release all connections.

                // java.lang.ClassCastException: java.lang.Long cannot be cast to [B
                // (zadd/zcard 返回 long 类型,而 quit 返回 string 类型。从这里看,上一次的请求结果并未读取)
                logger.warn("quit jedis connection for server fail: " + toServerString(jedis), e);
            }

            try {
                // 2. 客户端主动关闭连接
                jedis.disconnect();
            } catch (Exception e) {
                // ignore the exception node, so that all other normal nodes can release all connections.

                logger.warn("disconnect jedis connection fail: " + toServerString(jedis), e);
            }
        }
    }

 

分享到:
评论

相关推荐

    Jedis API中文使用文档.-比较详细

    单例模式下,每个线程中使用相同的 Jedis 实例可能会发生奇怪的错误,而创建太多的实例也不好,因为这意味着会建立很多 socket 连接,也会导致奇怪的错误发生。为了避免这些问题,可以使用 JedisPool,JedisPool 是...

    使用redisson替代jedis

    ### 使用Redisson替代Jedis 在分布式系统中,Redis作为一种高性能的键值存储数据库,被广泛应用于缓存、消息队列、数据同步等场景。在Java开发领域,开发者可以选择多种客户端来与Redis进行交互,其中最常用的是...

    jedis jedis.jar

    Jedis是Java开发的一款高效、轻量级的Redis客户端,专为处理Redis数据库服务而设计。Redis是一款开源的、高性能的...在实际使用中,应根据具体项目需求选择合适的版本,并确保正确配置和使用Jedis,以充分发挥其优势。

    jedis开发使用包

    在你的"jedis开发使用包"中,包含了两个重要的Java库文件: 1. **commons-pool-1.6.jar**:这是Apache Commons Pool库的1.6版本,它是一个通用的对象池服务。在Jedis中,它用于管理Redis连接的池化,避免频繁地创建...

    jedis源码 (学习jedis)

    在学习Jedis的过程中,了解其源码对于深入理解Redis操作的底层实现以及提升Java编程能力非常有帮助。这个压缩包文件"jedis-master"很可能包含了Jedis的完整源代码,包括测试用例,是学习Jedis的绝佳资源。 1. **...

    jedis相关jar包

    总的来说,这个压缩包提供了在Java环境中使用Jedis与Redis交互的基础。无论是开发简单的单机应用还是构建复杂的分布式系统,这些jar包都是不可或缺的。正确理解和使用它们,可以极大地提升你在Java和Redis开发中的...

    Redis及使用Jedis开发所需jar

    Redis是一种高性能的键值对数据存储系统,常用于数据库、缓存和消息中间件等场景。它支持多种数据结构,包括...在使用过程中,理解不同数据结构的特性和合理利用连接池,能够帮助我们构建高效、可靠的分布式应用程序。

    linux中安装redis和jedis及jedispool

    linux中安装redis和jedis及jedispool; redis安装所遇到的问题;

    英文 Jedis API 2.9.0

    Jedis是Java语言中用于操作Redis的最常用客户端库,其2.9.0版本提供了丰富的API接口,以满足开发者的各种需求。本文将深入探讨Jedis 2.9.0的API特性,帮助开发者更好地理解和使用这一工具。 ### 1. 连接与配置 ...

    jedis依赖jar包

    要在项目中使用Jedis,首先需要将`jedis-2.7.0.jar`添加到你的类路径中。如果你使用Maven,可以在`pom.xml`文件中添加以下依赖: ```xml &lt;groupId&gt;redis.clients &lt;artifactId&gt;jedis &lt;version&gt;2.7.0 ``` 对于...

    jedis-3.6.0-API文档-中文版.zip

    赠送jar包:jedis-3.6.0.jar; 赠送原API文档:jedis-3.6.0-javadoc.jar; 赠送源代码:jedis-3.6.0-sources.jar; ...人性化翻译,文档中的代码和结构保持不变,注释和说明精准翻译,请放心使用。

    jedis-2.9.0-API文档-中文版.zip

    赠送jar包:jedis-2.9.0.jar; 赠送原API文档:jedis-2.9.0-javadoc.jar; 赠送源代码:jedis-2.9.0-sources.jar;...人性化翻译,文档中的代码和结构保持不变,注释和说明精准翻译,请放心使用。

    jedis中的redis命令

    在使用时,建议开发者根据实际需求和Jedis的文档,选择合适的方法调用。此外,Jedis支持连接池管理,能够有效地管理Redis连接,提高性能。开发者应该根据应用场景选择合适的连接管理方式,确保Redis服务的高效和稳定...

    Jedis所需jar包

    在压缩包"jedis"中,包含了Jedis的jar包,这是Java项目中使用Jedis时必不可少的依赖。将这个jar包添加到项目的类路径中,即可开始编写与Redis交互的Java代码。记得根据实际需求选择合适的Jedis版本,以兼容你的Redis...

    redis jredis jedis 使用

    同时,Jedis还支持Sentinel和Cluster,可以方便地在高可用和分布式环境中使用。 在实际应用中,选择JRedis还是Jedis通常取决于项目需求。如果项目简单且不需要最新的Redis特性,JRedis可能足够使用。相反,如果需要...

    jedis5.1.0.jar

    jedis5.1.0.jar

    jedis-jedis-2.7.2

    Jedis是Java语言中用于操作Redis数据库的一个开源客户端库,其版本2.7.2是Jedis的一个稳定版本,提供了丰富的API来支持Redis的各种数据结构和功能。在本文中,我们将深入探讨Jedis如何作为缓存技术应用于实际项目中...

    jedis-2.4.2版本

    **Jedis 2.4.2 版本详解** Jedis是Java开发人员用来与Redis内存数据存储进行交互的一个开源客户端库。Redis是一款高性能的键值对...在使用过程中,理解并掌握它的特性和最佳实践,将有助于提升应用的性能和可靠性。

    redis集成 jedis使用

    Jedis是Java语言中广泛使用的Redis客户端,提供了丰富的API来操作Redis服务器。在本文中,我们将深入探讨如何集成Redis并使用Jedis进行五种基本数据类型的测试:字符串(Strings)、哈希(Hashes)、列表(Lists)、...

    jedis安装包

    Jedis是Java开发的一款高效的Redis客户端库,专为处理Redis数据结构而设计。在本文中,我们将深入探讨Jedis的安装、配置、基本操作以及它...了解并熟练使用Jedis,能帮助你在开发中充分利用Redis的数据存储和处理能力。

Global site tag (gtag.js) - Google Analytics