建立高并发服务器的一种有效方式

前一段时间,用到了串口通信和TCP通信,学习了epoll机制,实现了对多个IO通信触发的监听,业务处理和IO监听放在同一个线程处理,这种做法处理几个通信请求还行,若有成千上万个链接请求,则处理业务就会浪费大量的时间,而延缓的对新的链接的监听和响应。一种解决方法是每次获取一个请求,但这会耗费大量的创建线程的时间,而在多个线程里进行监听,则对同一个请求,会有多个线程去处理。用下面的方法可以很好的解决这种问题,基本意思是预先创建多个线程来监听服务socket,当accpet到新的请求后,添加到epoll队列,且设置epoll选项为EPOLLONESHOT,即监听到链接请求后将此socket从epoll队列中去除,由于epoll是线程安全的,保证其他监听线程再也收不到此请求, 如果想要在监听则在添加到消息队列中。从而保证了在多线程的情况下,只有单个线程处理业务。

最近在探索借助epoll做为reactor, 设计高效的服务端的方法. 


  常见的基于epoll的编程方式主要为单线程的事件循环, 用于一些非阻塞的业务逻辑开发是比较高效并且简单易懂的.
  
  但实际开发业务的时候, 往往面临着查数据库, 访问磁盘, 通过网络访问其他主机的需求, 耗时往往较长, 所以单线程的epoll并不能轻松的适用, 往往需要做一些额外的设计与构思才能得到解决.
  
  解决此类慢处理的服务端架构主要以leader-follower架构以及half-sync-half-async为主, 通过多线程的并发能力来满足同时执行多个慢处理业务逻辑.  其中, leader-follower因为较half-sync-half-async而言减少了从异步层 与 同步层间必要的内存拷贝, 并且half-sync-half-async的异步层是单线程实现, 是很容易达到单核CPU瓶颈的, 所以基本完败于leader-follower.

  公司的UB框架使用的应该是简化版的leader-follower模型, 即线程池共享同一个epoll set, 而epoll set中只注册了监听socket, 从而保证同一个时刻只由leader线程accept得到connected socket并使用带超时的阻塞read接口先后读取nshead header与nshead body, 所以UB的设计是不适合暴露给外网的, 很容易受到攻击而影响服务.
    
   正统的leader-follower应当由线程池共享同一个epoll set, 并将所有的socket注册到该Epoll set中, 然后由leader负责epoll_wait获取一个event socket并将其从epoll set中暂时移除, 之后Leader便可转换为follower并处理event socket, 并在处理完成后将event socket重新注册回epoll set以便继续监监听.  

  可以看到, leader-follower按照程序设计方法, 需要使用mutex+cond实现leader-follower职责间的转换, 在恢复event socket的时候, 因为epoll set被多线程共享的原因, 涉及到epoll_ctl的线程安全问题, 简单的例子: 假设A线程希望恢复socket1的事件注册, 但此时B线程正在epoll_wait, 那么A线程是否可以线程安全的使用epoll_ctl恢复socket1的事件.


