Memcached网络模型

memcached通过epoll(也是使用libevent)进行实现的异步服务器,在其中担任主要任务的线程有两种,一个是主线程,一个是worker线程

一、libevent

memcached使用libevent实现事件监听。在这简单介绍一下libevent的使用,一般有以下几步:

1)event_base = event_init(); 初始化事件基地。

2)event_set(event, fd, event_flags, event_handler, args); 创建事件event,fd为要监听的fd,event_flags为监听的事件类型,event_handler为事件发生后的处理函数,args为调用处理函数时传递的参数。

3)event_base_set(event_base, event); 为创建的事件event指定事件基地。

4)event_add(event, timeval); 把事件加入到事件基地进行监听

5)event_base_loop(event_base, flag); 进入事件循环,即epoll_wait。

memcached主线程和worker线程各有自己的监听队列,故有主线程和每个worker线程都有一个独立的event_base。

简单介绍了libevent,现在来看下memcached。

二、memcached线程基本结构

在memcached线程中有三种结构需要注意下:

2.1  CQ_ITEM

主要存储的是用户链接Socket的链接的基本信息,主要的作用是主线程和工作线程沟通的媒介。

1

2

3

4

5

6

7

8

9

typedef struct conn_queue_item CQ_ITEM;

struct conn_queue_item {

    int               sfd;                    //socket的fd

    enum conn_states  init_state;             //事件类型

    int               event_flags;            //libevent的flags

    int               read_buffer_size;       //读取的buffer的大小

    enum network_transport     transport;    

    CQ_ITEM          *next;                  

};

2.2  CQ

CQ是CQ_ITEM的集合信息,是每个线程的处理队列。

1

2

3

4

5

6

7

/* A connection queue. */

typedef struct conn_queue CQ;

struct conn_queue {

    CQ_ITEM *head;

    CQ_ITEM *tail;

    pthread_mutex_t lock;

};

2.3  LIBEVENT_THREAD

这个结构是工作线程的结构,每一个工作线程都具有的

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

typedef struct {

    //线程Id

    pthread_t thread_id;        /* unique ID of this thread */

    //libevent句柄                 

    struct event_base *base;    /* libevent handle this thread uses */             

    //异步event事件

    struct event notify_event;  /* listen event for notify pipe */

    //pipe管道接收端

    int notify_receive_fd;      /* receiving end of notify pipe */

    //pipe管道发送端

    int notify_send_fd;         /* sending end of notify pipe */

    //线程状态

    struct thread_stats stats;  /* Stats generated by this thread */

    //也就是上面的结构CQ,CQ_ITEM队列

    struct conn_queue *new_conn_queue; /* queue of new connections to handle */

    cache_t *suffix_cache;      /* suffix cache */

    uint8_t item_lock_type;     /* use fine-grained or global item lock */

} LIBEVENT_THREAD;

三、memcached的网络处理流程

在了解了上面三种简单的结构以后,首先来看下memcached的网络处理流程。

1)主线程的主要工作就是监听和接收listen和accpet进入新的链接,主线程为自己分配一个event_base句柄,用于监听连接,即listen fd。

2)主线程启动的时候会创建n个worker线程(默认情况下是4个,可根据配置进行修改),同时每个worker线程也分配了独立的event_base句柄。

3)每个worker线程通过管道方式与其它线程(主要是主线程)进行通信,调用pipe函数创建匿名管道。worker线程把管道读取fd加到自己的event_base,监听管道读取fd的可读事件,即当主线程往某个线程的管道写入fd写数据时,触发事件。

4)主线程监听到有一个连接到达时,accept连接,产生一个client fd,然后通过求余的方式选择一个worker线程,把这个client fd包装成一个CQ_ITEM对象,然后压到worker线程的CQ_ITEM队列里面去,同时主线程往选中的worker线程的管道写入fd中写入一个1,来通知woker线程(触发worker线程)。

5)当worker线程监听到自己的管道读取fd可读,触发事件处理,而此是的事件处理是:从自己的CQ_ITEM队列中取出CQ_ITEM对象(相当于收信,看看主线程给了自己什么东西),worker线程把此client fd加入到自己的event_base(创建libevent的读写事件),从此负责该连接的读写工作。

 

 

 

如上图所示,整个流程还是相对比较简单的,下面我们来看下Memcache的网络模型部分源码。

