热门标签 | HotTags
当前位置:  开发笔记 > 数据库 > 正文

memcached的线程模型-mysql教程

Memcached数据结构memcached的多线程主要是通过实例化多个libevent实现的,分别是一个主线程和n个workers线程。每个线程都是一个单独的libevent实例,主线程eventloop负责处理监听fd,监听客户端的建立连接请求,以及accept连接,将已建立的连接roundrobin到

Memcached数据结构 memcached的多线程主要是通过实例化多个libevent实现的,分别是一个主线程和n个workers线程。每个线程都是一个单独的libevent实例,主线程eventloop负责处理监听fd,监听客户端的建立连接请求,以及accept连接,将已建立的连接round robin到

Memcached数据结构

memcached的多线程主要是通过实例化多个libevent实现的,分别是一个主线程和n个workers线程。每个线程都是一个单独的libevent实例,主线程eventloop负责处理监听fd,监听客户端的建立连接请求,以及accept连接,将已建立的连接round robin到各个worker。workers线程负责处理已经建立好的连接的读写等事件。”one event loop per thread”.

首先看下主要的数据结构

thread.c

CQ_ITEM是主线程accept后返回的已建立连接的fd的封装。

/* An item in the connection queue. */
typedef struct conn_queue_item CQ_ITEM;
struct conn_queue_item {
    int     sfd;
    int     init_state;
    int     event_flags;
    int     read_buffer_size;
    int     is_udp;
    CQ_ITEM *next;
};

CQ是一个管理CQ_ITEM的单向链表

/* A connection queue. */  
typedef struct conn_queue CQ;  
struct conn_queue {  
    CQ_ITEM *head;  
    CQ_ITEM *tail;  
    pthread_mutex_t lock;  
    pthread_cond_t  cond;  
};  

LIBEVENT_THREAD 是memcached里的线程结构的封装,可以看到每个线程都包含一个CQ队列,一条通知管道pipe和一个libevent的实例event_base。

typedef struct {
    pthread_t thread_id;        /* unique ID of this thread */
    struct event_base *base;    /* libevent handle this thread uses */
    struct event notify_event;  /* listen event for notify pipe */
    int notify_receive_fd;      /* receiving end of notify pipe */
    int notify_send_fd;         /* sending end of notify pipe */
    CQ  new_conn_queue;         /* queue of new connections to handle */
} LIBEVENT_THREAD;

Memcached对每个网络连接的封装conn

    typedef struct{  
      int sfd;  
      int state;  
      struct event event;  
      short which;  
      char *rbuf;  
      ... //这里省去了很多状态标志和读写buf信息等  
    }conn;  

memcached主要通过设置/转换连接的不同状态,来处理事件(核心函数是drive_machine,连接的状态机)。

Memcached线程处理流程

Memcached.c 里main函数,先对主线程的libevent实例进行初始化, 然后初始化所有的workers线程,并启动。接着主线程调用server_socket(这里只分析tcp的情况)创建监听socket,绑定地址,设置非阻塞模式并注册监听socket的libevent 读事件等一系列操作。最后主线程调用event_base_loop接收外来连接请求。

Main() {
/* initialize main thread libevent instance */  
     main_base = event_init();  
/* start up worker threads if MT mode */  
thread_init(settings.num_threads, main_base);   
server_socket(settings.port, 0);
/* enter the event loop */  
event_base_loop(main_base, 0);  
}

最后看看memcached网络事件处理的最核心部分- drive_machine drive_machine是多线程环境执行的,主线程和workers都会执行drive_machine。

