5.2 ShardedJedis
简单的说, 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的设计细节,设计类图如下:
关于ShardedJedis类图设计,省略了对象池,以及Jedis设计的以下细节介绍:
类名 | 职责 |
Sharded | 抽象了基于一致性哈希算法的划分设计,设计思路
|
BinaryShardedJedis | 同BinaryJedis类似,实现BinaryJedisCommands对外提供基于Byte[]的key,value操作 |
ShardedJedis | 同Jedis类似,实现JedisCommands对外提供基于String的key,value操作 |
shared一致性哈希采用以下方案:
Redis服务器节点划分:将每台服务器节点采用hash算法划分为160个虚拟节点(可以配置划分权重)
将划分虚拟节点采用TreeMap存储
对每个Redis服务器的物理连接采用LinkedHashMap存储
对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/