尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Linux epoll ET模式实战:非阻塞IO、循环读写与缓冲区策略
1. 这不是概念辨析而是生产环境里必须踩过的坑“Epoll水平触发Level Triggered和边缘触发Edge Triggered区别”——这句话在面试题库、技术博客标题栏里反复出现但绝大多数人只记住了“LT是只要就绪就通知ET是只在状态变化时通知”这句教科书定义。我带过三届后端开发新人在某次压测故障复盘会上一个线上服务在高并发下CPU飙到95%连接堆积却无日志报错最后定位到epoll_wait()调用逻辑里混用了LT语义写法去处理ET模式的fd。那一刻我才意识到这不是理论选择题而是Linux I/O多路复用底层机制与应用层代码契约的一次硬性对齐。LT和ET根本不是“两种可选模式”而是两套完全不同的编程范式——LT像老式电铃门一开就一直响ET像智能门禁只在门从关到开那一瞬间滴一声。你用按电铃的方式去等门禁提示永远等不到下一次响声。本文不讲源码注释不画状态转换图只说我在某高并发消息网关、某实时风控引擎、某自研RPC框架中真实踩过的7个坑、3种必须配套的缓冲区策略、2类不可绕过的系统调用组合以及为什么你在用libevent/libuv时其实早已被封装层替你做了关键决策。如果你正在写网络库、调试连接超时、优化单机QPS或者刚被面试官问懵了“ET为什么必须配合非阻塞IO”请把这篇文章当操作手册来读。2. 核心设计逻辑为什么Linux要硬生生拆出两种触发模式2.1 LT模式的本质状态轮询的友好封装LT模式的设计哲学非常朴素让应用层代码尽可能接近传统阻塞I/O的思维惯性。它的核心契约是——只要内核缓冲区有数据可读或有空间可写epoll_wait()就一定会返回该fd。这意味着你哪怕只读了一半数据下次调用epoll_wait()时它还会立刻再次通知你。这种“懒人模式”极大降低了初学者的入门门槛。我最早写的TCP回显服务器就是LT模式代码结构清晰得像伪代码// LT典型写法一次读不完没关系下次还通知你 while (1) { int nfds epoll_wait(epoll_fd, events, MAX_EVENTS, -1); for (int i 0; i nfds; i) { if (events[i].data.fd listen_fd) { // 处理新连接 } else { // 处理已连接fd ssize_t n read(events[i].data.fd, buf, sizeof(buf)-1); if (n 0) { // 只读了1024字节但缓冲区还有200字节 // 没关系下次epoll_wait()还会返回这个fd write(events[i].data.fd, buf, n); } else if (n 0) { close(events[i].data.fd); } } } }这种写法的底层支撑是内核对每个就绪fd的状态快照机制。每次epoll_wait()返回前内核会扫描所有注册fd的socket接收队列长度、发送队列剩余空间等指标只要满足就绪条件如sk_receive_queue不为空就将其加入就绪链表。这就带来一个隐含成本LT模式下epoll_wait()的返回时间与当前就绪fd总数强相关。当你的服务同时维持10万个连接其中9.9万个处于半关闭状态但接收队列仍有残留数据时每次epoll_wait()都要遍历全部10万项做状态判断——这就是为什么某些LT模式服务在连接数暴涨时出现“epoll_wait延迟突增”的根本原因。某次我们做长连接推送服务压测连接数从5万升到8万平均延迟从12ms跳到47ms最终发现是LT模式下内核态遍历开销呈线性增长。2.2 ET模式的底层动机为百万级连接而生的减法设计ET模式诞生于2004年Linux 2.6.12内核直接动因是解决C10K问题向C100K/C1M演进时的性能瓶颈。它的设计哲学是彻底反直觉的内核只保证在文件描述符状态发生“跃变”时通知一次之后无论状态是否持续就绪都不会重复通知。所谓“跃变”特指从“不可读”到“可读”、从“不可写”到“可写”、从“无错误”到“有错误”这三个瞬时事件。这个设计砍掉了LT模式中最耗时的“全量状态扫描”环节转而采用更轻量的事件标记单次投递机制。内核只需维护一个“状态变更标记位”当socket接收队列从空变为非空时置位并投递事件之后即使队列一直有数据也不会再置位。但这个性能红利是有严苛前提的应用层必须一次性把所有可用数据读完否则就会永久丢失后续通知。这直接导致ET模式强制要求两个配套措施第一fd必须设为非阻塞O_NONBLOCK第二必须使用循环读写直到返回EAGAIN/EWOULDBLOCK。我见过最典型的反模式是在ET模式下这样写// ❌ ET模式下的致命错误只读一次就结束 ssize_t n read(fd, buf, sizeof(buf)-1); if (n 0) { process_data(buf, n); // 错误如果内核缓冲区还有剩余数据 // 且没有再次触发epoll_wait()这个fd将永远沉默 }正确的ET处理必须是// ✅ ET标准写法循环读取直到阻塞 while (1) { ssize_t n read(fd, buf, sizeof(buf)-1); if (n 0) { process_data(buf, n); } else if (n 0) { // 对端关闭 close_connection(fd); break; } else { if (errno EAGAIN || errno EWOULDBLOCK) { // 缓冲区已空本次处理完成 break; } else { // 真实错误 handle_error(fd); break; } } }这个看似简单的while循环背后是应用层主动承担了原本由内核代劳的状态跟踪职责。ET把“状态感知”的复杂度从内核下放到用户空间换来的是epoll_wait()调用时近乎常数级的响应时间——无论你注册了1000个还是100万个fd只要就绪事件只有3个内核就只处理这3个标记位。某金融行情网关在切换ET模式后epoll_wait()平均耗时从15μs降至0.8μs这是LT模式永远无法达到的天花板。2.3 为什么不能“混合使用”一个被低估的系统级约束很多开发者试图在同一个epoll实例中对不同fd设置不同触发模式比如监听fd用LT业务连接fd用ET这是被内核明确禁止的。epoll_ctl()的EPOLL_CTL_ADD操作中events字段的EPOLLET标志位是fd级别的属性但epoll实例本身具有模式一致性约束。当你对一个已添加的fd执行EPOLL_CTL_MOD修改事件类型时内核会校验新旧事件的ET/LT属性是否一致。若不一致系统调用直接返回EINVAL错误。这个限制的深层原因是内核数据结构的物理隔离。LT模式下就绪fd存储在rdllistready list中每次epoll_wait()返回后不清空下次继续扫描ET模式下就绪fd存入rbrred-black tree的就绪节点一旦投递即从就绪集合移除。两种模式对应完全不同的内存布局和锁竞争模型。强行混合会导致内核无法统一管理就绪状态可能引发竞态条件。我们在某次重构中曾尝试对HTTP短连接fd用LT减少小包处理开销、对WebSocket长连接fd用ET提升吞吐结果在高并发下出现epoll_wait()随机丢失事件的现象最终确认是内核版本2.6.32对混合模式的支持存在未公开的bug。从此所有项目都严格遵循“单实例单模式”铁律。3. 实操细节解析从系统调用到缓冲区策略的完整链条3.1 ET模式下非阻塞IO的不可替代性不只是建议而是生存法则在ET模式中将fd设为非阻塞O_NONBLOCK不是最佳实践而是内核强制要求的生存条件。这里的关键在于read()/write()系统调用的行为差异。阻塞模式下当socket接收缓冲区为空时read()会挂起当前线程直至有数据到达而非阻塞模式下read()立即返回-1并置errno为EAGAIN。ET模式的“只通知一次”特性决定了我们必须依赖EAGAIN作为“数据读尽”的信号。我曾在一个实时音视频转发服务中犯过致命错误为简化逻辑对UDP socket保持阻塞模式仅在epoll中启用ET。结果在弱网环境下某个客户端突发大量丢包服务端UDP接收缓冲区被填满后recvfrom()调用开始阻塞。由于ET只在缓冲区从空到非空时通知一次这次阻塞导致整个事件循环卡死——其他999个正常连接的事件再也无法被处理。排查时用strace看到进程卡在recvfrom()系统调用上而epoll_wait()再无返回。解决方案极其简单fcntl(fd, F_SETFL, O_NONBLOCK)一行代码但代价是整晚的线上故障。非阻塞IO带来的另一个连锁反应是应用层缓冲区管理策略的根本性重构。LT模式下你可以依赖epoll的“温柔提醒”来分批处理数据ET模式下你必须预判单次read()能获取的最大数据量并设计环形缓冲区ring buffer来应对突发流量。我们自研的物联网设备接入网关采用双缓冲区策略主缓冲区4KB用于快速接收溢出数据自动转入扩展缓冲区64KB。当read()返回EAGAIN时主缓冲区数据送入协议解析器扩展缓冲区则通过splice()系统调用零拷贝转发至下游。这种设计使单机处理能力从LT模式的12万TPS提升至28万TPS。3.2 边缘触发的“边缘”究竟指什么三次握手与四次挥手中的状态跃变“边缘触发”中的“边缘”常被误解为网络包边界实际它精确对应TCP状态机中的特定状态转换事件。理解这一点对调试连接异常至关重要。以TCP连接建立为例LT模式下当三次握手完成socket进入ESTABLISHED状态时epoll会通知此后只要连接保持活跃每次epoll_wait()都会返回该fd。而ET模式下只有从SYN_RECV到ESTABLISHED的这一次状态跃变才会触发通知。这意味着如果你在accept()后没有立即处理新连接而是先去做其他耗时操作这个新连接fd将永远不会被再次通知——除非它后续发生读写事件。同样在连接关闭阶段ET模式只在FIN包到达导致socket从ESTABLISHED变为CLOSE_WAIT时通知一次。如果此时你没有调用read()读取剩余数据触发EOF这个fd就会滞留在CLOSE_WAIT状态成为TIME_WAIT风暴的源头。某次我们发现服务端TIME_WAIT连接数暴增用ss -s查看发现大量连接卡在CLOSE_WAIT。追踪代码发现ET模式下对新连接的read()调用被放在了业务逻辑处理之后而业务逻辑平均耗时80ms期间对端已发送FIN包。由于ET只通知一次状态跃变这个FIN事件被彻底错过socket永远停在CLOSE_WAIT。解决方案是严格遵循ET的“事件驱动即时处理”原则在epoll_wait()返回新连接fd的瞬间必须立即accept()并设置非阻塞然后马上read()检查是否有初始数据。我们为此抽象出标准初始化模板// ET模式下新连接的标准处理流程 struct sockaddr_in client_addr; socklen_t client_len sizeof(client_addr); int conn_fd accept(listen_fd, (struct sockaddr*)client_addr, client_len); if (conn_fd 0) { if (errno EAGAIN || errno EWOULDBLOCK) return; // 无新连接 else handle_accept_error(); } // 立即设置非阻塞 int flags fcntl(conn_fd, F_GETFL, 0); fcntl(conn_fd, F_SETFL, flags | O_NONBLOCK); // 立即尝试读取防止FIN丢失 char dummy[1]; ssize_t n read(conn_fd, dummy, 1); if (n 0) { // 对端已关闭立即清理 close(conn_fd); return; } else if (n 0 (errno EAGAIN || errno EWOULDBLOCK)) { // 正常等待后续读事件 } else if (n 0) { // 有初始数据放入接收缓冲区 append_to_buffer(conn_fd, dummy, n); }这个模板现在是我们所有网络项目的准入检查项任何违反都将被CI流水线拒绝合并。3.3 写就绪事件的特殊陷阱EPOLLOUT的“虚假唤醒”与流控反模式EPOLLOUT事件在ET模式下存在一个极易被忽视的陷阱它会在socket发送缓冲区有可用空间时立即触发但这个“可用空间”可能远小于应用层期望发送的数据量。例如TCP发送缓冲区默认128KB当应用层要发送2MB文件时第一次EPOLLOUT通知后write()可能只成功写入16KB受拥塞窗口限制此时缓冲区仍有112KB空间ET模式不会再次通知——因为“有空间”这个状态并未发生跃变从无到有只是空间量在减少。更危险的是“虚假唤醒”当socket刚创建或连接刚建立时发送缓冲区默认是空的但内核会立即触发一次EPOLLOUT事件因为从“未连接”到“已连接”发生了状态跃变。如果此时应用层未做连接状态校验就盲目write()会触发SIGPIPE信号导致进程崩溃。我们在某RPC框架中就因此出现过批量core dump日志显示write()返回-1且errno为EPIPE。正确做法是永远不要单独依赖EPOLLOUT事件来驱动发送而应将发送逻辑与业务数据就绪状态解耦。我们采用“发送队列就绪标记”双机制所有待发送数据先进入环形发送队列当队列非空且socket发送缓冲区有空间时启动发送循环write()返回EAGAIN时停止发送并等待下一次EPOLLOUT连接建立后首次EPOLLOUT必须先getsockopt()检查SO_ERROR确认连接成功。// ET模式下安全的发送逻辑 void safe_send(int fd) { while (!send_queue_empty(fd)) { struct iovec iov[2]; int iovcnt build_iovec(fd, iov); // 构建iovec数组 ssize_t n writev(fd, iov, iovcnt); if (n 0) { consume_from_queue(fd, n); } else if (n 0) { // 对端关闭清理 close_connection(fd); break; } else { if (errno EAGAIN || errno EWOULDBLOCK) { // 缓冲区满等待EPOLLOUT enable_epollout(fd); // 重新注册EPOLLOUT break; } else if (errno EPIPE || errno ECONNRESET) { close_connection(fd); break; } else { handle_write_error(fd); break; } } } }这个设计使我们的RPC框架在千兆网卡下达到98%的带宽利用率而LT模式下因频繁EPOLLOUT通知导致的上下文切换开销使其峰值仅72%。4. 完整实操流程从零构建一个ET模式HTTP服务器4.1 环境准备与基础配置绕不开的内核参数调优在动手写代码前必须调整三个关键内核参数否则ET模式的性能优势将大打折扣net.core.somaxconn控制listen()的backlog上限。默认值128在高并发场景下会导致连接被内核丢弃。我们设为65535echo 65535 /proc/sys/net/core/somaxconnnet.ipv4.tcp_max_syn_backlogSYN队列长度需与somaxconn匹配echo 65535 /proc/sys/net/ipv4/tcp_max_syn_backlogfs.file-max系统级文件描述符上限。ET模式下每个连接至少占用2个fdsocketepoll10万连接需20万fdecho 200000 /proc/sys/fs/file-max提示这些参数需写入/etc/sysctl.conf永久生效否则重启后失效。某次线上发布后连接成功率骤降排查发现是容器环境未继承宿主机sysctl配置导致somaxconn仍为128。4.2 核心代码实现137行完成ET模式HTTP服务骨架以下是一个精简但生产可用的ET模式HTTP服务器核心基于Linux 3.10#include sys/epoll.h #include sys/socket.h #include netinet/in.h #include fcntl.h #include unistd.h #include string.h #include stdio.h #include stdlib.h #define MAX_EVENTS 1024 #define BUFFER_SIZE 8192 struct connection { int fd; char recv_buf[BUFFER_SIZE]; size_t recv_len; char send_buf[BUFFER_SIZE]; size_t send_len; size_t send_off; }; int set_nonblocking(int fd) { int flags fcntl(fd, F_GETFL, 0); return fcntl(fd, F_SETFL, flags | O_NONBLOCK); } int create_listen_socket(int port) { int sock socket(AF_INET, SOCK_STREAM, 0); if (sock 0) return -1; int opt 1; setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, opt, sizeof(opt)); setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, opt, sizeof(opt)); struct sockaddr_in addr; memset(addr, 0, sizeof(addr)); addr.sin_family AF_INET; addr.sin_addr.s_addr INADDR_ANY; addr.sin_port htons(port); if (bind(sock, (struct sockaddr*)addr, sizeof(addr)) 0) { close(sock); return -1; } if (listen(sock, 65535) 0) { close(sock); return -1; } set_nonblocking(sock); return sock; } int main() { int listen_fd create_listen_socket(8080); if (listen_fd 0) { perror(create_listen_socket); return 1; } int epoll_fd epoll_create1(0); if (epoll_fd 0) { perror(epoll_create1); close(listen_fd); return 1; } // 注册监听fdET模式 struct epoll_event ev; ev.events EPOLLIN | EPOLLET; ev.data.fd listen_fd; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, ev) 0) { perror(epoll_ctl listen_fd); close(listen_fd); close(epoll_fd); return 1; } struct epoll_event events[MAX_EVENTS]; struct connection *conns calloc(65535, sizeof(struct connection)); while (1) { int nfds epoll_wait(epoll_fd, events, MAX_EVENTS, -1); if (nfds 0) { if (errno EINTR) continue; perror(epoll_wait); break; } for (int i 0; i nfds; i) { if (events[i].data.fd listen_fd) { // 处理新连接 while (1) { struct sockaddr_in client_addr; socklen_t client_len sizeof(client_addr); int conn_fd accept(listen_fd, (struct sockaddr*)client_addr, client_len); if (conn_fd 0) { if (errno EAGAIN || errno EWOULDBLOCK) break; else { perror(accept); break; } } set_nonblocking(conn_fd); conns[conn_fd].fd conn_fd; // 注册新连接fdET模式 ev.events EPOLLIN | EPOLLET; ev.data.fd conn_fd; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn_fd, ev) 0) { perror(epoll_ctl conn_fd); close(conn_fd); continue; } } } else { // 处理已连接fd int fd events[i].data.fd; if (events[i].events EPOLLIN) { // 循环读取直到EAGAIN while (1) { ssize_t n read(fd, conns[fd].recv_buf conns[fd].recv_len, BUFFER_SIZE - conns[fd].recv_len); if (n 0) { conns[fd].recv_len n; // 简单HTTP解析检测\r\n\r\n if (conns[fd].recv_len 4) { char *pos memmem(conns[fd].recv_buf, conns[fd].recv_len, \r\n\r\n, 4); if (pos) { // 构造HTTP响应 const char *resp HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nHello World!; memcpy(conns[fd].send_buf, resp, strlen(resp)); conns[fd].send_len strlen(resp); conns[fd].send_off 0; // 启用写就绪监控 ev.events EPOLLIN | EPOLLOUT | EPOLLET; ev.data.fd fd; epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, ev); } } } else if (n 0) { // 对端关闭 close(fd); conns[fd].fd -1; break; } else { if (errno EAGAIN || errno EWOULDBLOCK) { break; // 读取完成 } else { perror(read); close(fd); conns[fd].fd -1; break; } } } } if (events[i].events EPOLLOUT) { // 循环发送直到EAGAIN while (conns[fd].send_off conns[fd].send_len) { ssize_t n write(fd, conns[fd].send_buf conns[fd].send_off, conns[fd].send_len - conns[fd].send_off); if (n 0) { conns[fd].send_off n; } else if (n 0) { if (errno EAGAIN || errno EWOULDBLOCK) { break; // 缓冲区满 } else { perror(write); close(fd); conns[fd].fd -1; break; } } } if (conns[fd].send_off conns[fd].send_len) { // 发送完成关闭连接 close(fd); conns[fd].fd -1; } } } } } free(conns); close(listen_fd); close(epoll_fd); return 0; }这段代码虽仅137行但已包含ET模式所有关键要素非阻塞设置、循环读写、EPOLLOUT安全启用、连接生命周期管理。编译命令gcc -o http_server http_server.c -O24.3 性能压测对比LT vs ET的真实数据我们使用wrk对同一台机器上的LT/ET版本进行压测4核8G千兆网卡并发连接数LT模式 QPSET模式 QPSQPS提升平均延迟(LT)平均延迟(ET)1,00024,50026,8009.4%41ms38ms10,00031,20048,60055.8%320ms205ms50,00022,80062,300173%2180ms805ms关键发现当连接数超过1万时LT模式因内核态遍历开销导致QPS不升反降而ET模式保持线性增长。延迟方面ET模式在高并发下优势更为明显——这正是ET设计的初衷为海量连接而生。注意压测时务必关闭TCP延迟确认echo 1 /proc/sys/net/ipv4/tcp_delack_min否则ET模式下小包延迟会因ACK延迟而失真。5. 常见问题与实战排障那些文档里不会写的血泪教训5.1 “epoll_wait()永不返回”问题的三层排查法当ET模式服务突然停止响应epoll_wait()长时间阻塞按以下顺序排查检查fd是否真的就绪用ss -i查看socket接收队列长度ss -i state established ( dport :8080 ) | head -20如果rcv_rtt为0且rcv_space极小说明对端未发数据或网络中断。验证fd是否被意外关闭用lsof -p pid检查目标fd是否存在。ET模式下若fd被其他线程close()epoll实例不会自动移除导致epoll_wait()永远等待一个已销毁的fd。确认事件注册是否正确用cat /proc/pid/fdinfo/fd检查epoll注册详情cat /proc/12345/fdinfo/3 # 查看flags字段是否含0x20000000EPOLLET标志我们曾在线上遇到一个诡异问题epoll_wait()在凌晨3点固定超时。最终发现是监控脚本每小时执行lsof导致文件描述符泄漏第3次执行后耗尽了fd资源新连接无法accept()而旧连接因ET特性不再触发读事件。5.2 ET模式下惊群效应的规避方案在多进程模型中多个worker进程共用一个epoll实例时ET模式可能引发惊群效应当一个新连接到达所有worker的epoll_wait()同时返回但只有一个能accept()成功其余进程陷入忙等。解决方案是使用SO_REUSEPORTLinux 3.9int opt 1; setsockopt(listen_fd, SOL_SOCKET, SO_REUSEPORT, opt, sizeof(opt));开启后内核在收到SYN包时直接哈希分发到某个worker避免惊群。某CDN边缘节点采用此方案后进程间锁竞争下降92%。5.3 调试工具链比strace更高效的ET模式诊断针对ET模式的特殊性我们构建了轻量级诊断工具集epoll-traceLD_PRELOAD劫持epoll_ctl()记录每次事件注册/修改的fd和eventsfd-state-dump定时dump/proc/pid/fdinfo/中所有epoll相关fd的状态buffer-peek利用process_vm_readv()在运行时读取应用层接收缓冲区内容这些工具帮助我们在某次SSL握手失败排查中发现是ET模式下read()未处理SSL_ALERT消息导致握手卡在SSL_read()阻塞。通过buffer-peek直接看到缓冲区中残留的alert帧30分钟定位根因。6. 工具选型与生态适配别再自己造轮子了6.1 libev/libuv的ET模式封装真相大多数开发者认为libuv默认使用ET模式这是严重误解。libuv在Linux上实际采用LT模式边缘事件模拟的混合策略。其uv__io_poll()函数中对每个就绪fd执行read()/write()时会检查返回值是否为EAGAIN若是则认为“事件已处理完毕”不再重复通知——这本质上是用LT内核接口模拟ET语义。这种设计牺牲了纯ET的极致性能但换取了跨平台一致性Windows IOCP无ET概念。我们在某跨平台RPC框架中测试发现libuv在Linux下的QPS比原生ET低18%但在macOS上反而高12%因为kqueue天然支持类似ET的EV_CLEAR语义。因此若服务仅部署在Linux原生ET仍是首选若需跨平台libuv的兼容性收益大于性能损失。6.2 生产环境推荐架构ET模式的最佳实践组合经过数十个项目的验证我们总结出ET模式生产部署的黄金组合组件推荐方案理由说明网络库自研轻量级ET框架2000行避免libevent/libuv的通用性开销精准控制缓冲区策略内存管理jemalloc slab分配器减少小对象分配延迟ET模式下连接生命周期短需高频分配/释放日志系统异步无锁日志如g3log防止日志I/O阻塞事件循环ET模式下任何阻塞都可能导致事件丢失监控指标epoll_wait()耗时、就绪fd数、EAGAIN次数直接反映ET模式健康度EAGAIN次数突增往往预示网络拥塞或对端发送异常某支付网关采用此架构后P99延迟稳定在8ms以内GC暂停时间从LT模式的120ms降至7ms。7. 最后的经验之谈什么时候该坚持LT尽管ET模式性能卓越但并非万能钥匙。根据我们的项目经验以下场景强烈建议回归LT模式协议解析复杂且耗时如XML/JSON深度解析单次处理可能耗时50ms以上。ET模式下这期间所有新事件都被阻塞而LT模式允许分片处理。需要精细流控LT模式下可通过控制epoll_wait()超时时间实现软性限流ET模式必须依赖应用层令牌桶实现更复杂。调试阶段LT模式的“宽容性”让新手更容易发现逻辑错误ET模式的“零容错”会掩盖底层问题。我个人在做技术选型时会用一个简单测试写一个模拟10万连接的压测脚本分别跑LT/ET观察在连接数突增50%时哪个模式的延迟抖动更小。过去三年这个测试在7个项目中帮我们避开了4次线上事故。技术没有银弹只有对场景的敬畏。
RELATED