四、源码

这里的代码看的版本是1.4.21。

其中网络模型部分的源码主要在两个文件上,一个是Memcached.c,一个是Thread.c两个文件。其中关于工作线程的数据结构部分是在Memcached.h文件中。

4.1  main函数代码

main函数的代码存放在Memcached.c文件中,

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

int main (int argc, char **argv) {

    //省略。。。

    //前面代码主要是根据输入调整配置等。

 

    /* initialize main thread libevent instance */

    //初始化,得到句柄

    main_base = event_init();

 

    /* initialize other stuff */

    stats_init();

    assoc_init(settings.hashpower_init);

    conn_init();

    slabs_init(settings.maxbytes, settings.factor, preallocate);

 

    //......

    //这里是用来创建工作线程的代码。

    /* start up worker threads if MT mode */

    thread_init(settings.num_threads, main_base);

 

    //.......

        errno = 0;

        //这边的server_sockets方法主要是socket的bind、listen、accept等操作

        //主线程主要用于接收客户端的socket连接,并且将连接交给工作线程接管。

        if (settings.port && server_sockets(settings.port, tcp_transport,

                                           portnumber_file)) {

            vperror("failed to listen on TCP port %d", settings.port);

            exit(EX_OSERR);

        }

 

    /* enter the event loop */

    //主事件循环

    if (event_base_loop(main_base, 0) != 0) {

        retval = EXIT_FAILURE;

    }

    //...

}

所以我们可以看到,在main函数中主要是以下几个函数比较重要:

  • 这里是用来创建工作线程的代码。
    thread_init(settings.num_threads, main_base);

     

  • 通过此方法创建主线程,主线程接收客户端的socket链接,并且将链接交给工作线程保管。
    server_sockets(settings.port,  tcp_transport, portnumber_file)

4.2  thread_init创建工作线程代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

/*

 * 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) {

 

    //加锁代码删除....

    //省略...

    for (i = 0; i < nthreads; i++) {

        int fds[2];

        if (pipe(fds)) {

            perror("Can't create notify pipe");

            exit(1);

        }

        //此处的threads是工作线程的结构

        //接收端

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

        //写入端

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

 

        //创建现线程自己的libevent的 event_base

        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. */

    //这里循环创建线程,设置回调函数为worker_libevent

    for (i = 0; i < nthreads; i++) {

        create_worker(worker_libevent, &threads[i]);

    }

 

    //省略......

}

如上创建工作线程代码主要有两部分:

  • 一个setup_thread是给每个工作线程创建属于自己的event_base句柄。
  • 一个是create_worker真正的创建工作线程以及设置每个线程的回调函数。

4.2.1  setup_thread设置句柄

下面这段代码主要做了工作线程设置event句柄以及填写了工作线程结构LIBEVENT_THREAD里面的变量。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

/*

 * Set up a thread's information.

 */

static void setup_thread(LIBEVENT_THREAD *me) {

    //创建一个event_base

    //根据libevent的使用文档,我们可以知道一般情况下每个独立的线程都应该有自己独立的event_base

    me->base = event_init();

    if (! me->base) {

        fprintf(stderr, "Can't allocate event base\n");

        exit(1);

    }

 

    /* Listen for notifications from other threads */

    //读事件EV_READ的监听,如果pipe中有写事件的时候,libevent就会调用thread_libevent_process方法

    event_set(&me->notify_event, me->notify_receive_fd,

              EV_READ | EV_PERSIST, thread_libevent_process, me);

    event_base_set(me->base, &me->notify_event);

 

    //添加事件操作

    if (event_add(&me->notify_event, 0) == -1) {

        fprintf(stderr, "Can't monitor libevent notify pipe\n");

        exit(1);

    }

 

    //初始化一个工作队列

    me->new_conn_queue = malloc(sizeof(struct conn_queue));

    if (me->new_conn_queue == NULL) {

        perror("Failed to allocate memory for connection queue");

        exit(EXIT_FAILURE);

    }

    cq_init(me->new_conn_queue);

 

    if (pthread_mutex_init(&me->stats.mutex, NULL) != 0) {

        perror("Failed to initialize mutex");

        exit(EXIT_FAILURE);

    }

 

    me->suffix_cache = cache_create("suffix", SUFFIX_SIZE, sizeof(char*),

                                    NULL, NULL);

    if (me->suffix_cache == NULL) {

        fprintf(stderr, "Failed to create suffix cache\n");

        exit(EXIT_FAILURE);

    }

}

