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

从收到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
    }
    ...
}

多主

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的注释很少,大量的全局变量。。。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值