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

标签: faq jedis | 发表时间:2015-02-06 21:13 | 作者:bert82503
出处:http://www.iteye.com


1. 一个 大坑:若实例化 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());
    }
  }

  ...

}

 

2. 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”)导致关闭操作异常中断,问题的根源代码位于“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);
            }
        }
    }

 

 



已有 0 人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



相关 [faq jedis] 推荐:

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

- - 互联网 - ITeye博客
1. 一个 大坑:若实例化 JedisShardInfo 时不设置节点名称(name属性),那么当Redis节点列表的顺序发生变化时,会发生“ 键 rehash 现象”. 使用BTrace追踪redis.clients.util.Sharded的实时状态,验证“Jedis分片机制的一致性哈希算法”实现;.

FastDFS FAQ

- - 企业架构 - ITeye博客
定位问题首先要看日志文件. 出现问题时,先检查返回的错误号和错误信息. 然后查看服务器端日志,相信可以定位到问题所在. FastDFS适用的场景以及不适用的场景. FastDFS是为互联网应用量身定做的一套分布式文件存储系统,非常适合用来存储用户图片、视频、文档等文件. 对于互联网应用,和其他分布式文件系统相比,优势非常明显.

Linux基金会更新FAQ

- ArmadilloCommander - Solidot
51开源社区 写道 "之前报道Linux.com、kernel.org等皆无法访问处于安全维护中,目前服务尚未恢复.不过Linux基金会已更新官方公告,发布FAQ:“为了尽快恢复服务,我们的团队正在日夜不停地工作. 服务会在未来几天恢复,我们将第一时间通知大家每一步的进度. 虽然Linux基金会存储的密码是加密的,但攻击者会尝试暴力破解,如果你使用该帐号用于其他网站,建议立即更改您的密码.

MariaDB常见问题FAQ

- - OurMySQL
MariaDB常见问题,同样适用于MySQL. 老版本MariaDB服务的相关旧信息. via似乎是个关键字,但是至少在MySQL5.1文档中找不到. 在MySQL5.1中执行成功,但是会出现1064错误 (毫无疑问,用avia替代via就可以). 答           elenst. 这个bug(https://bugs.launchpad.net/maria/+bug/1010351)被修复.

Redis客户端之Spring整合Jedis

- - 开源软件 - ITeye博客
1.下载相关jar包,并引入工程:. 2.将以下XML配置引入spring. 3.将shardedJedisPool注入相关的类中即可使用. * 设置一个key的过期时间(单位:秒). * @param key key值. * @param seconds 多少秒后过期. * @return 1:设置了过期时间 0:没有设置过期时间/不能设置过期时间.

使用Jedis的ShardedJedis做Redis集群

- - 丕子
之前一直没仔细看过ShardedJedis的代码,最近遇到了shard后集群扩容后的 数据迁移问题. 今天在看ShardedJedis源码的时候,发现ShardedJedis并没有使用节点的Ip和port做hash,而是用的instance的顺序或者name,太赞了. 一开始你可以设置足够多的instance,数据扩容的时候,只需要将几个instance的数据copy到别的机器上.

Redis客户端Jedis使用示例

- - 移动开发 - ITeye博客
Jedis 是 Redis 官方首选的 Java 客户端开发包. 工作过程总结的一个示例,贴出来,如下:.      * 在不同的线程中使用相同的Jedis实例会发生奇怪的错误. 但是创建太多的实现也不好因为这意味着会建立很多sokcet连接, .      * 也会导致奇怪的错误发生. 单一Jedis实例不是线程安全的.

Jedis的Publish/Subscribe功能的运用

- - 开源软件 - ITeye博客
转自: http://kingxss.iteye.com/blog/1420264. 一、Redis服务器端的安装和客户端Jedis的安装.    下载地址: http://redis.googlecode.com/files/redis-2.4.8.tar.gz. 在linux下运行如下命令进行安装.

[MySQL FAQ]系列 -- mysqldump选项之skip-opt

- - MySQL 中文网
最近在用mysqldump备份时,想要把数据表和数据分开备份,因此做了2次备份. 执行备份数据库表结构时,指定了 --skip-opt 选项,相当于:. 选项 --create-option 看起来比较不起眼:. 事实上,如果把它disable的话,备份出来的表结构,会少了:. 等MySQL特有的数据表属性,需要注意下.

百度开源 FAQ 问答系统—AnyQ

- - 机器之心
近年来,随着人工智能技术的发展,人机对话技术得到越来越多的关注,人机对话产品也不断涌现. 其中,智能客服作为人机对话的一个典型场景表现出极大的商业潜力和很强的研究价值,各企业也争先恐后的推出自己的智能客服产品. FAQ 问答技术作为智能客服系统最核心技术之一,在智能客服系统中发挥重要作用. 通过该技术,可实现在知识库中快速找到与用户问题相匹配的问答,为用户提供满意的答案,从而极大提升客服人员效率,改善客服人员服务化水平,降低企业客服成本.