Redis 分布式

 

如果要实现 Redis 数据的分片,我们有三种方案

  • 第一种是在客户端实现相关的逻辑,例如用取模或者一致性哈希对 key 进行分片,查询和修改都先判断 key 的路由
  • 第二种是把做分片处理的逻辑抽取出来,运行一个独立的代理服务,客户端连接到这个代理服务,代理服务做请求的转发
  • 第三种就是基于服务端实现

 

客户端 Sharding

Jedis 客户端提供了 Redis Sharding 的方案,并且支持连接池

ShardedJedis

public class ShardingTest {
    public static void main(String[] args) {
        JedisPoolConfig poolConfig = new JedisPoolConfig();​
        // Redis 服务器
        JedisShardInfo shardInfo1 = new JedisShardInfo("127.0.0.1", 6379);
        JedisShardInfo shardInfo2 = new JedisShardInfo("192.168.1.205", 6379);​
        // 连接池
        List <JedisShardInfo> infoList = Arrays.asList(shardInfo1, shardInfo2);
        ShardedJedisPool jedisPool = new ShardedJedisPool(poolConfig, infoList);
        ShardedJedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            for (int i = 0; i < 100; i++) {
                jedis.set("k" + i, "" + i);
            }
            for (int i = 0; i < 100; i++) {
                System.out.println(jedis.get("k" + i));
            }​
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }
}

使用 ShardedJedis 之类的客户端分片代码的优势是配置简单,不依赖于其他中间件,分区的逻辑可以自定义,比较灵活。但是基于客户端的方案,不能实现动态的服务增减,每个客户端需要自行维护分片策略,存在重复代码。

第二种思路就是把分片的代码抽取出来,做成一个公共服务,所有的客户端都连接到这个代理层。由代理层来实现请求和转发。

 

代理 Proxy

典型的代理分区方案有: Twitter 开源的 Twemproxy 和国内的豌豆荚开源的 Codis

Twemproxy

优点

  • 比较稳定,可用性高

不足

  • 出现故障不能自动转移,架构复杂,需要借助其他组件(LVS/HAProxy +Keepalived)实现 HA
  • 扩缩容需要修改配置,不能实现平滑地扩缩容(需要重新分布数据)

 

Codis

Codis 是一个代理中间件,用 Go 语言开发的

功能:客户端连接 Codis 跟连接 Redis 没有区别

 CodisTwemproxyRedis Cluster
重新分片不需要重启YesNoYes
pipelineYesYesNo
多 key 操作的 hash tags {} YesYesYes
重新分片时的多 key 操作 Yes-No
客户端支持 所有所有支持 cluster 协议的客户

分片原理:

Codis 把所有的 key 分成了 N 个槽(例如 1024),每个槽对应一个分组,一个分组对应于一个或者一组 Redis 实例。Codis 对 key 进行 CRC32 运算,得到一个32 位的数字,然后模以 N(槽的个数),得到余数,这个就是 key 对应的槽,槽后面就是 Redis 的实例。

比如 4 个槽:

Codis 的槽位映射关系是保存在 Proxy 中的,如果要解决单点的问题,Codis 也要做集群部署,

多个 Codis 节点怎么同步槽和实例的关系呢?

需要运行一个 Zookeeper (或者 etcd/本地文件)

 

在新增节点的时候,可以为节点指定特定的槽位。Codis 也提供了自动均衡策略


Codis 不支持事务,其他的一些命令也不支持。不支持的命令

获取数据原理(mget):在 Redis 中的各个实例里获取到符合的 key,然后再汇总到 Codis 中。


Codis 是第三方提供的分布式解决方案,在官方的集群功能稳定之前,Codis 也得到了大量的应用。

 

Redis Cluster

Redis Cluster 是在 Redis 3.0 的版本正式推出的,用来解决分布式的需求,同时也可以实现高可用。跟 Codis 不一样,它是去中心化的,客户端可以连接到任意一个可用节点

 

数据分片有几个关键的问题需要解决:

  • 数据怎么相对均匀地分片
  • 客户端怎么访问到相应的节点和数据
  • 重新分片的过程,怎么保证正常服务

