C1000K 기반 서버 구축

13744 단어 사이트
유명한 C10K 문제가 제기된 시기는 바로 2001년이었다. 현재 12년이 지난 2013년까지 C10K는 문제가 되지 않았다. 어떤 일반적인 프로그래머도 수중의 언어와 라이브러리를 이용하여 C10K의 서버를 쉽게 쓸 수 있다.이것은 소프트웨어의 진보 덕분일 뿐만 아니라 하드웨어 성능의 향상 덕분이기도 하다.
이제 C1000K, 즉 백만 연결 문제를 고려할 때다.트위터, weibo, 페이스북 같은 사이트들은 동시 접속자가 수천만 명에 달하는 동시에 소식이 실시간으로 사용자에게 전달되기를 희망한다. 이것은 서버가 수천만 명의 사용자와 TCP 네트워크 연결을 유지할 수 있어야 한다. 비록 수백 수천 대의 서버를 사용하여 이렇게 많은 사용자를 지탱할 수 있지만 서버당 100만 개의 연결(C1000K)을 지원할 수 있다면 10대의 서버만 필요하다.
많은 기술들이 C1000K 문제를 해결할 수 있다고 주장한다. 예를 들어 Erlang, Java NIO 등이다. 그러나 우리는 어떤 요소가 C1000K 문제의 해결을 제한했는지 먼저 알아야 한다.주로 이 몇 가지:
 ?
 ?
 ?
 ?

