libevent2 bufferevent多线程问题

时间:2022-12-01 00:14:38
准备写用libeveent2写一个游戏服务器,之中使用到了bufferevent,我的想法是这样的,主线程管理接受新连接,然后一个线程负责读取数据,一个线程负责写数据,一个线程负责处理逻辑,那么怎样实现bufferevent在不同线程上的读写?

10 个解决方案

#1


网关可以多线程, 因为不涉及业务, 也不涉及基于共享内存的会话间通信。

逻辑一般是单线程的,因为涉及太多业务以及插件处理。

#2


另外,多线程架构不是说读写分离,而是I/O多线程并发。

你可以学习一下: 半同步半异步, 领导者追随者 这两种多线程模式。

#3


多谢了,研究了下memcached的多线程模式,准备在整理整理

#4


引用 2 楼 qq120848369 的回复:
另外,多线程架构不是说读写分离,而是I/O多线程并发。

你可以学习一下: 半同步半异步, 领导者追随者 这两种多线程模式。


我想在问下,现在准备做个多线程的网关服务器(基于半同步半异步)模型的,比如其中一个工作线程接受到了客户端发来的休息,做个简单的包解析判断需要发向哪个逻辑服务器,然后发送,这一套流程应该是在这个工作线程中完成的。问题来了,连接到逻辑服务器的这个socket是不是应该将所有的写任务排队发?接到数据后怎么判断回给哪个客户端?
新手问题~~

#5


引用 4 楼 kcl_70 的回复:
Quote: 引用 2 楼 qq120848369 的回复:

另外,多线程架构不是说读写分离,而是I/O多线程并发。

你可以学习一下: 半同步半异步, 领导者追随者 这两种多线程模式。


我想在问下,现在准备做个多线程的网关服务器(基于半同步半异步)模型的,比如其中一个工作线程接受到了客户端发来的休息,做个简单的包解析判断需要发向哪个逻辑服务器,然后发送,这一套流程应该是在这个工作线程中完成的。问题来了,连接到逻辑服务器的这个socket是不是应该将所有的写任务排队发?接到数据后怎么判断回给哪个客户端?
新手问题~~


长连接服务一般是ID关联请求与应答,所以包序不需要担心,客户端根据应答里携带的ID可以找到自己发出的对应请求,服务端要做的就是始终保留这个ID。

网关在认证了每一个登录连接后应该给每个连接生成一个唯一的session id,使用session id用于网关与逻辑进程间的会话追踪,这样逻辑进程返回网关进程,网关可以根据session id找到对应连接,将应答送回给客户端,而客户端可以根据ID找到当时发出的请求。


上面这些都是一个系统的基本要素,剩下到实现细节,半同步半异步是一个线程负责跑epoll,但它不做I/O,它将(fd, session id, event)通过队列交给业务线程池(同步层),由同步层根据event进行I/O以及业务处理,并在处理完成后将(fd, session id, register event)返回给异步层,以便异步层对事件进行重注册。 异步层和同步层是绝对不会并发处理同一个fd的,即异步层将(fd,sess id, event)交给同步层后,自己就取消了fd的epoll注册,直到同步层将结果返回给异步层。 这一点需要epoll的EPOLLONESHOT选项支持,以便异步层完全隔离掉事件触发,直到同步层返回。

因为同步层是多线程I/O, 所以任何一个线程里的某个客户端的包要转发给逻辑进程,涉及到了多线程并发访问逻辑进程网络连接问题,而半同步半异步环境下是不容易从A连接的事件处理回调中修改B连接的注册事件的,并且要处理基本变成了全局锁,并发I/O意义就不在了。

结论:无论半同步半异步,领导者追随者,都面临着一个问题:就是你不知道某个客户端连接在哪个线程里正在被处理,导致逻辑进程回包的时候,面临着修改客户端连接事件的窘境。

建议做法:一个线程一个event loop,维护全局加锁session id -> thread映射关系,独立的监听线程,独立的逻辑连接线程(因为对于每一个回来的包都要加锁查session id->thread映射),独立的监听线程(accept连接,修改全局session id->thread映射关系,round-robin派发到一个thread)。

