16384,别问为啥,问就是 2K!

官方回答

CRC16 算法产生的值有 16 位,能产生 2^16 也就是 65536 个值,那为什么 Redis 在集群模式下要把槽数固定为 16384,这个问题有人在 Redis 的 Github 仓库中提过 Issue,开发者也给了解答:

20220423-1

理解一下就是:

  1. Redis 节点的心跳数据中包含槽的数据,一个槽作为一个 bit,16384 个 bit 相当与 2048 个 Byte 也就是 2KB 的大小,而 65536 个槽的大小为 8K
  2. Redis 集群节点数量不会超过 1000 个,超过可能引发拥堵,因为要同步信息的节点太多
  3. Redis Master 的配置信息中负责的哈希槽通过 Bitmap 来维护,传输过程中需要对 Bitmap 进行压缩,节点数为 N,Bitmap 压缩率就为 slots/N,如果 slots 太多的话那么压缩率就很低

Redis 的心跳数据

截至目前 Redis 最新的稳定版本为 6.2,找一下 Github 仓库中 6.2 版本的集群文件 cluster.h 源码,路径为:https://github.com/redis/redis/blob/6.2/src/cluster.h

typedef struct {
    char sig[4];        /* Signature "RCmb" (Redis Cluster message bus). */
    uint32_t totlen;    /* Total length of this message */
    uint16_t ver;       /* Protocol version, currently set to 1. */
    uint16_t port;      /* TCP base port number. */
    uint16_t type;      /* Message 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. */
    char sender[CLUSTER_NAMELEN]; /* Name of the sender node */
    unsigned char myslots[CLUSTER_SLOTS/8]; /* 这里,节点负责的槽位 */
    char slaveof[CLUSTER_NAMELEN];
    char myip[NET_IP_STR_LEN];    /* Sender IP, if not all zeroed. */
    char notused1[32];  /* 32 bytes reserved for future usage. */
    uint16_t pport;      /* Sender TCP plaintext port, if base port is TLS */
    uint16_t cport;      /* Sender TCP cluster bus port */
    uint16_t flags;      /* Sender node flags */
    unsigned char state; /* Cluster state from the POV of the sender */
    unsigned char mflags[3]; /* Message flags: CLUSTERMSG_FLAG[012]_... */
    union clusterMsgData data;
} clusterMsg;

在结构体 clusterMsg 中有一个 myslots 的 char 数组 unsigned char myslots[CLUSTER_SLOTS/8],长度为 16384/8 = 2048,除以 8 的原因是在 C 语言里一个 char 占一个 Byte 也就是 8 bit,这个 char 数组组成一张 Bitmap,每一个 bit 表示一个槽,如果某个 bit 为 1,就表示这个 bit 对应的槽归这个 master 管,如果槽位为 65536,每次的 ping 消息的消息头太大了,浪费带宽

总的来说,从心跳数据大小、节点数、网络带宽、消息压缩率等多方面考虑,Redis 的开发者认为 16384 个槽位能满足日常业务需求并且数量是比较合适的