久久精品人人爽,华人av在线,亚洲性视频网站,欧美专区一二三

如何分析高性能服務器Server中的Reactor模型

149次閱讀
沒有評論

共計 9488 個字符,預計需要花費 24 分鐘才能閱讀完成。

丸趣 TV 小編今天帶大家了解如何分析高性能服務器 Server 中的 Reactor 模型,文中知識點介紹的非常詳細。覺得有幫助的朋友可以跟著丸趣 TV 小編一起瀏覽文章的內容,希望能夠幫助更多想解決這個問題的朋友找到問題的答案,下面跟著丸趣 TV 小編一起深入學習“如何分析高性能服務器 Server 中的 Reactor 模型”的知識吧。

在這個充斥著云的時代, 我們使用的軟件可以說 99% 都是 C / S 架構的!

你發(fā)郵件用的 Outlook,Foxmail 等

你看視頻用的優(yōu)酷,土豆等

你寫文檔用的 Office365,googleDoc,Evernote 等

你瀏覽網頁用的 IE,Chrome 等(B/ S 是特殊的 C /S)

C/ S 架構的軟件帶來的一個明顯的好處就是:只要有網絡,你可以在任何地方干同一件事。

例如:你在家里使用 Office365 編寫了文檔。到了公司,只要打開編輯地址就可以看到在家里編寫的文檔,進行展示或者繼續(xù)編輯。甚至在手機上進行閱讀與編輯。不再需要 U 盤拷來拷去了。

C/ S 架構可以抽象為如下模型:

C 就是 Client(客戶端), 上面的 B 是 Browser(瀏覽器)

S 就是 Server(服務器):服務器管理某種資源,并且通過操作這種資源來為它的客戶端提供某種服務

C/ S 架構之所以能夠流行的一個主要原因就是網速的提高以及費用的降低,特別是無線網絡速度的提高。試想在 2G 時代,大家最多就是看看文字網頁,小說什么的。看圖片,那簡直就是奢侈! 更別說看視頻了!

網速的提高,使得越來越多的人使用網絡,例如:優(yōu)酷,微信都是上億用戶量,更別說天貓雙 11 的瞬間訪問量了! 這就對服務器有很高的要求! 能夠快速處理海量的用戶請求! 那服務器如何能快速的處理用戶的請求呢?

高性能服務器

高性能服務器至少要滿足如下幾個需求:

效率高:既然是高性能,那處理客戶端請求的效率當然要很高了

高可用:不能隨便就掛掉了

編程簡單:基于此服務器進行業(yè)務開發(fā)需要足夠簡單

可擴展:可方便的擴展功能

可伸縮:可簡單的通過部署的方式進行容量的伸縮,也就是服務需要無狀態(tài)

而滿足如上需求的一個基礎就是高性能的 IO!

Socket

無論你是發(fā)郵件,瀏覽網頁,還是看視頻~實際底層都是使用的 TCP/IP,而 TCP/IP 的編程抽象就是 Socket!

我一直對 Socket 的中文翻譯很困惑,個人覺得是我所接觸的技術名詞翻譯里最莫名其妙的,沒有之一!

Socket 中文翻譯為”套接字”! 什么鬼? 在很長的時間里我都無法將其和網絡編程關聯上! 后來專門找了一些資料,*** 在知乎上找到了一個還算滿意的答案(具體鏈接,請見文末的參考資料鏈接)!

Socket 的原意是插口,想表達的意思是插口與插槽的關系!”send socket”插到”receive  socket”里,建立了鏈接,然后就可以通信了!

套接字的翻譯,應該是參考了套接管(如下圖)! 從這個層面上來看,是有那么點意思!

套接字這個翻譯已經是標準了,不糾結這個了!

我們看一下 Socket 之間建立鏈接及通信的過程! 實際上就是對 TCP/IP 連接與通信過程的抽象:

服務端 Socket 會 bind 到指定的端口上,Listen 客戶端的”插入”

客戶端 Socket 會 Connect 到服務端

當服務端 Accept 到客戶端連接后

就可以進行發(fā)送與接收消息了

通信完成后即可 Close

對于 IO 來說,我們聽得比較多的是:

BIO: 阻塞 IO

NIO: 非阻塞 IO

同步 IO

異步 IO

以及其組合:

同步阻塞 IO

同步非阻塞 IO

異步阻塞 IO

異步非阻塞 IO

那么什么是阻塞 IO、非阻塞 IO、同步 IO、異步 IO 呢?

一個 IO 操作其實分成了兩個步驟:發(fā)起 IO 請求和實際的 IO 操作

阻塞 IO 和非阻塞 IO 的區(qū)別在于 *** 步:發(fā)起 IO 請求是否會被阻塞,如果阻塞直到完成那么就是傳統的阻塞 IO; 如果不阻塞,那么就是非阻塞 IO