架构

Redis Cluster 可以看成是由多个 Redis 实例组成的数据集合。客户端不需要关注数据的子集到底存储在哪个节点,只需要关注这个集合整体

以 3 主 3 从为例,节点之间两两交互,共享数据分片、节点状态等信息:

搭建

为了节省机器,我们直接把6个Redis实例安装在同一台机器上(3主3从),只是使用不同的端口号。192.168.1.207

更新:新版的cluster已经不需要通过ruby脚本创建,删掉了ruby相关依赖的安装

cd /usr/local/soft/redis-5.0.5

mkdir redis-cluster

cd redis-cluster

# 创建集群的文件夹
mkdir 7291 7292 7293 7294 7295 7296

复制redis配置文件到7291目录

cp /usr/local/soft/redis-5.0.5/redis.conf /usr/local/soft/redis-5.0.5/redis-cluster/7291

修改7291的redis.conf配置文件,内容:

cd /usr/local/soft/redis-5.0.5/redis-cluster/7291
>redis.conf
>vim redis.conf
port 7291

daemonize yes

protected-mode no

dir /usr/local/soft/redis-5.0.5/redis-cluster/7291/

cluster-enabled yes

cluster-config-file nodes-7291.conf

cluster-node-timeout 5000

appendonly yes

pidfile /var/run/redis_7291.pid

把7291下的redis.conf复制到其他5个目录

cd /usr/local/soft/redis-5.0.5/redis-cluster/7291
cp redis.conf ../7292
cp redis.conf ../7293
cp redis.conf ../7294
cp redis.conf ../7295
cp redis.conf ../7296

批量替换内容

cd /usr/local/soft/redis-5.0.5/redis-cluster
sed -i 's/7291/7292/g' 7292/redis.conf
sed -i 's/7291/7293/g' 7293/redis.conf
sed -i 's/7291/7294/g' 7294/redis.conf
sed -i 's/7291/7295/g' 7295/redis.conf
sed -i 's/7291/7296/g' 7296/redis.conf

启动6个Redis节点

cd /usr/local/soft/redis-5.0.5/
./src/redis-server redis-cluster/7291/redis.conf
./src/redis-server redis-cluster/7292/redis.conf
./src/redis-server redis-cluster/7293/redis.conf
./src/redis-server redis-cluster/7294/redis.conf
./src/redis-server redis-cluster/7295/redis.conf
./src/redis-server redis-cluster/7296/redis.conf

是否启动了6个进程

ps -ef|grep redis

创建集群
旧版本中的redis-trib.rb已经废弃了,直接用–cluster命令
注意用绝对IP,不要用127.0.0.1

cd /usr/local/soft/redis-5.0.5/src/
redis-cli --cluster create 192.168.1.207:7291 192.168.1.207:7292 192.168.1.207:7293 192.168.1.207:7294 192.168.1.207:7295 192.168.1.207:7296 --cluster-replicas 1

Redis会给出一个预计的方案,对6个节点分配3主3从,如果认为没有问题,输入yes确认

>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 127.0.0.1:7295 to 127.0.0.1:7291
Adding replica 127.0.0.1:7296 to 127.0.0.1:7292
Adding replica 127.0.0.1:7294 to 127.0.0.1:7293
>>> Trying to optimize slaves allocation for anti-affinity
[WARNING] Some slaves are in the same host as their master
M: dfdc9c0589219f727e4fd0ad8dafaf7e0cfb4f1c 127.0.0.1:7291
   slots:[0-5460] (5461 slots) master
M: 8c878b45905bba3d7366c89ec51bd0cd7ce959f8 127.0.0.1:7292
   slots:[5461-10922] (5462 slots) master
M: aeeb7d7076d9b25a7805ac6f508497b43887e599 127.0.0.1:7293
   slots:[10923-16383] (5461 slots) master
S: ebc479e609ff8f6ca9283947530919c559a08f80 127.0.0.1:7294
   replicates aeeb7d7076d9b25a7805ac6f508497b43887e599
