redis内存碎片率很低——下篇
接上篇讨论:https://blog.csdn.net/damanchen/article/details/102833201
一、问题复现
自己又测试,部署了一个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的内存吗??
二、分析
2.1 查看redis源码关于redis集群的初始化定义
在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的内存分配不是用在集群初始化函数中的?
2.2 分析导入配置文件的函数定义
所以重新找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的内存,,,
看来真有可能是保存(集群中)其他节点的信息导致占用那么大内存的,不只是配置文件,还有其他的各种信息,,,
这个讨论就暂告一段落,不一定完全对,先记下来,为了以后再遇到这种问题时能多为自己提供一点分析思路。或者看看自己当初的想法有多愚蠢。
欢迎大家一起讨论,点评,,(轻喷)