memcached的执行流程
来源:互联网 发布:appium python ios 编辑:程序博客网 时间:2024/04/28 20:28
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()之后介绍。
- memcached的执行流程
- Memcached软件源码级执行流程解读
- memcached学习的整体流程
- memcached缓存的执行过程
- memcached源码分析(一): memcached.c主函数分析 执行流程
- memcached源码分析(一): memcached.c主函数分析 执行流程
- PsLookupProcessByProcessId的执行流程
- struts2的执行流程
- struts2的执行流程
- SSH 的执行流程
- jsp的执行流程
- libmemcached的执行流程
- struts2的执行流程
- struts2的执行流程
- Struts2的执行流程
- ci的执行流程
- struts2的执行流程
- springMVC的执行流程
- 处理机调度算法的实现
- JBossESB教程(五)——怎样去调用一个WebService
- 误解程序运行
- java对时间日期的处理
- Sublime Text 2
- memcached的执行流程
- rand()在windows和linux下的异同分析
- java 的*.class文件结构
- NMAP使用实例(六)
- Oracle常用命令——工作记录
- poj 2409
- 数据结构与算法系列-树-二叉树的定义与性质
- hdoj-1002 大数加法
- Androidpn学习与使用6