memcached客户端连接建立过程笔记

memcached在启动过程初始化server_sockets时,根据启动参数决定系统是进行tcp监听还是udp监听,这里暂时只关注tcp的情况。

server_socket在初始化时会向系统申请监听socket之后设置地址,bind以及开始listen等操作,之后比较关键的一步是
为监听socket创建了一个conn, conn是用来描述一个客户端请求的上下文,显然memcached将监听socket也当做conn处理,方便管理,
主要代码:

/* 设置conn的初始状态为conn_listening,表示这是一个监听socket,在之后的事件状态机中专门用于接收用户连接 */
if (!(listen_conn_add = conn_new(sfd, conn_listening,	
EV_READ | EV_PERSIST, 1,
transport, main_base))) {
fprintf(stderr, "failed to create listening connection\n");
exit(EXIT_FAILURE);
}
listen_conn_add->next = listen_conn;
listen_conn = listen_conn_add; /* listen_conn是一个全局变量,用来保存所有的监听连接 */

conn数据结构部分字段注释:

struct conn {
int sfd; /** 连接对应的fd,即监听fd或者用户连接fd*/

char *rbuf; /** 存储读取到的命令 */
char *rcurr; /** 已经解析到的rbuf的位置 */
int rsize; /** rbuf的全部长度 */
int rbytes; /** 从rcurr开始还有多少未解析的数据 */

char *wbuf;
char *wcurr;
int wsize;
int wbytes;
/** 标识当前状态结束之后的下一个状态,在状态机中使用 */
enum conn_states write_and_go;
void *write_and_free; /** free this memory after finishing writing */

char *ritem; /** 用来存储key-value中的value,在状态机中做了接收到的数据复制到存储位置的操作 */
int rlbytes;

/* 用于读取value的数据结构*/
/* data for the nread state */

/**
* item is used to hold an item structure created after reading the command
* line of set/add/replace commands, but before we finished reading the actual
* . The data is read into ITEM_data(item) to avoid extra copying.
*/
/**
* 在未读取到key-value中的value数据时,item用来存储处理set/add/replace这三个命令锁生成的item1数据结构,
* 之后读取到value会被直接读进item1的data区域,防止多一次的数据复制,这里需要多体会一下
*/ void *item; /* 在这几个命令set/add/replace会用到 */ /* data for the swallow state */ int sbytes; /* how many bytes to swallow */ enum protocol protocol; /* 标识传送数据类型 char型或者是二进制 */ enum network_transport transport; /* 标识数据传输的方式tcp或者udp或unix socket */ int hdrsize; /* number of headers' worth of space is allocated */ bool noreply; /* 标识该命令是否需要回复 */ conn *next; /* 指向下一个连接形成单链表结构 */ LIBEVENT_THREAD *thread; /* 指向所属的线程,每一个用户连接都会被固定分配给一个worker线程 */ }; 

在new_conn中的关键代码标识了这个conn的初始状态以及处理函数:

c->state = init_state; /** 设置连接的初始状态 */
...
event_set(&c->event, sfd, event_flags, event_handler, (void *)c); /** 设置事件监听以及event_handler回调函数*/
event_base_set(base, &c->event); /**注册到libevent*/

event_handler进行简单fd校验之后将事件转交给drive_machine处理,这是memcached事件处理状态机的实现,
下面看看drive_machine连接建立部分逻辑:

