避免惊群效应的措施、原理及实现方式
- 使用SO_REUSEPORT选项
- 原理:该选项允许多个套接字绑定到同一个IP地址和端口上。当有新连接到达时,内核会基于一定的负载均衡算法(如按源IP、源端口哈希)将连接分配给其中一个监听套接字,而不是唤醒所有等待在该端口上的进程或线程,从而避免惊群。
- 实现方式:在创建监听套接字后,设置
SO_REUSEPORT
选项。以C语言为例:
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
int optval = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval));
// 后续绑定地址、监听等操作
- 使用epoll的EPOLLEXCLUSIVE标志
- 原理:当一个文件描述符(如监听套接字)以
EPOLLEXCLUSIVE
标志添加到epoll实例中时,它将在事件分发上具有独占性。当该文件描述符对应的事件发生时,只有注册了该标志的epoll实例中的等待事件的文件描述符会被唤醒,而其他未注册该标志的epoll实例不会被唤醒,减少了不必要的唤醒。
- 实现方式:在将监听套接字添加到epoll实例时设置
EPOLLEXCLUSIVE
标志。例如:
struct epoll_event event;
event.data.fd = listenfd;
event.events = EPOLLIN | EPOLLEXCLUSIVE;
epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &event);
- 采用单线程或线程池处理连接
- 原理:如果采用单线程监听和处理连接,不存在多个线程同时竞争连接的情况,自然也就没有惊群效应。若使用线程池,将连接的接受操作固定在一个线程(如主线程),然后将接收到的连接分发给线程池中的线程处理,这样也避免了多个线程同时监听同一端口导致的惊群。
- 实现方式:
- 单线程:在一个线程中完成监听、接受连接及后续处理操作。
while (1) {
int connfd = accept(listenfd, NULL, NULL);
// 处理connfd
}
- **线程池**:主线程接受连接,然后将连接任务分发给线程池中的线程。例如使用简单的任务队列:
// 主线程
while (1) {
int connfd = accept(listenfd, NULL, NULL);
// 将connfd加入任务队列
}
// 线程池中的线程从任务队列取任务并处理
while (1) {
int connfd = get_task_from_queue();
// 处理connfd
}