同步 IO 和異步 IO 的區(qū)別就在于第二個步驟是否阻塞,如果實際的 IO 讀寫阻塞請求進程,那么就是同步 IO,因此阻塞 IO、非阻塞 IO、IO 復用、信號驅動 IO 都是同步 IO; 如果不阻塞,而是操作系統幫你做完 IO 操作再將結果返回給你,那么就是異步 IO

舉個不太恰當的例子:比如你家網絡斷了,你打電話去中國電信報修!

你撥號 mdash;- 客戶端連接服務器

電話通了 mdash;- 連接建立

你說:“我家網斷了, 幫我修下”mdash;- 發(fā)送消息

說完你就在那里等,那么就是阻塞 IO

如果正好你有事,你放下帶電話,然后處理其他事情了,過一會你來問下,修好了沒 mdash;- 那就是非阻塞 IO

如果客服說:“馬上幫你處理,你稍等”mdash;- 同步 IO

如果客服說:“馬上幫你處理,好了通知你”,然后掛了電話 mdash;- 異步 IO

本文只討論 BIO 和 NIO,AIO 使用度沒有前兩者普及,暫不討論!

下面從代碼層面看看 BIO 與 NIO 的流程!

BIO

客戶端代碼

//Bind,Connect Socket client = new Socket(127.0.0.1 ,7777); // 讀寫  PrintWriter pw = new PrintWriter(client.getOutputStream()); BufferedReader br= new BufferedReader(new InputStreamReader(System.in)); pw.write(br.readLine()); //Close pw.close(); br.close();

服務端代碼

Socket socket; //Bind,Listen ServerSocket ss = new ServerSocket(7777); while (true) { //Accept socket = ss.accept(); // 一般新建一個線程執(zhí)行讀寫  BufferedReader br = new BufferedReader( new InputStreamReader(socket .getInputStream())); System.out.println(you input is :   + br.readLine()); }

上面的代碼可以說是學習 Java 的 Socket 的入門級代碼了

代碼流程和前面的圖可以一一對上

模型圖如下所示:

BIO 優(yōu)缺點

優(yōu)點

模型簡單

編碼簡單

缺點

性能瓶頸低

優(yōu)缺點很明顯。這里主要說下缺點:主要瓶頸在線程上。每個連接都會建立一個線程。雖然線程消耗比進程小,但是一臺機器實際上能建立的有效線程有限,以 Java 來說,1.5 以后,一個線程大致消耗 1M 內存! 且隨著線程數量的增加,CPU 切換線程上下文的消耗也隨之增加,在高過某個閥值后,繼續(xù)增加線程,性能不增反降! 而同樣因為一個連接就新建一個線程,所以編碼模型很簡單!

就性能瓶頸這一點,就確定了 BIO 并不適合進行高性能服務器的開發(fā)! 像 Tomcat 這樣的 Web 服務器,從 7 開始就從 BIO 改成了 NIO,來提高服務器性能!

NIO

NIO 客戶端代碼(連接)

// 獲取 socket 通道  SocketChannel channel = SocketChannel.open(); channel.configureBlocking(false); // 獲得通道管理器  selector=Selector.open(); channel.connect(new InetSocketAddress(serverIp, port)); // 為該通道注冊 SelectionKey.OP_CONNECT 事件  channel.register(selector, SelectionKey.OP_CONNECT);

NIO 客戶端代碼(監(jiān)聽)

