源码memcached.c中,main入口函数。
第一步,根据memcached的启动参数做校验参数、配置。
main函数中,几乎600行代码都是这些参数校验。吐槽一个。
第二步,初始化。
2.1:初始化主线程的libevent。
main_base = event_init();
2.2:初始化memcached的stats信息。
在文本协议的memcached中,我们nc/telent后输入stats命令,会很快地输出一些当前memcached的信息的。这些就是stats信息。并不是输入stats的时候才遍历统计出来的。而是已经保存好了这份信息。代码调用在main函数中的:
stats_init();
具体的统计信息,可以在memcached.h这个文件中找到:
/** * Global stats. */ struct stats { pthread_mutex_t mutex; unsigned int curr_items; unsigned int total_items; uint64_t curr_bytes; unsigned int curr_conns; unsigned int total_conns; uint64_t rejected_conns; uint64_t malloc_fails; unsigned int reserved_fds; unsigned int conn_structs; uint64_t get_cmds; uint64_t set_cmds; uint64_t touch_cmds; uint64_t get_hits; uint64_t get_misses; uint64_t touch_hits; uint64_t touch_misses; uint64_t evictions; uint64_t reclaimed; time_t started; /* when the process was started */ bool accepting_conns; /* whether we are currently accepting */ uint64_t listen_disabled_num; unsigned int hash_power_level; /* Better hope it's not over 9000 */ uint64_t hash_bytes; /* size used for hash tables */ bool hash_is_expanding; /* If the hash table is being expanded */ uint64_t expired_unfetched; /* items reclaimed but never touched */ uint64_t evicted_unfetched; /* items evicted but never touched */ bool slab_reassign_running; /* slab reassign in progress */ uint64_t slabs_moved; /* times slabs were moved around */ uint64_t lru_crawler_starts; /* Number of item crawlers kicked off */ bool lru_crawler_running; /* crawl in progress */ uint64_t lru_maintainer_juggles; /* number of LRU bg pokes */ };
2.3:hash桶初始化
代码main函数中的:
assoc_init(settings.hashpower_init);
在memcached中,保存着一份hash表用来存放memcached key。默认这个hash表是2^16(65536)个key。后续会根据规则动态扩容这个hash表的。如果希望启动的时候,这个hash表更大,可以-o 参数调节。
hash表中, memcached key作为key,value是item指针,并不是item value。
2.4:初始化connection。
conn_init()
也就是 memcached启动参数中的-c参数,默认1024。
为了更快地找到connection的fd(文件描述符),实际上申请的connection会比配置的更大一点。
/* * Initializes the connections array. We don't actually allocate connection * structures until they're needed, so as to avoid wasting memory when the * maximum connection count is much higher than the actual number of * connections. * * This does end up wasting a few pointers' worth of memory for FDs that are * used for things other than connections, but that's worth it in exchange for * being able to directly index the conns array by FD. */ static void conn_init(void) { /* We're unlikely to see an FD much higher than maxconns. */ int next_fd = dup(1); int headroom = 10; /* account for extra unexpected open FDs */ struct rlimit rl; max_fds = settings.maxconns + headroom + next_fd; /* But if possible, get the actual highest FD we can possibly ever see. */ if (getrlimit(RLIMIT_NOFILE, &rl) == 0) { max_fds = rl.rlim_max; } else { fprintf(stderr, "Failed to query maximum file descriptor; " "falling back to maxconns "); } close(next_fd); if ((conns = calloc(max_fds, sizeof(conn *))) == NULL) { fprintf(stderr, "Failed to allocate connection structures "); /* This is unrecoverable so bail out early. */ exit(1); } }
2.5:初始化slabs。
在2.3的hash桶中初始化的是key。slabs初始化的是这些key对应的value。下面摘录关键代码:
while (++i < MAX_NUMBER_OF_SLAB_CLASSES-1 && size <= settings.item_size_max / factor) { /* Make sure items are always n-byte aligned */ if (size % CHUNK_ALIGN_BYTES) size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES); slabclass[i].size = size; slabclass[i].perslab = settings.item_size_max / slabclass[i].size; size *= factor; if (settings.verbose > 1) { fprintf(stderr, "slab class %3d: chunk size %9u perslab %7u ", i, slabclass[i].size, slabclass[i].perslab); } }
在初始化slab的时候,下一个slab的size(chunk size)总是大于等于当前slab的size的。
2.6:初始化worker线程。
memcached_thread_init(settings.num_threads, main_base);
worker线程和main线程,组成了libevent的reactor模式。
2.7:定时器
clock_handler(0, 0, 0);
用于对比对象是否过期。
第三步、libevent主线程监听事件。
/* enter the event loop */ if (event_base_loop(main_base, 0) != 0) { retval = EXIT_FAILURE; }
主线程启动堆栈:
server_sockets——> server_socket——> conn_new——> event_handler——> drive_machine——> try_read_command(这里会判定,是文本协议还是二进制协议)
第四步、关闭hash桶线程。
在2.3的初始化步骤中,有线程操作。这里明确关闭这个线程。
void stop_assoc_maintenance_thread() { mutex_lock(&maintenance_lock); do_run_maintenance_thread = 0; pthread_cond_signal(&maintenance_cond); mutex_unlock(&maintenance_lock); /* Wait for the maintenance thread to stop */ pthread_join(maintenance_tid, NULL); }
memcached启动的主要流程就是这些了。
最后来个图片,描述一下启动后的memcached结构。
源码github上有,见:https://github.com/memcached/memcached/blob/master/memcached.c 文件有点大,可能浏览器卡顿一下。这里肯定就不贴出来了^_^