memcached的执行流程

1、libevent的使用方法

初始化   struct event_base main_base = event_init();  //多孔插座

事件设置 event_set(&ev, listen_fd, EV_READ | EV_PERSIST, cb, *arg) //设置灯泡

                  event_base_set(main_base, &ev);//设置灯泡属于哪个插座

事件添加 event_add(&ev, NULL)  //插上电板

进入事件循环 event_base_loop(main_base, 0); //进入事件循环

 

struct event {  
	TAILQ_ENTRY (event) ev_next;  
	TAILQ_ENTRY (event) ev_active_next;  
	TAILQ_ENTRY (event) ev_signal_next;  
	unsigned int min_heap_idx; /* for managing timeouts */  
	struct event_base *ev_base;  
	int ev_fd;  
	short ev_events;  
	short ev_ncalls;  
	short *ev_pncalls; /* Allows deletes in callback */  
	struct timeval ev_timeout;  
	int ev_pri;  /* smaller numbers are higher priority */  
	void (*ev_callback)(int, short, void *arg);  
	void *ev_arg;  
	int ev_res;  /* result passed to event callback */  
	int ev_flags;  
};    

 

ev_events:event关注的事件类型,它可以是以下3种类型:I/O事件: EV_WRITE和EV_READ;定时事件:EV_TIMEOUT;信号: EV_SIGNAL;辅助选项:EV_PERSIST,表明是一个永久事件。

#define EV_TIMEOUT 0x01   
#define EV_READ  0x02   
#define EV_WRITE 0x04   
#define EV_SIGNAL 0x08   
#define EV_PERSIST 0x10 /* Persistant event */

2、主程序详细流程

(1)初始化阶段

settings_init()初始化settings结构体。

static void settings_init(void) {
    settings.use_cas = true;
    settings.access = 0700;
    settings.port = 11211;
    settings.udpport = 11211;
    /* By default this string should be NULL for getaddrinfo() */
    settings.inter = NULL;
    settings.maxbytes = 64 * 1024 * 1024; /* default is 64MB */
    settings.maxconns = 1024;         /* to limit connections-related memory to about 5MB */
    settings.verbose = 0;
    settings.oldest_live = 0;
    settings.evict_to_free = 1;       /* push old items out of cache when memory runs out */
    settings.socketpath = NULL;       /* by default, not using a unix socket */
    settings.factor = 1.25;
    settings.chunk_size = 48;         /* space for a modest key and value */
    settings.num_threads = 4;         /* N workers */
    settings.num_threads_per_udp = 0;
    settings.prefix_delimiter = ':';
    settings.detail_enabled = 0;
    settings.reqs_per_event = 20;
    settings.backlog = 1024;
    settings.binding_protocol = negotiating_prot;
    settings.item_size_max = 1024 * 1024; /* The famous 1MB upper limit. */
    settings.maxconns_fast = false;
    settings.hashpower_init = 0;
    settings.slab_reassign = false;
    settings.slab_automove = 0;
}

setbuf(stderr, NULL);设置stderr为无缓冲。

int getopt(int argc, char* const argv[ ], const char* optstring);获取参数用于设置settings结构体。

查看和设置资源限制(getrlimit(), setrlimit()),放弃root权利等。

event_init();

stats_init();初始化struct stats(global stats)。

assoc_init(settings.hashpower_init);->初始化primary_hashtable(Main hash table, 类型为static item**)。

conn_init();->初始化freeconns(Free list management for connections, 类型为static conn)为全0,初始化freecurr为0。

slabs_init(settings.maxbytes, settings.factor, preallocate);初始化slab class,详见数据结构一节。

 

(2)线程创建阶段

thread_init(settings.num_threads, main_base);

        初始化锁和条件变量,尤其是item_locks[ ],初始化static LIBEVENT_THREAD *threads和static LIBEVENT_DISPATCHER_THREAD dispatcher_thread(即为主线程),之后调用setup_thread(&threads[i])和create_worker()。其中setup_thread()设置事件处理函数thread_libevent_process,但尚未进入事件循环,也还没有创建线程;之后初始化new_conn_queue成员(queue of new connections to handle);之后调用cache_create()初始化suffix_cache成员,创建了一个cache,name为“suffix”。create_worker(worker_libevent, &threads[i])创建4个线程,线程函数为worker_libevent()。worker_libevent()只是简单的进入事件循环

        注意:利用条件变量使得,thread_init()会阻塞,直到所有线程都已创建完毕。

        关于其中的pthread_key_create()和pthread_setspecific(),可见http://blog.csdn.net/yangzhiloveyou/article/details/8043573.