4.2.2  create_worker创建工作线程

下面这段代码主要做了创建工作线程以及设置线程回掉函数。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

/*

 * Creates a worker thread.

 */

static void create_worker(void *(*func)(void *), void *arg) {

    pthread_t       thread;

    pthread_attr_t  attr;

    int             ret;

 

    pthread_attr_init(&attr);

 

    //pthread_create来创建线程

    if ((ret = pthread_create(&thread, &attr, func, arg)) != 0) {

        fprintf(stderr, "Can't create thread: %s\n",

                strerror(ret));

        exit(1);

    }

}

 

其中回调函数的内容如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

/*

 * Worker thread: main event loop

 */

static void *worker_libevent(void *arg) {

    LIBEVENT_THREAD *me = arg;

 

    /* Any per-thread setup can happen here; thread_init() will block until

     * all threads have finished initializing.

     */

 

    /* set an indexable thread-specific memory item for the lock type.

     * this could be unnecessary if we pass the conn *c struct through

     * all item_lock calls...

     */

    me->item_lock_type = ITEM_LOCK_GRANULAR;

    pthread_setspecific(item_lock_type_key, &me->item_lock_type);

 

    register_thread_initialized();

 

    //主要是开启事件循环,因此每个工作线程都具有事件循环

    //memcache的每个工作线程都会独立处理自己接管的连接

    event_base_loop(me->base, 0);

    return NULL;

}

4.2.3  工作线程pipe监听发生后调用thread_libevent_process

这段代码是setup_thread设置句柄函数中的管道pipe监听事件发生后所调用的函数。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

/*

 * Processes an incoming "handle a new connection" item. This is called when

 * input arrives on the libevent wakeup pipe.

 */

static void thread_libevent_process(int fd, short which, void *arg) {

    LIBEVENT_THREAD *me = arg;

    CQ_ITEM *item;

    char buf[1];

 

    //回调函数中回去读取pipe中的信息

    //主线程中如果有新的连接,会向其中一个线程的pipe中写入1

    //这边读取pipe中的数据,如果为1,则说明从pipe中获取的数据是正确的

    if (read(fd, buf, 1) != 1)

        if (settings.verbose > 0)

            fprintf(stderr, "Can't read from libevent pipe\n");

 

    switch (buf[0]) {

    case 'c':

    //从工作线程的队列中获取一个CQ_ITEM连接信息

    item = cq_pop(me->new_conn_queue);

 

    if (NULL != item) {

        //conn_new这个方法非常重要,主要是创建socket的读写等监听事件。

        //init_state 为初始化的类型,主要在drive_machine中通过这个状态类判断处理类型

        conn *c = conn_new(item->sfd, item->init_state, item->event_flags,

                           item->read_buffer_size, item->transport, me->base);

        if (c == NULL) {

            if (IS_UDP(item->transport)) {

                fprintf(stderr, "Can't listen for events on UDP socket\n");

                exit(1);

            else {

                if (settings.verbose > 0) {

                    fprintf(stderr, "Can't listen for events on fd %d\n",

                        item->sfd);

                }

                close(item->sfd);

            }

        else {

            c->thread = me;

        }

        cqi_free(item);

    }

        break;

    /* we were told to flip the lock type and report in */

    case 'l':

    me->item_lock_type = ITEM_LOCK_GRANULAR;

    register_thread_initialized();

        break;

    case 'g':

    me->item_lock_type = ITEM_LOCK_GLOBAL;

    register_thread_initialized();

        break;

    }

}

4.2.4 conn_new创建socket的读写等监听事件

在4.2.3中有一个conn_new方法。

1

2

3

4

5

6

7

8

9

10

11

//我们发现这个方法中又在创建event了,这边实际上是监听socket的读写等事件

//主线程主要是监听用户的socket连接事件;工作线程主要监听socket的读写事件

//当用户socket的连接有数据传递过来的时候,就会调用event_handler这个回调函数

event_set(&c->event, sfd, event_flags, event_handler, (void *)c);

event_base_set(base, &c->event);

c->ev_flags = event_flags;

//将事件添加到libevent的loop循环中

if (event_add(&c->event, 0) == -1) {

    perror("event_add");

    return NULL;

}

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值