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. 基于jquery 移动插件的实现

    引用谢灿勇 地址  http://www.cnblogs.com/st-leslie/p/6002148.html 一个思路分析:大致上实现的思路有以下两种. 一.判断块是否被按下(mousedown ...

  2. git commit -a -m "DM 1、获取aliOssSTS值,计算签名,实现视频PUT/POST2种上传方式上传;"

    git commit -a -m "DM 1.获取aliOssSTS值,计算签名,实现视频PUT/POST2种上传方式上传:" 微信小程序的视频上传

  3. python基本数据类型之操作

    python注释 当行注视:# 被注释内容多行注释:""" 被注释内容 """ 字符串操作 # 代表单行注释                ...

  4. servlet3.0 的新特性之二注解代替了web.xml配置文件

    servlet3.0 的新特性: 注解代替了 web.xml 文件 支持了对异步的处理 对上传文件的支持 1.注解代替了配置文件 1.删除了web.xml 文件 2. 在Servlet类上添加@Web ...

  5. ABAP发邮件函数

    步骤: 一.检查输入参数, (1)未指定文件類別代碼,(2)未指定郵件主題, (3)未指定郵件內容, (4)未指定發送人郵件地址, (5)未指定接收人郵件地址, 二.调用发送功能, (1)创建发送请求 ...

  6. Fidder详解之抓包

    前言 本文是博主发表的第一篇文章,如有傻逼之处,请大家见谅.最近遇到很多人说接口相关的问题,比如:什么是接口,我该怎么做接口测试,还有我总是抓不到APP上的https请求(这个巨坑,不知道坑了多少小白 ...

  7. 如何安装/更新ruby,安装cocoapods,为开发做好准备!(2016年12月07日更新内容)

    一:首先来说一下如何安装/更新ruby: 一般情况下,即使是新买的Mac电脑也会安装有ruby,可以在终端中键入一下命令查看ruby版本 ruby -v 正常情况下下面会打印出ruby的版本信息,如果 ...

  8. java多线程笔记

    一,线程的状态 1,新建状态:新创建了一个线程对象 2,就绪状态:线程创建对象后,线程调用star()的方法,等待获取CPU的使用权. 3,运行状态:获取了cpu的使用权,执行程序代码 4,阻塞状态: ...

  9. xml转换为对象 微信接口

    public sealed class XMLSerilizable { /// <summary> /// XML转换为对象 /// </summary> /// <t ...

  10. eclipse修改项目默认编码为UTF-8

    1.windows->Preferences...打开"首选项"对话框,左侧导航树,导航到general->Workspace,右侧 Text file encodin ...