这样就非常清晰了:
1,监听线程独立,添加映射关系,派发连接到某工作线程。
2,工作线程跑event loop,将派发来的连接加入检测集合,整个处理无锁,除非某个连接断开,那么只要加锁删掉映射关系即可。 客户端发来的包,发给工作线程。 逻辑线程发来的包,看本线程内是否有session id对应的连接,有则将包发给客户端。
3,逻辑线程负责与逻辑进程通信,负责将工作线程发来的包发给逻辑进程,负责将逻辑进程发来的包加锁查映射关系后,发给对应的线程。

#6


引用 5 楼 qq120848369 的回复:
Quote: 引用 4 楼 kcl_70 的回复:

Quote: 引用 2 楼 qq120848369 的回复:

另外,多线程架构不是说读写分离,而是I/O多线程并发。

你可以学习一下: 半同步半异步, 领导者追随者 这两种多线程模式。


我想在问下,现在准备做个多线程的网关服务器(基于半同步半异步)模型的,比如其中一个工作线程接受到了客户端发来的休息,做个简单的包解析判断需要发向哪个逻辑服务器,然后发送,这一套流程应该是在这个工作线程中完成的。问题来了,连接到逻辑服务器的这个socket是不是应该将所有的写任务排队发?接到数据后怎么判断回给哪个客户端?
新手问题~~


长连接服务一般是ID关联请求与应答,所以包序不需要担心,客户端根据应答里携带的ID可以找到自己发出的对应请求,服务端要做的就是始终保留这个ID。

网关在认证了每一个登录连接后应该给每个连接生成一个唯一的session id,使用session id用于网关与逻辑进程间的会话追踪,这样逻辑进程返回网关进程,网关可以根据session id找到对应连接,将应答送回给客户端,而客户端可以根据ID找到当时发出的请求。


上面这些都是一个系统的基本要素,剩下到实现细节,半同步半异步是一个线程负责跑epoll,但它不做I/O,它将(fd, session id, event)通过队列交给业务线程池(同步层),由同步层根据event进行I/O以及业务处理,并在处理完成后将(fd, session id, register event)返回给异步层,以便异步层对事件进行重注册。 异步层和同步层是绝对不会并发处理同一个fd的,即异步层将(fd,sess id, event)交给同步层后,自己就取消了fd的epoll注册,直到同步层将结果返回给异步层。 这一点需要epoll的EPOLLONESHOT选项支持,以便异步层完全隔离掉事件触发,直到同步层返回。

因为同步层是多线程I/O, 所以任何一个线程里的某个客户端的包要转发给逻辑进程,涉及到了多线程并发访问逻辑进程网络连接问题,而半同步半异步环境下是不容易从A连接的事件处理回调中修改B连接的注册事件的,并且要处理基本变成了全局锁,并发I/O意义就不在了。

结论:无论半同步半异步,领导者追随者,都面临着一个问题:就是你不知道某个客户端连接在哪个线程里正在被处理,导致逻辑进程回包的时候,面临着修改客户端连接事件的窘境。

建议做法:一个线程一个event loop,维护全局加锁session id -> thread映射关系,独立的监听线程,独立的逻辑连接线程(因为对于每一个回来的包都要加锁查session id->thread映射),独立的监听线程(accept连接,修改全局session id->thread映射关系,round-robin派发到一个thread)。

这样就非常清晰了:
1,监听线程独立,添加映射关系,派发连接到某工作线程。
2,工作线程跑event loop,将派发来的连接加入检测集合,整个处理无锁,除非某个连接断开,那么只要加锁删掉映射关系即可。 客户端发来的包,发给工作线程。 逻辑线程发来的包,看本线程内是否有session id对应的连接,有则将包发给客户端。
3,逻辑线程负责与逻辑进程通信,负责将工作线程发来的包发给逻辑进程,负责将逻辑进程发来的包加锁查映射关系后,发给对应的线程。



打错字了:
客户端发来的包,发给 逻辑线程。 逻辑线程发来的包,看本线程内是否有session id对应的连接,有则将包发给客户端。

#7


一个程序或者一个架构,想要达到分布式可扩展,或者高性能的目标,一定要避免依赖共享内存,而要依赖消息通信。

