Redis在数据容灾处理方面可以通过服务器端配置Master-Slave模式来实现,而在分布式集群方面目前只能通过客户端工具来实现一致性哈希分布存储,即key分片存储。Redis可能会在3.0版本支持服务器端的分布存储。Redis只能依靠各个Client做Sharding。可能会在Redis 3.0系列支持Server端Sharding。
Redis是Master-Slave,如果 想把Reids,做成集群模式,无外乎多做几套Master-Slave,每套Master-Slave完成各自的容灾处理,通过Client工具,完成一致性哈希。
jedis源码中ShardedJedis实现sharding
一个JedisShardInfo类,里面包含了jedis服务器的一些信息
最重要的是它的父类中有一个weight字段,作为本jedis服务器的权值。
这个类还有一个继承自父类的方法createResource,用来生成该类对应的Jedis对象.在构造ShardedJedis的时候传入一个JedisShardInfo的list列表。然后ShardedJedis的父类的父类及Sharede就会对这个list进行一些操作
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>();
Sharded中的三个字段,nodes是用来模拟一致性哈希算法用的;algo是用来对字符串产生哈希值的hash函数,这里默认的是 murmurhash,这个算法的随机分布特征表现比较好;resources这个map是用来存储JedisShardInfo与其对应的Jedis类 之间的映射关系。
在上面提到的对list的操作:
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());
}
}
if-else块 完成虚拟节点 到真实节点的映射
resources.put(shardInfo, shardInfo.createResource()) 完成真实节点 到具体Jedis实例的映射
在for循环中,遍历主机列表(shards.get(i)),之后对每个主机按照单权重160的比例计算shard值,将shard值和主机信息(shardInfo)放到nodes中,将主机信息(shardInfo)和其对应的链接资源(Jedis)映射放入到resources中。
Weight是权重,用于调节单个主机被映射值个数,如果weight为1,那么当前主机将被映射为160个值,weight为2,当前主机将被映射为320个值,因此weight为2的节点被访问到的概率就会高一些。
遍历list中的每一个shardInfo,将其权重weight*160生成n,然后用名字或者编号来生成n个哈希值(这个是为了保证哈希算法的平衡 性而生成的虚拟节点),然后将其和本shardInfo的对应关系存储到treemap里面(这是在模拟一致性哈希算法中将虚拟节点映射到环上的操作), 最后将shardInfo与对应的Jedis类的映射关系存储到resources里面。
- 在使用ShardedJedis进行操作的时候,每个方法都要先获得key经过hash后对应的Jedis对象,才能执行对应的方法,这个Jedis对象获取步骤如下:
1、首先根据传入的key按照hash算法(默认为murmurhash)取得其value,然后用这个value到treemap中找key大于前面生成的value值的第一个键值对,这个键值对的value既是对应的shardedInfo
public S getShardInfo(byte[] key) {
SortedMap<Long, S> tail = nodes.tailMap(algo.hash(key));
if (tail.isEmpty()) {
return nodes.get(nodes.firstKey());
}
return tail.get(tail.firstKey());
}
2、根据得到的shardedInfo从resources中取得对应的Jedis对象
Feature速览
- 所有数据都在内存中。
- 五种数据结构:String / Hash / List / Set / Ordered Set。
- 数据过期时间支持。
- 不完全的事务支持。
- 服务端脚本:使用Lua Script编写,类似存储过程的作用。
- PubSub:捞过界的消息一对多发布订阅功能,起码Redis-Sentinel使用了它。
- 持久化:支持定期导出内存的Snapshot 与 记录写操作日志的Append Only File两种模式。
- Replication:Master-Slave模式,Master可连接多个只读Slave,暂无专门的Geographic Replication支持。
- Fail-Over:Redis-Sentinel节点负责监控Master节点,在master失效时提升slave,- 独立的仲裁节点模式有效防止脑裂。
- Sharding:开发中的Redis-Cluser。
- 动态配置:所有参数可用命令行动态配置不需重启,并重新写回配置文件中,对云上的大规模部署非常合适。