redis源代码分析14–命令处理的一般过程

这个部分我们介绍下命令处理的一般过程。
在createClient时,为client的read事件设置了readQueryFromClient函数。我们来看看怎么处理client的命令的。
readQueryFromClient使用read一次读入REDIS_IOBUF_LEN字节,并保存在client中的querybuf参数中,然后调用processInputBuffer继续处理。 继续阅读

发表在 redis | 一条评论

redis源代码分析13–client连接(下)

这一节我们介绍下client连接的几个标志。

client的flags的取值有如下几种:

#define REDIS_SLAVE 1      /* This client is a slave server */
#define REDIS_MASTER 2     /* This client is a master server */
#define REDIS_MONITOR 4    /* This client is a slave monitor, see MONITOR */
#define REDIS_MULTI 8      /* This client is in a MULTI context */
#define REDIS_BLOCKED 16   /* The client is waiting in a blocking operation */
#define REDIS_IO_WAIT 32   /* The client is waiting for Virtual Memory I/O */

redis支持主从复制、监控特性,对这些server的连接状态也是保存在当前server的redisClient结构中, 并在redisClient的flags标志中设置REDIS_SLAVE 、REDIS_MASTER、 REDIS_MONITOR参数。我们在后续的章节中详细分析redis的主从复制、监控等特性。

一般的client仅会设置REDIS_MULTI、REDIS_BLOCKED、REDIS_IO_WAIT中的1个或多个。REDIS_MULTI跟redis的事务支持相关,我们后续介绍。REDIS_BLOCKED标志跟redis支持的list阻塞式pop(BLPOP、BRPOP)有关,也就是当list为空的时候,会阻塞client,一直到有元素加入list,此时再pop。REDIS_IO_WAIT跟命令字有关,对于某些命令,如果启用vm的话,需要提前加载其key。我们在命令处理章节中分析。

发表在 redis | 留下评论

redis源代码分析12–client连接(中)

这一节我们简略介绍下client连接的3个核心函数。涉及的很多参数跟redis的诸多特性有关,可在阅读后续章节后返回查看其相关值。

redis将新client的连接状态保存在redisClient结构体中,该结构体的代码如下: 继续阅读

发表在 redis | 一条评论

redis源代码分析11–client连接(上)

接下来的三节我们介绍client连接,按接受client连接、client连接的3个核心函数、client连接的几个标志3个部分顺序介绍。

这一节我们介绍下redis如何接受client连接。

在main函数中调用的initServer中,可看到如下代码:

static void initServer() {
  ---
   server.fd = anetTcpServer(server.neterr, server.port, server.bindaddr);
  ---
   if (aeCreateFileEvent(server.el, server.fd, AE_READABLE,
        acceptHandler, NULL) == AE_ERR) oom("creating file event");
  ---
}

继续阅读

发表在 redis | 留下评论

redis源代码分析10–事件处理(下)

serverCron做的工作很多,后续的很多章节都与此有关。该函数较复杂,分段分析。
一开始将当前时间保存,方便后续vm等机制对当前时间的访问:

/* We take a cached value of the unix time in the global state because
* with virtual memory and aging there is to store the current time
* in objects at every object access, and accuracy is not needed.
* To access a global var is faster than calling time(NULL) */
// 缓存时间
server.unixtime = time(NULL);

接着,如果收到SIGTERM等信号,则会在信号处理函数中设置server.shutdown_asap为1,此时就会调用prepareForShutdown做结束运行前的结尾工作: 继续阅读

发表在 redis | 留下评论

redis源代码分析9–事件处理(中)

接下来,我们分析下redis中事件的处理逻辑。

在函数initServer中调用aeCreateEventLoop完成初始化后,在main函数中调用ae_main,该函数是一个死循环:

static void initServer() {
    ---
    server.el = aeCreateEventLoop();
    ---
}

int main(int argc, char **argv) {
   ---
   initServer();
   ---
   aeSetBeforeSleepProc(server.el,beforeSleep);
   aeMain(server.el);
   ---
}

void aeMain(aeEventLoop *eventLoop) {
    eventLoop->stop = 0;
    while (!eventLoop->stop) {
        if (eventLoop->beforesleep != NULL)
            eventLoop->beforesleep(eventLoop);
        aeProcessEvents(eventLoop, AE_ALL_EVENTS);
    }
}

继续阅读

发表在 redis | 8 条评论

redis源代码分析8–事件处理(上)

redis是单进程单线程事件多路循环处理所有的客户端连接,它的运行都是靠事件触发的。

redis 的事件处理支持select、kqueue、epoll机制。其核心的poll函数aeApiPoll其实是一个封装函数,最终是调用 ae_select.c、ae_epoll.c还是ae_kqueue.c中的aeApiPoll(分别实现select、kqueue、epoll机制),取决于如下的宏定义:

#ifdef HAVE_EPOLL
#include "ae_epoll.c"
#else
    #ifdef HAVE_KQUEUE
    #include "ae_kqueue.c"
    #else
    #include "ae_select.c"
    #endif
#endif

ae_select.c、ae_epoll.c、ae_kqueue.c分别对select、kqueue、epoll进制进行了封装,对select、kqueue、epoll的性能比较可在网上找到详细资料。

所有的事件保存在server.el中,el是如下的一个结构: 继续阅读

发表在 redis | 一条评论

redis源代码分析7–内存(下)

上一节提到的used_memory变量保存了redis当前所使用的内存。其值常用来跟server.vm_max_memory、server.maxmemory进行比较。vm_max_memory表示redis vm启动swap的内存阈值,在超过该值后应启动vm的swap操作;maxmemory表示redis允许分配的最大内存,在超过该值后应进行内存的释放。这些比较主要在rdbLoad、loadAppendOnlyFile、serverCron、processCommand、vmThreadedIOCompletedJob等函数中。值得注意的是,尽管redis会尽量将内存使用量降低到server.maxmemory(甚至server.vm_max_memory)之下,但并不对此保证。 继续阅读

发表在 redis | 2 条评论

redis源代码分析6–内存(中)

在上一节介绍zmalloc/zfree函数时,我们看到redis会调用increment_used_memory/decrement_used_memory,这两个宏其实就是对static变量used_memory进行指定大小的增加/减少,该变量保存了redis所使用的内存大小:

//     sizeof(long)字节对齐
#define increment_used_memory(__n) do { \
    size_t _n = (__n); \
    if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \
    if (zmalloc_thread_safe) { \
        pthread_mutex_lock(&used_memory_mutex);  \
        used_memory += _n; \
        pthread_mutex_unlock(&used_memory_mutex); \
    } else { \
        used_memory += _n; \
    } \

} while(0)

分配内存的线程安全性是由底层系统的malloc系列函数来保证的,而used_memory变量的线程安全性取决于线程锁zmalloc_thread_safe的值。由于redis处理客户端连接是单进程单线程事件多路循环的,那么就有一个疑问,redis在什么情况下,需要多线程保护了?在后续的介绍中,我们会看到,redis的虚拟内存(VM)可能启用多线程。 继续阅读

发表在 redis | 留下评论

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
}

可以看到,系统中除了分配请求大小的内存外,还在该内存块头部保存了该内存块的大小,这样,释放的时候可以通过该大小找到该内存块的起始位置: 继续阅读

发表在 redis | 4 条评论