所以映射关系是session id -> thread id,至于thread内是否有session id->connection,这是thread自己维护的映射关系,所以逻辑连接线程可以查一下session id->thread id,然后把包交给id标识的thread,由thread自己根据自己的session id->connection查找物理连接,整个过程都是基于消息而不是说基于共享内存,所以锁是非常小面积的。

#8


引用 7 楼 qq120848369 的回复:
一个程序或者一个架构,想要达到分布式可扩展,或者高性能的目标,一定要避免依赖共享内存,而要依赖消息通信。

所以映射关系是session id -> thread id,至于thread内是否有session id->connection,这是thread自己维护的映射关系,所以逻辑连接线程可以查一下session id->thread id,然后把包交给id标识的thread,由thread自己根据自己的session id->connection查找物理连接,整个过程都是基于消息而不是说基于共享内存,所以锁是非常小面积的。


非常感谢您的,回答学到了许多东西,这几天自己也在尝试,先谢了,我再好好研究研究,可以的话加个qq吧164406769 也不知道版主是不是愿意所以就把自己的qq留上啦

#9


引用 7 楼 qq120848369 的回复:
一个程序或者一个架构,想要达到分布式可扩展,或者高性能的目标,一定要避免依赖共享内存,而要依赖消息通信。

所以映射关系是session id -> thread id,至于thread内是否有session id->connection,这是thread自己维护的映射关系,所以逻辑连接线程可以查一下session id->thread id,然后把包交给id标识的thread,由thread自己根据自己的session id->connection查找物理连接,整个过程都是基于消息而不是说基于共享内存,所以锁是非常小面积的。


我现在准备用libevent2的bufferevent来做处理,由于libevent2里支持多线程了,所以在逻辑进程发送消息到网关服务器的逻辑线程时,我找到该消息对应的客户端的bufferevent然后直接向这个buffereent的evbuffer中写数据,数据应该就会通过bufferevent所在的工作线程(即客户端链接的线程)发送回客户端,不知道这个想法是不是可行,准备测试下

#10


其实还有个地方不是太明白,就是我有多个逻辑进程,那么我网关服务器是对应每个逻辑进程都开个逻辑线程,还是开一个逻辑线程与其他逻辑进程的通信都在这个线程上完成,如果只有一个逻辑线程,会不会大大的降低效率,因为其他工作线程需要将数据排队放入该逻辑线程中等待逻辑线程一一处理

#1


网关可以多线程, 因为不涉及业务, 也不涉及基于共享内存的会话间通信。

逻辑一般是单线程的,因为涉及太多业务以及插件处理。

#2


另外,多线程架构不是说读写分离,而是I/O多线程并发。

你可以学习一下: 半同步半异步, 领导者追随者 这两种多线程模式。

#3


多谢了,研究了下memcached的多线程模式,准备在整理整理

#4


引用 2 楼 qq120848369 的回复:
另外,多线程架构不是说读写分离,而是I/O多线程并发。

你可以学习一下: 半同步半异步, 领导者追随者 这两种多线程模式。


我想在问下,现在准备做个多线程的网关服务器(基于半同步半异步)模型的,比如其中一个工作线程接受到了客户端发来的休息,做个简单的包解析判断需要发向哪个逻辑服务器,然后发送,这一套流程应该是在这个工作线程中完成的。问题来了,连接到逻辑服务器的这个socket是不是应该将所有的写任务排队发?接到数据后怎么判断回给哪个客户端?
新手问题~~

#5


引用 4 楼 kcl_70 的回复:
Quote: 引用 2 楼 qq120848369 的回复:

另外,多线程架构不是说读写分离,而是I/O多线程并发。

你可以学习一下: 半同步半异步, 领导者追随者 这两种多线程模式。


我想在问下,现在准备做个多线程的网关服务器(基于半同步半异步)模型的,比如其中一个工作线程接受到了客户端发来的休息,做个简单的包解析判断需要发向哪个逻辑服务器,然后发送,这一套流程应该是在这个工作线程中完成的。问题来了,连接到逻辑服务器的这个socket是不是应该将所有的写任务排队发?接到数据后怎么判断回给哪个客户端?
新手问题~~


长连接服务一般是ID关联请求与应答,所以包序不需要担心,客户端根据应答里携带的ID可以找到自己发出的对应请求,服务端要做的就是始终保留这个ID。

