自己又测试,部署了一个redis-4.0.12版本的集群模式的单节点,其info memory信息如下:
used_memory:1505864 used_memory_human:1.44M used_memory_rss:4800512 used_memory_rss_human:4.58M mem_fragmentation_ratio:3.19这个信息就感觉比较正常。
再部署一个redis-3.0.7集群版,其info memory信息如下:
used_memory:211047248 used_memory_human:201.27M used_memory_rss:4411392 used_memory_peak:211049112 used_memory_peak_human:201.27M used_memory_lua:36864 mem_fragmentation_ratio:0.02 mem_allocator:jemalloc-3.6.0再部署一个redis-3.0.7版的的集群模式的单节点,其info信息如下:
# Memory used_memory:1215984 used_memory_human:1.16M used_memory_rss:4083712 used_memory_peak:1215984 used_memory_peak_human:1.16M used_memory_lua:36864 mem_fragmentation_ratio:3.36 mem_allocator:jemalloc-3.6.0难道这个是不管redis-4.0.12版本或者redis-3.0.7版本,只要是在集群模式下,就会多分配200多M的内存吗??
在cluster.c中关于redis初始化的定义如下:
// 初始化集群 void clusterInit(void) { int saveconf = 0; // 初始化配置 server.cluster = zmalloc(sizeof(clusterState)); server.cluster->myself = NULL; server.cluster->currentEpoch = 0; server.cluster->state = REDIS_CLUSTER_FAIL; server.cluster->size = 1; server.cluster->todo_before_sleep = 0; server.cluster->nodes = dictCreate(&clusterNodesDictType,NULL); server.cluster->nodes_black_list = dictCreate(&clusterNodesBlackListDictType,NULL); server.cluster->failover_auth_time = 0; server.cluster->failover_auth_count = 0; server.cluster->failover_auth_rank = 0; server.cluster->failover_auth_epoch = 0; server.cluster->lastVoteEpoch = 0; server.cluster->stats_bus_messages_sent = 0; server.cluster->stats_bus_messages_received = 0; memset(server.cluster->slots,0, sizeof(server.cluster->slots)); clusterCloseAllSlots(); /* Lock the cluster config file to make sure every node uses * its own nodes.conf. */ if (clusterLockConfig(server.cluster_configfile) == REDIS_ERR) exit(1); /* Load or create a new nodes configuration. */ if (clusterLoadConfig(server.cluster_configfile) == REDIS_ERR) { /* No configuration found. We will just use the random name provided * by the createClusterNode() function. */ myself = server.cluster->myself = createClusterNode(NULL,REDIS_NODE_MYSELF|REDIS_NODE_MASTER); redisLog(REDIS_NOTICE,"No cluster configuration found, I'm %.40s", myself->name); clusterAddNode(myself); saveconf = 1; } // 保存 nodes.conf 文件 if (saveconf) clusterSaveConfigOrDie(1); /* We need a listening TCP port for our cluster messaging needs. */ // 监听 TCP 端口 server.cfd_count = 0; /* Port sanity check II * The other handshake port check is triggered too late to stop * us from trying to use a too-high cluster port number. */ if (server.port > (65535-REDIS_CLUSTER_PORT_INCR)) { redisLog(REDIS_WARNING, "Redis port number too high. " "Cluster communication port is 10,000 port " "numbers higher than your Redis port. " "Your Redis port number must be " "lower than 55535."); exit(1); } if (listenToPort(server.port+REDIS_CLUSTER_PORT_INCR, server.cfd,&server.cfd_count) == REDIS_ERR) { exit(1); } else { int j; for (j = 0; j < server.cfd_count; j++) { // 关联监听事件处理器 if (aeCreateFileEvent(server.el, server.cfd[j], AE_READABLE, clusterAcceptHandler, NULL) == AE_ERR) redisPanic("Unrecoverable error creating Redis Cluster " "file event."); } } /* The slots -> keys map is a sorted set. Init it. */ // slots -> keys 映射是一个有序集合 server.cluster->slots_to_keys = zslCreate(); resetManualFailover(); }看到其中关于内存分配的就只有下面这一句:
server.cluster = zmalloc(sizeof(clusterState));而有关 clusterStatus 函数的定义如下:
// 集群状态,每个节点都保存着一个这样的状态,记录了它们眼中的集群的样子。 // 另外,虽然这个结构主要用于记录集群的属性,但是为了节约资源, // 有些与节点有关的属性,比如 slots_to_keys 、 failover_auth_count // 也被放到了这个结构里面。 typedef struct clusterState { // 指向当前节点的指针 clusterNode *myself; /* This node */ // 集群当前的配置纪元,用于实现故障转移 uint64_t currentEpoch; // 集群当前的状态:是在线还是下线 int state; /* REDIS_CLUSTER_OK, REDIS_CLUSTER_FAIL, ... */ // 集群中至少处理着一个槽的节点的数量。 int size; /* Num of master nodes with at least one slot */ // 集群节点名单(包括 myself 节点) // 字典的键为节点的名字,字典的值为 clusterNode 结构 dict *nodes; /* Hash table of name -> clusterNode structures */ // 节点黑名单,用于 CLUSTER FORGET 命令 // 防止被 FORGET 的命令重新被添加到集群里面 // (不过现在似乎没有在使用的样子,已废弃?还是尚未实现?) dict *nodes_black_list; /* Nodes we don't re-add for a few seconds. */ // 记录要从当前节点迁移到目标节点的槽,以及迁移的目标节点 // migrating_slots_to[i] = NULL 表示槽 i 未被迁移 // migrating_slots_to[i] = clusterNode_A 表示槽 i 要从本节点迁移至节点 A clusterNode *migrating_slots_to[REDIS_CLUSTER_SLOTS]; // 记录要从源节点迁移到本节点的槽,以及进行迁移的源节点 // importing_slots_from[i] = NULL 表示槽 i 未进行导入 // importing_slots_from[i] = clusterNode_A 表示正从节点 A 中导入槽 i clusterNode *importing_slots_from[REDIS_CLUSTER_SLOTS]; // 负责处理各个槽的节点 // 例如 slots[i] = clusterNode_A 表示槽 i 由节点 A 处理 clusterNode *slots[REDIS_CLUSTER_SLOTS]; // 跳跃表,表中以槽作为分值,键作为成员,对槽进行有序排序 // 当需要对某些槽进行区间(range)操作时,这个跳跃表可以提供方便 // 具体操作定义在 db.c 里面 zskiplist *slots_to_keys; /* The following fields are used to take the slave state on elections. */ // 以下这些域被用于进行故障转移选举 // 上次执行选举或者下次执行选举的时间 mstime_t failover_auth_time; /* Time of previous or next election. */ // 节点获得的投票数量 int failover_auth_count; /* Number of votes received so far. */ // 如果值为 1 ,表示本节点已经向其他节点发送了投票请求 int failover_auth_sent; /* True if we already asked for votes. */ int failover_auth_rank; /* This slave rank for current auth request. */ uint64_t failover_auth_epoch; /* Epoch of the current election. */ /* Manual failover state in common. */ /* 共用的手动故障转移状态 */ // 手动故障转移执行的时间限制 mstime_t mf_end; /* Manual failover time limit (ms unixtime). It is zero if there is no MF in progress. */ /* Manual failover state of master. */ /* 主服务器的手动故障转移状态 */ clusterNode *mf_slave; /* Slave performing the manual failover. */ /* Manual failover state of slave. */ /* 从服务器的手动故障转移状态 */ long long mf_master_offset; /* Master offset the slave needs to start MF or zero if stil not received. */ // 指示手动故障转移是否可以开始的标志值 // 值为非 0 时表示各个主服务器可以开始投票 int mf_can_start; /* If non-zero signal that the manual failover can start requesting masters vote. */ /* The followign fields are uesd by masters to take state on elections. */ /* 以下这些域由主服务器使用,用于记录选举时的状态 */ // 集群最后一次进行投票的纪元 uint64_t lastVoteEpoch; /* Epoch of the last vote granted. */ // 在进入下个事件循环之前要做的事情,以各个 flag 来记录 int todo_before_sleep; /* Things to do in clusterBeforeSleep(). */ // 通过 cluster 连接发送的消息数量 long long stats_bus_messages_sent; /* Num of msg sent via cluster bus. */ // 通过 cluster 接收到的消息数量 long long stats_bus_messages_received; /* Num of msg rcvd via cluster bus. } clusterState;可以看到 clusterStatus 函数是关于集群状态信息的定义,但是根据这些定义来算内存占用的话,怎么算都不可能会达到200多M那么大的内存啊,,,
难道这200多M的内存分配不是用在集群初始化函数中的?
所以重新找cluster中使用到内存分配函数的地方,希望有所发现. 观察到有一个关于导入配置文件的内存分配比较可疑,其函数定义如下:
clusterLoadConfig /* Parse the file. Note that single liens of the cluster config file can * be really long as they include all the hash slots of the node. * 集群配置文件中的行可能会非常长, * 因为它会在行里面记录所有哈希槽的节点。 * * This means in the worst possible case, half of the Redis slots will be * present in a single line, possibly in importing or migrating state, so * together with the node ID of the sender/receiver. * * 在最坏情况下,一个行可能保存了半数的哈希槽数据, * 并且可能带有导入或导出状态,以及发送者和接受者的 ID 。 * * To simplify we allocate 1024+REDIS_CLUSTER_SLOTS*128 bytes per line. * * 为了简单起见,我们为每行分配 1024+REDIS_CLUSTER_SLOTS*128 字节的空间 */ maxline = 1024+REDIS_CLUSTER_SLOTS*128; line = zmalloc(maxline);所以一行配置文件最多占用内存:16384*128+1024 bytes = 2049KB = 2MB
redis3 55行 redis4 67行
这样算下来好像也没有200M那么多,,
之后创建主从模式的时候发现,在刚创建好两个节点之后,内存使用量还只是几百K,但是一旦执行 slaveof 建立主从之后,就会使用225M的内存,,,
看来真有可能是保存(集群中)其他节点的信息导致占用那么大内存的,不只是配置文件,还有其他的各种信息,,,
这个讨论就暂告一段落,不一定完全对,先记下来,为了以后再遇到这种问题时能多为自己提供一点分析思路。或者看看自己当初的想法有多愚蠢。
欢迎大家一起讨论,点评,,(轻喷)