溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

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

發(fā)布時間:2022-01-12 17:15:56 來源:億速云 閱讀:124 作者:柒染 欄目:服務器

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

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

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

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

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

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

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

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

C/S架構(gòu)可以抽象為如下模型:

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

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

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

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

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

高性能服務器

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

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

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

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

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

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

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

Socket

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

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

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

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

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

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

套接字這個翻譯已經(jīng)是標準了,不糾結(jié)這個了!

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

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

  • 服務端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請求是否會被阻塞,如果阻塞直到完成那么就是傳統(tǒng)的阻塞IO;如果不阻塞,那么就是非阻塞IO

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

舉個不太恰當?shù)睦?:比如你家網(wǎng)絡斷了,你打電話去中國電信報修!

  • 你撥號—-客戶端連接服務器

  • 電話通了—-連接建立

  • 你說:“我家網(wǎng)斷了,幫我修下”—-發(fā)送消息

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

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

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

  • 如果客服說:“馬上幫你處理,好了通知你”,然后掛了電話—-異步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的入門級代碼了

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

模型圖如下所示:

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

BIO優(yōu)缺點

優(yōu)點

  • 模型簡單

  • 編碼簡單

缺點

  • 性能瓶頸低

優(yōu)缺點很明顯。這里主要說下缺點:主要瓶頸在線程上。每個連接都會建立一個線程。雖然線程消耗比進程小,但是一臺機器實際上能建立的有效線程有限,以Java來說,1.5以后,一個線程大致消耗1M內(nèi)存!且隨著線程數(shù)量的增加,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()){ //有可讀數(shù)據(jù)事件。            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()){//有可讀數(shù)據(jù)事件            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模型示例如下:

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

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

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

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

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

NIO優(yōu)缺點

優(yōu)點

  • 性能瓶頸高

缺點

  • 模型復雜

  • 編碼復雜

  • 需處理半包問題

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

半包問題

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

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

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

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

Reactor模型

  • AWT Events

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

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

Reactor中的組件

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

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

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

對應上面的NIO代碼來看:

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

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

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

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

Reactor單線程模型

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

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

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

Reactor多線程模型

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

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

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

主從Reactor模型

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

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

Netty

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

  • 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內(nèi)容,請訪問Netty官網(wǎng)!

Netty的問題

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

二就是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();             }         })     } });

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

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

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

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

AI