【原创】 Redis Cluster 的实现 - cluster 消息的接收和分包

因为针对 READ 事件的 clusterReadHandler 处理器主要工作就是解析 cluster bus 上接收的数据并进行消息分包,然后对消息进行处理,而对于消息的分包首先需要了解一下消息结构,Redis Cluster 节点之间通信的消息结构定义如下:

typedef struct {
    char sig[4];        /* Siganture "RCmb" (Redis Cluster message bus). */
    uint32_t totlen;    /* Total length of this message */
    uint16_t ver;       /* Protocol version, currently set to 0. */
    uint16_t notused0;  /* 2 bytes not used. */
    
    /* Message type,如:PING, PONG,定义参考宏定义 CLUSTERMSG_TYPE_* */
    uint16_t type;
    
    uint16_t count;     /* Only used for some kind of messages. */
    uint64_t currentEpoch;  /* The epoch accordingly to the sending node. */
    uint64_t configEpoch;   /* The config epoch if it‘s a master, or the last
                               epoch advertised by its master if it is a
                               slave. */
    uint64_t offset;    /* Master replication offset if node is a master or
                           processed replication offset if node is a slave. */
                           
    // 节点发送方,为 NodeID 表示,如: 123ed65d59ff22370f2f09546f410d31207789f6
    char sender[REDIS_CLUSTER_NAMELEN]; /* Name of the sender node */
    
    // 本节点维护的 slots bits
    unsigned char myslots[REDIS_CLUSTER_SLOTS/8];
    
    // 如果本节点为 slave 节点,则 slaveof 记录对应的 master 节点ID
    char slaveof[REDIS_CLUSTER_NAMELEN];
    
    char notused1[32];  /* 32 bytes reserved for future usage. */
    uint16_t port;      /* Sender TCP base port */
    uint16_t flags;     /* Sender node flags */
    
    // cluster 状态, 如:REDIS_CLUSTER_OK, REDIS_CLUSTER_FAIL ...
    unsigned char state; /* Cluster state from the POV of the sender */
    unsigned char mflags[3]; /* Message flags: CLUSTERMSG_FLAG[012]_... */
    
    // 指向不同 消息类型 的消息体
    // 参考 clusterMsgData 结构的说明
    union clusterMsgData data;
} clusterMsg;

从上面结构可以看到消息分包,主要解析前 8 个字节,分别为:
 
  - char sig[4];      // 消息签名,对于 cluster 消息,固定为字符序列 RCmb
  - uint32_t totlen;  // 消息总长度
 
其他结构成员都是在处理消息时使用的,后续讲解消息处理流程时进行分析。

/* Read data. Try to read the first field of the header first to check the
 * full length of the packet. When a whole packet is in memory this function
 * will call the function to process the packet. And so forth. */
void clusterReadHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
    char buf[sizeof(clusterMsg)];
    ssize_t nread;
    clusterMsg *hdr;
    clusterLink *link = (clusterLink*) privdata;
    int readlen, rcvbuflen;
    REDIS_NOTUSED(el);
    REDIS_NOTUSED(mask);
    while(1) { /* Read as long as there is data to read. */
        rcvbuflen = sdslen(link->rcvbuf);
        if (rcvbuflen < 8) {
            /* First, obtain the first 8 bytes to get the full message
             * length. */
            readlen = 8 - rcvbuflen;
        } else {
            // 已经知道了本条消息的长度
            // 本块代码主要计算剩余还需读入的字节数(readlen)才是完整的消息
            /* Finally read the full message. */
            hdr = (clusterMsg*) link->rcvbuf;
            if (rcvbuflen == 8) {
                /* Perform some sanity check on the message signature
                 * and length. */
                if (memcmp(hdr->sig,"RCmb",4) != 0 ||
                    ntohl(hdr->totlen) < CLUSTERMSG_MIN_LEN)
                {
                    redisLog(REDIS_WARNING,
                        "Bad message length or signature received "
                        "from Cluster bus.");
                    handleLinkIOError(link);
                    return;
                }
            }
            readlen = ntohl(hdr->totlen) - rcvbuflen;
            if (readlen > sizeof(buf)) readlen = sizeof(buf);
        }
        
        // 读入本条消息记录的剩余 readlen 个字节的数据
        // 因为这里的 fd 是非阻塞的,所以需要判断 EAGAIN
        nread = read(fd,buf,readlen);
        if (nread == -1 && errno == EAGAIN) return; /* No more data ready. */
        if (nread <= 0) {
            /* I/O error... */
            redisLog(REDIS_DEBUG,"I/O error reading from node link: %s",
                (nread == 0) ? "connection closed" : strerror(errno));
            handleLinkIOError(link);
            return;
        } else {
            /* Read data and recast the pointer to the new buffer. */
            link->rcvbuf = sdscatlen(link->rcvbuf,buf,nread);
            hdr = (clusterMsg*) link->rcvbuf;
            rcvbuflen += nread;
        }
        /* Total length obtained? Process this packet. */
        if (rcvbuflen >= 8 && rcvbuflen == ntohl(hdr->totlen)) {
            // 表明 link 上的 rcvbuf 已经是一个完整的 cluster 消息
            // 下面开始处理此消息
            if (clusterProcessPacket(link)) {
                sdsfree(link->rcvbuf);
                link->rcvbuf = sdsempty();
            } else {
                return; /* Link no longer valid. */
            }
        }
    }
}
时间: 2024-11-03 21:49:20

