Muduo 網(wǎng)絡(luò)編程示例之六:限制服務(wù)器的最大并發(fā)連接數(shù)
陳碩 (giantchen_AT_gmail)
Blog.csdn.net/Solstice t.sina.com.cn/giantchen
這是《Muduo 網(wǎng)絡(luò)編程示例》系列的第六篇文章。
Muduo 全系列文章列表: http://blog.csdn.net/Solstice/category/779646.aspx
本文已以大家都熟悉的 EchoServer 介紹如何限制服務(wù)器的并發(fā)連接數(shù)。
本文的代碼見(jiàn) http://code.google.com/p/muduo/source/browse/trunk/examples/maxconnection/
《Muduo 網(wǎng)絡(luò)編程示例 系列》計(jì)劃中的第六篇文章原本是“用于測(cè)試兩臺(tái)機(jī)器的帶寬的 pingpong 程序”,pingpong 協(xié)議的程序已經(jīng)在《muduo 與 boost asio 吞吐量對(duì)比》和《muduo 與 libevent2 吞吐量對(duì)比》兩篇文章中介紹過(guò)了,所以我改為寫另外一個(gè)有點(diǎn)意思的主題。
這篇文章中的“并發(fā)連接數(shù)”是指一個(gè) server program 能同時(shí)支持的客戶端連接數(shù),連接系由客戶端主動(dòng)發(fā)起,服務(wù)端被動(dòng)接受(accept)連接。(如果要限制應(yīng)用程序主動(dòng)發(fā)起的連接,則問(wèn)題要簡(jiǎn)單得多,畢竟主動(dòng)權(quán)和決定權(quán)都在程序本身。)
為什么要限制并發(fā)連接數(shù)?
一方面,我們不希望服務(wù)程序超載,另一方面,更因?yàn)?file descriptor 是稀缺資源,如果出現(xiàn) file descriptor 耗盡,很棘手(跟 “malloc 失敗/new() 拋出 std::bad_alloc”差不多同樣棘手)。
我在《分布式系統(tǒng)的工程化開(kāi)發(fā)方法》一文中曾談到 libev 作者建議的一種應(yīng)對(duì)“accept()ing 時(shí) file descriptor 耗盡”的辦法。


Muduo 的 acceptor 正是這么實(shí)現(xiàn)的,但是,這個(gè)做法在多線程下不能保證正確,會(huì)有 race condition。(思考題:是什么 race condition?)
其實(shí)有另外一種比較簡(jiǎn)單的辦法:file descriptor 是 hard limit,我們可以自己設(shè)一個(gè)稍低一點(diǎn)的 soft limit,如果超過(guò) soft limit 就主動(dòng)關(guān)閉新連接,這樣就避免觸及“file descriptor 耗盡”這種邊界條件。比方說(shuō)當(dāng)前進(jìn)程的 max file descriptor 是 1024,那么我們可以在連接數(shù)達(dá)到 1000 的時(shí)候進(jìn)入“拒絕新連接”狀態(tài),這樣留給我們足夠的騰挪空間。
Muduo 中限制并發(fā)連接數(shù)
Muduo 中限制并發(fā)連接數(shù)的做法簡(jiǎn)單得出奇。以在《Muduo 網(wǎng)絡(luò)編程示例之零:前言》中出場(chǎng)過(guò)的 EchoServer 為例,只需要為它增加一個(gè) int 成員,表示當(dāng)前的活動(dòng)連接數(shù)。(如果是多線程程序,應(yīng)該用 muduo::AtomicInt32。)
class EchoServer
{
public:
EchoServer(muduo::net::EventLoop* loop,
const muduo::net::InetAddress& listenAddr,
int maxConnections);
void start();
private:
void onConnection(const muduo::net::TcpConnectionPtr& conn);
void onMessage(const muduo::net::TcpConnectionPtr& conn,
muduo::net::Buffer* buf,
muduo::Timestamp time);
muduo::net::EventLoop* loop_;
muduo::net::TcpServer server_;
int numConnected_; // should be atomic_int
const int kMaxConnections;
};
然后,在 EchoServer::onConnection() 中判斷當(dāng)前活動(dòng)連接數(shù),如果超過(guò)最大允許數(shù),則踢掉連接。
void EchoServer::onConnection(const TcpConnectionPtr& conn)
{
LOG_INFO << "EchoServer - " << conn->peerAddress().toHostPort() << " -> "
<< conn->localAddress().toHostPort() << " is "
<< (conn->connected() ? "UP" : "DOWN");
if (conn->connected())
{
++numConnected_;
if (numConnected_ > kMaxConnections)
{
conn->shutdown();
}
}
else
{
--numConnected_;
}
LOG_INFO << "numConnected = " << numConnected_;
}
這種做法可以積極地防止耗盡 file descriptor。
另外,如果是有業(yè)務(wù)邏輯的服務(wù),可以在 shutdown() 之前發(fā)送一個(gè)簡(jiǎn)單的響應(yīng),表明本服務(wù)程序的負(fù)載能力已經(jīng)飽和,提示客戶端嘗試下一個(gè)可用的 server(當(dāng)然,下一個(gè)可用的 server 地址不一定要在這個(gè)響應(yīng)里給出,客戶端可以自己去 name service 查詢),這樣方便客戶端快速 failover。
后文將介紹如何處理空閑連接的超時(shí):如果一個(gè)連接長(zhǎng)時(shí)間(若干秒)沒(méi)有輸入數(shù)據(jù),則踢掉此連接。辦法有很多種,我用 Time Wheel 解決。