完成端口应用:效率与冲突的烦恼(300分)

  • 主题发起人 主题发起人 Another_eYes
  • 开始时间 开始时间
A

Another_eYes

Unregistered / Unconfirmed
GUEST, unregistred user!
某个服务程序需要监听6-8个端口,并且向3-5台服务器转发数据,目标:每台机器峰值支持1000-1500连接。原先使用消息模式,开6-8个进程,每个进程监听一个端口并向特定服务器转发数据。现改为完成端口模式,一个进程同时监听6-8个端口,另有3-5个client连接连到不同服务器用于转发数据,工作线程数为cpu数×2+2(机器配双cpu并超线程成4cpu)。由于在转发数据时必须对数据进行过滤与处理,因此必须保存各个client的当前应用状态,这样在转发数据与处理时必然存在线程间的冲突。
冲突点依发生可能性排列主要有:
1.可能同时有多个线程向同一个socket发送数据,必须同步这些发送请求;
2.某一线程正在处理读到的客户端数据时,另一线程被触发并处理服务器发来的数据,而这些数据极有可能属于正在处理的这个客户端;
3.某一线程正在处理客户端或服务器发送来的数据时,另一线程被触发处理客户端断开连接请求并开始释放资源;
4.第一个客户端断开连接并成功释放资源,之后第二个客户端建立连接并且socket handle与前一个被释放的相同,而此时server发来第一个客户端某次请求的回应数据,造成第二个客户端收到的数据发生紊乱(尽管可能性极小,但实践中却发生了)。

第一次的解决方案: 每个client连接后都创建两个Critical Section,一个用于同步send,另一个用于同步read,client断开连接时释放这两个CriticalSection。实际效果:700-800连接,峰值cpu占用<5%,大部分时间0%-1%,客户端运行流畅。运行10-40分钟左右收发数据开始下降(看表现似乎某些客户端的读或写死锁)继续运行一段时间整个程序死锁(无法关闭)或崩溃(彻底消失)
第二次:每个client连接后都从全局分配的一个Critical Section列表中获取两个用于同步read和send,client断开连接时将这两个Critical Section归还全局列表供以后使用,另外单独开了一个线程同步所有的断开连接操作。实际效果:700-800连接,峰值cpu<5%,大部分时间0%-2%,客户端运行流畅。运行不超过20分钟部分监听端口或连接服务器端口的read或write死锁(收或发送字节数变为0),但程序可以正常退出。
第三次:不使用Critical Section,而是通过自己写代码实现同步锁(主要就是while+sleep(0))。实际效果:700-800连接,峰值cpu<10%,大部分时间0%-2%,客户端运行有延时,效率与消息模式相同(甚至更低一点)。运行4-5小时死锁。程序能正常退出。
第四次:完成端口工作线程数减少一半,另一半创建处理线程,每个client连接关联到某个处理线程,在client连接的周期中一直由这个线程处理这个client的所有操作,完成端口工作线程通过postthreadmessage调度相应的处理线程处理数据。实际效果:700-800连接,峰值cpu>30%,大部分时间25-30%,运行1天半没有任何问题。客户端延时严重,效率甚至不如消息模式。

各位大拿有什么好的办法解决这个问题?
 
这些问题我基本上都考虑过,很麻烦很麻烦,有一些问题我有一些自己的办法,
不过了不能便宜了那些不思考的人。[:D]
 
张无忌:哈哈,你就是厉害,说了和没说一样。
 
我的一些处理办法:
1:每个CClient一个发送队列,发到该client的数据全部丢进去,在操作队列的时候互斥一下
3:对投递的每一个重叠I/O都要进行计数,只有该CClient的未完成重叠I/O数量==0时才释放CClient

这些方法都没经过大规模的压力测试,仅供各位参考[:D]

下面两个感觉是业务逻辑的问题,说说想法:
2:业务逻辑决定了每个client的数据应该互斥访问,除非有巧妙的办法处理这个业务逻辑
4:因为Server没法修改,暂时想不出好办法,也许传CClient对象地址给GS会减少这种情况

另外还有点小想法:
完成端口可以提供高效的网络I/O,和业务逻辑夹放在一起势必对两者都有影响,感觉剥离开更好
 
各位高手,能否给在下扫扫盲;简单介绍一下完成端口是怎么一回事?
其原理如何!请教。
 