网关在认证了每一个登录连接后应该给每个连接生成一个唯一的session id,使用session id用于网关与逻辑进程间的会话追踪,这样逻辑进程返回网关进程,网关可以根据session id找到对应连接,将应答送回给客户端,而客户端可以根据ID找到当时发出的请求。


上面这些都是一个系统的基本要素,剩下到实现细节,半同步半异步是一个线程负责跑epoll,但它不做I/O,它将(fd, session id, event)通过队列交给业务线程池(同步层),由同步层根据event进行I/O以及业务处理,并在处理完成后将(fd, session id, register event)返回给异步层,以便异步层对事件进行重注册。 异步层和同步层是绝对不会并发处理同一个fd的,即异步层将(fd,sess id, event)交给同步层后,自己就取消了fd的epoll注册,直到同步层将结果返回给异步层。 这一点需要epoll的EPOLLONESHOT选项支持,以便异步层完全隔离掉事件触发,直到同步层返回。

因为同步层是多线程I/O, 所以任何一个线程里的某个客户端的包要转发给逻辑进程,涉及到了多线程并发访问逻辑进程网络连接问题,而半同步半异步环境下是不容易从A连接的事件处理回调中修改B连接的注册事件的,并且要处理基本变成了全局锁,并发I/O意义就不在了。

结论:无论半同步半异步,领导者追随者,都面临着一个问题:就是你不知道某个客户端连接在哪个线程里正在被处理,导致逻辑进程回包的时候,面临着修改客户端连接事件的窘境。

建议做法:一个线程一个event loop,维护全局加锁session id -> thread映射关系,独立的监听线程,独立的逻辑连接线程(因为对于每一个回来的包都要加锁查session id->thread映射),独立的监听线程(accept连接,修改全局session id->thread映射关系,round-robin派发到一个thread)。

这样就非常清晰了:
1,监听线程独立,添加映射关系,派发连接到某工作线程。
2,工作线程跑event loop,将派发来的连接加入检测集合,整个处理无锁,除非某个连接断开,那么只要加锁删掉映射关系即可。 客户端发来的包,发给工作线程。 逻辑线程发来的包,看本线程内是否有session id对应的连接,有则将包发给客户端。
3,逻辑线程负责与逻辑进程通信,负责将工作线程发来的包发给逻辑进程,负责将逻辑进程发来的包加锁查映射关系后,发给对应的线程。

#6


引用 5 楼 qq120848369 的回复:
Quote: 引用 4 楼 kcl_70 的回复:

Quote: 引用 2 楼 qq120848369 的回复:

另外,多线程架构不是说读写分离,而是I/O多线程并发。

你可以学习一下: 半同步半异步, 领导者追随者 这两种多线程模式。


我想在问下,现在准备做个多线程的网关服务器(基于半同步半异步)模型的,比如其中一个工作线程接受到了客户端发来的休息,做个简单的包解析判断需要发向哪个逻辑服务器,然后发送,这一套流程应该是在这个工作线程中完成的。问题来了,连接到逻辑服务器的这个socket是不是应该将所有的写任务排队发?接到数据后怎么判断回给哪个客户端?
新手问题~~


长连接服务一般是ID关联请求与应答,所以包序不需要担心,客户端根据应答里携带的ID可以找到自己发出的对应请求,服务端要做的就是始终保留这个ID。

网关在认证了每一个登录连接后应该给每个连接生成一个唯一的session id,使用session id用于网关与逻辑进程间的会话追踪,这样逻辑进程返回网关进程,网关可以根据session id找到对应连接,将应答送回给客户端,而客户端可以根据ID找到当时发出的请求。


上面这些都是一个系统的基本要素,剩下到实现细节,半同步半异步是一个线程负责跑epoll,但它不做I/O,它将(fd, session id, event)通过队列交给业务线程池(同步层),由同步层根据event进行I/O以及业务处理,并在处理完成后将(fd, session id, register event)返回给异步层,以便异步层对事件进行重注册。 异步层和同步层是绝对不会并发处理同一个fd的,即异步层将(fd,sess id, event)交给同步层后,自己就取消了fd的epoll注册,直到同步层将结果返回给异步层。 这一点需要epoll的EPOLLONESHOT选项支持,以便异步层完全隔离掉事件触发,直到同步层返回。