【原创】 Redis Cluster 的实现 - cluster 消息的接收和分包的相关文章

Redis 5.0 redis-cli --cluster help说明

背景: Redis Cluster 在5.0之后取消了ruby脚本 redis-trib.rb的支持(手动命令行添加集群的方式不变),集合到redis-cli里,避免了再安装ruby的相关环境.直接使用redis-clit的参数--cluster 来取代.为方便自己后面查询就说明下如何使用该命令进行Cluster的创建和管理,关于Cluster的相关说明可以查看官网或则Redis Cluster部署.管理和测试. 环境: 系统版本:Ubuntu 14.04 Redis版本:5.0.5 机器IP:

Percona XtraDB Cluster vs Galera Cluster vs MySQL Group Replication

Overview Galera Cluster Percona XtraDB Cluster MySQL Group Replication MySQL InnoDB Cluster Similarities Similar - Use Cases Similar Limitation Differences GR & Galera Group Communication System 组通信系统 Binlogs & Gcache Node Provisioning 节点配置 GTID v

用redis实现支持优先级的消息队列

用redis实现支持优先级的消息队列 为什么需要消息队列 系统中引入消息队列机制是对系统一个非常大的改善.例如一个web系统中,用户做了某项操作后需要发送邮件通知到用户邮箱中.你可以使用同步方式让用户等待邮件发送完成后反馈给用户,但是这样可能会因为网络的不确定性造成用户长时间的等待从而影响用户体验. 有些场景下是不可能使用同步方式等待完成的,那些需要后台花费大量时间的操作.例如极端例子,一个在线编译系统任务,后台编译完成需要30分钟.这种场景的设计不可能同步等待后在回馈,必须是先反馈用户随后异步

[原创]chromium源码阅读-进程间通信IPC.消息的接收与应答

chromium源码阅读-进程间通信IPC.消息的接收与应答 chromium源码阅读-进程间通信IPC.消息的接收与应答 介绍 chromium进程间通信在win32下是通过命名管道的方式实现的,最后的数据都是以二进制流的方式进行传播,pickle类就是负责消息的封包与解包功能,它将各种数据已二进制的形式写到内存缓冲区中,在实际通信的时候通过与其他一些辅助类与模板函数来实现具体数据结构的写与读.本文主要介绍的是chromium在将消息发送与接收过程中,以及chromium如何通过各种消息宏与C

探讨erlang消息选择性接收和改进

从 rabbitMQ 代码中找到 gen_server2 , 对gen_server进行了一些优化.看到前辈写的博文也提到这个,引发了我的思考.见 gen_server2 - OTP gen_server优化版 . gen_server2 引发的思考 正如 litaocheng 所说的: gen_server 和 gen_server2 最大的不同是: gen_server2 收到任何一条消息放到外部的队列中,当VM内部消息队列为空后,才进行消息处理,继续循环 gen_server 收到任何一条

java开发微信公众平台(二)-- 消息的接收与回复文本消息

前面完成了服务器的对接,这一篇主要是实现各类消息的接收,并封装成map形式,供后面的使用,以及回复文本消息. 前面介绍的服务器的对接是通过get请求,而微信服务器准发过来用户的信息则是通过post请求,因此我们的方法要在post中实现. 同样,sae在接收微信服务器发过来消息时仍需要验证消息的可靠性,与上一讲中类似,只不过当验证成功后不是返回echostr,而是对 微信服务器发过来的消息进行解析处理. 1 protected void doPost(HttpServletRequest requ

微信开发之消息的接收与相应--文本消息(五)

一.消息格式 <xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[fromUser]]></FromUserName> <CreateTime>1348831860</CreateTime> <MsgType><![CDATA[text]]></MsgType> <

[029] 微信公众帐号开发教程第5篇-各种消息的接收与响应

前一篇文章里我们已经把微信公众平台接口中消息及相关操作都进行了封装,本章节将主要介绍如何接收微信服务器发送的消息并做出响应. 明确在哪接收消息 从微信公众平台接口消息指南中可以了解到,当用户向公众帐号发消息时,微信服务器会将消息通过POST方式提交给我们在接口配置信息中填写的URL,而我们就需要在URL所指向的请求处理类CoreServlet的doPost方法中接收消息.处理消息和响应消息.   接收.处理.响应消息 下面先来看我已经写好的CoreServlet的完整代码: [java] vie

Java与微信不得不说的故事——消息的接收与发送

Java与微信的知识也是自学阶段,代码都是参照柳峰老师的.具体可以查看此博:http://blog.csdn.net/lyq8479/article/details/8949088. 下面说一下消息的接收和发送吧. 消息的推送:当普通用户向公众账号发送消息是,微信服务器将POST消息到填写的URL上.消息是一个xml包. 消息的回复:对于每一个POST请求,开发者在响应包中返回特定的xml包,对消息进行响应. 所以,需要有解析xml包和包装xml包的方法.于是,引进了dom4j.jar和xstr