张不大的博客

高性能服务器

高性能服务器分析


声明:一点点自己的总结与感悟吧,如果有不对的地方,烦请指正

服务器最重要的是能够解决复杂的网络问题,大量并发请求,如经典的c10K ,当然后面这个并发问题延伸到c10M

当然这个 c10k 已经被从硬件和软件两个方面解决了,硬件方面应该是在 摩尔定律上下功夫? 软件方面就有很多方向了,然后接下来着重讲软件方面的解决方案

OS & question

有人说,操作系统的底层设计让通信开销增大

原话是,os 的内核不是解决c10M问题的办法,相反os 的内核正是导致C10M问题的关键

简单解释一下,比如网络包进入网卡,然后通过网卡进入内核处理,内核接着拷贝把数据转给用户程序处理,再把处理的数据通过网卡发送客户端

大致的思路是这样,然后就有人在这里分析,分析的结论有以下几个方面

当然也有解决方案

解决案例

是的,有一些包含上述的解决方案的网络框架(🤦)好像有很多很多网络框架,都说自己贼牛逼(高可用、分布式、高性能

具体到的有,6wind、Windriver、Netmap、DPDK

这个DPDK 其实好像很火,后面可以有空研究一下

心得

好像,自己写过的项目,高性能是这样走的,

首先是对数据的处理,使用各种池

线程池(Thread Pool)

线程池是预先创建一组线程,任务提交后将分配给空闲的线程处理。线程池用于避免线程频繁创建和销毁的开销,提高并发性能。

数据库连接池(Connection Pool)

数据库连接池管理数据库连接。它预先创建一组数据库连接,并在需要时分配。这样可以减少每次连接数据库的开销,提高数据库操作的性能。

对象池(Object Pool)

对象池用于管理可重复使用的对象,避免对象的频繁创建和销毁。对象池可以用在各种场景,如游戏开发中的游戏对象、数据库连接、网络资源等。

缓存池(Cache Pool)

缓存池用于管理缓存,提供快速访问数据的能力。缓存池通常用于提高系统性能和响应速度,减少数据库或其他后端系统的负载。

线程本地池(Thread Local Pool)

线程本地池为每个线程创建一组独立的资源,确保每个线程都有自己独立的资源池。这在需要避免线程间资源共享的情况下非常有用。

内存池(Memory Pool)

内存池用于管理内存块。通过预先分配一块大内存,然后从中划分小块,减少内存分配和释放的开销。这种技术在需要频繁分配和释放内存的应用中非常有用。

连接池(Connection Pool)

连接池可以用于网络连接管理。它类似于数据库连接池,但用于管理网络连接,如与服务器、外部服务等的连接。

资源池(Resource Pool)

资源池可以管理各种类型的资源,如线程、对象、连接等。资源池的目的是提高资源利用率,减少资源创建和销毁的开销。

消息池(Message Pool)

消息池用于管理消息对象,通常用于消息队列、事件系统等。消息池可以减少消息对象的创建和销毁,提高消息处理的性能。

然后是io多路复用

比如常见的“三剑客” select、poll、epoll

(环境linux c/c++)

#include <sys/select.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <iostream>
#include <cstring>

int main() {
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    sockaddr_in address = {0};
    address.sin_family = AF_INET;
    address.sin_port = htons(8080);
    address.sin_addr.s_addr = INADDR_ANY;

    bind(server_fd, (struct sockaddr *)&address, sizeof(address));
    listen(server_fd, 3);

    fd_set readfds;
    while (true) {
        FD_ZERO(&readfds);
        FD_SET(server_fd, &readfds);
        int max_fd = server_fd;

        int activity = select(max_fd + 1, &readfds, nullptr, nullptr, nullptr);
        if (activity < 0 && errno != EINTR) {
            std::cerr << "Select error" << std::endl;
            break;
        }

        if (FD_ISSET(server_fd, &readfds)) {
            int client_fd = accept(server_fd, nullptr, nullptr);
            const char *message = "Hello from select server\n";
            send(client_fd, message, strlen(message), 0);
            close(client_fd);
        }
    }

    close(server_fd);
    return 0;
}
#include <sys/poll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <iostream>
#include <cstring>

int main() {
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    sockaddr_in address = {0};
    address.sin_family = AF_INET;
    address.sin_port = htons(8080);
    address.sin_addr.s_addr = INADDR_ANY;

    bind(server_fd, (struct sockaddr *)&address, sizeof(address));
    listen(server_fd, 3);

    pollfd fds[1];
    fds[0].fd = server_fd;
    fds[0].events = POLLIN;

    while (true) {
        int activity = poll(fds, 1, -1);
        if (activity < 0) {
            std::cerr << "Poll error" << std::endl;
            break;
        }

        if (fds[0].revents & POLLIN) {
            int client_fd = accept(server_fd, nullptr, nullptr);
            const char *message = "Hello from poll server\n";
            send(client_fd, message, strlen(message), 0);
            close(client_fd);
        }
    }

    close(server_fd);
    return 0;
}
#include <sys/epoll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <iostream>
#include <cstring>

int main() {
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    sockaddr_in address = {0};
    address.sin_family = AF_INET;
    address.sin_port = htons(8080);
    address.sin_addr.s_addr = INADDR_ANY;

    bind(server_fd, (struct sockaddr *)&address, sizeof(address));
    listen(server_fd, 3);

    int epoll_fd = epoll_create(1);
    epoll_event event = {0};
    event.events = EPOLLIN;
    event.data.fd = server_fd;
    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event);

    while (true) {
        epoll_event events[10];
        int activity = epoll_wait(epoll_fd, events, 10, -1);
        if (activity < 0) {
            std::cerr << "Epoll error" << std::endl;
            break;
        }

        for (int i = 0; i < activity; i++) {
            if (events[i].data.fd == server_fd) {
                int client_fd = accept(server_fd, nullptr, nullptr);
                const char *message = "Hello from epoll server\n";
                send(client_fd, message, strlen(message), 0);
                close(client_fd);
            }
        }
    }

    close(server_fd);
    return 0;
}

