• SO_REUSEPORT 负载均衡


    1、前言

      昨天总结了一下Linux下网络编程“惊群”现象,给出Nginx处理惊群的方法,使用互斥锁。为例发挥多核的优势,目前常见的网络编程模型就是多进程或多线程,根据accpet的位置,分为如下场景:

      (1)单进程或线程创建socket,并进行listen和accept,接收到连接后创建进程和线程处理连接

      (2)单进程或线程创建socket,并进行listen,预先创建好多个工作进程或线程accept()在同一个服务器套接字、

                          

    这两种模型解充分发挥了多核CPU的优势,虽然可以做到线程和CPU核绑定,但都会存在:

    • 单一listener工作进程胡线程在高速的连接接入处理时会成为瓶颈
    • 多个线程之间竞争获取服务套接字
    • 缓存行跳跃
    • 很难做到CPU之间的负载均衡
    • 随着核数的扩展,性能并没有随着提升

    参考:http://www.blogjava.net/yongboy/archive/2015/02/12/422893.html

    Linux kernel 3.9带来了SO_REUSEPORT特性,可以解决以上大部分问题。

    2、SO_REUSEPORT解决了什么问题

    在Linux 3.9版本引入了socket套接字选项SO_REUSEPORT,Linux 3.9版本之前,一个进程通过bind一个三元组({, <src_addr>, <src_port>})组合之后,其他进程不能再bind同样的三元组,Linux 3.9版本之后,凡是传入选项SO_REUSEPORT且为同一个用户下(安全考虑)的socket套接字都可以bind和监听同样的三元组。内核对这些监听相同三元组的socket套接字实行负载均衡,将TCP连接请求均匀地分配给这些socket套接字。
    这里的负载均衡基本原理为:当有TCP连接请求到来时,用数据包的({<src_addr>, <src_port>})作为一个hash函数的输入,将hash后的结果对SO_REUSEPORT套接字的数量取模,得到一个索引,该索引指示的数组位置对应的套接字便是要处理连接请求的套接字。

    SO_REUSEPORT支持多个进程或者线程绑定到同一端口,提高服务器程序的性能,解决的问题:

    • 允许多个套接字 bind()/listen() 同一个TCP/UDP端口
      • 每一个线程拥有自己的服务器套接字
      • 在服务器套接字上没有了锁的竞争
    • 内核层面实现负载均衡
    • 安全层面,监听同一个端口的套接字只能位于同一个用户下面

    其核心的实现主要有三点:

    • 扩展 socket option,增加 SO_REUSEPORT 选项,用来设置 reuseport。
    • 修改 bind 系统调用实现,以便支持可以绑定到相同的 IP 和端口
    • 修改处理新建连接的实现,查找 listener 的时候,能够支持在监听相同 IP 和端口的多个 sock 之间均衡选择。

    有了SO_RESUEPORT后,每个进程可以自己创建socket、bind、listen、accept相同的地址和端口,各自是独立平等的。让多进程监听同一个端口,各个进程中accept socket fd不一样,有新连接建立时,内核只会唤醒一个进程来accept,并且保证唤醒的均衡性。

    include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h> 
    #include <sys/socket.h> 
    #include <netinet/in.h> 
    #include <arpa/inet.h> 
    #include <assert.h> 
    #include <sys/wait.h>
    #include <string.h>
    #include <errno.h>
    #include <stdlib.h>
    #include <fcntl.h>
     
    #define IP   "127.0.0.1"
    #define PORT  8888
    #define WORKER 4
    #define MAXLINE   4096
     
    int worker(int i)
    {
        struct sockaddr_in address; 
        bzero(&address, sizeof(address)); 
        address.sin_family = AF_INET; 
        inet_pton( AF_INET, IP, &address.sin_addr); 
        address.sin_port = htons(PORT); 
     
        int listenfd = socket(PF_INET, SOCK_STREAM, 0); 
        assert(listenfd >= 0); 
     
        int val =1;
        /*set SO_REUSEPORT*/
        if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val))<0) {
            perror("setsockopt()");        
        }   
        int ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address)); 
        assert(ret != -1); 
     
        ret = listen(listenfd, 5); 
        assert(ret != -1); 
        while (1) {
            printf("I am worker %d, begin to accept connection.
    ", i);
            struct sockaddr_in client_addr; 
            socklen_t client_addrlen = sizeof( client_addr ); 
            int connfd = accept( listenfd, ( struct sockaddr* )&client_addr, &client_addrlen ); 
            if (connfd != -1) {
                printf("worker %d accept a connection success. ip:%s, prot:%d
    ", i, inet_ntoa(client_addr.sin_addr), client_addr.sin_port);
            } else {
                printf("worker %d accept a connection failed,error:%s", i, strerror(errno));
            }
            char buffer[MAXLINE];
            int nbytes = read(connfd, buffer, MAXLINE);
            printf("read from client is:%s
    ", buffer);
            write(connfd, buffer, nbytes);
            close(connfd);
        }
        return 0;
    }
     
    int main()
    {
        int i = 0;
        for (i = 0; i < WORKER; i++) {
            printf("Create worker %d
    ", i);
            pid_t pid = fork();
            /*child  process */
            if (pid == 0) {
                worker(i);
            }
            if (pid < 0) {
                printf("fork error");
            }
        }
        /*wait child process*/
        while (wait(NULL) != 0)
            ;
        if (errno == ECHILD) {
            fprintf(stderr, "wait error:%s
    ", strerror(errno));
        }
        return 0;
    }

  • 相关阅读:
    javascript获得浏览器工作区域的大小
    javascript禁止输入数字
    Extjs之遍历Store内的数据
    Extjs之rowEditing编辑状态时列不对齐
    Extjs中numberfield小数位数设置
    【转】vscode常用快捷键整理
    【转】如何提高WEB的性能?
    【转】echarts 使用示例
    uni-app页面配置和跳转
    【转】.NetCore如何将特性和依赖注入有效结合
  • 原文地址:https://www.cnblogs.com/dream397/p/14685965.html
Copyright © 2020-2023  润新知