while(true){ // 選擇注冊過的 io 操作的事件(*** 次為 SelectionKey.OP_CONNECT) selector.select(); while(SelectionKey key : selector.selectedKeys()){ if(key.isConnectable()){ SocketChannel channel=(SocketChannel)key.channel(); if(channel.isConnectionPending()){ channel.finishConnect();// 如果正在連接,則完成連接  } channel.register(selector, SelectionKey.OP_READ); }else if(key.isReadable()){ // 有可讀數據事件。 SocketChannel channel = (SocketChannel)key.channel(); ByteBuffer buffer = ByteBuffer.allocate(10); channel.read(buffer); byte[] data = buffer.array(); String message = new String(data); System.out.println(recevie message from server:, size:  + buffer.position() +   msg:   + message); } } }

NIO 服務端代碼(連接)

// 獲取一個 ServerSocket 通道  ServerSocketChannel serverChannel = ServerSocketChannel.open(); serverChannel.configureBlocking(false); serverChannel.socket().bind(new InetSocketAddress(port)); // 獲取通道管理器  selector = Selector.open(); // 將通道管理器與通道綁定,并為該通道注冊 SelectionKey.OP_ACCEPT 事件, serverChannel.register(selector, SelectionKey.OP_ACCEPT);

NIO 服務端代碼(監(jiān)聽)

while(true){ // 當有注冊的事件到達時,方法返回,否則阻塞。 selector.select(); for(SelectionKey key : selector.selectedKeys()){ if(key.isAcceptable()){ ServerSocketChannel server = (ServerSocketChannel)key.channel(); SocketChannel channel = server.accept(); channel.write(ByteBuffer.wrap( new String( send message to client).getBytes())); // 在與客戶端連接成功后,為客戶端通道注冊 SelectionKey.OP_READ 事件。 channel.register(selector, SelectionKey.OP_READ); }else if(key.isReadable()){// 有可讀數據事件  SocketChannel channel = (SocketChannel)key.channel(); ByteBuffer buffer = ByteBuffer.allocate(10); int read = channel.read(buffer); byte[] data = buffer.array(); String message = new String(data); System.out.println(receive message from client, size:  + buffer.position() +   msg:   + message); } } }

NIO 模型示例如下:

Acceptor 注冊 Selector,監(jiān)聽 accept 事件

當客戶端連接后,觸發(fā) accept 事件

服務器構建對應的 Channel,并在其上注冊 Selector,監(jiān)聽讀寫事件

當發(fā)生讀寫事件后,進行相應的讀寫處理

NIO 優(yōu)缺點

優(yōu)點

性能瓶頸高

缺點

模型復雜

編碼復雜

需處理半包問題

NIO 的優(yōu)缺點和 BIO 就完全相反了! 性能高,不用一個連接就建一個線程,可以一個線程處理所有的連接! 相應的,編碼就復雜很多,從上面的代碼就可以明顯體會到了。還有一個問題,由于是非阻塞的,應用無法知道什么時候消息讀完了,就存在了半包問題!

半包問題

簡單看一下下面的圖就能理解半包問題了!

我們知道 TCP/IP 在發(fā)送消息的時候,可能會拆包(如上圖 1)! 這就導致接收端無法知道什么時候收到的數據是一個完整的數據。例如: 發(fā)送端分別發(fā)送了 ABC,DEF,GHI 三條信息,發(fā)送時被拆成了 AB,CDRFG,H,I 這四個包進行發(fā)送,接受端如何將其進行還原呢? 在 BIO 模型中,當讀不到數據后會阻塞,而 NIO 中不會! 所以需要自行進行處理! 例如,以換行符作為判斷依據,或者定長消息發(fā)生,或者自定義協議!

NIO 雖然性能高,但是編碼復雜,且需要處理半包問題! 為了方便的進行 NIO 開發(fā),就有了 Reactor 模型!

Reactor 模型

AWT Events

Reactor 模型和 AWT 事件模型很像,就是將消息放到了一個隊列中,通過異步線程池對其進行消費!

Reactor 中的組件

Reactor:Reactor 是 IO 事件的派發(fā)者。

Acceptor:Acceptor 接受 client 連接,建立對應 client 的 Handler,并向 Reactor 注冊此 Handler。

Handler: 和一個 client 通訊的實體,按這樣的過程實現業(yè)務的處理。一般在基本的 Handler 基礎上還會有更進一步的層次劃分,  用來抽象諸如 decode,process 和 encoder 這些過程。比如對 Web Server 而言,decode 通常是 HTTP 請求的解析, process 的過程會進一步涉及到 Listener 和 Servlet 的調用。業(yè)務邏輯的處理在 Reactor 模式里被分散的 IO 事件所打破,  所以 Handler 需要有適當的機制在所需的信息還不全 (讀到一半) 的時候保存上下文,并在下一次 IO 事件到來的時候 (另一半可讀了) 能繼續(xù)中斷的處理。為了簡化設計,Handler 通常被設計成狀態(tài)機,按 GoF 的 state  pattern 來實現。

對應上面的 NIO 代碼來看:

Reactor:相當于有分發(fā)功能的 Selector

Acceptor:NIO 中建立連接的那個判斷分支

Handler:消息讀寫處理等操作類

Reactor 從線程池和 Reactor 的選擇上可以細分為如下幾種:

Reactor 單線程模型

這個模型和上面的 NIO 流程很類似,只是將消息相關處理獨立到了 Handler 中去了!

雖然上面說到 NIO 一個線程就可以支持所有的 IO 處理。但是瓶頸也是顯而易見的! 我們看一個客戶端的情況,如果這個客戶端多次進行請求,如果在 Handler 中的處理速度較慢,那么后續(xù)的客戶端請求都會被積壓,導致響應變慢! 所以引入了 Reactor 多線程模型!

Reactor 多線程模型

Reactor 多線程模型就是將 Handler 中的 IO 操作和非 IO 操作分開,操作 IO 的線程稱為 IO 線程,非 IO 操作的線程稱為工作線程! 這樣的話,客戶端的請求會直接被丟到線程池中,客戶端發(fā)送請求就不會堵塞!

但是當用戶進一步增加的時候,Reactor 會出現瓶頸! 因為 Reactor 既要處理 IO 操作請求,又要響應連接請求! 為了分擔 Reactor 的負擔,所以引入了主從 Reactor 模型!

主從 Reactor 模型

主 Reactor 用于響應連接請求,從 Reactor 用于處理 IO 操作請求!

Netty

Netty 是一個高性能 NIO 框架,其是對 Reactor 模型的一個實現!

Netty 客戶端代碼

EventLoopGroup workerGroup = new NioEventLoopGroup(); try { Bootstrap b = new Bootstrap(); b.group(workerGroup); b.channel(NioSocketChannel.class); b.option(ChannelOption.SO_KEEPALIVE, true); b.handler(new ChannelInitializer SocketChannel () { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new TimeClientHandler()); } }); ChannelFuture f = b.connect(host, port).sync(); f.channel().closeFuture().sync(); } finally { workerGroup.shutdownGracefully(); }