pr & create ?

(这块我暂时想不了,系统的写一篇博客真的要点东西啊)

边缘触发 && 水平触发

边缘触发 edge trigger (ET) 水平触发 level trigger

源码参考

//server
#include <stdlib.h>
#include <stdio.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <fcntl.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#include <unistd.h>

static int do_listen(const char* host, int port) {
    struct addrinfo ai_hints;
    struct addrinfo* ai_list = NULL;
    char portstr[16];
    sprintf(portstr, "%d", port);
    memset(&ai_list, 0, sizeof(ai_hints));

    ai_hints.ai_socktype = SOCK_STREAM;
    ai_hints.ai_protocol = IPPROTO_TCP;

    int status = getaddrinfo(host, portstr, &ai_hints, &ai_list);
    if (status != 0) {
        return -1;
    }

    int fd = socket(ai_list->ai_family, ai_list->ai_socktype, 0);
    if (fd < 0) {
        freeaddrinfo(ai_list);
        return -1;
    }

    status = bind(fd, (struct sockaddr*)ai_list->ai_addr, ai_list->ai_addrlen);
    if (status != 0) {
        close(fd);
        freeaddrinfo(ai_list);
        return -1;
    }

    listen(fd, 30);

    printf("do_listen success fd:%d\n", fd);
    return fd;
}