to gxcooo:
呵呵,你的意思就是我第四种解决方案呀,你所谓的队列就是我的处理线程,你所谓的互斥就是我postthreadmessage。只是效率太低,而且占用cpu太高。
 
不用postthreadmessage,直接互斥写队列试试?
感觉用消息可能会影响效率
 
至于将业务逻辑与网络I/O剥离的说法我持保留意见:
假设剥离,那么剥离出来的业务逻辑处理放哪执行?全放主线程? 那和消息模式根本没有任何区别呀,nt内核下消息模式也是通过完成端口实现的。而且根据需求,我这里业务逻辑的处理复杂度与资源消耗绝对大于网络I/O,将大部分复杂代码同步而只异步少量简单代码那还不如同步全部操作来得简单与强壮呢。
 
to gxcooo:
你感觉看来不正确,自己的互斥队列和windows的消息队列哪个高效?至少windows的消息队列的互斥是在内核进行的,而自己的互斥队列处理是在用户层。
 
业务逻辑放到ThreadPool里
其实完成端口本身就是一个ThreadPool,既然业务逻辑太耗时也可以增加线程数量[?]
还有线程时间片的问题,一般NT启动起来都会有200+个线程,正常运行时消耗时间片的恐怕也有几十个吧,即使你的完成端口的工作线程和cpu成比例,但还是有别的线程来给你抢夺时间片,可能导致的情况是增加线程反而会使整个进程得到更多的CPU资源
 
哇,有道理,有空测试一下[:)]
 
To gxcooo:
我没说清楚,我的处理线程就是一个threadpool, thread个数=cpu数+1(与完成端口工作线程数相同),这个pool主要用来处理所有的client连接,另外还有几个thread专门处理listen的端口(每个listen端口一个thread) 主要用来处理client断开连接后修改listen端口对应的clients列表和所有对clients的遍历操作(比如广播)。所有需要同步操作的地方都是通过postthreadmessage调度进相应的thread进行处理的。
遗憾的是效率的确不高(与第一种解决方案相差太大,特别是cpu占用提高了至少7-8倍但效率至少下降了20%)。
 
从被窝里爬出来,想到一个问题,
第四个解决方案的瓶颈会不会是一个“锁”上绑了太多的Client,导致经常冲突?
还有调用postthreadmessage至少要进行一次user mode <-> kernel mode 切换,不过应该可以不考虑[:)]
 
瓶颈应该是在每个Thread上绑了多个Client上(平均每个Thread绑了近100个Client), 不过没有冲突了,呵呵,因为这100个client的操作肯定是同步的。如果不绑到指定Thread上而是哪个Thread空闲就用哪个则又牵涉到和第1,2,3个解决方案一样的死锁和冲突的问题了。 尝试过处理线程增加了一倍,除了cpu占用微有上升外效率没有提高。
 
精彩!!!
 
我说的瓶颈就是那个意思
但增加线程没有效果就奇怪了,可能真正的瓶颈不在那里
可能是我对消息这个东西疑心太重了[:D]
 
我觉得转发Client不应该对Socket操作吧,转发的工作就是一个ClientSocket, 连接到需转发的服务器上,将转发用ClientID和数据发送出去就完了,这样就没有什么冲突了。是不是这样?
 
就没明白这句话:
>>由于在转发数据时必须对数据进行过滤与处理,因此必须保存各个client的当前应用状态

为什么转发的时候要保存侦听线程里面中的Client状态。
对于转发连接来说,它是基于你IOCP服务端得到的数据,它只要有这些数据和对应连接的ID,有了数据,有了ID,它就能工作了,为什么要还跟Socket状态联系起来?

ID不是用Socket句柄来表示,最好有一个内部自定的标识,这样就不会有你在上面所说第4个问题。
我的意思就是说:转发连接,只是处理数据,它用ID表示这个数据是谁的,它跟IOCP对应的SOCKET无关,IOCP收到或发送数据时,只是通过将数据和Socket的ID提交给转发线程去处理,我就没想明白为什么会需要互斥,同步什么的。
 
完成端口模式处理数据包收发的也是一个线程呀,在里面处理涉及到非线程安全的东东(主要是显示业务逻辑),不用临界区或互斥之类的方法怎么保持同步呀?
但我用了临界区之后,服务器的处理变得很慢[:(!],张无忌不能稍微透露一点? [:D]
 
后退
顶部