Linux kernel 3.9 の新機能 SO_REUSEPORT を試してみる
Linux kernel 3.9 のマージウィンドウでは SO_REUSEPORT というソケットオプションがマージされました。
Merge branch 'soreuseport' · c617f39 · torvalds/linux · GitHub
これは同一ポートに複数のリスナー(listen ソケット)が bind できるようになるというもので、Webサーバなど単一のポートに多くのコネクションが来るようなワークロードで、複数プロセスでうまく負荷分散ができるようになるそうです。
(これまでだと一人がaccept()してそれぞれの worker に渡すというようなモデルがありましたが、これでは accept() する部分がボトルネックになってしまいがちでした。)
BSDでは元々 SO_REUSEPORT オプションはあったようですが、マルチキャスト通信で使うもののようです。Linux のこの実装は目的が違うので少し混乱を招きそうですが…
まずは面白そうな機能なので実際に試してみました。
準備
カーネルは最新のものをgitで取ってきました。
# uname -r 3.9.0-rc2+
お試しプログラム
お手軽に試すため Python で書きました。
10プロセスが同一ポートに bind して、誰が accept したかを表示するものです。
#!/usr/bin/env python import sys, socket, time from multiprocessing import Process PORT = 8000 NR_LISTENERS = 10 SO_REUSEPORT = 15 def listener_work(num): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, SO_REUSEPORT, 1) # set SO_REUSEPORT s.bind(("", PORT)) s.listen(5) while True: conn, addr = s.accept() print '%2d: accepted!' % num ret = conn.recv(16) conn.close() def server(): processes = [] for i in xrange(NR_LISTENERS): p = Process(target=listener_work, args=(i,)) p.start() processes.append(p) for p in processes: p.join() def client(): while True: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((sys.argv[1], PORT)) s.sendall("a" * 16) s.close() time.sleep(1) def main(): if '-s' in sys.argv: server() else: client() if __name__ == '__main__': main()
実行結果
クライアント側
# ./reuseport.py <サーバのIP>
サーバ側
# ./reuseport.py -s 0: accepted! 2: accepted! 3: accepted! 4: accepted! 0: accepted! 6: accepted! 1: accepted! 3: accepted! 9: accepted! 5: accepted! :
SO_REUSEPORT を使うことで、10プロセスが同一ポートに bind できました。
また、accept する人も少しムラはありますがそこそこうまく分散されているようです。
実際に効果があるかを見るにはもっと高負荷でないと分からないですが、
まずは動くことが確認できました。
最近は TCP のプロトコルを改良するようなパッチが色々とマージされていて面白いですね。