static void drive_machine(conn *c) {
bool stop = false;
int sfd;
socklen_t addrlen;
struct sockaddr_storage addr;
int nreqs = settings.reqs_per_event;
int res;
const char *str;
assert(c != NULL);
while (!stop) {
switch(c->state) {
case conn_listening: /** 状态为conn_listening的监听连接负责接收客户端连接*/
/** 关键部分,接收连接并设置为非阻塞*/
accept(c->sfd, (struct sockaddr *)&addr, &addrlen);
fcntl(sfd, F_SETFL, fcntl(sfd, F_GETFL) | O_NONBLOCK) < 0)
/** 先判断是否达到设置的最大连接数量*/
if (settings.maxconns_fast &&
stats.curr_conns + stats.reserved_fds >= settings.maxconns - 1) {
...
} else { /** 未达到最大连接数则分配该连接并创建conn表示客户单连接上下文*/
/** 新conn的初始状态为conn_new_cmd,监听读事件,传输协议为tcp*/
/** -------------------跳到下面dispach_conn_new的大致逻辑----------*/
dispatch_conn_new(sfd, conn_new_cmd, EV_READ | EV_PERSIST,
DATA_BUFFER_SIZE, tcp_transport);
}
stop = true; /** 跳出while循环*/
break;

/** 下面的状态基本由客户端建立连接之后的事件驱动*/
case ...:
...
return;
}

/**
* 这个函数只能由主线程来调用,用于将新接收的连接分配给worker线程
*/
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(); /*CQ_ITEM是主线程与worker线程数据交互的数据结构,对一个连接的包装*/
char buf[1];
if (item == NULL) {
close(sfd);
/* given that malloc failed this may also fail, but let's try */
fprintf(stderr, "Failed to allocate memory for connection object\n");
return ;
}
int tid = (last_thread + 1) % settings.num_threads; /** robin-round循环获取一个目标worker线程*/

LIBEVENT_THREAD *thread = threads + tid;
...
cq_push(thread->new_conn_queue, item); /** 将新的连接包装实体压入到woker线程待处理连接队列*/

MEMCACHED_CONN_DISPATCH(sfd, thread->thread_id);
buf[0] = 'c';
/** 主线程通过初试化线程时创建的pipe通道给worker发送消息'c'表示分配给一个新的连接*/
if (write(thread->notify_send_fd, buf, 1) != 1) {
...
}
}
/**
*worker线程收到消息后会回调线程启动时pipe关注的事件注册的回调函数
*thread_libevent_process,下面继续看看该函数逻辑
*/
static void thread_libevent_process(int fd, short which, void *arg) {
LIBEVENT_THREAD *me = arg;
CQ_ITEM *item;
char buf[1];
/** 读取消息*/
if (read(fd, buf, 1) != 1)
if (settings.verbose > 0)
fprintf(stderr, "Can't read from libevent pipe\n");
/** 判断消息类型*/
switch (buf[0]) {
case 'c': /** 新连接*/
item = cq_pop(me->new_conn_queue); /** pop出主线程压入的新连接结构*/

if (NULL != item) {
/** 创建新连接 me->base 表示将该连接关注的事件注册到的libevent 事件监听结构*/
conn *c = conn_new(item->sfd, item->init_state, item->event_flags,
item->read_buffer_size, item->transport, me->base);
...
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;
}
}

新的连接在创建conn_new时又被注册了回调函数为event_handler,event_handler将事件处理转交drive_machine状态机,

也就是由客户端发送命令事件驱动drive_machine其他状态的逻辑,以上是客户端连接建立的过程,drive_machine状态机
其他状态注释留待下一次笔记。

转载于:https://www.cnblogs.com/bicowang/p/3821291.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在 Memcached 客户端中,通常有两个超时相关的设置:连接超时和操作超时。 1. 连接超时:指当客户端尝试连接 Memcached 服务器时,如果在规定的时间内无法建立连接,则会超时。可以通过设置客户端连接超时时间来调整这个设置。具体方法取决于你使用的客户端库,例如在 PHP 中,可以使用 `Memcached::setOption()` 方法设置 `OPT_CONNECT_TIMEOUT` 选项来调整连接超时时间。 2. 操作超时:指当客户端发送一个请求给 Memcached 服务器时,如果在规定的时间内没有收到响应,则会超时。可以通过设置客户端的操作超时时间来调整这个设置。同样,具体方法取决于你使用的客户端库,例如在 Python 中,可以使用 `memcache.Client()` 的 `socket_timeout` 参数设置操作超时时间。 需要注意的是,超时时间设置过短可能会导致误判或性能问题,设置过长则可能会增加系统响应时间。因此,需要根据具体的场景和系统负载情况来进行调整。 ### 回答2: 要调整 Memcached 客户端的超时设置,可以按照以下步骤进行操作: 1. 确定需要调整超时设置的客户端程序和操作系统。 2. 获取客户端程序的配置文件或代码文件。 3. 打开配置文件或代码文件,查找超时相关的配置项或代码片段。 4. 根据需要,调整超时时间的数值。可以增加或减少超时时间,以适应不同的网络环境和业务需求。 5. 保存配置文件或代码文件,并重新启动或重新编译客户端程序,使新的超时设置生效。 需要注意的是,不同的客户端程序可能有不同的超时设置方式,以下是一些常见的调整超时设置的方法: - 如果使用的是一些开源的 Memcached 客户端库,可以查阅它们的文档,寻找相关的超时设置选项,并按照文档的指导进行调整。 - 如果使用的是自己编写的客户端程序,可以在代码中找到与超时设置相关的部分,根据需要进行调整。例如,可以修改连接超时、读取超时和写入超时等参数。 - 如果使用的是配套的命令行工具(如memcached-tool),可以在命令行中指定超时参数。例如,使用"-t"参数指定超时时间为X秒。 总之,根据具体的情况,通过修改配置文件、调整代码或加入参数等方式,可以方便地调整 Memcached 客户端的超时设置,以满足实际需求。 ### 回答3: 要调整 Memcached 客户端的超时设置,可以通过以下步骤进行操作: 1. 首先,确定你所使用的 Memcached 客户端库。不同的编程语言可能有不同的 Memcached 客户端库,如 php-memcached、python-memcached 等。 2. 查阅所使用的 Memcached 客户端库的文档或官方网站,了解相关的超时设置参数和默认值。通常,超时设置参数的名称可能会有所不同,例如 timeout、connect_timeout、socket_timeout 等。 3. 根据文档或官方网站的指引,找到设置超时的方法或函数。这些方法或函数通常会在创建 Memcached 客户端对象或连接Memcached 服务器时被调用。调用方法可能会接受一个超时值作为参数,单位可以是秒或毫秒。 4. 根据自己的需求,设置一个适当的超时值。超时值应该与你的应用逻辑和网络环境相匹配。通常情况下,可以将超时值设置为几秒钟或几毫秒,以确保在超时期限内获得响应。 5. 在代码中修改相应的设置,并重新编译或重启应用程序,以使设置生效。 6. 测试代码的修改是否生效。可以使用一些测试脚本或工具来模拟访问 Memcached 服务器并检查超时设置是否符合预期。 请注意,不同的 Memcached 客户端库和版本可能会有不同的超时设置方式和参数名称。因此,在进行设置时,应仔细阅读和理解所使用的客户端库的文档,并根据实际情况进行相应的调整和测试。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值