S: 49385ed6e58469ef900ec48e5912e5f7b7505f6e 127.0.0.1:7295
   replicates dfdc9c0589219f727e4fd0ad8dafaf7e0cfb4f1c
S: 8d6227aefc4830065624ff6c1dd795d2d5ad094a 127.0.0.1:7296
   replicates 8c878b45905bba3d7366c89ec51bd0cd7ce959f8
Can I set the above configuration? (type 'yes' to accept): 

注意看slot的分布:

7291  [0-5460] (5461个槽) 
7292  [5461-10922] (5462个槽) 
7293  [10923-16383] (5461个槽)

集群创建完成

>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
....
>>> Performing Cluster Check (using node 127.0.0.1:7291)
M: dfdc9c0589219f727e4fd0ad8dafaf7e0cfb4f1c 127.0.0.1:7291
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
M: 8c878b45905bba3d7366c89ec51bd0cd7ce959f8 127.0.0.1:7292
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
M: aeeb7d7076d9b25a7805ac6f508497b43887e599 127.0.0.1:7293
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
S: 8d6227aefc4830065624ff6c1dd795d2d5ad094a 127.0.0.1:7296
   slots: (0 slots) slave
   replicates aeeb7d7076d9b25a7805ac6f508497b43887e599
S: ebc479e609ff8f6ca9283947530919c559a08f80 127.0.0.1:7294
   slots: (0 slots) slave
   replicates dfdc9c0589219f727e4fd0ad8dafaf7e0cfb4f1c
S: 49385ed6e58469ef900ec48e5912e5f7b7505f6e 127.0.0.1:7295
   slots: (0 slots) slave
   replicates 8c878b45905bba3d7366c89ec51bd0cd7ce959f8
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

重置集群的方式是在每个节点上个执行cluster reset,然后重新创建集群

连接到客户端

redis-cli -p 7291
redis-cli -p 7292
redis-cli -p 7293

批量写入值

cd /usr/local/soft/redis-5.0.5/redis-cluster/
vim setkey.sh

脚本内容

#!/bin/bash
for ((i=0;i<20000;i++))
do
echo -en "helloworld" | redis-cli -h 192.168.1.207 -p 7291 -c -x set name$i >>redis.log
done
# 给脚本赋予执行权限
chmod +x setkey.sh

# 执行
./setkey.sh

每个节点分布的数据

127.0.0.1:7292> dbsize
(integer) 6683
127.0.0.1:7293> dbsize
(integer) 6665
127.0.0.1:7291> dbsize
(integer) 6652

其他命令,比如添加节点、删除节点,重新分布数据:

redis-cli --cluster help
Cluster Manager Commands:
  create         host1:port1 ... hostN:portN
                 --cluster-replicas <arg>
  check          host:port
                 --cluster-search-multiple-owners
  info           host:port
  fix            host:port
                 --cluster-search-multiple-owners
  reshard        host:port
                 --cluster-from <arg>
                 --cluster-to <arg>
                 --cluster-slots <arg>
                 --cluster-yes
                 --cluster-timeout <arg>
                 --cluster-pipeline <arg>
                 --cluster-replace
  rebalance      host:port
                 --cluster-weight <node1=w1...nodeN=wN>
                 --cluster-use-empty-masters
                 --cluster-timeout <arg>
                 --cluster-simulate
                 --cluster-pipeline <arg>
                 --cluster-threshold <arg>
                 --cluster-replace
  add-node       new_host:new_port existing_host:existing_port
                 --cluster-slave
                 --cluster-master-id <arg>
  del-node       host:port node_id
  call           host:port command arg arg .. arg
  set-timeout    host:port milliseconds
  import         host:port
                 --cluster-from <arg>
                 --cluster-copy
                 --cluster-replace
  help           

For check, fix, reshard, del-node, set-timeout you can specify the host and port of any working node in the cluster.
类型命令
集群

cluster info :打印集群的信息


cluster nodes :列出集群当前已知的所有节点(node),以及这些节点的相关信息

