redis源代码分析5–内存(上)

这一节介绍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)

此条目发表在 redis 分类目录。将固定链接加入收藏夹。

redis源代码分析5–内存(上)》有 4 条评论

  1. 项仲 说:

    问你一个问题:在void flushAppendOnlyFile(int force)函数里有个 nwritten = write(server.appendfd,server.aofbuf,sdslen(server.aofbuf));操作,这个是不是会导致主线程阻塞?如果是的话这个fd为什么不放到file event中?谢谢!

    • petermao 说:

      理论上,不会阻塞;设计要求上看,不能放到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:码了很多字。。。。。。也重新查阅了一些资料。。。。。。。。

  2. ericuni 说:

    hi, petermao,
    在很多地方, 看到有时用zmalloc, 有时用zcalloc, 为什么要区分?
    查资料得知, calloc 比 malloc 多了一个置” 的操作, 可是在用到zcalloc 的地方, 没有看到有置” 的必要.
    比如 sds.c sdsnewlen函数中和 dict.c 中 dictExpand 中

  3. ericuni 说:

    inset.h 和 intset.c 中定义的整数集合, 在intset.c 最后给出的测试代码中, 有 intsetNew, 这个函数实现中有zmalloc 的操作, 但是为什么没有找到 intsetRelease 函数, 内存最后有释放吗?

发表评论

电子邮件地址不会被公开。 必填项已被标记为 *

*

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>