Netty Client Handler

public class TimeClientHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { ByteBuf m = (ByteBuf) msg; try { long currentTimeMillis = (m.readUnsignedInt() - 2208988800L) * 1000L; System.out.println(new Date(currentTimeMillis)); ctx.close(); } finally { m.release(); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); ctx.close(); } }

Netty 服務端代碼

EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer SocketChannel () { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new TimeServerHandler()); } }) .option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true); // Bind and start to accept incoming connections. ChannelFuture f = b.bind(port).sync(); f.channel().closeFuture().sync(); } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); }

Netty Server Handler

public class TimeServerHandler extends ChannelInboundHandlerAdapter { @Override public void channelActive(final ChannelHandlerContext ctx) { final ByteBuf time = ctx.alloc().buffer(4); time.writeInt((int) (System.currentTimeMillis() / 1000L + 2208988800L)); final ChannelFuture f = ctx.writeAndFlush(time); f.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) { assert f == future; ctx.close(); } }); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); ctx.close(); } }

我們從 Netty 服務器代碼來看,與 Reactor 模型進行對應!

EventLoopGroup 就相當于是 Reactor,bossGroup 對應主 Reactor,workerGroup 對應從 Reactor

TimeServerHandler 就是 Handler

child 開頭的方法配置的是客戶端 channel,非 child 開頭的方法配置的是服務端 channel

具體 Netty 內容,請訪問 Netty 官網!

Netty 的問題

Netty 開發(fā)中一個很明顯的問題就是回調,一是打破了線性編碼習慣,

二就是 Callback Hell!

看下面這個例子:

a.doing1(); //1 a.doing2(); //2 a.doing3(); //3

1,2,3 處代碼如果是同步的,那么將按順序執(zhí)行! 但是如果不是同步的呢? 我還是希望 2 在 1 之后執(zhí)行,3 在 2 之后執(zhí)行! 怎么辦呢? 想想 AJAX! 我們需要寫類似如下這樣的代碼!

a.doing1(new Callback(){ public void callback(){ a.doing2(new Callback(){ public void callback(){ a.doing3(); } }) } });

那有沒有辦法解決這個問題呢? 其實不難,實現一個類似 Future 的功能! 當 Client 獲取結果時,進行阻塞,當得到結果后再繼續(xù)往下走! 實現方案,一個就是使用鎖了,還有一個就是使用 RingBuffer。經測試,使用 RingBuffer 比使用鎖 TPS 有 2000 左右的提高!

如何分析高性能服務器 Server 中的 Reactor 模型

感謝大家的閱讀,以上就是“如何分析高性能服務器 Server 中的 Reactor 模型”的全部內容了,學會的朋友趕緊操作起來吧。相信丸趣 TV 丸趣 TV 小編一定會給大家?guī)砀鼉?yōu)質的文章。謝謝大家對丸趣 TV 網站的支持!

正文完
 
丸趣
版權聲明:本站原創(chuàng)文章,由 丸趣 2023-08-04發(fā)表,共計9488字。
轉載說明:除特殊說明外本站除技術相關以外文章皆由網絡搜集發(fā)布,轉載請注明出處。
評論(沒有評論)
主站蜘蛛池模板: 牟定县| 南充市| 嵩明县| 贺兰县| 咸阳市| 元谋县| 阿合奇县| 嘉义市| 许昌市| 泰兴市| 平舆县| 镇赉县| 大安市| 台中县| 广东省| 金川县| 通辽市| 卢湾区| 宁南县| 商河县| 尼玛县| 莆田市| 同德县| 长汀县| 酒泉市| 孝义市| 五莲县| 榕江县| 小金县| 汶川县| 阳西县| 沾益县| 罗定市| 岳阳市| 陇南市| 海南省| 尼木县| 田林县| 方正县| 石柱| 忻城县|