因为同步层是多线程I/O, 所以任何一个线程里的某个客户端的包要转发给逻辑进程,涉及到了多线程并发访问逻辑进程网络连接问题,而半同步半异步环境下是不容易从A连接的事件处理回调中修改B连接的注册事件的,并且要处理基本变成了全局锁,并发I/O意义就不在了。

结论:无论半同步半异步,领导者追随者,都面临着一个问题:就是你不知道某个客户端连接在哪个线程里正在被处理,导致逻辑进程回包的时候,面临着修改客户端连接事件的窘境。

建议做法:一个线程一个event loop,维护全局加锁session id -> thread映射关系,独立的监听线程,独立的逻辑连接线程(因为对于每一个回来的包都要加锁查session id->thread映射),独立的监听线程(accept连接,修改全局session id->thread映射关系,round-robin派发到一个thread)。

这样就非常清晰了:
1,监听线程独立,添加映射关系,派发连接到某工作线程。
2,工作线程跑event loop,将派发来的连接加入检测集合,整个处理无锁,除非某个连接断开,那么只要加锁删掉映射关系即可。 客户端发来的包,发给工作线程。 逻辑线程发来的包,看本线程内是否有session id对应的连接,有则将包发给客户端。
3,逻辑线程负责与逻辑进程通信,负责将工作线程发来的包发给逻辑进程,负责将逻辑进程发来的包加锁查映射关系后,发给对应的线程。



打错字了:
客户端发来的包,发给 逻辑线程。 逻辑线程发来的包,看本线程内是否有session id对应的连接,有则将包发给客户端。

#7


一个程序或者一个架构,想要达到分布式可扩展,或者高性能的目标,一定要避免依赖共享内存,而要依赖消息通信。

所以映射关系是session id -> thread id,至于thread内是否有session id->connection,这是thread自己维护的映射关系,所以逻辑连接线程可以查一下session id->thread id,然后把包交给id标识的thread,由thread自己根据自己的session id->connection查找物理连接,整个过程都是基于消息而不是说基于共享内存,所以锁是非常小面积的。

#8


引用 7 楼 qq120848369 的回复:
一个程序或者一个架构,想要达到分布式可扩展,或者高性能的目标,一定要避免依赖共享内存,而要依赖消息通信。

所以映射关系是session id -> thread id,至于thread内是否有session id->connection,这是thread自己维护的映射关系,所以逻辑连接线程可以查一下session id->thread id,然后把包交给id标识的thread,由thread自己根据自己的session id->connection查找物理连接,整个过程都是基于消息而不是说基于共享内存,所以锁是非常小面积的。


非常感谢您的,回答学到了许多东西,这几天自己也在尝试,先谢了,我再好好研究研究,可以的话加个qq吧164406769 也不知道版主是不是愿意所以就把自己的qq留上啦

#9


引用 7 楼 qq120848369 的回复:
一个程序或者一个架构,想要达到分布式可扩展,或者高性能的目标,一定要避免依赖共享内存,而要依赖消息通信。

所以映射关系是session id -> thread id,至于thread内是否有session id->connection,这是thread自己维护的映射关系,所以逻辑连接线程可以查一下session id->thread id,然后把包交给id标识的thread,由thread自己根据自己的session id->connection查找物理连接,整个过程都是基于消息而不是说基于共享内存,所以锁是非常小面积的。


我现在准备用libevent2的bufferevent来做处理,由于libevent2里支持多线程了,所以在逻辑进程发送消息到网关服务器的逻辑线程时,我找到该消息对应的客户端的bufferevent然后直接向这个buffereent的evbuffer中写数据,数据应该就会通过bufferevent所在的工作线程(即客户端链接的线程)发送回客户端,不知道这个想法是不是可行,准备测试下

#10


其实还有个地方不是太明白,就是我有多个逻辑进程,那么我网关服务器是对应每个逻辑进程都开个逻辑线程,还是开一个逻辑线程与其他逻辑进程的通信都在这个线程上完成,如果只有一个逻辑线程,会不会大大的降低效率,因为其他工作线程需要将数据排队放入该逻辑线程中等待逻辑线程一一处理