int main(int argc, char** argv) {
    if (argc < 2) {
        printf("please input mode, lt or et\n");
        return -1;
    }

    int ep_event = 0;
    if (strcmp(argv[1], "et") == 0) {
        ep_event = EPOLLET;
    }
    else if (strcmp(argv[1], "lt") == 0) {
        ep_event = 0;
    }
    else {
        printf("unknow mode %s please input lt or et\n", argv[1]);
        return -1;
    }


    int epfd = epoll_create(1024);
    if (epfd == -1) {
        printf("fail to create epoll %d\n", errno);
        return -1;
    }

    int listen_fd = do_listen("127.0.0.1", 8001);
    if (listen_fd < 0) {
        printf("do listen fail");
        return -1;
    }

    struct epoll_event ee;
    ee.events = EPOLLIN;
    ee.data.fd = listen_fd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &ee);

    for(;;) {
        printf("before epoll epoll_wait\n");
        struct epoll_event ev[16];
        int n = epoll_wait(epfd, ev, 16, -1);
        if (n == -1) {
            printf("epoll_wait error %d", errno);
            break;
        }

        printf("after epoll_wait event n:%d\n", n);

        for (int i = 0; i < n; i ++) {
            struct epoll_event* e = &ev[i];
            if (e->data.fd == listen_fd) {
                struct sockaddr s;
                socklen_t len = sizeof(s);
                int client_fd = accept(listen_fd, &s, &len);
                if (client_fd < 0) {
                    break;
                }

                fcntl(client_fd, F_SETFL, O_NONBLOCK);
                ee.events = EPOLLIN | ep_event;
                ee.data.fd = client_fd;
                epoll_ctl(epfd, EPOLL_CTL_ADD, client_fd, &ee);
                printf("accpet new connection fd:%d\n", client_fd);
            }
            else {
                int client_fd = e->data.fd;
                int flag = e->events;
                int r = (flag & EPOLLIN) != 0;
                if (r) {
                    printf("read fd:%d\n", client_fd);
                    char buffer[2] = {0};
                    int n = read(client_fd, buffer, 2);
                    printf("read number %d\n", n);
                    if (n < 0) {
                        switch(errno) {
                            case EINTR: break;
                            case EWOULDBLOCK: break;
                            default: break;
                        }
                    }
                    else if (n == 0) {
                        epoll_ctl(epfd, EPOLL_CTL_DEL, client_fd, NULL);
                        close(client_fd);
                        break;
                    }
                    else {
                        printf("----%c%c\n", buffer[0], buffer[1]);
                    }
                }
            }
        }
    }

    return 0;
}
//client
#include <stdlib.h>
#include <stdio.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <fcntl.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#include <unistd.h>

static int do_listen(const char* host, int port) {
    struct addrinfo ai_hints;
    struct addrinfo* ai_list = NULL;
    char portstr[16];
    sprintf(portstr, "%d", port);
    memset(&ai_list, 0, sizeof(ai_hints));

    ai_hints.ai_socktype = SOCK_STREAM;
    ai_hints.ai_protocol = IPPROTO_TCP;

    int status = getaddrinfo(host, portstr, &ai_hints, &ai_list);
    if (status != 0) {
        return -1;
    }

    int fd = socket(ai_list->ai_family, ai_list->ai_socktype, 0);
    if (fd < 0) {
        freeaddrinfo(ai_list);
        return -1;
    }

    status = bind(fd, (struct sockaddr*)ai_list->ai_addr, ai_list->ai_addrlen);
    if (status != 0) {
        close(fd);
        freeaddrinfo(ai_list);
        return -1;
    }

    listen(fd, 30);

    printf("do_listen success fd:%d\n", fd);
    return fd;
}

int main(int argc, char** argv) {
    if (argc < 2) {
        printf("please input mode, lt or et\n");
        return -1;
    }

    int ep_event = 0;
    if (strcmp(argv[1], "et") == 0) {
        ep_event = EPOLLET;
    }
    else if (strcmp(argv[1], "lt") == 0) {
        ep_event = 0;
    }
    else {
        printf("unknow mode %s please input lt or et\n", argv[1]);
        return -1;
    }


    int epfd = epoll_create(1024);
    if (epfd == -1) {
        printf("fail to create epoll %d\n", errno);
        return -1;
    }

    int listen_fd = do_listen("127.0.0.1", 8001);
    if (listen_fd < 0) {
        printf("do listen fail");
        return -1;
    }

    struct epoll_event ee;
    ee.events = EPOLLIN;
    ee.data.fd = listen_fd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &ee);

    for(;;) {
        printf("before epoll epoll_wait\n");
        struct epoll_event ev[16];
        int n = epoll_wait(epfd, ev, 16, -1);
        if (n == -1) {
            printf("epoll_wait error %d", errno);
            break;
        }

        printf("after epoll_wait event n:%d\n", n);

        for (int i = 0; i < n; i ++) {
            struct epoll_event* e = &ev[i];
            if (e->data.fd == listen_fd) {
                struct sockaddr s;
                socklen_t len = sizeof(s);
                int client_fd = accept(listen_fd, &s, &len);
                if (client_fd < 0) {
                    break;
                }

                fcntl(client_fd, F_SETFL, O_NONBLOCK);
                ee.events = EPOLLIN | ep_event;
                ee.data.fd = client_fd;
                epoll_ctl(epfd, EPOLL_CTL_ADD, client_fd, &ee);
                printf("accpet new connection fd:%d\n", client_fd);
            }
            else {
                int client_fd = e->data.fd;
                int flag = e->events;
                int r = (flag & EPOLLIN) != 0;
                if (r) {
                    printf("read fd:%d\n", client_fd);
                    char buffer[2] = {0};
                    int n = read(client_fd, buffer, 2);
                    printf("read number %d\n", n);
                    if (n < 0) {
                        switch(errno) {
                            case EINTR: break;
                            case EWOULDBLOCK: break;
                            default: break;
                        }
                    }
                    else if (n == 0) {
                        epoll_ctl(epfd, EPOLL_CTL_DEL, client_fd, NULL);
                        close(client_fd);
                        break;
                    }
                    else {
                        printf("----%c%c\n", buffer[0], buffer[1]);
                    }
                }
            }
        }
    }

    return 0;
}

