5.2 ShardedJedis

2016-03-20 19:59:52 14,576 1

简单的说, ShardedJedis 是一种帮助提高读/写并发能力的群集,群集使用一致性 hash 来确保一个 key 始终被指向相同的 redis server。每个 redis server 被称为一个 shard

因为每个 shard 都是一个 master,因此使用 sharding 机制会产生一些限制:不能在 sharding中直接使用 jedis 的 transactions、pipelining、pub/sub 这些 API,基本的原则是不能跨越 shard。但 jedis 并没有在 API 的层面上禁止这些行为,但是这些行为会有不确定的结果。一种可能的方式是使用 keytags 来干预 key 的分布,当然,这需要手工的干预。

另外一个限制是正在使用的 shards 是不能被改变的,因为所有的 sharding 都是预分片的。

注:如果希望使用可以改变的 shards,可以使用 yaourt - dynamic sharding implementation(一个jedis 的实现分支)。

ShardedJedis 的使用方法:

1. 定义 shards:

List<JedisShardInfo> shards = new ArrayList<JedisShardInfo>();
JedisShardInfo si = new JedisShardInfo("localhost", 6379);
si.setPassword("foobared");
shards.add(si);
si = new JedisShardInfo("localhost", 6380);
si.setPassword("foobared");
shards.add(si);

2.a) 直接使用:

ShardedJedis jedis = new ShardedJedis(shards);
jedis.set("a", "foo");
jedis.disconnect;

2.b) 或 使用连接池:

ShardedJedisPool pool = new ShardedJedisPool(new Config(), shards);
ShardedJedis jedis = pool.getResource();
jedis.set("a", "foo");
.... // do your work here
pool.returnResource(jedis);
.... // a few moments later
ShardedJedis jedis2 = pool.getResource();
jedis.set("z", "bar");
pool.returnResource(jedis);
pool.destroy();

判断使用的是那个 shards:

ShardInfo si = jedis.getShardInfo(key);
si.getHost/getPort/getPassword/getTimeout/getName

也可以通过 keytags 来确保 key 位于相同的 shard。如:

ShardedJedis jedis = new ShardedJedis(shards,
ShardedJedis.DEFAULT_KEY_TAG_PATTERN);

这样,默认的 keytags 是”{}”,这表示在”{}”内的字符会用于决定使用那个 shard。如:

jedis.set("foo{bar}", "12345");

jedis.set("car{bar}", "877878");

会使用同一个 shard。

注:如果 key 和 keytag 不匹配,会使用原来的 key 作为选择 shard 的 key。

使用 ShardedJedisPipeline

ShardedJedisPipeline 其实是一个很鸡肋的功能。

为了能在 ShardedJedis 中平滑的支持 redis 的 pipeline 的功能, ShardedJedis 通过ShardedJedisPipeline 类对 pipeline 提供了支持。

简单的说, ShardedJedis 是通过向每个用到的 shard 发起 pipeline 来实现 ShardedJedisPipeline的功能,这种方式如果累积的 key 不够多,很难达到提高效率的目的。

如果需要在 ShardedJedis 中使用 pipeline,还是建议尽量通过 keytag 将关联的 key 放到同一shard 之中。

ShardedJedisPipeline 简单的示例代码如下:

ShardedJedis jedis = new ShardedJedis(shards);
ShardedJedisPipeline p = jedis.pipelined();
p.set("foo", "bar");
p.get("foo");
List<Object> results = p.syncAndReturnAll();
//assertEquals(2, results.size());
//assertEquals("OK", results.get(0));
//assertEquals("bar", results.get(1));

ShardedJedisPipeline 相对复杂的示例代码:

ShardedJedis jedis = new ShardedJedis(shards);
jedis.set("string", "foo");
jedis.lpush("list", "foo");
jedis.hset("hash", "foo", "bar");
jedis.zadd("zset", 1, "foo");
jedis.sadd("set", "foo");
ShardedJedisPipeline p = jedis.pipelined();
Response<String> string = p.get("string");
Response<Long> del = p.del("string");
Response<String> emptyString = p.get("string");
Response<String> list = p.lpop("list");
Response<String> hash = p.hget("hash", "foo");
Response<Set<String>> zset = p.zrange("zset", 0, -1);
Response<String> set = p.spop("set");
Response<Boolean> blist = p.exists("list");
Response<Double> zincrby = p.zincrby("zset", 1, "foo");
Response<Long> zcard = p.zcard("zset");
p.lpush("list", "bar");
Response<List<String>> lrange = p.lrange("list", 0, -1);
Response<Map<String, String>> hgetAll = p.hgetAll("hash");
p.sadd("set", "foo");
Response<Set<String>> smembers = p.smembers("set");
Response<Set<Tuple>> zrangeWithScores = p.zrangeWithScores("zset", 0,-1);
p.sync();
assertEquals("foo", string.get());
assertEquals(Long.valueOf(1), del.get());
assertNull(emptyString.get());
assertEquals("foo", list.get());
assertEquals("bar", hash.get());
assertEquals("foo", zset.get().iterator().next());
assertEquals("foo", set.get());
assertFalse(blist.get());
assertEquals(Double.valueOf(2), zincrby.get());
assertEquals(Long.valueOf(1), zcard.get());
assertEquals(1, lrange.get().size());
assertNotNull(hgetAll.get().get("foo"));
assertEquals(1, smembers.get().size());
assertEquals(1, zrangeWithScores.get().size());

ShardedJedis实现分析

ShardedJedis是基于一致性哈希算法实现的分布式Redis集群客户端;ShardedJedis的设计分为以下几块:

  • 对象池设计:Pool,ShardedJedisPool,ShardedJedisFactory

  • 面向用户的操作封装:BinaryShardedJedis,BinaryShardedJedis

  • 一致性哈希实现:Sharded

关于ShardedJedis设计,忽略了Jedis的设计细节,设计类图如下:

8bd3b170-018d-36a2-b2e5-44cde24caceb.jpg

关于ShardedJedis类图设计,省略了对象池,以及Jedis设计的以下细节介绍: 

类名职责
Sharded

抽象了基于一致性哈希算法的划分设计,设计思路

 

  1. 基于hash算法划分redis服务器

  2. 保持每台Redis服务器的Jedis客户端

  3. 提供基于Key的划分方法;提供了ShardKeyTag实现

BinaryShardedJedis同BinaryJedis类似,实现BinaryJedisCommands对外提供基于Byte[]的key,value操作
ShardedJedis同Jedis类似,实现JedisCommands对外提供基于String的key,value操作

shared一致性哈希采用以下方案:

  1. Redis服务器节点划分:将每台服务器节点采用hash算法划分为160个虚拟节点(可以配置划分权重)

  2. 将划分虚拟节点采用TreeMap存储

  3. 对每个Redis服务器的物理连接采用LinkedHashMap存储

  4. 对Key or KeyTag 采用同样的hash算法,然后从TreeMap获取大于等于键hash值得节点,取最邻近节点存储;当key的hash值大于虚拟节点hash值得最大值时,存入第一个虚拟节点

sharded采用的hash算法:MD5 和 MurmurHash两种;默认采用64位的MurmurHash算法;有兴趣的可以研究下,MurmurHash是一种高效,低碰撞的hash算法;参考地址:

http://blog.csdn.net/yfkiss/article/details/7337382

https://sites.google.com/site/murmurhash/