Netty、MINA、Twisted一起學系列10:執行緒模型
文章已獲得作者授權,原文地址:
xxgblog.com/2014/10/16/mina-netty-twisted-10
要想開發一個高效能的TCP伺服器,熟悉所使用框架的執行緒模型非常重要。MINA、Netty、Twisted本身都是高效能的網路框架,如果再搭配上高效率的程式碼,才能實現一個高大上的伺服器。但是如果不瞭解它們的執行緒模型,就很難寫出高效能的程式碼。框架本身效率再高,程式寫的太差,那麼伺服器整體的效能也不會太高。就像一個電腦,CPU再好,記憶體小硬碟慢散熱差,整體的效能也不會太高。
玩過Android開發的同學會知道,在Android應用中有一個非常重要執行緒:UI執行緒(即主執行緒)。UI執行緒是負責一個Android的介面顯示以及和使用者互動。Activity的一些方法,例如onCreate、onStop、onDestroy都是執行在UI執行緒中的。但是在編寫Activity程式碼的時候有一點需要非常注意,就是絕對不能把阻塞的或者耗時的任務寫在這些方法中,如果寫在這些方法中,則會阻塞UI執行緒,導致使用者操作的介面反應遲鈍,體驗很差。所以在Android開發中,耗時或者阻塞的任務會另外開執行緒去做。
同樣在MINA、Netty、Twisted中,也有一個非常重要的執行緒:IO執行緒。
傳統的BIO實現的TCP伺服器,特別對於TCP長連線,通常都要為每個連線開啟一個執行緒,執行緒也是作業系統的一種資源,所以很難實現高效能高併發。而非同步IO實現的TCP伺服器,由於IO操作都是非同步的,可以用一個執行緒或者少量執行緒來處理大量連線的IO操作,所以只需要少量的IO執行緒就可以實現高併發的伺服器。
在網路程式設計過程中,通常有一些業務邏輯是比較耗時、阻塞的,例如資料庫操作,如果網路不好,加上資料庫效能差,SQL不夠最佳化,資料量大,一條SQL可能會執行很久。由於IO執行緒本身數量就不多,通常只有一個或幾個,而如果這種耗時阻塞的程式碼在IO執行緒中執行的話,IO執行緒的其他事情,例如網路read和write,就無法進行了,會影響IO效能以及整個伺服器的效能。
所以,無論是使用MINA、Netty、Twisted,如果有耗時的任務,就絕對不能在IO執行緒中執行,而是要另外開啟執行緒來處理。
1. MINA
在MINA中,有三種非常重要的執行緒:Acceptor thread、Connector thread、I/O processor thread。
Acceptor thread:這個執行緒用於TCP伺服器接收新的連線,並將連線分配到I/O processor thread,由I/O processor thread來處理IO操作。每個NioSocketAcceptor建立一個Acceptor thread,執行緒數量不可配置。
Connector thread:用於處理TCP客戶端連線到伺服器,並將連線分配到I/O processor thread,由I/O processor thread來處理IO操作。每個NioSocketConnector建立一個Connector thread,執行緒數量不可配置。
I/O processor thread:用於處理TCP連線的I/O操作,如read、write。I/O processor thread的執行緒數量可透過NioSocketAcceptor或NioSocketConnector構造方法來配置,預設是CPU核心數+1。
由於本文主要介紹TCP伺服器的執行緒模型,所以就沒有Connector thread什麼事了。下面說下Acceptor thread和I/O processor thread處理TCP連線的流程:
MINA的TCP伺服器包含一個Acceptor thread和多個I/O processor thread,當有新的客戶端連線到伺服器,首先會由Acceptor thread獲取到這個連線,同時將這個連線分配給多個I/O processor thread中的一個執行緒,當客戶端傳送資料給伺服器,對應的I/O processor thread負責讀取這個資料,並執行IoFilterChain中的IoFilter以及IoHandle。
由於I/O processor thread本身數量有限,通常就那麼幾個,但是又要處理成千上萬個連線的IO操作,包括read、write、協議的編碼解碼、各種Filter以及IoHandle中的業務邏輯,特別是業務邏輯,比如IoHandle的messageReceived,如果有耗時、阻塞的任務,例如查詢資料庫,那麼就會阻塞I/O processor thread,導致無法及時處理其他IO事件,伺服器效能下降。
針對這個問題,MINA中提供了一個ExecutorFilter,用於將需要執行很長時間的會阻塞I/O processor thread的業務邏輯放到另外的執行緒中,這樣就不會阻塞I/O processor thread,不會影響IO操作。ExecutorFilter中包含一個執行緒池,預設是OrderedThreadPoolExecutor,這個執行緒池保證同一個連線的多個事件按順序依次執行,另外還可以使用UnorderedThreadPoolExecutor,它不會保證同一連線的事件的執行順序,並且可能會併發執行。二者之間可以根據需要來選擇。
public class TcpServer {
public static void main(String[] args) throws IOException {
IoAcceptor acceptor = new NioSocketAcceptor(4); // 配置I/O processor thread執行緒數量
acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new TextLineCodecFactory()));
acceptor.getFilterChain().addLast("executor", new ExecutorFilter()); // 將TcpServerHandle中的業務邏輯拿到ExecutorFilter的執行緒池中執行
acceptor.setHandler(new TcpServerHandle());
acceptor.bind(new InetSocketAddress(8080));
}
}
class TcpServerHandle extends IoHandlerAdapter {
@Override
public void messageReceived(IoSession session, Object message)
throws Exception {
// 假設這裡有個變態的SQL要執行3秒
Thread.sleep(3000);
}
}
2. Netty
Netty的TCP伺服器啟動時,會建立兩個NioEventLoopGroup,一個boss,一個worker:
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
NioEventLoopGroup實際上是一個執行緒組,可以透過構造方法設定執行緒數量,預設為CPU核心數*2。boss用於伺服器接收新的TCP連線,boss執行緒接收到新的連線後將連線註冊到worker執行緒。worker執行緒用於處理IO操作,例如read、write。
Netty中的boss執行緒類似於MINA的Acceptor thread,work執行緒和MINA的I/O processor thread類似。不同的一點是MINA的Acceptor thread是單個執行緒,而Netty的boss是一個執行緒組。實際上Netty的ServerBootstrap可以監聽多個埠號,如果只監聽一個埠號,那麼只需要一個boss執行緒即可,推薦將bossGroup的執行緒數量設定成1。
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
當有新的TCP客戶端連線到伺服器,將由boss執行緒來接收連線,然後將連線註冊到worker執行緒,當客戶端傳送資料到伺服器,worker執行緒負責接收資料,並執行ChannelPipeline中的ChannelHandler。
和MINA的I/O processor thread 類似,Netty的worker執行緒本身數量不多,而且要實時處理IO事件,如果有耗時的業務邏輯阻塞住worker執行緒,例如在channelRead中執行一個耗時的資料庫查詢,會導致IO操作無法進行,伺服器整體效能就會下降。
在Netty 3中,存在一個ExecutionHandler,它是ChannelHandler的一個實現類,用於處理耗時的業務邏輯,類似於MINA的ExecutorFilter,但是在Netty 4中被刪除了。所以這裡不再介紹ExecutionHandler。
Netty 4中可以使用EventExecutorGroup來處理耗時的業務邏輯:
public class TcpServer {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup(1); // 伺服器監聽一個埠號,boss執行緒數建議設定成1
EventLoopGroup workerGroup = new NioEventLoopGroup(4); // worker執行緒數設定成4
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
// 建立一個16個執行緒的執行緒組來處理耗時的業務邏輯
private EventExecutorGroup group = new DefaultEventExecutorGroup(16);
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new LineBasedFrameDecoder(80));
pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
// 將TcpServerHandler中的業務邏輯放到EventExecutorGroup執行緒組中執行
pipeline.addLast(group, new TcpServerHandler());
}
});
ChannelFuture f = b.bind(8080).sync();
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
class TcpServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws InterruptedException {
// 假設這裡有個變態的SQL要執行3秒
Thread.sleep(3000);
}
}
3.Twisted
Twisted的執行緒模型是最簡單粗暴的:單執行緒,即reactor執行緒。也就是,所有的IO操作、編碼解碼、業務邏輯等都是在一個執行緒中執行。實際上,即使是單執行緒,其效能也是非常高的,可以同時處理大量的連線。在單執行緒的環境下程式設計,不需要考慮執行緒安全的問題。不過,單執行緒帶來一個問題,就是耗時的業務邏輯,如果執行在reactor執行緒中,那麼其他事情,例如網路IO,就要等到reactor執行緒空閒時才能繼續做,會影響到伺服器的效能。
下面的程式碼,透過reactor.callInThread將耗時的業務邏輯放到單獨的執行緒池中執行,而不在reactor執行緒中執行。這樣就不會影響到reactor執行緒的網路IO了。可以透過reactor.suggestThreadPoolSize設定這個執行緒池的執行緒數量。
# -*- coding:utf-8 –*-
import time
from twisted.internet.protocol import Protocol
from twisted.internet.protocol import Factory
from twisted.internet import reactor
# 耗時、阻塞的業務邏輯
def logic(data):
print data
time.sleep(3) # 假設這裡有個變態的SQL要執行3秒
class TcpServerHandle(Protocol):
def dataReceived(self, data):
reactor.callInThread(logic, data) # 線上程池中執行logic(data)耗時任務,不在reactor執行緒中執行
reactor.suggestThreadPoolSize(8) # 設定執行緒池的執行緒數量為8
factory = Factory()
factory.protocol = TcpServerHandle
reactor.listenTCP(8080, factory)
reactor.run()
由於Twisted的reactor的單執行緒設計,它的很多程式碼都不是執行緒安全的。所以在非reactor執行緒中執行的程式碼需要注意執行緒安全問題。例如transport.write就不是執行緒安全的。不過在非reactor執行緒中可以呼叫reactor.callFromThread方法,這個方法功能和callInThread相反,將一個函式從別的執行緒放到reactor執行緒中執行。不過還是要注意,reactor.callFromThread呼叫的函式由於執行在reactor執行緒中,如果執行耗時,同樣會阻塞reactor執行緒,影響IO。
# -*- coding:utf-8 –*-
import time
from twisted.internet.protocol import Protocol
from twisted.internet.protocol import Factory
from twisted.internet import reactor
# 非執行緒安全的程式碼
def notThreadSafe():
print "notThreadSafe"
# 耗時、阻塞的業務邏輯
def logic(data):
print data
time.sleep(3) # 假設這裡有個變態的SQL要執行3秒
reactor.callFromThread(notThreadSafe) # 在reactor執行緒中執行notThreadSafe()
class TcpServerHandle(Protocol):
def dataReceived(self, data):
reactor.callInThread(logic, data) # 線上程池中執行logic(data)耗時任務,不在reactor執行緒中執行
reactor.suggestThreadPoolSize(8) # 設定執行緒池的執行緒數量為8
factory = Factory()
factory.protocol = TcpServerHandle
reactor.listenTCP(8080, factory)
reactor.run()
此外,twisted.internet.threads中提供了許多很方便的函式。例如threads.deferToThread用於將一個耗時任務放線上程池中執行,與reactor.callInThread不同的是,它的返回值是Deferred型別,可以透過新增回撥函式,處理耗時任務完成後的結果(返回值)。
# -*- coding:utf-8 –*-
import time
from twisted.internet.protocol import Protocol
from twisted.internet.protocol import Factory
from twisted.internet import reactor, threads
# 耗時、阻塞的業務邏輯
def logic(data):
print data
time.sleep(3) # 假設這裡有個變態的SQL要執行3秒
return "success"
# 回撥函式
def logicSuccess(result):
# result即為logic函式的返回值,即"success"
print result
class TcpServerHandle(Protocol):
def dataReceived(self, data):
d = threads.deferToThread(logic, data) # 將耗時的業務邏輯logic(data)放到執行緒池中執行,deferToThread返回值型別是Deferred
d.addCallback(logicSuccess) # 新增回撥函式
reactor.suggestThreadPoolSize(8) # 設定執行緒池的執行緒數量為8
factory = Factory()
factory.protocol = TcpServerHandle
reactor.listenTCP(8080, factory)
reactor.run()
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31558358/viewspace-2565057/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Netty、MINA、Twisted一起學系列10:SSL / TLSNettyTLS
- Netty、MINA、Twisted一起學系列05:整合protobufNetty
- Netty、MINA、Twisted一起學系列04:定製自己的協議Netty協議
- Netty、MINA、Twisted一起學系列03:TCP訊息固定大小的字首(Header)NettyTCPHeader
- Netty、MINA、Twisted一起學系列01:實現簡單的TCP伺服器NettyTCP伺服器
- Netty、MINA、Twisted一起學系列02:TCP訊息邊界問題及按行分割訊息NettyTCP
- Netty 框架學習 —— EventLoop 和執行緒模型Netty框架OOP執行緒模型
- 淺談Netty的執行緒模型Netty執行緒模型
- Netty原始碼解析一——執行緒池模型之執行緒池NioEventLoopGroupNetty原始碼執行緒模型OOP
- Netty原始碼死磕一(netty執行緒模型及EventLoop機制)Netty原始碼執行緒模型OOP
- netty系列之:在netty中實現執行緒和CPU繫結Netty執行緒
- Reactor執行緒模型及其在Netty中的應用React執行緒模型Netty
- Netty原始碼分析之Reactor執行緒模型詳解Netty原始碼React執行緒模型
- Java開發中Netty執行緒模型原理解析!JavaNetty執行緒模型
- Netty是什麼,Netty為什麼速度這麼快,執行緒模型分析Netty執行緒模型
- 執行緒模型執行緒模型
- 深入Netty邏輯架構,從Reactor執行緒模型開始Netty架構React執行緒模型
- 多執行緒系列之 執行緒安全執行緒
- Netty(二) 從執行緒模型的角度看 Netty 為什麼是高效能的?Netty執行緒模型
- 深入學習redis 的執行緒模型Redis執行緒模型
- redis執行緒模型-學習小結Redis執行緒模型
- 在netty3.x中存在兩種執行緒:boss執行緒和worker執行緒。Netty執行緒
- Dubbo執行緒模型執行緒模型
- WPF執行緒模型執行緒模型
- redis執行緒模型Redis執行緒模型
- Netty中的執行緒處理EventLoopNetty執行緒OOP
- 多執行緒系列(十七) -執行緒組介紹執行緒
- 多執行緒系列(1),多執行緒基礎執行緒
- 多執行緒系列(三):執行緒池基礎執行緒
- Dubbo的執行緒模型執行緒模型
- 程式和執行緒模型執行緒模型
- Redis的執行緒模型Redis執行緒模型
- 03.執行緒模型執行緒模型
- webrtc執行緒模型分析Web執行緒模型
- 執行緒系列四AQS執行緒AQS
- 一起分析執行緒的狀態及執行緒通訊機制執行緒
- 多執行緒之共享模型執行緒模型
- 執行緒池執行模型原始碼全解析執行緒模型原始碼