服务器多线程方案的选择

Posted on 2011-11-20 22:35 冷锋 阅读(2927) 评论(11)  编辑 收藏 引用 所属分类: linux
最近想写个多线程模型的服务器,但是一直纠结要选哪种方式,参考了memcached,但是觉得不完美.
备选方案,当然这些都是NIO
1.一个IO线程,专门处理连接读写数据,一个逻辑线程,专门处理数据。
2.一个IO线程,一个线程池,可任意配置线程池的数量。
先考虑1,假设连接层用epoll的ET模式实现,当IO线程发生可读事件时,必须把接收缓冲区的数据全部收完,一直read直到发生EAGAIN错误,否则就必须自己在上层维护一个可读队列。
方法a:每收到一个协议包,就转发给逻辑处理.处理完再接着收取剩下的包。但是TCP是无边界的,有可能收到0.5个或者2.5个包,这时你得为每个连接准备个buf,并且每收到个包都要跟逻辑线程同步加锁一次,还有个很大的弊端就是,连接层已经跟逻辑协议相关了,这似乎不是很好。
方法b:IO线程把所有数据都收完再通知逻辑线程。当然这样也无法避免收到半个协议包的情况,所以我是想维护一个recv到的数据队列,IO线程把每次收到的包都丢到这个队列。
a,b2种方法都面临着send的麻烦,现在send也有以下方案.
方法a.每次需要write的时候就直接send发不完的就加到发送队列中去。在发送前必须要先判断一下队列是不是空,如果不为空就必须先处理队列,剩余数据待可读事件发生时再处理,   于是脑子中又出现一大堆锁了。有2个线程可能对socket进行写操作。
方法b.每次需要send的时候都直接把数据丢到发送队列去,等到可读事件到来时再尽量把队列都发送完。但是响应可能没那么迅速了。
这两种方法都必须为每个数据包增加一个标识TCP连接的字段,因为socket fd是可以重复使用的,比如用户A连接分配的socket是100,逻辑线程正在为A处理数据,但是用户A断开连接了,同时立即有另外个用户连接进来并且分配的socket是100,这时悲剧就发生了。
再考虑下线程池的2种情况.
a.主线程负责IO,包括处理连接,读写,把读到的数据加入recv队列,子线程把需要写的加入send队列.
这个方案有个很大的缺陷:无法保证先到的请求先返回,这是很致命的,只能通过客户端来确保收到前一个请求的结果以后再发送下一个请求。
b.主线程只负责监听连接,收到连接到来事件后,通知线程池去accept连接,因此每个连接的数据都会由同一个线程处理,也就保证了顺序,但是这样的话子线程就必须承担起IO的任务了,这样好像就有些分工不清了,这个是memcached中用的方案.
PS:用epoll的ET模式需要一直recv,这样有可能是使得活跃的连接占用了全部带宽,因此需要在上层对连接进行限速,因此也就需要维护可读事件了。
纠结好几天了,第一次写多线程服务器,一直为选方案纠结啊,我本人倾向于线程池,毕竟可以利用多核的优势啊。不知道大家实际上用的都是什么方案呢,非常想知道,请指教。
再PS:做了个小测试,nginx+memcached,下载一个700多K的文件,开100个线程,每个线程下载100次,全部命中跟全部不命中的情况几乎没有差别,这是为什么呢?难道linux系统本身就已经有cached了吗?

Feedback

# re: 服务器多线程方案的选择  回复  更多评论   

2011-11-21 07:08 by 一念天堂
单线程的非阻塞IO应该还是最高效的,因为一般来说瓶颈是 IO,不管开多少个线程,IO 还是一套。阻塞多线程只是把切换从用户代码换到了内核,编程会容易,提高效率就不一定了

# re: 服务器多线程方案的选择  回复  更多评论   

2011-11-21 08:48 by 冷锋
我说的是非阻塞的多线程啊,单线程的话如果要操作数据库的话怎么办?@一念天堂

# re: 服务器多线程方案的选择  回复  更多评论   

