一方面,我們不希望服務程序超載,另一方面,更因為 file descriptor 是稀缺資源,如果出現 file descriptor 耗盡,很棘手(跟 “malloc 失敗/new() 拋出 std::bad_alloc”差不多同樣棘手)。
Muduo 的 acceptor 正是這麼實現的,但是,這個做法在多線程下不能保證正確,會有 race condition。(思考題:是什麼 race condition?)
其實有另外一種比較簡單的辦法:file descriptor 是 hard limit,我們可以自己設一個稍低一點的 soft limit,如果超過 soft limit 就主動關閉新連接,這樣就避免觸及“file descriptor 耗盡”這種邊界條件。比方說當前進程的 max file descriptor 是 1024,那麼我們可以在連接數達到 1000 的時候進入“拒絕新連接”狀態,這樣留給我們足夠的騰挪空間。
Muduo 中限制並發連接數
Muduo 中限制並發連接數的做法簡單得出奇。以在《Muduo 網絡編程示例之零:前言》中出場過的 EchoServer 為例,只需要為它增加一個 int 成員,表示當前的活動連接數。(如果是多線程程序,應該用 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() 中判斷當前活動連接數,如果超過最大允許數,則踢掉連接。
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。
另外,如果是有業務邏輯的服務,可以在 shutdown() 之前發送一個簡單的響應,表明本服務程序的負載能力已經飽和,提示客戶端嘗試下一個可用的 server(當然,下一個可用的 server 地址不一定要在這個響應裡給出,客戶端可以自己去 name service 查詢),這樣方便客戶端快速 failover。
後文將介紹如何處理空閒連接的超時:如果一個連接長時間(若干秒)沒有輸入數據,則踢掉此連接。辦法有很多種,我用 Time Wheel 解決。