TCP网络服务器设计

最近设计了一个网络服务器程序,对于4C8G的机器配置,TPS可以达到5W。业务处理逻辑是简单的字符串处理。服务器接收请求后对下游进行类似广播的发送。在此分享一下设计方式,如果有改进思路欢迎大家交流分享。

程序运行在CentOS7.9操作系统上,GCC使用4.8.5版本,网络是千兆网。

会话状态机

会话存在已连接/正连接/断开/等待重连,这4种状态。如果监听端口有连接请求,创建新会话,状态为连接状态,但是目前会话还不具备发送能力,需要等到套接字上有EPOLLOUT信号时才表示会话已经可以发送了,否则如果没准备好发送的时候发送出现报错,程序会认为会话已经出现了异常情况,然后直接将会话关闭。

服务器主动连接其他服务的时候也会创建一个会话,这个会话具备重连功能。创建的新会话开始也是连接状态。

会话发送接收错误,挂断等很多情况会导致会话进入断开或者等待重连状态。有一个专门的线程会定时检查有没有需要重连的会话,如果有的话会执行重连,在对端没有应答的情况下进入正连接的状态,直到有数据接收到。重连对端直接回应则表示重连成功,进入已连接状态。

 网络事务处理线程

这个线程的作用很简单,有数据来就用event中的会话指针调用读;有写的调用写;有挂断的置挂断标志然后清理会话组。(这里在调用会话读、写的时候会对会话组加读锁;调用清理会话组时会加写锁) 

接收流程

展示如下:

epollin进来之后会进行会话组的读锁锁定,这样会话不可能在上锁期间析构掉,保证了会话指针(包括其内部的接收缓存)的安全。在会话处理内部对消息缓冲区进行了尝试上锁。如果上锁失败则返回(这样保证了如果一个会话的数据特别多,其他网络接收线程也可以及时处理其他会话进来的数据)。

 发送流程

如下:

发送线程有两种模式:直接发送、缓冲发送。直接发送模式就是直接将需要发送的数据发送处理,缓冲发送是将数据写到会话的缓冲区,然后进行发送。直接发送的好处就是可以不用复制数据,这样可以减少CPU和内存的占用,但是坏处就是由于没有对于每个会话进行单独的缓冲,因此需要遍历每个会话,对数据进行依次发送。此时,如果有一个会话的接收速度特别慢,就会导致整体的发送效率降低。缓冲发送模式则不存在这个问题,一个会话的接收速度慢,但是它有自己的缓冲区,所以可以直接把数据复制到它的缓冲区中,然后继续下一个会话的发送。

缓冲模式使用双缓冲区,写入和发送缓冲区两者操作不冲突。

系统使用优先直接发送,如果遇到EAGAIN时候直接转到缓冲区发送的方式。这样就可以保证尽量不复制缓冲区,同时在发送遇到阻塞时候也能不影响其他会话。

自检流程

会话组自检

 在主线程启动会话组自检工作。会话组自检时对于需要重连的会话进行重连,然后执行各个会话的自检,再执行发送心跳包和删除已关闭会话的工作。

会话自检

会话自检主要是预防一个会话已经断开了,但是网络事务处理线程没有能够处理这个挂断的事务从而导致死掉的会话还一直存在的问题。

其他设计

封装内存管理类,用于管理内存。内存管理类每次分配固定数值倍数的内存,在析构时自动对管理的内存进行析构。封装读、写操作,封装读整理操作(从内存头部读取一定大小的内存以后将后面的内存拷贝到前面来,这样就不用析构这部分内存,可以下次有数据写入继续使用)。

 效果测试

在5W的TPS下可以接收8个下游系统,上下游网络流量已经几乎达到带宽极值,CPU占用率67%,内存在运行48小时后会达到78M。

但是还是存在问题。1)单独使用缓存发送模式的时候有一个问题,就是CPU占用率特别高,每多一个会话则CPU的占用率升值需要升高10%-20%(这里似乎没有CAS导致的CPU占用,同时,使用的锁也全都是普通锁,并没有自旋锁);2)下游接收速度很慢的时候CPU占用率会提高到70%以上。

设计注意事项

以前一直没有对系统涉及的注意事项进行总结,借此总结一下。高并发的Linux服务器程序设计应该注意以下一些方面:

1)最基础的,当然是多线程对于共享资源一致性的保护。可以使用内存屏障、锁、条件变量、原子操作等多线程同步机制;

2)尽量减少系统调用。系统调用要将CPU模式从用户模式切换成系统模式,然后再切换回来。这是比较消耗时间的,因此尽量避免系统调用。

3)做好缓存。不管是对于服务器机器内的系统调用还是对其他机器的远程调用,如果有必要,一定要做好缓存,特别是哪些可能会重复访问的数据。

4)在频繁调用的函数中尽量不开辟释放内存。这一点要注意,最好不要在频繁调用的函数中使用STL数据类型的局部变量,因为STL的很多容器会隐式的在堆上进行内存分配。内存的分配是会消耗时间的,另外,小块内存的分配会使得系统产生内存碎片。在长时间运行以后,操作系统的内存碎片变多以后操作系统会对内存进行合并,这会降低系统的整体性能。

5)注意IO的系统缓存和用户缓存。对于重要的数据,要知道内核中对于IO的处理方式,对于普通文件,在操作系统层面会有一个文件缓冲区,用户程序对文件进行写入以后都会进入系统缓冲区,如果此时出现意外情况,会导致重要数据的丢失。因此,对于重要数据需要写文件的可以使用同步模式进行文件操作。

6)注意快速缓存命中率和线程上下文切换。快速缓存是从内存中取出来的一部分数据,用来加速CPU访问内存的速度。怎么做到增加缓存命中率呢?我觉的,应该将存有相关信息的内存放在一起。快速缓存从内存读取缓冲数据的时候会预存一部分附近的数据,根据这个原理,我们就能增加快速缓存的命中率。线程上下文切换是线程在时间片用完或者等待IO时候会切出,然后切换另外一个线程来执行。线程的上下文切换比较耗时,为了避免频繁切换上下文,线程的数量要注意,不能太多,太多就会频繁的进行上下文切换。通过vmstat命令可以看到系统的上下文切换频率,可以据此对应用进程的线程数目进行运行前调优。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

腾昵猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值