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);
}
}
}

Jedis使用过程中踩过的那些坑的更多相关文章

  1. jenkins配置过程中踩过的一些坑

    1,编译通过之后,想要将编译好的war包放到远程服务器上,并解压 unzipBus.sh的脚本如下: #!/bin/bash jar -xvf bus.war 编译后报错:jar:Command no ...

  2. RxJava 1升级到RxJava 2过程中踩过的一些“坑”

    RxJava2介绍 RxJava2 发布已经有一段时间了,是对 RxJava 的一次重大的升级,由于我的一个库cv4j使用了 RxJava2 来尝鲜,但是 RxJava2 跟 RxJava1 是不能同 ...

  3. 细数阿里云在使用 Docker 过程中踩过的那些坑

    昨天下午道哥在微信上丢给我一条新闻,看看,我们阿里云支持 Docker 企业版了.我打开一看,果然,阿里云发布了飞天敏捷版,开始支持企业级的 Docker 容器. 美国中部时间4月19日,阿里云在容器 ...

  4. 【Fine原创】JMeter分布式测试中踩过的那些坑

    最近因为项目需要,研究了性能测试的相关内容,并且最终选用了jmeter这一轻量级开源工具.因为一直使用jmeter的GUI模式进行脚本设计,到测试执行阶段工具本身对资源的过量消耗给性能测试带来了瓶颈, ...

  5. Jmeter Web 性能测试入门 (七):Performance 测试中踩过 Jmeter 的坑

    脚本运行的过程中,大量request抛error,但没有地方能够查看request是因为什么error的. 原因:Jmeter默认禁掉了运行过程中每个request的具体response信息收集,只保 ...

  6. 使用ffmpeg视频编码过程中踩的一个坑

           今天说说使用ffmpeg在写视频编码程序中踩的一个坑,这个坑让我花了好多时间,回头想想,非常多时候一旦思维定势真的挺难突破的.以下是不对的编码结果:                   ...

  7. spring-data-redis 使用过程中踩过的坑

    spring-data-redis简介 Spring-data-redis是spring大家族的一部分,提供了在srping应用中通过简单的配置访问redis服务,对reids底层开发包(Jedis, ...

  8. 谈谈使用echarts过程中踩过的坑

    小结: 1. 使用jquery获取json对象遇到的问题 由于自己对ajax用的还不熟练,之前都是拷贝别人的代码拿来用的,这次自己写的时候倒是碰到好多麻烦一一列举如下: 1.1 在$ 与ajax之间还 ...

  9. NetCore部署到Linux服务器+Supervisor的步骤及过程中踩过的坑

    本文作备忘使用 服务器配置: 下面是所有操作的具体步骤: 1.安装nginx   参考 1.1 添加源:默认情况Centos7中没有Nginx源,最近Nginx官网提供了Centos的源地址. sud ...

随机推荐

  1. linux linux 互传文件 win 不通过 ftp sftp 往linux 传文件(文件夹)

    linux 传入 传出文件 swp  port  22 怎样通过swp通过docker 容器向外传文件 通过scp Linux互传文件,需要知道文件源 file source 所在系统的ip wuse ...

  2. Centos之常见目录作用介绍(九)

    我们先切换到系统根目录 / 看看根目录下有哪些目录 [root@localhost ~]# cd / [root@localhost /]# ls bin   dev  home  lib64  mn ...

  3. 实用 35 个 jQuery 小技巧

    1. 禁止右键点击 $(document).ready(function(){ $(document).bind("contextmenu",function(e){ return ...

  4. [转载]使用iscroll.js-tab左右滑动导航--tab点击无效果

     转载自:http://blog.csdn.net/zuoyiran520081/article/details/77369421 最近在页面中用iscroll.js,但是但是有跳转,用a标签的hre ...

  5. 超出字数部分省略(主要解决不兼容;display: -webkit-box;的浏览器)

    注明:内容于http://www.cnblogs.com/chentongtong/p/5474553.html进一步整理. 1.现webkit内核的浏览器支持display: -webkit-box ...

  6. linux下查看cpu,内存,硬盘等硬件信息的方法

    说明:Linux下可以在/proc/cpuinfo中看到每个cpu的详细信息.但是对于双核的cpu,在cpuinfo中会看到两个cpu.常常会让人误以为是两个单核的cpu. 一.linux CPU大小 ...

  7. 什么是lambda函数?它有什么好处?

    lambda 函数是一个可以接收任意多个参数(包括可选参数)并且返回单个表达式值的函数. lambda 函数不能包含命令,它们所包含的表达式不能超过一个.不要试图向lambda 函数中塞入太多的东西: ...

  8. Java8 FutureTask 分析

    实现FutureTask的要点 1.需要实现一个链表(每个节点包含当前线程的引用) 2.通过LockSupport.park 对线程进行阻塞 3.节点的唤醒(task完成, 线程Interrupt, ...

  9. 【WEB HTTP】集成点:网关、隧道及中继

    网关:网关可以作为某种翻译器使用,它抽象出了一种能够到达资源的方法.网关是资源与应用程序之间的粘合剂. 在不同http版本之间进行转换的Web代理和网关一样,他们会执行复杂的逻辑,以便在各个端点之间进 ...

  10. MySQL忘记密码的正确解决方法

    MySQL忘记密码解决方案:破解本地密码: Windows: 1.用系统管理员登陆系统. 2.停止MySQL的服务. 3.进入命令窗口,然后进入 MySQL的安装目录,比如我的安装目录是c:\mysq ...