/*
 * Initializes the thread subsystem, creating various worker threads.
 *
 * nthreads  Number of worker event handler threads to spawn
 * main_base Event base for main thread
 */
void thread_init(int nthreads, struct event_base *main_base) {
    int         i;
    int         power;

    pthread_mutex_init(&cache_lock, NULL);
    pthread_mutex_init(&stats_lock, NULL);

    pthread_mutex_init(&init_lock, NULL);
    pthread_cond_init(&init_cond, NULL);

    pthread_mutex_init(&cqi_freelist_lock, NULL);
    cqi_freelist = NULL;

    /* Want a wide lock table, but don't waste memory */
    if (nthreads < 3) {
        power = 10;
    } else if (nthreads < 4) {
        power = 11;
    } else if (nthreads < 5) {
        power = 12;
    } else {
        /* 8192 buckets, and central locks don't scale much past 5 threads */
        power = 13;
    }

    item_lock_count = hashsize(power);

    item_locks = calloc(item_lock_count, sizeof(pthread_mutex_t));
    if (! item_locks) {
        perror("Can't allocate item locks");
        exit(1);
    }
    for (i = 0; i < item_lock_count; i++) {
        pthread_mutex_init(&item_locks[i], NULL);
    }
    pthread_key_create(&item_lock_type_key, NULL);    //用于在线程间传递当前锁类型,全局锁还是细粒度锁
    pthread_mutex_init(&item_global_lock, NULL);

    threads = calloc(nthreads, sizeof(LIBEVENT_THREAD));
    if (! threads) {
        perror("Can't allocate thread descriptors");
        exit(1);
    }

    dispatcher_thread.base = main_base;
    dispatcher_thread.thread_id = pthread_self();

    for (i = 0; i < nthreads; i++) {
        int fds[2];
        if (pipe(fds)) {
            perror("Can't create notify pipe");
            exit(1);
        }

        threads[i].notify_receive_fd = fds[0];
        threads[i].notify_send_fd = fds[1];

        setup_thread(&threads[i]);
        /* Reserve three fds for the libevent base, and two for the pipe */
        stats.reserved_fds += 5;
    }

    /* Create threads after we've done all the libevent setup. */
    for (i = 0; i < nthreads; i++) {
        create_worker(worker_libevent, &threads[i]);
    }

    /* Wait for all the threads to set themselves up before returning. */
    pthread_mutex_lock(&init_lock);
    wait_for_thread_registration(nthreads);
    pthread_mutex_unlock(&init_lock);
}


start_assoc_maintenance_thread() 创建线程maintenance_tid,线程函数为assoc_maintenance_thread()。

        assoc_maintenance_thread()在expanding(如何expanding暂不讨论)之后,调用switch_item_lock_type(ITEM_LOCK_GRANULAR),使得所有线程开始使用细粒度锁(通过管道通知各线程,并阻塞等待各线程设置完毕);之后调用slabs_rebalancer_resume()解锁slabs_rebalance_lock锁(什么时候上的锁?什么作用?);最后调用pthread_cond_wait(&maintenance_cond, &cache_lock);阻塞,等待其他线程使用pthread_cond_signal()唤醒(注意pthread_cond_signal()不能在pthread_cond_wait()之前执行,否则唤醒信号丢失)。

如果settings.slab_reassign为true,调用start_slab_maintenance_thread()创建相关线程。此处为false,所以不调用,暂不讨论。

clock_handler(0, 0, 0);调用evtimer_add()添加计数器clockevent,定时一秒,处理函数为clock_handler(),即自身。可以看出clock_handler()的作用就是每一秒钟更新一次current_time。

 

(3)socket创建阶段

如果settings.socketpath != NULL,就调用server_socket_unix()创建Unix套接字;此处为NULL,不执行,暂不讨论。

