重新实现reuseport逻辑,实现一致性哈希
有部分应用场景采用的仍然是无连接协议,例如 DNS、StatsD 等,都是采用的 UDP 。
UDP 不是面向连接的,所以不能像 TCP 通过建立多个连接来提高对服务器的并发访问,如果通过多线程共享一个 UDP Socket 可能会无法充分利用所有的 CPU 资源。
这里简单介绍其优化方法,当然,这里的策略也适用与像 ICMP 这样的协议。
Reuse Port
Google 为了解决 DNS 服务器性能问题,就给 Linux 内核打了一个 SO_REUSEPORT
的 Patch,用来优化 UDP 服务器在多核机器上的性能。
REUSEPORT 允许多线程 (或多进程) 服务器的每个线程都可以监听同一个端口,并且每个线程都拥有一个独立的 Socket,而不是所有线程都共享同一个 Socket。
如果没有设置该选项,那么在尝试绑定同一个端口时会返回报错。
分发策略
使用了 ReusePort 策略之后,因为存在了多个 Socket ,那么服务端收到客户端发送的报文后,会按照四元组 <ClientIP ClientPort ServerIP ServerPort>
的 Hash 值进行包的分发,目的有如下几个
- 保证同一个客户度过来的包会发送到同一个 Socket 上;
- 当客户端量足够多时,基本可以将请求均衡到所有的 Socket 上。
这也就意味着在压测时,需要使用尽量多的客户端,以保证压力的均衡。
Recvmmsg
这是一个批量的系统 API 接口,可以从 Socket 中一次读取多个 UDP 数据包,而像 recvmsg()recvfrom()
一次只能读取一个报文。
注意,通过该接口读取的多个报文不一定是属于同一台机器的,大部分是属于不同的机器。
server
root@ubuntu:~/c++# cat udpserv.c #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #define PORT 9080 #define MAXLINE 1024 int main(int argc, char **argv) { int sockfd; char buffer[MAXLINE]; struct sockaddr_in server, client; int optval = 1; int len; int ret; memset(&server, 0, sizeof(server)); memset(&client, 0, sizeof(client)); if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { perror("socket"); return -1; } if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval)) < 0) { perror("bind"); return -1; } server.sin_family = AF_INET; server.sin_port = htons(PORT); server.sin_addr.s_addr = INADDR_ANY; if (bind(sockfd, (const struct sockaddr *)&server, sizeof(server)) < 0) { perror("bind"); return -1;; } while (1) { ret = recvfrom(sockfd, (char *)buffer, MAXLINE, MSG_WAITALL, ( struct sockaddr *) &client, &len); char ipaddr[20]; inet_ntop(AF_INET, &client.sin_addr, ipaddr, sizeof(struct sockaddr)); printf("from IP: %s, port: %d and ", ipaddr, ntohs(client.sin_port)); buffer[ret] = '\0'; printf("recv :%s\n", buffer); } return 0; }
udp client
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #define PORT 9080 #define MAXLINE 1024 int main(int argc, char **argv) { int sockfd; char *ser = argv[1]; char *buff = argv[2]; struct sockaddr_in server; int ret; int len; if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { perror("socket"); return -1; } memset(&server, 0, sizeof(server)); server.sin_family = AF_INET; server.sin_port = htons(PORT); server.sin_addr.s_addr = inet_addr(ser);; while (1) { sendto(sockfd, (const char *)buff, strlen(buff), MSG_CONFIRM, (const struct sockaddr *) &server, sizeof(server)); printf("%s\n", buff); sleep(1); } close(sockfd); return 0; }
测试
sever1
sever2
server3
重启server1
demo2 关掉server1后
demo3 重启server1
server1
重启server后原来在server2和server3的conn发生了迁移
demo4 关闭client1<s1,s2,s3工作正常>
重启client1 跑在了server2上而不是像以前一样跑在server1
demo5 停掉client2
重启client2
再次重启83
同一个client2的源端口改变了
重新实现reuseport逻辑,实现一致性哈希
关于Linux UDP/TCP reuseport 二三事: https://blog.csdn.net/dog250/article/details/80458669