相关推荐

epoll ET模式实战:非阻塞fd、EAGAIN循环与EPOLLONESHOT三定律

epoll ET模式实战:非阻塞fd、EAGAIN循环与EPOLLONESHOT三定律

1. 项目概述:为什么搞懂LT和ET不是“背概念”,而是写好高性能网络服务的生死线你有没有遇到过这样的情况:用epoll写了个HTTP服务器,压测时CPU跑满80%,但QPS卡在5000上不去;换了一台配置更高的机器&#xff…

📅 2026/10/15 7:23:01
基于微信小程序与Spring Boot的农业信息管理系统实战解析

基于微信小程序与Spring Boot的农业信息管理系统实战解析

基于微信小程序的农业信息管理系统,标题听着像是个典型的毕业设计题目,我最初也是冲着凑一个完整项目去的。但真正做完一轮从数据库到小程序端再到后台接口的闭环之后,我发现这类系统远不只是“增删改查”那么简单:它牵扯到多角色…

📅 2026/10/15 7:23:01
基于Flask与Vue3的二手书籍交易系统设计与实现

基于Flask与Vue3的二手书籍交易系统设计与实现

做在线书籍商城这个项目,很多人第一反应是先找现成的开源模板,抄一抄改一改就上线。但我从一开始就决定用 Flask Vue3 从零写一套二手书籍交易系统,不是因为它有多高级,而是想真正把“商品发布、搜索、下单、库存扣减”这一整条链…

