首页 > 代码库 > redis预分片技术及实现

redis预分片技术及实现

    通常情况下,我们会建立多个redis实例来缓解单个redis实例的压力。但是,随着缓存数量的增加,对redis进行扩容是一件非做不可的事情。对redis进行扩容可以有多种办法,比如增加每个redis实例的最大内存。这只是解决办法之一,而且缺乏灵活性和可扩展性。在redis官网上,提到了预分片技术。本文将对预分片技术进行讲解,同时讲解jedis对分片是如何支持的。

    一、redis预分片技术

    在单个server上搭建多个redis实例。当需要扩展时,可以利用复制机制进行扩展,可参照如下步骤:

    1)在新的服务器上创建空的redis实例。

    2)配置新的redis实例作为源实例的从实例,将源数据导到新实例上。

    3)停止客户端(如jedis)。

    4)将客户端配置的实例ip更新为新的服务器地址。注意此处是替换老的ip地址,不能进行追加或调换各地址的顺序。

    5)在新服务器上发送SLAVEOF NO ONE命令,使其不再作为从实例。

    6)用新的配置重启客户端。

    7)最后停止老服务器上不再使用的旧实例。

    在这里提出个问题,将老的ip地址替换后,之前映射到旧的redis实例上关键字是否能映射到对应的新redis实例上?本文后面将会进行分析。

    二、jedis对分片技术的实现

    jedis支持分片技术,上图为涉及到的几个主要类,主要分成两部分:

    1)记录各个redis实例的地址信息。从类图中可以看出,ShardInfo和JedisShardInfo实现了此功能;

    2)使用一致性hash算法,对关键字及redis实例进行映射。从类图可以看出Sharded提供了此功能的一个基类,BinaryShardedJedis和ShardedJedis分别是字节和字符串的实现。

    在Sharded类创建时,会执行一个初始化方法。通过hash算法,对每个redis实例得出160个hash值,并将该值作为TreeMap的key,将Redis实例的ShardInfo信息作为value。注意:在计算hash值时,并不是使用的ip地址,而是用的一个别名。该别名要么是在ShardInfo中设置,要么就是以一定规则生成。具体可看下面的代码:

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

    不使用ip地址进行哈希,就可以避免在ip地址变更时,缓存里的数据无法访问。这里回答了之前提出的问题。

    实际上,这里就是一致性哈希算法的实现。memcached使用的也是此算法。160个哈希值,实际上是单个实例在哈希环上的虚拟节点。如果不建立虚拟节点,在增肌或删除节点时,会对某个节点造成压力,如果建立虚拟节点,可以将压力分解到各个redis实例上。




redis预分片技术及实现