阻塞与非阻塞

阻塞 I/O 在阻塞模式下,当一个 I/O 操作(例如读取数据、写入数据)被调用时,如果数据不可用或操作无法立即完成,程序会等待,直到操作完成。这意味着程序的执行将暂停,直到 I/O 操作完成。

特点 简单易用:阻塞 I/O 通常更简单,编程更容易理解。程序只需等待操作完成,无需处理异步操作。 可能导致性能瓶颈:阻塞 I/O 会导致整个线程暂停,这在处理大量并发请求时可能导致性能瓶颈。 适用于简单应用:对于不需要高并发的应用,阻塞 I/O 是一种简单有效的解决方案。 适用场景 单线程应用:在单线程环境中,阻塞 I/O 可能更容易实现,因为没有并发问题。 低并发应用:在不需要处理大量并发请求的情况下,阻塞 I/O 可以简化编程。 批处理任务:在不需要即时响应的批处理任务中,阻塞 I/O 可能是合适的选择。 非阻塞 I/O 在非阻塞模式下,I/O 操作立即返回,而不等待操作完成。如果操作无法立即完成,程序会得到一个标志(如 EAGAIN 或 EWOULDBLOCK),指示稍后再尝试。非阻塞 I/O 通常与 I/O 多路复用技术(如 select、poll、epoll)结合使用。

特点 高并发:非阻塞 I/O 适合处理大量并发请求,因为线程不会因为 I/O 操作而阻塞。 需要异步处理:非阻塞 I/O 通常需要处理异步操作,可能增加代码的复杂性。 更复杂的设计:非阻塞 I/O 需要考虑并发和同步问题,设计复杂度更高。 适用场景 高并发服务器:在处理大量并发请求时,非阻塞 I/O 可以提高服务器的吞吐量和性能。 事件驱动架构:非阻塞 I/O 通常与事件驱动架构结合使用,实现高性能服务器。 实时应用:在需要实时响应的应用中,非阻塞 I/O 可以提供更好的性能。 总结 阻塞和非阻塞 I/O 在性能和应用场景上有显著区别。阻塞 I/O 适用于简单、低并发的应用,而非阻塞 I/O 适用于高并发、事件驱动的环境。选择哪种模式取决于应用的需求、并发量和性能要求。

对于高性能服务器,非阻塞 I/O 通常是更好的选择,因为它可以处理大量并发请求,提高服务器的性能和可扩展性。阻塞 I/O 更适合简单、低并发的应用。选择哪种模式要根据具体场景和需求而定。

(问的gpt)

总结

高性能服务器,首先要完成最基本的通信问题,其次在考虑根据各种复杂环境做相应的处理。 不如,多线程就不适合做阻塞 但是,事件处理机制就不能用阻塞而要用非阻塞

有很多时候,会有这样一个idea,一个问题,站在不同角度去分析又是另个味道,下次具体化说来听听🙊

#高性能服务器 #性能分析