📅 2026/10/15 7:23:01
MORE NEWS

更多资讯

📰

HNSW原理与调参实战:向量数据库面试要点全解析

你在准备向量数据库相关的面试,如果只背八股不搞懂原理,很容易被面试官一句“HNSW 的插入过程具体怎么走”问懵。这篇就围绕“HNSW 到底在干什么”这件事,把原理、参数、面试追问、实战调参一次说透,顺带附上我实际调试时踩过的坑…

📰

npm install太慢?用Bun把依赖安装提速10倍(附踩坑指南)

你有没有过这种时刻:项目要上线了,代码已经改好了,然后你站在终端前等npm install转圈,一等就是三四分钟。我最近在维护一个前端工程,依赖从 500 个慢慢涨到 800 多个,npm install一次冷安装跑出了 80 多秒…

📰

涡轮变速箱传动轴端面铣削工艺与夹具设计优化

1. 涡轮变速箱零件加工工艺设计概述涡轮变速箱作为机械传动系统的核心部件,其零件加工质量直接影响整机性能和使用寿命。本次工艺设计针对变速箱中关键零件——82mm尺寸规格的传动轴端面加工需求,需要综合考虑材料特性、加工精度、生产效率等多重因素。在…

📰

LoE:用专家分工攻克隐式神经表示的高频难题

说实话,我第一次看到“Implicit Neural Representations with Levels-of-Experts”这个标题时,第一反应是:INR 都已经被位置编码、哈希编码、正弦激活这些方法卷成那样了,再加一套专家路由机制,到底是锦上添花还是纯粹…

📰

2026音质好的蓝牙耳机推荐,四款热门机型选购指南

蓝牙耳机已经成为日常听歌、通勤出行和影音娱乐的重要设备。对于注重听音体验的消费者来说,挑选音质好的蓝牙耳机,不能只看发声单元尺寸或频响范围,还需要综合考虑音频编码、调音风格、人声表现、声场层次以及不同使用场景下的声音稳定性。如果还需要兼顾主动降噪、通话和续航,…

📰

视频模糊修复技术分层与实战指南

1. 这不是“超分辨率”那么简单:模糊视频修复背后的真实技术分层“模糊视频秒变高清”——这句标题在短视频平台刷屏时,我正蹲在客户现场调试一套工业视觉检测系统。客户指着屏幕上因高速运动导致拖影的流水线零件视频,说:“你们不…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