2011-11-21 08:51 by 一念天堂
其实我们说的原则可能差不多,我说的单线程,其实是一个 epoll 一个线程,或者更准确的说,一套 IO 一个线程

# re: 服务器多线程方案的选择  回复  更多评论   

2011-11-21 09:07 by yanxinmeng
你以上说的几种方案都很对。 但是没有一种方案可以适用很多情况。
memcache也有适用的场景。

# re: 服务器多线程方案的选择  回复  更多评论   

2011-11-21 09:57 by 冷锋
假如需要异步访问数据库的话怎么来保证顺序呢,由客户端来保证吗?一定要前一个请求返回了才发送下一个请求?假如是写游戏服务器的话呢?@yanxinmeng

# re: 服务器多线程方案的选择  回复  更多评论   

2011-11-21 13:38 by mentalmap
不是一定非加锁不可的,如果是单一的读和写线程的话

# re: 服务器多线程方案的选择  回复  更多评论   

2011-11-22 09:45 by zuhd
正如你说的多线程对于管理网络包的顺序很麻烦,IO线程+逻辑线程做起来比较清晰,估计大家都倾向于此吧,至于粘包和切包这个就是对IO数据流的重新整理分类,自己肯定要做个包长度吧。
a.在while中检查是否有完整包,有完整包,就调用下层session的逻辑,也能清晰分层做到的。
b.即使100的前一个用户断开,新的用户来了,也不会有麻烦,底层的网络库根本不会关心这个socket是谁的,照常处理IO就是,只是逻辑层的session要自己处理了
c.Send我是倾向于第一种实时的方案,也没有一堆的锁,一个连接一个而已,总算很清晰是吧,也值了
总结:1个IO+1个逻辑+1个session池

# re: 服务器多线程方案的选择[未登录]  回复  更多评论   

2011-11-22 21:52 by 冷锋
b.新的用户来了,还是用100,就会把本该发给用户A的发给用户B了,不过这个可以自己维护一个session ID搞定,c实时send的话如果发不完得有个缓冲区延迟到下次再发,由于主线程跟逻辑线程都在操作同一个fd,所以要加锁,除非你把fd分给逻辑线程单独维护,负责它的读写,我已经按照1a实现服务器的逻辑层了,你说的session是线程池还是全局的一个表?我这边事维护了一个全局的connection的表@zuhd

# re: 服务器多线程方案的选择  回复  更多评论   

2011-11-23 09:13 by zuhd
send是要加锁的,session是逻辑层的session,从网络库继承出去的,能被网络回调的,至于是哪种设计模式我也记不住那名

# re: 服务器多线程方案的选择  回复  更多评论   

2011-11-23 12:59 by Todd
个人觉得要看你的应用是什么类型。 如果是像Web服务器这样的,可以为每个连接分配一个线程, 从IO到逻辑都由这一个线程来处理, 线程之间没什么好同步的, 因为两个客户端之间没什么交互。而且Web服务器服务每一个请求的时间不长(长了客户端不等你)。

如果是游戏服务器这种, 那IO和逻辑肯定要隔离的。 逻辑层可以用一个线程(如果并发用户量很大必须是一个线程了, 开几K个线程不现实,锁来锁去也浪费), IO层我个人理解上还存在障碍, 不确定是多线程同步IO+线程池好还是单线程异步IO好, 但肯定不能单线程同步IO, 否则 客户端之间要相互阻塞了。 在IO层与逻辑层之间共享一个收发缓冲区,肯定要有锁了,但只是存取缓冲区,代价不高, 单核心时甚至不存在什么代价吧? 再有我觉得高并发服务器难以维持长连接,应该是客户端定时请求吧(IO层需限定请求频次吧)

# re: 服务器多线程方案的选择  回复  更多评论   

2011-11-25 22:01 by 冷锋
如果玩家同时发两个消息给服务端,前一个是需要操作数据库的,假如应用服务器跟数据库服务器之间是用异步回调方式通信的,那么在应用服务器要怎么保证返回给客户端的是顺序的呢?@Todd

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


posts - 15, comments - 18, trackbacks - 0, articles - 0

Copyright © 冷锋