“惊群”问题

本文讨论了一种由子进程主动获取客户端连接的服务器设计方法,并分析了其存在的“惊群”问题。通过对加锁版本和“传递文件描述符”版本的对比,指出了“分配”而非“竞争”是解决该问题的有效途径。
 读《UNIX网络编程》第二版的第一卷时,发现作者在第27章“客户-服务器程序其它设计方法”中的27.6节“TCP预先派生子进程服务器程序,accept无上锁保护”中提到了一种由子进程去竞争客户端连接的设计方法,用伪码描述如下:

服务器主进程:

listen_fd = socket(...);
bind(listen_fd, ...);
listen(listen_fd, ...);
pre_fork_children(...);
close(listen_fd);
wait_children_die(...);


服务器服务子进程:

while (1) {
        conn_fd = accept(listen_fd, ...);
        do_service(conn_fd, ...);
}


初识上述代码,真有眼前一亮的感觉,也正如作者所说,以上代码确实很少见(反正我读此书之前是确实没见过)。作者真是构思精巧,巧妙地绕过了常见的预先创建子进程的多进程服务器当主服务进程接收到新的连接必须想办法将这个连接传递给服务子进程的“陷阱”,上述代码通过共享的倾听套接字,由子进程主动地去向内核“索要”连接套接字,从而避免了用UNIX域套接字传递文件描述符的“淫技”。

不过,当接着往下读的时候,作者谈到了“惊群”(Thundering herd)问题。所谓的“惊群”就是,当很多进程都阻塞在accept系统调用的时候,即使只有一个新的连接达到,内核也会唤醒所有阻塞在accept上的进程,这将给系统带来非常大的“震颤”,降低系统性能。

除了这个问题,accept还必须是原子操作。为此,作者在接下来的27.7节讲述了加了互斥锁的版本:

while (1) {
        lock(...);
        conn_fd = accept(listen_fd, ...);
        unlock(...);
        do_service(conn_fd, ...);
}


原子操作的问题算是解决了,那么“惊群”呢?文中只是提到在Solaris系统上当子进程数由75变成90后,CPU时间显著增加,并且作者认为这是因为进程过多,导致内存互换。对“惊群”问题回答地十分含糊。通过比较书中图27.2的第4列和第7列的内容,我们可以肯定“真凶”绝对不是“内存对换”。

“元凶”到底是谁?

仔细分析一下,加锁真的有助于“惊群”问题么?不错,确实在同一时间只有一个子进程在调用accept,其它子进程都阻塞在了lock语句,但是,当accept返回并unlock之后呢?unlock肯定是要唤醒阻塞在这个锁上的进程的,不过谁都没有规定是唤醒一个还是唤醒多个。所以,潜在的“惊群”问题还是存在,只不过换了个地方,换了个形式。而造成Solaris性能骤降的“罪魁祸首”很有可能就是“惊群”问题。

崩溃了!这么说所有的锁都有可能产生惊群问题了?

似乎真的是这样,所以减少锁的使用很重要。特别是在竞争比较激烈的地方。

作者在27.9节所实现的“传递文件描述符”版本的服务器就有效地克服了“惊群”问题,在现实的服务器实现中,最常用的也是此节所提到的基于“分配”形式。

把“竞争”换成“分配”是避免“惊群”问题的有效方法,但是也不要忽视“分配”的“均衡”问题,不然后果可能更加严重哦!
参考资料未直接提及Redisson问题的产生原因及解决方案,但可从相关问题信息进行一定推测。 效应一般指多个进程或线程在等待同一事件时,当事件发生,所有等待的进程或线程都会被唤醒,但只有一个能处理该事件,其他被唤醒的则是做了无用功。在Redis相关场景中,“缓存过期产生的现象”是指在并发量比较大的情况下,当一个缓存数据失效之后,会导致同时有多个并发线程去向后端数据库发起请求去获取同一业务数据,使后端数据库压力陡增 [^4]。 Redisson是基于Redis的分布式锁实现方案,推测Redisson问题产生原因可能是在高并发场景下,多个客户端尝试获取同一把锁时,Redis集中多个节点的响应会唤醒多个等待锁的客户端线程,而最终只有一个线程能成功获取锁,其余线程的唤醒就是无效操作,造成资源浪费和性能损耗。 对于解决方案,可参考Zookeeper解决效应的方法,Zookeeper通过临时顺序节点解决效应 [^3]。Redisson可以借鉴类似思路,在获取锁时引入顺序机制,让等待锁的客户端按顺序排队,避免多个客户端同时竞争同一把锁。另外,也可以优化Redis集的配置和性能,提高其处理并发请求的能力,减少不必要的唤醒操作。 ```python # 以下为Redisson获取锁的简单示例代码 import org.redisson.Redisson; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.redisson.config.Config; public class RedissonLockExample { public static void main(String[] args) { // 创建配置 Config config = new Config(); config.useSingleServer().setAddress("redis://127.0.0.1:6379"); // 创建Redisson客户端 RedissonClient redisson = Redisson.create(config); // 获取锁 RLock lock = redisson.getLock("myLock"); try { // 尝试获取锁 lock.lock(); // 执行业务逻辑 System.out.println("获取到锁,执行业务逻辑"); } finally { // 释放锁 lock.unlock(); } } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值