다음은 이 몇 가지 문제에 대해 각각 분석을 진행한다.
  • 1. 운영체제는 백만 개의 연결을 지원할 수 있습니까?

  • 대부분의 Linux 운영 체제에서는 기본적으로 C1000K가 지원되지 않습니다!운영 체제에는 시스템 전역 및 프로세스 수준의 최대 열기 파일 수(Max Open Files) 제한이 포함되어 있기 때문입니다.글로벌 제한 사항
    Linux에서 다음을 수행합니다.
    cat/proc/sys/fs/file-nr
    다음과 같은 출력이 인쇄됩니다.
    5100 0 101747
    세 번째 숫자 101747은 현재 시스템의 전체 최대 열기 파일 수(Max Open Files)입니다. 볼 수 있는 숫자는 10만 개이기 때문에 이 서버에서는 C1000K를 지원할 수 없습니다.많은 시스템의 이 수치가 더 작습니다. 이 수치를 수정하기 위해 루트 권한으로/etc/sysctl를 수정합니다.conf 파일:
    fs.file-max = 1020000 net.ipv4.ip_conntrack_max = 1020000 net.ipv4.netfilter.ip_conntrack_max = 1020000
    프로세스 제한 사항
    다음을 수행합니다.
    ulimit -n
    출력:
    1024
    현재 Linux 시스템의 프로세스당 최대 1024개의 파일만 열 수 있음을 나타냅니다.C1000K를 지원하기 위해서도 이 제한을 수정해야 합니다.
    임시 수정
    ulimit -n 1020000
    단, 만약 당신이 루트가 아니라면, 1024를 초과하면 오류가 발생할 수 있습니다.
    -bash: ulimit: open files: cannot modify limit: Operation not permitted
    영구 수정
    /etc/security/limits를 편집합니다.conf 파일, 아래와 같이 추가:
    # /etc/security/limits.conf
    work         hard    nofile      1020000
    work         soft    nofile      1020000

    첫 번째 열의work는work 사용자를 표시합니다. * 또는 루트를 작성할 수 있습니다.그런 다음 종료를 저장하고 서버에 다시 로그인합니다.
    참고: 리눅스 커널의 원본 코드에는 상수(NR_OPEN in/usr/include/linux/fs.h)가 있습니다. 예를 들어 RHEL 5는 1048576(2^20)이기 때문에 C1000K를 지원하려면 커널을 다시 컴파일해야 할 수도 있습니다.
  • 운영 체제가 백만 개의 연결을 유지하려면 메모리가 얼마나 필요합니까?


  • 운영체제의 매개 변수 제한을 해결했으니, 다음은 메모리의 점용 상황을 보아야 한다.우선, 운영체제 자체가 이 연결을 유지하는 메모리의 점용이다.Linux 운영체제에 대해 socket(fd)은 정수이기 때문에 운영체제가 백만 개의 연결을 관리하는 데 차지하는 메모리는 4M/8M이고 관리 정보를 포함하면 100M 정도가 될 것으로 추정된다.단, socket 전송과 수신 버퍼가 차지하는 메모리는 분석하지 않았습니다.이를 위해, 나는 가장 원시적인 C 네트워크 프로그램을 써서 검증했다: 서버
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <errno.h>
    #include <arpa/inet.h>
    #include <netinet/tcp.h>
    #include <sys/select.h>
    
    #define MAX_PORTS 10
    
    int main(int argc, char **argv){
        struct sockaddr_in addr;
        const char *ip = "0.0.0.0";
        int opt = 1;
        int bufsize;
        socklen_t optlen;
        int connections = 0;
        int base_port = 7000;
        if(argc > 2){
            base_port = atoi(argv[1]);
        }
    
        int server_socks[MAX_PORTS];
    
        for(int i=0; i<MAX_PORTS; i++){
            int port = base_port + i;
            bzero(&addr, sizeof(addr));
            addr.sin_family = AF_INET;
            addr.sin_port = htons((short)port);
            inet_pton(AF_INET, ip, &addr.sin_addr);
    
            int serv_sock;
            if((serv_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1){
                goto sock_err;
            }
            if(setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1){
                goto sock_err;
            }
            if(bind(serv_sock, (struct sockaddr *)&addr, sizeof(addr)) == -1){
                goto sock_err;
            }
            if(listen(serv_sock, 1024) == -1){
                goto sock_err;
            }
    
            server_socks[i] = serv_sock;
            printf("server listen on port: %d
    "
    , port); } //optlen = sizeof(bufsize); //getsockopt(serv_sock, SOL_SOCKET, SO_RCVBUF, &bufsize, &optlen); //printf("default send/recv buf size: %d
    ", bufsize);
    while(1){ fd_set readset; FD_ZERO(&readset); int maxfd = 0; for(int i=0; i<MAX_PORTS; i++){ FD_SET(server_socks[i], &readset); if(server_socks[i] > maxfd){ maxfd = server_socks[i]; } } int ret = select(maxfd + 1, &readset, NULL, NULL, NULL); if(ret < 0){ if(errno == EINTR){ continue; }else{ printf("select error! %s
    "
    , strerror(errno)); exit(0); } } if(ret > 0){ for(int i=0; i<MAX_PORTS; i++){ if(!FD_ISSET(server_socks[i], &readset)){ continue; } socklen_t addrlen = sizeof(addr); int sock = accept(server_socks[i], (struct sockaddr *)&addr, &addrlen); if(sock == -1){ goto sock_err; } connections ++; printf("connections: %d, fd: %d
    "
    , connections, sock); } } } return 0; sock_err: printf("error: %s
    "
    , strerror(errno)); return 0; }

    서버가 10개의 포트를 감청한 것은 테스트의 편의를 위해서입니다.클라이언트 테스트기 한 대만 있고 최대 30000여 개의 연결만 만들 수 있기 때문에 서버는 10개의 포트를 감청했다. 이렇게 하면 테스트기 한 대가 서버와 30만 개의 연결을 만들 수 있다.클라이언트
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <errno.h>
    #include <arpa/inet.h>
    #include <netinet/tcp.h>
    
    int main(int argc, char **argv){
        if(argc <=  2){
            printf("Usage: %s ip port
    "
    , argv[0]); exit(0); } struct sockaddr_in addr; const char *ip = argv[1]; int base_port = atoi(argv[2]); int opt = 1; int bufsize; socklen_t optlen; int connections = 0; bzero(&addr, sizeof(addr)); addr.sin_family = AF_INET; inet_pton(AF_INET, ip, &addr.sin_addr); char tmp_data[10]; int index = 0; while(1){ if(++index >= 10){ index = 0; } int port = base_port + index; printf("connect to %s:%d
    "
    , ip, port); addr.sin_port = htons((short)port); int sock; if((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1){ goto sock_err; } if(connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == -1){ goto sock_err; } connections ++; printf("connections: %d, fd: %d
    "
    , connections, sock); if(connections % 10000 == 9999){ printf("press Enter to continue: "); getchar(); } usleep(1 * 1000); /* bufsize = 5000; setsockopt(serv_sock, SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(bufsize)); setsockopt(serv_sock, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize)); */ } return 0; sock_err: printf("error: %s
    "
    , strerror(errno)); return 0; }

    나는 10만 개의 연결을 테스트했는데, 이 연결들은 한가해서, 어떤 데이터도 보내지 않고 받지 않는다.이때 프로세스는 1MB도 안 되는 메모리만 차지합니다.그러나 프로그램 종료 전후의free 명령을 비교해 보면 운영체제가 이 10만 개의 연결을 유지하기 위해 200M(대략) 메모리를 사용한 것을 발견했다!백만 연결이라면 운영체제 자체가 2GB 메모리를 차지해야 한다!즉 2KB 연결마다.
    수정 가능
    /proc/sys/net/ipv4/tcp_wmem /proc/sys/net/ipv4/tcp_rmem
    TCP 연결의 송신과 수신 버퍼의 크기를 제어합니다. (@egmkang 감사합니다.)
  • 3. 응용 프로그램이 백만 개의 연결을 유지하려면 얼마나 많은 메모리가 필요합니까?

  • 위의 테스트 코드를 통해 알 수 있듯이 응용 프로그램은 백만 개의 빈 연결을 유지하고 운영체제의 메모리만 차지한다. ps 명령을 통해 보면 응용 프로그램 자체가 메모리를 거의 차지하지 않는다는 것을 알 수 있다.
  • 백만 개의 연결 처리량이 네트워크 제한을 초과했습니까?


  • 백만 접속 중 20%가 활성 상태이고 접속당 1초에 1KB의 데이터를 전송한다고 가정할 때 필요한 네트워크 대역폭은 0.2M x 1KB/s x 8 = 1.6Gbps이며 서버는 최소 10Gbps(10Gbps)가 필요합니다.총결산
    Linux 시스템은 C1000K를 지원하려면 커널 매개변수 및 시스템 구성을 수정해야 합니다.C1000K의 응용 프로그램은 서버에 최소한 2GB 메모리가 필요합니다. 만약 응용 프로그램 자체에 메모리가 필요하다면 이 요구는 적어도 10GB 메모리가 필요합니다.동시에 네트워크 카드는 적어도 만 메가 네트워크 카드가 되어야 한다.
    물론 이것은 이론적 분석일 뿐이고 실제 응용은 더 많은 메모리와 CPU 자원으로 업무 데이터를 처리해야 한다.
    참조:
  • http://www.cyberciti.biz/faq/linux-increase-the-maximum-number-of-open-files/
  • http://www.lognormal.com/blog/2012/09/27/linux-tcpip-tuning/
  • 좋은 웹페이지 즐겨찾기