• KeyDB源码解析四——其他特性


    除了多线程,KeyDB在Redis的基础上发展除了一些其他特性

    Active Replication

    Redis集群模式下做横向扩展时,增加从节点来分摊读压力,从节点一般是只读的,可以配置从节点可写,但会造成主从数据不一致。KeyDB支持从节点可以,并且把从节点写入的操作同步给主节点,从节点和主节点是双向同步的。
    双向同步需要解决循环同步的问题,即:主收到客户端写请求,把写请求同步给从,从又再次把该写请求同步给主,这时候主是不能执行这个写请求的,为了解决这个问题,KeyDB为每个server在启动时生成一个唯一的64位uuid,在建立同步连接时,会告知对方自己的uuid,同时会告知对面是否具备active replica能力。
    当server收到客户端请求时,会将命令改写,加上命令的uuid、dictid、mvcctamp:

    void replicationFeedSlavesCore(list *slaves, int dictid, robj **argv, int argc) {
    	 ...
    	 char szDbNum[128];
         int cchDbNum = 0;
           if (!fSendRaw)
               cchDbNum = writeProtoNum(szDbNum, sizeof(szDbNum), dictid);
           
    
           char szMvcc[128];
           int cchMvcc = 0;
           incrementMvccTstamp();	// Always increment MVCC tstamp so we're consistent with active and normal replication
           if (!fSendRaw)
               cchMvcc = writeProtoNum(szMvcc, sizeof(szMvcc), getMvccTstamp());
    
           //size_t cchlen = multilen+3;
           struct redisCommand *cmd = lookupCommand(szFromObj(argv[0]));
           sds buf = catCommandForAofAndActiveReplication(sdsempty(), cmd, argv, argc);
           size_t cchlen = sdslen(buf);
    
           // The code below used to be: snprintf(proto, sizeof(proto), "*5\r\n$7\r\nRREPLAY\r\n$%d\r\n%s\r\n$%lld\r\n", (int)strlen(uuid), uuid, cchbuf);
           //  but that was much too slow
           static const char *protoRREPLAY = "*5\r\n$7\r\nRREPLAY\r\n$36\r\n00000000-0000-0000-0000-000000000000\r\n$";
           char proto[1024];
           int cchProto = 0;
           if (!fSendRaw)
           {
               char uuid[37];
               uuid_unparse(cserver.uuid, uuid);
    
               cchProto = strlen(protoRREPLAY);
               memcpy(proto, protoRREPLAY, strlen(protoRREPLAY));
               memcpy(proto + 22, uuid, 36); // Note UUID_STR_LEN includes the \0 trailing byte which we don't want
               cchProto += ll2string(proto + cchProto, sizeof(proto)-cchProto, cchlen);
               memcpy(proto + cchProto, "\r\n", 3);
               cchProto += 2;
           }
    
    		// 复制流中加入uuid
           feedReplicationBacklog(proto, cchProto);     
           // 复制流中加入命令       
           feedReplicationBacklog(buf, sdslen(buf));
    
           const char *crlf = "\r\n";
           // 复制流中加入dbid、mvcctamp
           feedReplicationBacklog(crlf, 2);
           feedReplicationBacklog(szDbNum, cchDbNum);
           feedReplicationBacklog(szMvcc, cchMvcc);
    
           sdsfree(buf);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    从收到master的复制流之后,判断是否是master,如果是master,则不会继续发送给master:

    void replicationFeedSlavesFromMasterStream(){
    	...
    	while((ln = listNext(&li))) {
            client *slave = (client*)ln->value;
            std::lock_guard<decltype(slave->lock)> ulock(slave->lock);
            if (FSameHost(slave, server.master))
                continue;   // Active Active case, don't feed back
        }
        ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    多主

    Redis中从节点只能有一个主节点,KeyDB将可以有多个主节点,这样,多个主节点的数据就可以同步给一个从节点。
    这一特性一般与active replica一起配合使用,可以灵活的构建出多种部署方式。比如:
    1、异地多活,多个地域都向一个中心地域同步数据,中心地域的数据用来做最终的备份。
    2、避免客户端的跨地域延迟,一个地域的客户端可以就近读到另一个地域的数据。
    但是这两个特性在集群模式下会有数据一致性问题,所以这两个特性不能在集群模式下使用,这就限制了集群规模。

    集群模式

    KeyDB的集群模式与Redis基本相同,在KeyDB的官方文档(https://docs.keydb.dev/docs/cluster-spec)中详细介绍了集群模式的设计思路和注意事项,这也是之前在阅读Redis代码时没有注意到的,下面列几点:

    1. 为了使得key落在同一个分片中,可以使用hash tag
    2. 异步主从复制以及集群模式下的failover,使得Redis是有可能丢数据的,以下集群模式下丢数据的两种场景:
      a、master收到并提交客户端请求后,客户端收到数据更新成功,但是master在向slave发送复制流时宕机,并且在master重启之前,已经failover成功(基本不会发生,因为master和slave一般同机房,网络传输一般比master与客户端快)
      b、failover成功之后,旧的master重启,还没有转换为slave之前,客户端的路由还是旧的,就往旧的master上写数据,数据也会丢失(基本不会发生,master重启之后,在与集群中的大多数节点取得通信之前,会拒写,但是如果客户端与少部分节点一个网络隔离,就极有可能丢失数据)
    3. 从节点迁移算法,该算法保证了集群的主节点都尽可能的只有有一个从节点,防止单节点失效问题,算法如下:如果一个master没有从节点,那么就从集群中所有master节点中选择从节点最多的节点,从中选择一个从节点,挂到之前的master节点上。
    4. configEpoch冲突解决,在failover成功或者slot迁移之后,节点都会自增自己的configEpoch,来保证在此时,自己的configEpoch是集群中最大的,但是在进行这两个操作的时候,有可能出现configEpoch冲突,这样当冲突的节点同时宣布负责某个slot时,其他节点就不知道如何处理了,所以遇到configEpoch冲突时,节点自身再次自增configEpoch

    ps:最后吐槽下:可能是由于多人开发,并且c和c++混合,KeyDB的代码可读性真的很差,代码风格不统一,新feature的注释很少,大量的全局变量。。。

  • 相关阅读:
    【UML分析、建模与设计】我在工作时遇到UML
    【Spring】循环依赖
    使用 Sahi 实现 Web 自动化测试
    【RuoYi-Vue-Plus】扩展笔记 03 - 实现简单的 EasyExcel 自定义导入监听器
    G管螺纹尺寸对照表
    [C语言、C++]数据结构作业:用双向链表实现折返约瑟夫问题
    Canal整合SpringBoot详解(一)
    C_9练习题
    14.Linux软件包管理(RPM包的管理(了解))
    EraseNet:End-to-End Text Removal in the wild
  • 原文地址:https://blog.csdn.net/yh88623131/article/details/128054170