【KV存储(2)】网络层设计


前言

本文将介绍kvstore的网络层设计


一、网络层设计介绍

由于本项目为基础项目,所以在网络这方面并未选择第三方库,比如libevent、muduo和boost等网络库。而是分别基于epoll来实现了reactor网络模型,基于ntyco协程库来实现网络message收发,基于io_uring来实现proactor网络模型。接下来将逐个介绍这三种方式实现的网络层。

二、reactor网络模型

2.1 单Reactor单线程网络模型

本网络模型基本沿用了【高性能网络(2)】采用Reactor网络模型实现HTTP服务器
我们只需要将收发message的逻辑改为kvstore的即可

#if ENABLE_KVSTORE
typedef int (*msg_handler)(char *msg, int length, char *response);
static msg_handler kvs_handler;
int kvs_request(struct conn *c) {
    c->wlength = kvs_handler(c->rbuffer, c->rlength, c->wbuffer);
}
int kvs_response(struct conn *c) {

}
#endif

其中kvs_handler会将客户端的消息进行解析并返回长度,通过send_cb客户端就可以得到服务端的响应结果。在kvs_handler中也就是会包含我们的KV引擎。

2.2 单Reactor多线程网络模型

如果我们需要用采用多线程来处理读写任务等,那么在这些任务中间accept、read、write是串行执行的,以accept之后的文件描述符作为区分 各个线程分别占有。
在这里插入图片描述

三、proactor网络模型

3.1 liburing

proactor网络模型通常采用异步io来实现,这里将基本沿用【高性能网络(1)】网络io与io多路复用的实现方法。下面将补充一些在liburing库中有关io_uring的函数。

int io_uring_queue_init_params(unsigned entries, struct io_uring *ring, const struct io_uring_params *p);
// 1)entries:指定 I/O uring 的入口数目,即同时处理的 I/O 事件数目。
// 2)ring:指向 struct io_uring 结构的指针,用于接收初始化后的 I/O uring 环境。
// 3)p:指向 struct io_uring_params 结构的指针,包含了自定义的初始化参数。

struct io_uring_sqe *io_uring_get_sqe(struct io_uring *ring);
// 1)ring:指向 struct io_uring 结构的指针,表示要操作的 I/O uring 环境。
// 该函数返回一个指向 struct io_uring_sqe 结构的指针,该结构表示一个 Submission Queue Entry (SQE)。SQE 包含了要执行的 I/O 操作的详细信息

void io_uring_prep_recv(struct io_uring_sqe *sqe, int fd, struct sockaddr *addr, socklen_t *addrlen, int flags);
// 用于准备执行接收数据的 SQE (Submission Queue Entry)。通过使用 io_uring_prep_recv 函数,你可以设置要接收数据的套接字描述符以及数据缓冲区等参数。

void io_uring_prep_accept(struct io_uring_sqe *sqe, int fd, struct sockaddr *addr, socklen_t *addrlen, int flags);
// 用于准备执行 accept 操作的 SQE (Submission Queue Entry)。通过使用 io_uring_prep_accept 函数,可以设置要接受连接的套接字描述符、新连接的文件描述符和连接地址等参数。

void io_uring_prep_send(struct io_uring_sqe *sqe, int fd, const void *buf, unsigned int len, int flags);
// 用于准备执行发送数据的 SQE (Submission Queue Entry)。通过使用 io_uring_prep_send 函数,你可以设置要发送数据的套接字描述符以及数据缓冲区等参数。

int io_uring_submit(struct io_uring *ring);
// 用于提交 SQE (Submission Queue Entry) 到 I/O uring 环境中进行处理。

int io_uring_wait_cqe(struct io_uring *ring, struct io_uring_cqe **cqe_ptr);
// 用于等待完成队列项(Completion Queue Entry,CQE)的到来。会阻塞等待。

int io_uring_peek_batch_cqe(struct io_uring *ring, struct io_uring_cqe **cqes, unsigned int count);
// 用于批量获取已完成的CQE(Completion Queue Entry)而无需等待.

void io_uring_cq_advance(struct io_uring *ring, unsigned int steps);
// 用于标记完成队列(Completion Queue,CQ)上已经处理的CQE数量。当一个或多个CQE被处理后,需要调用io_uring_cq_advance()函数来更新下一次读取CQE时应该从哪个位置开始。

3.2 问题与思考

3.2.1 事件分发

依赖于sqe的cqe的user_data字段。

__u64  user_data;  /* data to be passed back at completion time */

比如在执行accept操作时,我们将自定义的conn_info进行对应修改,当内核处理完成之后由cqe带出。

int set_event_accept(struct io_uring *ring, int sockfd, struct sockaddr *addr,
					socklen_t *addrlen, int flags) {
	struct io_uring_sqe *sqe = io_uring_get_sqe(ring);
	struct conn_info accept_info = {
		.fd = sockfd,
		.event = EVENT_ACCEPT,
	};
	io_uring_prep_accept(sqe, sockfd, (struct sockaddr*)addr, addrlen, flags);
	memcpy(&sqe->user_data, &accept_info, sizeof(struct conn_info));
}

3.2.2 sqe在多线程操作下,是否需要加锁

不需要。io_uring使用了无锁环形队列,通过原子操作来管理队列的头尾指针,可以确保多个线程安全高效并发提交到sqe中。

四、协程实现网络层

这里我们采用ntyco库来实现,对应协程的概念在【高性能网络(3)】采用协程重构网络io。首先安装ntyco

git clone https://github.com/wangbojing/NtyCo.git
cd NtyCo
make

源码:

#include "nty_coroutine.h"
#include <arpa/inet.h>

typedef int (*msg_handler)(char *msg, int length, char *response);
static msg_handler kvs_handler;

void server_reader(void *arg) {
	int fd = *(int *)arg;
	int ret = 0;

	while (1) {
		
		char buf[1024] = {0};
		ret = recv(fd, buf, 1024, 0);
		if (ret > 0) {
			char response[1024] = {0};
			int slength = kvs_handler(buf, ret, response);

			ret = send(fd, response, slength, 0);
			if (ret == -1) {
				close(fd);
				break;
			}
		} else if (ret == 0) {	
			close(fd);
			break;
		}
	}
}

void server(void *arg) {

	unsigned short port = *(unsigned short *)arg;

	int fd = socket(AF_INET, SOCK_STREAM, 0);
	if (fd < 0) return ;

	struct sockaddr_in local, remote;
	local.sin_family = AF_INET;
	local.sin_port = htons(port);
	local.sin_addr.s_addr = INADDR_ANY;
	bind(fd, (struct sockaddr*)&local, sizeof(struct sockaddr_in));

	listen(fd, 20);
	printf("listen port : %d\n", port);

	while (1) {
		socklen_t len = sizeof(struct sockaddr_in);
		int cli_fd = accept(fd, (struct sockaddr*)&remote, &len);
		nty_coroutine *read_co;
		nty_coroutine_create(&read_co, server_reader, &cli_fd);
	}
}

int ntyco_start(unsigned short port, msg_handler handler) {
	//int port = atoi(argv[1]);
	kvs_handler = handler;
	nty_coroutine *co = NULL;
	nty_coroutine_create(&co, server, &port);
	nty_schedule_run();
}

采用协程所实现的网络,代码风格与同步的网络io写法差不多却可以实现异步的性能,这是因为在协程库内部有一个调度器,将不同的协程句柄管理起来以实现资源的合理利用。


总结

kv存储中的网络层采用了这三种实现方式,针对不同场景可以随时切换。

参考链接:
https://github.com/0voice
2.10 高性能异步IO机制
高效异步:io_uring 技术解析_io uring

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值