客户端分片是把分片的逻辑放在 Redis 客户端实现,比如 jedis 已支持 Redis Sharding 功能,即 ShardedJedis,通过 Redis 客户端预先定义好的路由规则(如使用一致性哈希),把对 Key 的访问转发到不同的 Redis 实例中,查询数据时把返回结果汇集。
一致性哈希算法也是使用取模的方法,普通哈希算法取模算法是对服务器的数量进行取模,而一致性哈希算法是对 2^32 取模,具体步骤如下:
(1)一致性哈希算法将整个哈希值空间按照顺时针方向组织成一个虚拟的圆环,称为 Hash 环。
(2)接着将各个服务器使用 Hash 函数进行哈希,具体可以选择服务器的 IP 或主机名作为关键字进行哈希,从而确定每台机器在哈希环上的位置。
(3)最后使用算法定位数据访问到相应服务器:将数据 key 使用相同的函数 Hash 计算出哈希值,并确定此数据在环上的位置,从此位置沿环顺时针寻找,第一台遇到的服务器就是其应该定位到的服务器。
一致性哈希算法在服务节点太少的情况下,容易因为节点分部不均匀而造成数据倾斜问题,也就是被缓存的对象大部分集中缓存在某一台服务器上,从而出现数据分布不均匀的情况,这种情况就称为 hash 环的倾斜。
hash 环的倾斜在极端情况下,仍然有可能引起系统的崩溃,为了解决这种数据倾斜问题,一致性哈希算法引入了虚拟节点机制,即对每一个服务节点计算多个哈希,每个计算结果位置都放置一个此服务节点,称为虚拟节点,一个实际物理节点可以对应多个虚拟节点,虚拟节点越多,hash 环上的节点就越多,缓存被均匀分布的概率就越大,hash 环倾斜所带来的影响就越小,同时数据定位算法不变,只是多了一步虚拟节点到实际节点的映射。
对于大应用来说,单台 Redis 服务器肯定满足不了应用的需求。在 Redis3.0 之前,是不支持集群的。如果要使用多台 Redis 服务器,必须采用其他方式。很多公司使用了代理方式来解决 Redis 集群。对于 Jedis,也提供了客户端分片的模式来连接 “Redis集群”。其内部是采用 Key 的一致性 hash 算法来区分 key 存储在哪个 Redis 实例上的。
先看下怎么使用,如下:
// 配置连接池 JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(500); config.setTestOnBorrow(true); // 分片配置 List<JedisShardInfo> jdsInfoList = new ArrayList<>(2); jdsInfoList.add(new JedisShardInfo("192.168.2.128", 6379)); jdsInfoList.add(new JedisShardInfo("192.168.2.108", 6379)); pool = new ShardedJedisPool(config, jdsInfoList, Hashing.MURMUR_HASH, Sharded.DEFAULT_KEY_TAG_PATTERN); // 设置数据 jds.set(key, value); // ... // 释放连接 jds.close(); pool.close();
当然,采用这种方式也存在两个问题:
因为使用了一致性哈稀进行分片,那么不同的 key 分布到不同的 Redis Server 上,当我们需要扩容时,需要增加机器到分片列表中,这时候会使得同样的 key 算出来落到跟原来不同的机器上,这样如果要取某一个值,会出现取不到的情况。
对于扩容问题,Redis 的作者提出了一种名为 Pre-Sharding 的方式,即事先部署足够多的 Redis 服务。
当集群中的某一台服务挂掉之后,客户端再根据一致性 hash 无法从这台服务器取数据。
对于单点故障问题,我们可以使用 Redis 的 HA 高可用来实现。利用 Redis Sentinal 来通知主从服务的切换。当然,Jedis 没有实现这块。