高并发消息队列常用通知机制

常用的通知机制中比较典型的有以下几种:

1、signal — zeromq 唤醒线程
这种机制下,我们向被通知进程发送一个特殊的signal(比如SIGUSR1),这样正在睡眠的读进程就会被信号中断,然后醒来。
该方法的优点是:读进程不需要监听一个额外的eventfd,适合一些不方便使用eventfd的场景;另外,用户可以选择是使用实时信号(SIGRTMIN+1),还是使用非实时信号(SIGUSR1)。
该方法的缺点是:通知不实时。因为信号的检查只有在中断返回的时候才会进行,这个时间跟操作系统的HZ、jiffies有关。

2、socket — nginx socketpair
这种机制下,写进程往socket(domain socket)写一个字符,然后读进程通过epoll/select/poll得到数据到达的通知。

3、fifo
这种机制跟socket类似,写进程往fifo中写一个字符,然后读进程通过epoll/select/poll得到数据到达的通知。

4、pipe ----- 占用两个文件描述符
跟2、3差不多。

5、eventfd/signalfd — 不像pipe一样需要两个描述符,它只需要一个描述就可以实现进程间通信了。

跟前面差不多,不过是内核帮我们事先fifo、signal通知,只有比较新的内核版本才支持。这种方式存在的问题是需要在不同进程间传递句柄,非fork方式实现比较复杂。

上面这几种方式的共性是都需要陷入内核,被通知进程只有在内核态才能接收通知,对于处理性能要求高的场景,应该少用通知。
所以,当然就看业务场景发送通知的开销是不是很大了。如果请求量很大,读进程一直忙于处理,不会频繁触发通知,那就很合适了。

服务端设计方案

1、首先主进程根据机器CPU个数,创建对应数量的管道;
2、创建完对应的管道之后,再创建一样数量的线程,每个线程绑定一个CPU;
3、主进程开始初始化socket,然后accept,当接收到一个客户端连接时,就把conn_fd写到某个pipe中;
3、每个线程创建epoll,然后监听对应pipe的写端fd,当监听到pipe中有数据时,就读取该数据,格式化为fd,将该fd加入epoll进行监听。
在这里插入图片描述

memcached 实现方案

在这里插入图片描述
主线程和工作线程共享着pipe,主线程负责监听listenfd,当有客户端到来时,将clientfd封装成MSG_ITEM,放入消息队列,然后给管道写入一个字符。子线程是负责监听各个clientfd,也在一个libevent事件循环中,这时管道读端提醒可读事件,然后回调函数就去读取消息队列的MSG_ITEM,取出clientfd,放入libevent监听中~

参考:

https://www.cnblogs.com/i4oolish/p/3970402.html
https://www.cnblogs.com/i4oolish/p/3971322.html