Ruby分叉进程在macOS与Debian上的负载不均衡问题及解决方法咨询
macOS下Ruby多进程Socket负载均衡修正方案
问题现象
使用Ruby(测试版本:2.6.9、2.7.5、3.0.3、3.1.1)实现多进程Socket服务器时,Debian与macOS(Catalina)存在显著差异:
- Debian:子进程调用均衡,如10个进程处理100次请求时,每个进程处理10次,调度顺序稳定。
- macOS:进程调用极不均衡,部分进程可能处理大量请求,个别进程完全被闲置(例如5个进程处理20次请求时,某进程处理23次,另一进程0次)。
原测试代码
服务器代码(server.rb)
#!/usr/bin/env ruby require 'socket' socket = TCPServer.open('0.0.0.0', 9999) puts "Server started ..." wpids = [] [:INT, :QUIT].each do |signal| Signal.trap(signal) { wpids.each { |wpid| Process.kill(:KILL, wpid) } } end 5.times { wpids << fork do loop { connection = socket.accept connection.puts "Hello from #{ Process.pid }" connection.close } } end Process.waitall
测试命令
在另一个终端执行:
for i in {1..20}; do nc -d localhost 9999; done
问题根源
Debian(Linux)内核对共享监听套接字的多进程accept调用采用轮转调度,确保每个等待的进程公平获取连接;而macOS的BSD内核采用惊群唤醒+抢占式调度,所有等待accept的进程会被同时唤醒,抢到内核锁的进程处理连接,导致负载严重不均。
修正方案
方案:基于SO_REUSEPORT的内核级负载均衡
让每个子进程独立创建监听套接字,并设置SO_REUSEPORT选项,内核会自动将新连接均衡分发给所有监听同一端口的进程,该方案在macOS和Linux下均有效。
修正后的服务器代码
#!/usr/bin/env ruby require 'socket' PORT = 9999 WORKER_COUNT = 5 wpids = [] # 信号处理:终止所有子进程 [:INT, :QUIT].each do |signal| Signal.trap(signal) { wpids.each { |wpid| Process.kill(:KILL, wpid) } exit! } end WORKER_COUNT.times do wpids << fork do # 每个子进程独立创建监听套接字 socket = TCPServer.new('0.0.0.0', PORT) # 设置SO_REUSEPORT:允许多个进程绑定同一端口,内核负责均衡分发 reuse_port_const = Socket.const_defined?(:SO_REUSEPORT) ? Socket::SO_REUSEPORT : 0x000F socket.setsockopt(Socket::SOL_SOCKET, reuse_port_const, 1) puts "Worker #{Process.pid} ready to accept connections" loop do connection = socket.accept connection.puts "Hello from #{Process.pid}" connection.close end end end puts "Server started with #{WORKER_COUNT} workers on port #{PORT}" Process.waitall
验证
执行原测试命令后,macOS下每个子进程将处理等量请求(20次请求/5个进程=4次/进程),实现与Debian一致的均衡调度。
内容的提问来源于stack exchange,提问作者Tom Freudenberg
相关产品推荐
相关产品推荐