节点

cluster meet <ip> <port> :将 ip 和 port 所指定的节点添加到集群当中,让它成为集群的一份子


cluster forget <node_id> :从集群中移除 node_id 指定的节点(保证空槽道)


cluster replicate <node_id> :将当前节点设置为 node_id 指定的节点的从节点


cluster saveconfig :将节点的配置文件保存到硬盘里面

槽(slot)

cluster addslots <slot> [slot ...] :将一个或多个槽(slot)指派(assign)给当前节点


cluster delslots <slot> [slot ...] :移除一个或多个槽对当前节点的指派


cluster flushslots :移除指派给当前节点的所有槽,让当前节点变成一个没有指派任何槽的节点


cluster setslot <slot> node <node_id> :将槽 slot 指派给 node_id 指定的节点,如果槽已经指派给另一个节点,那么先让另一个节点删除该槽>,然后再进行指派


cluster setslot <slot> migrating <node_id> :将本节点的槽 slot 迁移到 node_id 指定的节点中


cluster setslot <slot> importing <node_id> :从 node_id 指定的节点中导入槽 slot 到本节点


cluster setslot <slot> stable :取消对槽 slot 的导入(import)或者迁移(migrate)

cluster keyslot <key> :计算键 key 应该被放置在哪个槽上 

 
cluster countkeysinslot <slot> :返回槽 slot 目前包含的键值对数量


cluster getkeysinslot <slot> <count> :返回 count 个 slot 槽中的键

数据分布

如果是希望数据分布相对均匀的话,我们首先可以考虑哈希后取模

  • 哈希后取模

例如,hash(key)%N,根据余数,决定映射到那一个节点。这种方式比较简单,属于静态的分片规则。但是一旦节点数量变化,新增或者减少,由于取模的 N 发生变化,数据需要重新分布。为了解决这个问题,我们又有了一致性哈希算法。

  • 一致性哈希

原理:把所有的哈希值空间组织成一个虚拟的圆环(哈希环),整个空间按顺时针方向组织。因为是环形空间,0 和 2^32-1 是重叠的


假设我们有四台机器要哈希环来实现映射(分布数据),我们先根据机器的名称或者 IP 计算哈希值,然后分布到哈希环中(红色圆圈)

现在有 4 条数据或者 4 个访问请求,对 key 计算后,得到哈希环中的位置(绿色圆圈)。沿哈希环顺时针找到的第一个 Node,就是数据存储的节点

在这种情况下,新增了一个 Node5 节点,不影响数据的分布

删除了一个节点 Node4,只影响相邻的一个节点

一致性哈希解决了动态增减节点时,所有数据都需要重新分布的问题,它只会影响到下一个相邻的节点,对其他节点没有影响。

但是这样的一致性哈希算法有一个缺点:

因为节点不一定是均匀地分布的,特别是在节点数比较少的情况下,所以数据不能得到均匀分布。解决这个问题的办法是引入虚拟节点(Virtual Node)

比如:2 个节点,5 条数据,只有 1 条分布到 Node2,4 条分布到 Node1,不均匀

Node1 设置了两个虚拟节点,Node2 也设置了两个虚拟节点(虚线圆圈)。
这时候有 3 条数据分布到 Node1,1 条数据分布到 Node2。

Redis 虚拟槽分区

Redis 既没有用哈希取模,也没有用一致性哈希,而是用虚拟槽来实现的。

Redis 创建了 16384 个槽(slot),每个节点负责一定区间的 slot。比如 Node1 负责 0-5460,Node2 负责 5461-10922,Node3 负责 10923-16383

Redis 的每个 master 节点维护一个 16384 位(2048bytes=2KB)的位序列,比如:序列的第 0 位是 1,就代表第一个 slot 是它负责;序列的第 1 位是 0,代表第二个 slot不归它负责。


对象分布到 Redis节点上时,对 key 用 CRC16 算法计算再%16384,得到一个 slot的值,数据落到负责这个 slot 的 Redis 节点上

 

查看 key 属于哪个 slot:

