这一节介绍redis中内存分配相关的api,下一节介绍redis内存使用过程中的一些细节。
redis中所有的内存分配都由自己接管。主要由zmalloc.c和zmalloc.h中的zmalloc、zremalloc、zfree实现。
void *zmalloc(size_t size) { void *ptr = malloc(size+PREFIX_SIZE); if (!ptr) zmalloc_oom(size); #ifdef HAVE_MALLOC_SIZE // apple系统,额外说明 increment_used_memory(redis_malloc_size(ptr)); return ptr; #else *((size_t*)ptr) = size; increment_used_memory(size+PREFIX_SIZE); return (char*)ptr+PREFIX_SIZE; #endif }
可以看到,系统中除了分配请求大小的内存外,还在该内存块头部保存了该内存块的大小,这样,释放的时候可以通过该大小找到该内存块的起始位置:
void zfree(void *ptr) { --- realptr = (char*)ptr-PREFIX_SIZE; oldsize = *((size_t*)realptr); decrement_used_memory(oldsize+PREFIX_SIZE); free(realptr); --- }
另外对于 apple系统,可以用malloc_size(redis_malloc_size是对它的封装)取得指针所指向的内存块大小,因此就不需要手动保存大小了。(笔者没有用过apple系统,仅看了下apple的相关文档:http://developer.apple.com /library/mac/#documentation/Darwin/Reference/ManPages/man3 /malloc_size.3.html)
问你一个问题:在void flushAppendOnlyFile(int force)函数里有个 nwritten = write(server.appendfd,server.aofbuf,sdslen(server.aofbuf));操作,这个是不是会导致主线程阻塞?如果是的话这个fd为什么不放到file event中?谢谢!
理论上,不会阻塞;设计要求上看,不能放到file event中~~
redis的工作机制是一开始client_fd可读,不可写,接着redis处理命令,并将返回结果保存到client_fd对应的buf中,并设置client_fd可写。 也就是说,client_fd要得到返回结果,需要再经历一个事件循环。而在下一次事件循环前,也就是返回给client响应结果前,会调用flushAppendOnlyFile来记录这次命令(aof只记录会修改db的命令)。
aof,类似于日志文件系统,得确保在命令成功前,将所有的修改操作记录下来(aof介绍可参看我另外的blog)。在这里,redis确保了client得到响应结果时,redis已做好了记录,以方便出现意外时恢复db。从client的角度,得到响应结果时,redis必须已做好了相应的记录。因此,如果将server.appendfd放到file event中,不满足这个要求。
另外一个问题,redis此时会不会阻塞?
看看APUE(中文版)上的说法:
“在这些低速系统调用中一个值得注意的例外是与磁盘I / O有关的系统调用。虽然读、写一个磁盘文件可能暂时阻塞调用者(在磁盘驱动程序将请求排入队列,然后在适当时间执行请求期间),但是除非发生硬件错误,I / O操作总会很快返回,并使调用者不再处于阻塞状态。”
不知道是不是翻译的问题,这里容易误解。Linux下读写普通文件不会因为数据不够(读)或者数据超过缓冲区(写)而阻塞进程(这跟网络设备、终端设备是不一样的)。APUE上说的阻塞并不是这种阻塞,那是正常的进程调度策略。
对于普通文件,如果写的数据超过缓冲区,也会立即返回已写的字节数,不会阻塞。对于Linux,此处的缓冲区指的是Linux内核分配的高速缓冲区。比如缓冲区数不够,此时会立即返回。
那如果出现返回已写的字节数< 缓冲区的实际数据大小,redis会如何处理? 很抱歉,此时redis会退出。
if (nwritten != (signed)sdslen(server.aofbuf)) {
/* Ooops, we are in troubles. The best thing to do for now is
* aborting instead of giving the illusion that everything is
* working as expected. */
—–
exit(1);
}
至于这里为何不继续循环调用write写缓冲区,作者一开头也说了:
While this will save us against the server being killed I don’t think
* there is much to do about the whole server stopping for power problems
* or alike
大意是问题很复杂,比如服务器正在关闭、电源问题、磁盘问题。。。。。
从另一个角度,redis设计的初衷是读多写少,况且aof方式只会记录修改db的命令,因此一次循环,数据量应该是很少的。。。。。
PS:码了很多字。。。。。。也重新查阅了一些资料。。。。。。。。
hi, petermao,
在很多地方, 看到有时用zmalloc, 有时用zcalloc, 为什么要区分?
查资料得知, calloc 比 malloc 多了一个置” 的操作, 可是在用到zcalloc 的地方, 没有看到有置” 的必要.
比如 sds.c sdsnewlen函数中和 dict.c 中 dictExpand 中
inset.h 和 intset.c 中定义的整数集合, 在intset.c 最后给出的测试代码中, 有 intsetNew, 这个函数实现中有zmalloc 的操作, 但是为什么没有找到 intsetRelease 函数, 内存最后有释放吗?