重新实现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] = '';
                printf("recv :%s
", 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
", 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

原文地址:https://www.cnblogs.com/dream397/p/14784238.html