负载测试后,每个新的 mio 连接都会立即挂断
Posted
技术标签:
【中文标题】负载测试后,每个新的 mio 连接都会立即挂断【英文标题】:After a load test, every new mio connection immediately hangs up 【发布时间】:2016-10-15 20:29:51 【问题描述】:我使用 mio 在 Rust 中编写了一个多线程异步 HTTP 服务器。当我运行负载测试(使用围攻)时,服务器在第一次负载测试时工作正常,但是当负载测试完成时,对服务器的所有请求都开始失败。
使用一些日志记录,我注意到我通过accept()
获得的每个新连接都会立即收到一个挂断事件。服务器连接本身不会收到任何错误或挂断事件。
我在 OS X 10.11 El Capitan 上运行 Rust 1.12.0 和 mio 0.6
这是我的服务器的主要事件循环:
pub fn run(self)
let poll = Poll::new().unwrap();
let server = TcpListener::bind(&SocketAddr::from_str(&self.host).unwrap()).unwrap();
poll.register(&server, SERVER, Ready::readable(), PollOpt::edge()).unwrap();
let mut events = Events::with_capacity(1024);
let mut next_conn: usize = 1;
let mut workers = Vec::new();
// Create worker threads.
for _ in 0..self.num_workers
let (tx, rx) = channel();
let worker_handler = self.event_handler.duplicate();
thread::spawn(move ||
Self::process_events(rx, worker_handler);
);
workers.push(tx);
loop
println!("Polling...");
match poll.poll(&mut events, None)
Err(e) => panic!("Error during poll(): ", e),
Ok(_) =>
for event in events.iter()
match event.token()
SERVER =>
println!("Accepting..");
match server.accept()
Ok((stream, _)) =>
println!("Registering new connection...");
match poll.register(&stream,
Token(next_conn),
Ready::readable(),
PollOpt::edge())
Err(e) => panic!("Error during register(): ", e),
Ok(_) =>
println!("New connection on worker ",
next_conn % self.num_workers);
workers[next_conn % self.num_workers]
.send(Msg::NewConn(next_conn, stream))
.unwrap();
next_conn += 1;
Err(e) => panic!("Error during accept() : ", e),
Token(id) =>
println!("Sending event on conn to worker ",
id,
id % self.num_workers);
workers[id % self.num_workers]
.send(Msg::ConnEvent(id, event.kind()))
.unwrap();
fn process_events(channel: Receiver<Msg>, mut event_handler: Box<EventHandler>)
loop
let msg = channel.recv().unwrap();
match msg
Msg::NewConn(id, conn) =>
event_handler.new_conn(id, conn);
Msg::ConnEvent(id, event) =>
event_handler.conn_event(id, event);
我正在使用的示例 webapp 的完整代码是 available on GitHub。
加载测试命令:
siege -b -c10 -d10 -t20S http://localhost:8080
【问题讨论】:
【参考方案1】:我不知道为什么负载测试应用没有更好地记录这一点。几个月前我可能遇到了同样的问题。听起来您已经联系到"Ephemeral Port Limit"。以下是文章中总结该想法的一些引述:
只要在客户端和服务器之间建立连接,系统就会将该连接绑定到一个临时端口——一组在有效端口范围的高端指定的端口。
OS X 上可用的临时端口总数为 16,383。
请注意,此限制不会影响对实时服务器的实际请求,因为每个 TCP 连接都由源 IP、源端口、目标 IP 和目标端口的元组定义——因此临时端口限制仅适用于单个客户端/服务器对。
换句话说,发生这种情况是因为您正在从 localhost 到 localhost 运行负载测试,并且可能在大约 16,383 个连接后耗尽了临时端口。
您可以做几件事来测试这是否是问题所在:
让您的负载测试器报告建立的连接数。如果是 16,000 左右,那么这可能是罪魁祸首。
增加临时端口限制并再次运行负载测试。如果您获得更多连接数,那么这可能就是问题所在。但请记住,如果这是问题所在,那么在野外就不会成为问题。
您可以使用以下命令查看您的临时端口范围:
$ sysctl net.inet.ip.portrange.first net.inet.ip.portrange.last
你可以使用这个命令来增加它:
$ sysctl -w net.inet.ip.portrange.first=32768
运行测试后,您可能应该将端口范围设置回原来的值,因为这种增加表示非标准范围。
【讨论】:
两点说明:(1)Linux以前有个bug,所有接口的端口都是唯一的,现在两个接口可以使用同一个端口号;可能值得绑定到 localhost 以外的其他接口以获得更多端口; (2) 可以将您的系统配置为从更大的池中分配临时端口号。显然,这并没有消除限制,只是将其推得更远。 很有趣,但我的负载测试实际上很小(20 秒,10 个用户)。我怀疑它可以通过这些设置达到 16k 连接。 @ElefEnt 这不是你必须怀疑任何事情的情况——证明它!每秒 800 个连接 * 20 秒 = 16000。这将是每毫秒 1 个连接,这是一个非常合理的时间范围。您的负载测试工具应该告诉您建立了多少连接。您还可以考虑运行strace
或等效项,以查看操作系统在连接期间报告了哪些错误。
当我运行负载测试时,很快就可以达到 16000 个连接。就像@Shepmaster 评论的那样,您应该能够要求 siege 报告建立的连接数。
@ElefEnt 我添加了一些关于如何测试这是否真的是问题的 cmets。以上是关于负载测试后,每个新的 mio 连接都会立即挂断的主要内容,如果未能解决你的问题,请参考以下文章