server_sockets(settings.port, tcp_transport, portnumber_file)调用server_socket()。server_socket()中调用new_socket()创建socket,并设置为非阻塞模式;设置SO_REUSEADDR;对于TCP,设置SO_KEEPALIVE,SO_LINGER,TCP_NODELAY;对于TCP,调用conn_new()(对于UDP,调用dispatch_conn_new())。
        conn_new()先调用conn_from_freelist()从freelist获得一个connection(此时freelist为空,所以返回NULL);初始化一个struct conn;设置listen套接字sfd的事件处理函数为event_handler();如果事件添加失败,则将已初始化好的conn加入freelist(conn_add_to_freelist())。

        此处server_sockets()创建了两个监听端口,一个IPV4,一个IPV6,两个struct conn最终链入static conn* listen_conn链表

        注意:在<netdb.h>中,#define NI_MAXSERV 32; #define NI_MAXHOST 1025。

        SO_REUSEADDR:允许重启的监听服务器bind其众所周知端口,即使以前建立的将该端口用作它们本地端口的连接仍存在。允许进程绑定一个处于TIME_WAIT的端口。

        SO_KEEPALIVE:如果两小时无数据交换,发送存活探测分节。

        SO_LINGER:设置close之后对缓冲区和套接口的处理;此处设置l_onff为0,即在套接口上不能再发出发送和接收请求,套接口发送缓冲区中内容被发送到对端。

        TCP_NODELAY:将数据包直接发送,而不是组成大的分组再发送。

 server_sockets(settings.udpport, udp_transport, portnumber_file)针对UDP,同样调用server_socket()。server_socket()中创建socket并调用maximize_sndbuf()设置发送缓存为最大值。之后bind()。最后调用dispatch_conn_new()。

         dispatch_conn_new()首先调用cqi_new()从cqi_freelist获得一个CQ_ITEM(struct conn_queue_item,An item in the connection queue),初始化CQ_ITEM;分配一个thread,调用cq_push(thread->new_conn_queue, item)将该item链入thread的conn_queue中;最后写一个‘c’到thread,这将使thread调用事件处理函数thread_libevent_process(),进一步调用conn_new(item->sfd, item->init_state, item->event_flags, item->read_buffer_size, item->transport, me->base))。可以看出,实际上主进程将item传送给thread,thread通过这个item来创建一个conn

/*
 * Dispatches a new connection to another thread. This is only ever called
 * from the main thread, either during initialization (for UDP) or because
 * of an incoming connection.
 */
void dispatch_conn_new(int sfd, enum conn_states init_state, int event_flags,
                       int read_buffer_size, enum network_transport transport) {
    CQ_ITEM *item = cqi_new();
    int tid = (last_thread + 1) % settings.num_threads;

    //以此种方式来取出线程
    LIBEVENT_THREAD *thread = threads + tid;

    last_thread = tid;

    item->sfd = sfd;
    item->init_state = init_state;
    item->event_flags = event_flags;
    item->read_buffer_size = read_buffer_size;
    item->transport = transport;

    //将新item放至threads的new_conn_queue队列中
    cq_push(thread->new_conn_queue, item);

    MEMCACHED_CONN_DISPATCH(sfd, thread->thread_id);
    //写一个字节启动新的线程
    if (write(thread->notify_send_fd, "", 1) != 1) {
        perror("Writing to thread notify pipe");
    }
}

        至此,创建了两个tcp socket(分别用于ipv4和ipv6),主进程创建conn并监听;主进程创建了两个udp socket(分别用于ipv4和ipv6),4个子进程创建conn并各自管理。如下:

<28 server listening (auto-negotiate)
<29 server listening (auto-negotiate)
<30 send buffer was 112640, now 268435456
<30 server listening (udp)
<30 server listening (udp)
<30 server listening (udp)
<30 server listening (udp)
<31 send buffer was 112640, now 268435456
<31 server listening (udp)
<31 server listening (udp)
<31 server listening (udp)
<31 server listening (udp)


(5)最后阶段

event_base_loop(main_base, 0)主进程进入事件循环。

        小结一下:主线程中的事件主要是针对两个tcp socket的,事件处理函数为event_handler();还有一个计数器事件clockevent,定时一秒,处理函数为clock_handler()。四个工作线程的事件一样,都是一个管道notify_receive_fd(事件处理函数为thread_libevent_process()),一个udp ipv4 socket,一个udp ipv6 socket,这两个的事件处理函数为event_handler() 。注意四个工作线程之前就进入事件循环了。

至此,程序就完全跑起来了。

 

(6)清理阶段

 默认情况下,event_base_loop()会一直循环,等待事件触发,然后调用他们的回调函数。直到没有添加的事件,或者调用了event_base_loopbreak()或者event_base_loopexit()。如果event_base_loop()退出,主进程将会执行一些清理过程,如下:

stop_assoc_maintenance_thread()简单的设置do_run_maintenance_thread = 0;线程maintenance_tid检测到do_run_maintenance_thread = 0就会自动退出。......

 

3、当client发起连接

执行命令telnet localhost 11211,向memcached发起tcp连接。memcached调用event_handler()处理。

        event_handler(const int fd, const short which, void *arg)其中which传入event.ev_events,为事件类型。event_handler()调用drive_machine(conn *c)。drive_machine()之后介绍。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值