• Redis分布式方案


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

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

    客户端 Sharding

    在我们用得非常多的Jedis客户端中,支持分片功能。它是Spring Boot 2.x版本之前默认的Redis客户端,RedisTemplate就是对Jedis的封装。

    Sharded Jedis

    Jedis有几种连接池,其中有一种支持分片。
    比如这里一个连接到16、一个连接到Windows的Redis服务。

    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.0.16", 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();
                }
            }
    }
    

    通过dbsize命令发现,一台服务器有44个key,—台服务器有56个key。
    ShardedJedis是怎么做到的呢?
    如果是希望数据分布相对均匀的话,我们首先可以考虑哈希后取模(因为key不一定是整数,所以先计算哈希)。

    哈希后取模

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

    一致性哈希

    一致性哈希的原理:
    把所有的哈希值空间组织成一个虚拟的圆环(哈希环),整个空间按顺时与十方向组织。因为是环形空间,0和2^32-1是重叠的。
    假设我们有四台机器要哈希环来实现映射(分布数据),我们先根据机器的名称或IP计算哈希值,然后分布到哈希环中(黄色圆圈)。

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

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

    删除了节点3顺延到下一个节点只影响其中一段

    —致性哈希解决了动态增减节点时,所有数据都需要重新分布的问题,它只会影响到下一个相邻的节点,对其他节点没有影响。
    但是这样的一致性哈希算法有一个缺点,因为节点不一定是均匀地分布的,特别是在节点数比较少的情况下,所以数据不能得到均匀分布。解决这个问题的办法是引入虚拟节点(Virtual Node)。

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

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

    —致性哈希在分布式系统中,负载均衡、分库分表等场景中都有应用,跟LRU—样, 是一个很基础的算法。

    那这样一个一致性哈希算法,到底怎么实现呢?哈希环是一个什么数据结构?虚拟节点又怎么实现?

    redis.clients.util.Sharded.initialize(), jedis 实例被放到了一颗红黑树 TreeMap 中。

    private void initialize(List<S> shards) {
      //创建一个红黑树
      nodes = new TreeMap<Long, S>();
      //把所有Redis节点放到红黑树中
      for (int i = 0; i != shards.size(); ++i) {
      final S shardlnfo = shards.get(i);
      //为每个Redis节点创建160个虚拟节点,放到红黑树中
      if (shardlnfo.getName() == null) for (int n = 0; n < 160 * shardlnfo.getWeight(); n++) (
        nodes.put(this.algo.hash("SHARD-" + i + "-NODE-" +n), shardInfo);
      }
      else for (int n = 0; n < 160 * shardInfo.getWeight(); n++) {
        //对名字计算哈希(MurmurHash),名称格式SHARD-0-NODE-0
        nodes.put(this. algo. hash (shardinfo.getName() +"*"+ shardlnfo. getWeight() + n), shardlnfo);
      }
      //添加到map中,键为ShardInfo,值为redis实例
      resources.put(shardlnfo, shardlnfo.createResource());
      }
    }
    

    当存取键值对时,计算键的哈希值,然后从红黑树上摘下比该值大的第一个节点上的 JedisShardlnfo,随后从 resources 取出 Jedis。

    public R getShard(String key) {
      return resources.get(getShardInfo(key));
    }
    

    获取红黑树子集,找出比它大的第一个节点。

      public S getShardInfo(byte[] key) {
        //获取比当前key的哈希值要大的红黑树的子集
        SortedMap<Long, S> tail = nodes.tailMap(algo.hash(key));
        if (tail.isEmpty()) {
          //没有比它大的了,直接从nodes中取出
          return nodes.get(nodes.firstKey());
        }
        //返回第一个比它大的JedisShardlnfo
        return tail.get(tail.firstKey());
      }
    

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

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

    代理proxy

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

    Twemproxy

    Twemproxy的优点:比较稳定,可用性高。

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

    Codis

    Codis是一个代理中间件,豌豆荚公司用GO语言开发的(快三年时间没有更新了)。跟数据库分库分表中间件的Mycat的工作层次是一样的。

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

    功能特性 Codis Tewm proxy Redis Cluster
    重新分片不需要重启 Yes No Yes
    pipeline Yes Yes
    多 key 操作的 hash tags () Yes Yes Yes
    重新分片时的多key操作 Yes No
    客户端支持 所有 所有 支持cluster协议的客户端

    分片原理:Codis把所有的key分成了 N个槽(例如1024),每个槽对应一个分组, 一个分组对应于一个或者一组Redis实例。Codis对key进行CRC32运算,得到一个 32位的数字,然后模以N (槽的个数),得到余数,这个就是key对应的槽,槽后面就
    是Redis的实例(跟Mycat的先哈希后范围的算法思想类似)。比如4个槽:

    Codis的槽位映射关系是保存在Proxy中的,如果要解决单点的问题,Codis也要做集群部署,多个Codis节点怎么同步槽和实例的关系呢?需要运行一个Zookeeper(或者etcd/本地文件)。
    在新增节点的时候,可以为节点指定特定的槽位。Codis也提供了自动均衡策略。Codis不支持事务,其他的一些命令也不支持。
    获取数据原理(mget):在Redis中的各个实例里获取到符合的key,然后再汇总到Codis中。
    Codis是第三方提供的分布式解决方案,在官网的集群功能稳定之前,Codis也得到了大量应用。

    Redis Cluster

    Redis Cluster是在Redis 3.0的版本正式推出的,用来解决分布式的需求,同时也可以实现高可用。跟Codis不一样,它是去中心化的,客户端可以连接到任意一个可用节点。
    数据分片有几个关键的问题需要解决:
    1、数据怎么相对均匀地分片
    2、客户端怎么访问到相应的节点和数据
    3、重新分片的过程,怎么保证正常服务

    架构

    Redis Cluster可以看成是由多个Redis实例组成的数据集合。客户端不需要关注数 据的子集到底存储在哪个节点,只需要关注这个集合整体。
    以3主3从为例,节点之间两两交互,共享数据分片、节点状态等信息。

    搭建

    类型 命令
    集群 cluster info :打印集群的信息
    cluster nodes :列出集群当前己知的所有节点(node),以及这些节点的相关信息。
    节点 cluster meet : 将ip和port所指定的节点添加到集群当中,让它成为集群的一份子。
    cluster forget <node_id> : 从集群中移除node_id指定的节点(保证空槽道)。
    cluster replicate <node_id> : 将当前节点设置为node_id指定的节点的从节点。
    cluster saveconfig : 将节点的配置文件保存到硬盘里面
    槽(slot) cluster addslots [slot...]:将一个或多个槽(slot)指派(assign)给当前节点。
    cluster delslots [slot...]:移除一个或多个槽对当前节点的指派。
    cluster flushslots :移除指派给当前节点的所有槽,让当前节点变成一个没有指派任何槽的节点。
    cluster setslot node <node_id> :将槽slot指派给node_id指定的节点,如果槽已经指派给另一个节点,那么先让另一个节点删除该槽〉,然后再进行指派。
    cluster setslot migrating <node_id> :将本节点的槽 slot 迁移到 node_id 指定的节点中。
    cluster setslot importing <node_id> :从 node_id 指定的节点中导入槽 slot 至本节点。
    cluster setslot stable :取消对槽 slot 的导入(import)或者迁移(migrate)。
    cluster keyslot :计算键key应该被放置在哪个槽上。
    cluster countkeysinslot :返回槽slot目前包含的键值对数量。
    cluster getkeysinslot : 返回 count 个 slot 槽中的键

    问题:Cluster解决分片的问题,数据怎么分布?

    数据分布

    Redis既没有用哈希取模,也没有用一致性哈希,而是用虚拟槽来实现的。
    Redis创建了 16384个槽(slot),每个节点负责一定区间的slot。比如Node1负责 0-5460, Node2 负责 5461-10922, Node3 负责 10923-16383。

    对象分布到Redis节点上时,对key用CRC16算法计算再%16384,得到一个slot 的值,数据落到负责这个slot的Redis节点上。
    Redis的每个master节点都会维护自己负责的slot。用一个bit序列实现,比如: 序列的第0位是1,就代表第一个slot是它负责;序列的第1位是0,代表第二个slot 不归它负责。

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

    查看key属于哪个slot:

    redis> cluster keyslot snail
    

    注意:key与slot的关系是永远不会变的,会变的只有slot和Redis节点的关系。
    问题:怎么让相关的数据落到同一个节点上?
    比如有些multi key操作是不能跨节点的,例如用户2673的基本信息和金融信息? 在key里面加入{hash tag}即可。Redis在计算槽编号的时候只会获取{}之间的字符串进行槽编号计算,这样由于上面两个不同的键,{}里面的字符串是相同的,因此他们可以被计算出相同的槽。

    user{2673}base=...
    user{2673}fin=…

    127.0.0.1:7293> set a(sn}a 1
    OK
    127.0.0.1:7293> set a(sn}b 1
    OK
    127.0.0.1:7293〉set a(sn}c 1
    OK
    127.0.0.1:7293〉set a(sn}d 1
    OK
    127.0.0.1:7293> set a{sn}e 1
    OK
    

    问题:客户端连接到哪一台服务器?访问的数据不在当前节点上,怎么办?

    客户端重定向

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

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

    服务端返回MOVED,也就是根据key计算出来的slot不归7291端口管理,而是归7293端口管理,服务端返回MOVED告诉客户端去7293端口操作。
    这个时候更换端口,用redis-cli-p 7293操作,才会返回OK。或者用./redis-cli -c -p port的命令。

    这样客户端需要连接两次。Jedis等客户端会在本地维护一份slot——node的映射关系,大部分时候不需要重定向,所以叫做smart jedis (需要客户端支持)。

    问题:新增或下线了 Master节点,数据怎么迁移(重新分配)?

    数据迁移

    因为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广播Pong通知其他集群节点

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

    总结

    Redis Cluster 特点:
    1.无中心架构。
    2.数据按照slot存储分布在多个节点,节点间数据共享,可动态调整数据分布。
    3.可扩展性,可线性扩展到1000个节点(官方推荐不超过1000个),节点可动态添加或删除。
    4.高可用性,部分节点不可用时,集群仍可用。通过增加Slave做standby数据副 本,能够实现故障自动failover,节点之间通过gossip协议交换状态信息,用投票机制完成Slave到Master的角色提升。
    5.降低运维成本,提高系统的扩展性和可用性。

  • 相关阅读:
    20210309-2 阅读任务
    20210309-1 准备工作
    课程总结
    第十四周课程总结&实验报告(简单记事本的实现)
    十三周课程总结
    十二周课程总结
    第十一周课程总结
    C语言ll作业01
    C语言寒假大作战04
    C语言寒假大作战03
  • 原文地址:https://www.cnblogs.com/snail-gao/p/14475048.html
Copyright © 2020-2023  润新知