幸运的是, epoll内置了leader-follower的选项支持, 可以避免mutex+cond的使用, 并且保障了epoll_ctl的线程安全性, 同时保证了epoll_wait是线程安全的并且会负载均衡事件到多个调用者, 不会引发惊群问题. 

  为了做到这一点, 只需要为每一个event socket设置选项: EPOLLONESHOT即可.
        EPOLLONESHOT
              Sets  the  One-Shot  behaviour  for  the  associated file descriptor. It means that after an event is pulled out with
              epoll_wait(2) the associated file descriptor is internally disabled and no other events will be reported by the epoll
              interface. The user must call epoll_ctl(2) with EPOLL_CTL_MOD to re-enable the file descriptor with a new event mask.
  
   更关键的说明需要man epoll, 其中:
       On the contrary, when used as a Level Triggered interface, epoll is by all means a faster poll(2), and can be used  wherever
       the  latter is used since it shares the same semantics. Since even with the Edge Triggered epoll multiple events can be gen-
       erated up on receival of multiple chunks of data, the caller has the option to specify the EPOLLONESHOT flag, to tell  epoll
       to  disable the associated file descriptor after the receival of an event with epoll_wait(2).  When the EPOLLONESHOT flag is
       specified, it is caller responsibility to rearm the file descriptor using epoll_ctl(2) with EPOLL_CTL_MOD.  

  有了这个机制的保障, 在实现leader-follower的时候, 只要创建多个线程, 所有线程epoll_wait同一个epoll set, 并且保证epoll set中每个注册的event socket都携带了EPOLLONESHOT选项即可.
  最初, epoll set里只有监听socket. 当多个线程调用epoll_wait时, 每个线程均会得到一批不同的event socekt. 这是因为在epoll_wait返回之前会为你设置发生event的socket的注册事件为0, 即其他线程不可能再次检测到该socket的事件, 相当于epoll_wait将poll与epoll_ctl(DEL)做了原子化, 这是浑然天成的leader-follower, 代码与单线程相比几乎一模一样.  在读写并处理完event socket后, 需要使用epoll_ctl(MOD)恢复event socket到epoll set中, 而epoll_ctl保证这是线程安全的, 并且如果socket有事件会立即通知某epoll_wait返回.

    有了上面的理论基础, 我试着开发了一个多线程基于epoll的server, 测试了长连接的qps, 只跑echo服务, 发现可以达到15万, 但出现了大面积线程D状态, 继续提高压力qps也没有提升, 程序的系统调用也仅仅是read/write/accept/close/epoll_ctl/epoll_wait, 程序是无内存分配的, 并且每个核心的idle还剩余60多.

    在猜测尝试了一些方法后, 包括单线程跑epoll, 竟然发现qps也是15万, 但单核CPU可以跑满, 怀疑了一顿网卡之后, 发现测试是单机的, 根本没走网卡, 所以不是网卡软中断过高的问题. 
   根据epoll_ctl/epoll_wait线程安全这个设计, 我猜测多个线程访问同一个epoll set是有锁的, 从而导致了程序瓶颈, 于是我创建了多个epoll set, 每个epoll set作为一个组, 组内有若干worker线程共享该epoll set, 从而减小了锁的面积.
   但这样设计, 需要考虑到监听socket怎么处理, 因为每个组都有自己的epoll set, 不能将监听socket注册给多个epoll set, 那样会有惊群问题. 于是, 我独立创建了一个监听线程, 并创建了一个独立的epoll set跑事件循环来专门做accept, 而因为epoll_ctl的线程安全特性, 所以监听线程采用了round robin轮转的向每个组的epoll set进行conntected socket的事件注册.
   
   通过以上的优化, 惊喜的发现服务端的qps达到了近40万, 而cpu idle也终于从60榨到了不到10%.
   唯一的缺点就是round robin的分发策略可能因为客户端主动断开行为导致各个组内的负载不均, 希望找到一种无锁并且代价低的解决方案, (PS: 多线程server, 对业务数据并发访问的同步问题是个老问题)暂时就到这里了.
内容概要:本文介绍了一个基于MATLAB实现的多目标粒子群优化算法(MOPSO)在无人机三维路径规划中的应用。该代码实现了完整的路径规划流程,包括模拟数据生成、障碍物随机生成、MOPSO优化求解、帕累托前沿分析、最优路径选择、代理模型训练以及丰富的可视化功能。系统支持用户通过GUI界面设置参数,如粒子数量、迭代次数、路径节点数等,并能一键运行完成路径规划与评估。代码采用模块化设计,包含详细的注释,同时提供了简洁版本,便于理解和二次开发。此外,系统还引入了代理模型(surrogate model)进行性能预测,并通过多种图表对结果进行全面评估。 适合人群:具备一定MATLAB编程基础的科研人员、自动化/控制/航空航天等相关专业的研究生或高年级本科生,以及从事无人机路径规划、智能优化算法研究的工程技术人员。 使用场景及目标:①用于教学演示多目标优化算法(如MOPSO)的基本原理与实现方法;②为无人机三维路径规划提供可复现的仿真平台;③支持对不同参数配置下的路径长度、飞行时间、能耗与安全风险之间的权衡进行分析;④可用于进一步扩展研究,如融合动态环境、多无人机协同等场景。 其他说明:该资源包含两份代码(详细注释版与简洁版),运行结果可通过图形界面直观展示,包括Pareto前沿、收敛曲线、风险热图、路径雷达图等,有助于深入理解优化过程与结果特性。建议使用者结合实际需求调整参数,并利用提供的模型导出功能将最优路径应用于真实系统。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值