static void drive_machine(conn *c) {  
    bool stop = false;  
    int sfd, flags = 1;  
    socklen_t addrlen;  
    struct sockaddr_storage addr;  
    int res;  
    assert(c != NULL);  
    while (!stop) {  
        switch(c->state) {  
        case conn_listening:  
            addrlen = sizeof(addr);  
            if ((sfd = accept(c->sfd, (struct sockaddr *)&addr, &addrlen)) == -1) {  
                //省去n多错误情况处理  
                break;  
            }  
            if ((flags = fcntl(sfd, F_GETFL, 0)) <0 ||  
                fcntl(sfd, F_SETFL, flags | O_NONBLOCK) <0) {  
                perror("setting O_NONBLOCK");  
                close(sfd);  
                break;  
            }  
            dispatch_conn_new(sfd, conn_read, EV_READ | EV_PERSIST,  
                                     DATA_BUFFER_SIZE, false);  
            break;  
        case conn_read:  
            if (try_read_command(c) != 0) {  
                continue;  
            }  
        ....//省略  
     }       
 }  

drive_machine主要是通过当前连接的state来判断该进行何种处理,因为通过libevent注册了读写事件后回调的都是这个核心函数,所以实际上我们在注册libevent相应事件时,会同时把事件状态写到该conn结构体里,libevent进行回调时会把该conn结构作为参数传递过来,就是该方法的形参。 连接的状态枚举如下。

    enum conn_states {  
        conn_listening,  /** the socket which listens for connections */  
        conn_read,       /** reading in a command line */  
        conn_write,      /** writing out a simple response */  
        conn_nread,      /** reading in a fixed number of bytes */  
        conn_swallow,    /** swallowing unnecessary bytes w/o storing */  
        conn_closing,    /** closing this connection */  
        conn_mwrite,     /** writing out many items sequentially */  
    };  

实际对于case conn_listening:这种情况是主线程自己处理的,workers线程永远不会执行此分支我们看到主线程进行了accept后调用了

dispatch_conn_new(sfd, conn_read, EV_READ | EV_PERSIST,DATA_BUFFER_SIZE, false);

这个函数就是通知workers线程的地方,看看

void dispatch_conn_new(int sfd, int init_state, int event_flags,  
                           int read_buffer_size, int is_udp) {  
        CQ_ITEM *item = cqi_new();  
        int thread = (last_thread + 1) % settings.num_threads;  
        last_thread = thread;  
        item->sfd = sfd;  
        item->init_state = init_state;  
        item->event_flags = event_flags;  
        item->read_buffer_size = read_buffer_size;  
        item->is_udp = is_udp;  
        cq_push(&threads[thread].new_conn_queue, item);  
        MEMCACHED_CONN_DISPATCH(sfd, threads[thread].thread_id);  
        if (write(threads[thread].notify_send_fd, "", 1) != 1) {  
            perror("Writing to thread notify pipe");  
        }  
} 

可以清楚的看到,主线程首先创建了一个新的CQ_ITEM,然后通过round robin策略选择了一个thread并通过cq_push将这个CQ_ITEM放入了该线程的CQ队列里,那么对应的workers线程是怎么知道的呢? 就是通过

write(threads[thread].notify_send_fd, "", 1)

向该线程管道写了1字节数据,则该线程的libevent立即回调了thread_libevent_process方法(上面已经描述过)。 然后那个线程取出item,注册读时间,当该条连接上有数据时,最终也会回调drive_machine方法,也就是drive_machine方法的 case conn_read:等全部是workers处理的,主线程只处理conn_listening 建立连接这个。 memcached的这套多线程event机制很值得设计linux后端网络程序时参考。

参考文献

  • memcache源码分析--线程模型
  • memcached结构分析——线程模型
  • Memcached的线程模型及状态机
推荐阅读
  • 本文将详细探讨 Linux 系统中的 netstat 命令,该命令用于查看网络状态和连接情况。通过了解 IP 地址和端口的基本概念,我们将更好地理解如何利用 netstat 命令来监控和管理网络服务。 ... [详细]
  • 字节跳动夏季招聘面试经验分享
    本文详细记录了字节跳动夏季招聘的面试经历,涵盖了一、二、三轮面试的技术问题及项目讨论,旨在为准备类似面试的求职者提供参考。 ... [详细]
  • Python Socket 基础入门
    本文介绍了客户端与服务端如何通过IP地址进行身份验证,并详细说明了在不同操作系统中查看和配置网络接口的方法。同时,文章还探讨了Socket的基本使用方法及其工作流程。 ... [详细]
  • 本文详细探讨了虚拟化的基本概念,包括服务器虚拟化、网络虚拟化及其在云计算环境中的应用。特别强调了SDN技术在网络虚拟化和云计算中的关键作用,以及网络虚拟化技术如何提升资源利用效率和管理灵活性。 ... [详细]
  • 本文详细记录了作者从7月份的提前批到9、10月份正式批的秋招经历,包括各公司的面试流程、技术问题及HR面的常见问题。通过这次秋招,作者深刻体会到了技术积累和面试准备的重要性。 ... [详细]
  • Linux环境下Memcached安装指南
    本文详细介绍如何在Linux虚拟机上安装Memcached,包括必要的依赖库安装,以及使用Xshell进行文件传输的具体步骤。 ... [详细]
  • ipvsadm命令简介:ipvsadm是LVS在应用层的管理命令,我们可以通过这个命令去管理LVS的配置。在fedora14、Linux6.0之后系统中 ... [详细]
  • 2023年最新:PHP本地端口配置详解
    本文详细介绍了PHP在不同环境下的本地端口配置方法及常见问题解决方案,帮助开发者更好地理解和配置PHP端口。 ... [详细]
  • 本文介绍了EasyRTSPClient这一高效、稳定的RTSP客户端工具库,并详细阐述了其在与大华球机对接过程中遇到的预览问题及解决方法。 ... [详细]
  • Python安全实践:Web安全与SQL注入防御
    本文旨在介绍Web安全的基础知识,特别是如何使用Python和相关工具来识别和防止SQL注入攻击。通过实际案例分析,帮助读者理解SQL注入的危害,并掌握有效的防御策略。 ... [详细]
  • 成为一名高效的Java架构师不仅需要掌握高级Java编程技巧,还需深入理解JVM的工作原理及其优化方法。此外,对池技术(包括对象池、连接池和线程池)的应用、多线程处理、集合对象的内部机制、以及常用的数据结构和算法的精通也是必不可少的。同时,熟悉Linux操作系统、TCP/IP协议栈、HTTP协议等基础知识,对于构建高效稳定的系统同样重要。 ... [详细]
  • 本文深入探讨了分布式文件系统的核心概念及其在现代数据存储解决方案中的应用,特别是针对大规模数据处理的需求。文章不仅介绍了多种流行的分布式文件系统和NoSQL数据库,还提供了选择合适系统的指导原则。 ... [详细]
  • 本文详细介绍了如何使用 Python 编程语言中的 Scapy 库执行 DNS 欺骗攻击,包括必要的软件安装、攻击流程及代码示例。 ... [详细]
  • 本文详细介绍了如何在PHP中使用Memcached进行数据缓存,包括服务器连接、数据操作、高级功能等。 ... [详细]
  • Kubernetes Services详解
    本文深入探讨了Kubernetes中的服务(Services)概念,解释了如何通过Services实现Pods之间的稳定通信,以及如何管理没有选择器的服务。 ... [详细]
author-avatar
好kc好先生之家
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有