redis> cluster keyslot vincent

注意:key 与 slot 的关系是永远不会变的,会变的只有 slot 和 Redis 节点的关系

怎么让相关的数据落到同一个节点上?

比如有些 multi key 操作是不能跨节点的,如果要让某些数据分布到一个节点上,例如用户666 的基本信息和金融信息,怎么办?

在 key 里面加入{hash tag}即可。Redis 在计算槽编号的时候只会获取{}之间的字符串进行槽编号计算,这样由于上面两个不同的键,{}里面的字符串是相同的,因此他们可以被计算出相同的槽。

127.0.0.1:7293> set a{vt}a 1
OK

127.0.0.1:7293> set a{vt}b 1
OK

127.0.0.1:7293> set a{vt}c 1
OK

127.0.0.1:7293> set a{vt}d 1
OK

127.0.0.1:7293> set a{vt}e 1
OK

客户端重定向

比如在 7291 端口的 Redis 的 redis-cli 客户端操作:

127.0.0.1:7291> set vt 1
(error) MOVED 13724 127.0.0.1:7293

服务端返回 MOVED,也就是根据 key 计算出来的 slot 不归 7191 端口管理,而是归 7293 端口管理,服务端返回 MOVED 告诉客户端去 7293 端口操作

这个时候更换端口,用 redis-cli –p 7293 操作,才会返回 OK。或者用./redis-cli -c-p port 的命令(c 代表 cluster)。这样客户端需要连接两次。

数据迁移

因为 key 和 slot 的关系是永远不会变的,当新增了节点的时候,需要把原有的 slot分配给新的节点负责,并且把相关的数据迁移过来

添加新节点(新增一个 7297)

redis-cli --cluster add-node 127.0.0.1:7291 127.0.0.1:7297

新增的节点没有哈希槽,不能分布数据,在原来的任意一个节点上执行

redis-cli --cluster reshard 127.0.0.1:7291

输入需要分配的哈希槽的数量(比如 500)哈希槽的来源节点(可以输入 all 或者 id)

 

高可用和主从切换原理

当 slave 发现自己的 master 变为 FAIL 状态时,便尝试进行 Failover,使其成为新的master。由于挂掉的master可能会有多个slave,从而存在多个slave竞争成为master节点的过程, 其过程如下:

1.slave 发现自己的 master 变为 FAIL


2.将自己记录的集群 currentEpoch 加 1,并广播 FAILOVER_AUTH_REQUEST 信息


3.其他节点收到该信息,只有 master 响应,判断请求者的合法性,并发送FAILOVER_AUTH_ACK,对每一个 epoch 只发送一次 ack


4.尝试 failover 的 slave 收集 FAILOVER_AUTH_ACK


5.超过半数后变成新 Master

6.广播 Ping 通知其他集群节点

Redis Cluster 既能够实现主从的角色分配,又能够实现主从切换,相当于集成了Replication 和 Sentinal 的功能

 

Redis Cluster总结

优势

1. 无中心架构


2. 数据按照 slot 存储分布在多个节点,节点间数据共享,可动态调整数据分布


3. 可扩展性,可线性扩展到 1000 个节点(官方推荐不超过 1000 个),节点可动态添加或删除


4. 高可用性,部分节点不可用时,集群仍可用。通过增加 Slave 做 standby 数据副本,能够实现故障自动 failover,节点之间通过 gossip 协议交换状态信息,用投票机制完成 Slave 到 Master 的角色提升


5. 降低运维成本,提高系统的扩展性和可用性

缺点

1. Client 实现复杂,驱动要求实现 Smart Client,缓存 slots mapping 信息并及时更新,提高了开发难度,客户端的不成熟影响业务的稳定性


2. 节点会因为某些原因发生阻塞(阻塞时间大于 clutser-node-timeout),被判断下线,这种 failover 是没有必要的


3. 数据通过异步复制,不保证数据的强一致性

4. 多个业务使用同一套集群时,无法根据统计区分冷热数据,资源隔离性较差,容易出现相互影响的情况

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值