Day13.高性能RPC設計 學習筆記1

1、引言

  1. 系統架構演變
    隨着互聯網的發展,網站應用的規模不斷擴大,常規的垂直應用(MVC)架構已沒法應對,分佈式服務架構以及流動計算架構(伸縮性)勢在必行,亟需一個治理系統確保架構有條不紊的演進。
    圖01
  • 單一架構:例如早期servlet/jsp - ORM(對象關係映射) Hibernate|MyBatis
  • 垂直架構:將一個應用分層,實現協同開發,便於後期項目升級維護 - MVC Struts1/2|SpringMVC
  • 分佈式服務:提高服務總體性能,將一些模塊獨立出去(SOA service orented architecture),獨立運行,隨着業務發展,不可避免須要調度第三方服務(物流信息、第三支付)。RPC - Remote Process Call (即時消息) webservice、Dubbo、SpringCloud
  • 流動式計算架構:要求服務提供方可以根據負載狀況,動態增長或者減小。同時實現服務可視化,簡化服務運維,提高系統資源的利用率。 - SOA治理框架 微服務 - 對服務進行集中管理調控。
  1. 高併發互聯網設計原則 —— 高負載高存儲高容錯 也稱 AKF拆分原則
    X軸:服務水平擴展
    服務具備可複製性 ,水平分割 ,解決一些併發問題
    (水平拓展,比性能的垂直提高成本低-摩爾定律)
    Y軸:服務間各司其職
    弱耦合,垂直分割,物理節點物盡其用
    (泳道設計)
    Z軸:對x,y打包,進行物理隔離。
    X中有Y,Y中有X x和y互相轉換
    y中有x:對y軸作垂直拆分,層與層間獨立,爲了保證每一層的可用性,每一層作水平分割java

  2. 高併發的場景問題 —— 優化思路
    如何讓服務支持x軸的水平擴展,利用分佈式的思惟解決高併發;如何作y中的垂直拆分,是應用之間儘量隔離,弱化服務件耦合度;結合兩個維度來解決高併發的場景問題。(Socket + IO)程序員

2、網絡基礎(TCP/IP)

  • 網絡編程:經過編碼的方式,讓不一樣計算機之間相互通訊(數據的傳遞)。web

  • 兩個核心問題:
    尋址問題:ip地址+port端口號
    協議問題:數據傳輸格式面試

  • OSI 七層模型(標準,瞭解就好)
    圖02編程

  • OSI 五層模型(七層的轉變)tomcat

  • 協議
    http協議:應用層協議,超文本傳輸協議,不一樣計算機間傳輸文本的協議(字符串),底層tcp協議
    tcp/ip協議:傳輸層協議安全

tcp協議:傳輸安全,效率低
udp協議:傳輸不安全,效率高服務器

  • 瞭解
    應用層(Http協議:WebService,tomcat協議)——普通程序員
    傳輸層(TCP/IP UDP:基於tcp/ip封裝的ftp、smtp/pop三、websocket…定製協議rpc協議)——架構程序員 也是代碼最終觸及到的地方
    物理層(嵌入式開發)

3、傳統 BIO 網絡編程模型(TCP/IP)

  1. IO模型分類
  • BIO 同步阻塞IO
    傳統IO 或者 Blocking IO
    特色:面向流Input | Output
    【每一個線程只能處理一個channel(同步的,該線程和該channel綁定)。
    線程發起IO請求,無論內核是否準備好IO操做,從發起請求起,線程一直阻塞,直到操做完成。】websocket

  • NIO 同步非阻塞IO
    New IO 或者Non Blocking IO
    特色:面向緩衝區Buffer(基於通道)
    【每一個線程能夠處理多個channel(異步)。
    線程發起IO請求,當即返回;內核在作好IO操做的準備以後,經過調用註冊的回調函數通知線程作IO操做,線程開始阻塞,直到操做完成 。】網絡

  • AIO(Async Non Blocking IO) 異步非阻塞
    【線程發起IO請求,當即返回;內存作好IO操做的準備以後,作IO操做,直到操做完成或者失敗,經過調用註冊的回調函數通知線程作IO操做完成或者失敗 。】

  1. BIO
    方向—— 輸入、輸出流(InputStream | OutputStream)
    類型—— 字節、字符流(Reader | Writer)
    功能—— 節點、過濾流(BufferedInputStream | BufferedOutputStream )

  2. BIO網絡編程

  • 服務端:ServerSocket 【接收和響應,請求轉發 - ServerSocket請求響應- Socket
    ①初始化服務器ServerSocket,綁定監聽端口
    ②等待客戶端鏈接serverSocket.accept();
    ③處理請求/響應sockect.getInputStream(); / socket.getOutputStream();
    ④關閉資源

  • 客戶端:Socket 【發送和接收,發送請求|接收響應:Socket
    ①初始化客戶端Socket,綁定服務器IP/端口
    ②發起請求/獲取響應socket.getOutputStream(); / socket.getInputStream();
    ③關閉資源

    socket編程圖

  1. BIO代碼
  • Server
public class BIOBootstrapServer {
    public static void main(String[] args) throws IOException {
        server();
    }
    public static void server() throws IOException {
        //建立服務轉發ServerSocket
        ServerSocket ss = new ServerSocket();
        ss.bind(new InetSocketAddress(9999)); //server能夠省略好hostname

        ExecutorService threadPool= Executors.newFixedThreadPool(10);
        while(true) {
            //等待請求到來,轉發產生Socket(系統內部資源)
            final Socket socket = ss.accept(); //沒有請求就等待,阻塞
            //final 匿名內部類使用局部變量要final修飾
            threadPool.submit(new Runnable() {
                public void run() {
                    try {
                        //利用IO,讀取用戶請求
                        InputStream req = socket.getInputStream();
                        InputStreamReader isr = new InputStreamReader(req);
                        BufferedReader br = new BufferedReader(isr);

                        String line = null;
                        StringBuilder sb = new StringBuilder();
                        while ((line = br.readLine()) != null) {
                            sb.append(line);
                        }
                        System.out.println("服務器收到:" + sb.toString());
                        //給出用戶響應
                        OutputStream res = socket.getOutputStream();
                        PrintWriter pw = new PrintWriter(res);
                        pw.println(new Date().toLocaleString());
                        pw.flush();
                        socket.shutdownOutput(); //告知客戶端寫結束
                        //關閉系統資源
                        socket.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}
  • Client
public class BIOBootstrapClient {
    public static void main(String[] args) throws IOException {
        send("這是一條來自client 的 message!");
    }
    public static void send(String msg) throws IOException {
        //建立Socket對象
        Socket s = new Socket();
        s.connect(new InetSocketAddress("127.0.0.1",9999)); //因此幾乎給ip和端口的,底層走的就是socket

        //發送請求
        OutputStream req = s.getOutputStream();
        PrintWriter pw=new PrintWriter(req);
        pw.println(msg);
        pw.flush();
        s.shutdownOutput();//告知服務端寫結束

        //接收響應
        InputStream res = s.getInputStream();
        InputStreamReader isr = new InputStreamReader(res);
        BufferedReader br = new BufferedReader(isr);
        String line=null;
        StringBuilder sb=new StringBuilder();
        while((line=br.readLine())!=null){
            sb.append(line);
        }
        System.out.println("客戶端收到:"+sb.toString());
        //關閉資源
        s.close();
    }
}

思考下,BIO模型的缺點?
accept轉發產生socket資源,一次請求轉發等待請求響應的過程有阻塞。main線程做accept請求轉發、子線程做socket讀寫IO處理。

  • 機器間通訊是靠傳輸 套接字 完成的。

套接字,是支持TCP/IP的網絡通訊的基本操做單元,能夠看作是不一樣主機之間的進程進行雙向通訊的端點,簡單的說就是通訊的兩方的一種約定,用套接字中的相關函數來完成通訊過程。
簡單的舉例說明下:Socket = Ip address + TCP/UDP + port。

每一個socket表明一個系統的隨機端口,用來在服務和客戶端之間創建鏈接。
在這裏插入圖片描述

  1. BIO網絡編程問題
    經過將相應處理和轉發進行分離,可是在轉發的時候,服務器是經過先開啓線程資源(計算),而後在計算中動用IO操做,IO存在總線佔用問題,致使當前線程操做的IO資源沒有就緒,致使系統計算資源的浪費。若是要解決該問題,必須先考慮讓IO就緒,而後在開啓線程處理或者理解爲讓線程去處理一些非阻塞的IO操做。

BIO編程詬病 :多線程模型解決服務端併發,可是沒法判斷當前處理的IO狀態是否就緒,此時就會致使線程在作無畏等待,致使系統資源利用率不高。 先開線程 -> 在線程等待IO就緒->處理IO->線程資源釋放

4、NIO編程模型

  • Channel(FileChannel-讀寫文件、ServerSocketChannel-請求轉發、SocketChannel-響應處理)
  • ByteBuffer (字節緩衝區)
  1. 建立FileChannel
//可讀的FileChannel
FileChannel fr=new FileInputStream("xxx路徑").getChannel();
//可寫的FileChannel
FileChannel fw=new FileOutputStream("xxx路徑").getChannel();

文件操做

//讀文件件
fr.read(緩衝區)
//寫文件
fw.write(緩衝區)
  1. 字節緩衝區
    圖03

flip:pos賦值給limit,post歸0
clear:恢復變量爲初始狀態

  1. 一個基於NIO的文件拷貝【面試&筆試】
    【NIO沒有流的概念,用通道來處理。】
public class FileCopyDemo {
    public static void main(String[] args) throws IOException {
        //建立讀通道 磁盤讀入數據 到 ByteBuffer
        FileInputStream in = new FileInputStream("C:\\Users\\Administrator\\Desktop\\123.txt");
        FileChannel fcr = in.getChannel(); //fileChannelRead

        //建立寫通道 將 ByteBuffer 數據寫入磁盤
        FileOutputStream out = new FileOutputStream("C:\\Users\\Administrator\\Desktop\\321.txt");
        FileChannel fcw= out.getChannel(); //fileChannelWrite

        //建立ByteBuffer
        ByteBuffer buffer = ByteBuffer.wrap(new byte[1024]);//.allocate()也能夠

        while (true){
            buffer.clear();
            int n = fcr.read(buffer);
            if(n==-1) break;
            buffer.flip();
            fcw.write(buffer);
        }
        //關閉資源
        fcr.close();
        fcw.close();
    }
}
  1. 「掛羊頭,賣狗肉」 —— NIO的API,BIO的編程模型
public class NIOBootstrapServer_Fake {
    public static void main(String[] args) throws IOException {
        server();
    }
    public static void server() throws IOException {
        //建立服務轉發ServerSocket
        ServerSocketChannel ss = ServerSocketChannel.open();
        ss.bind(new InetSocketAddress(9999));
        ExecutorService threadPool= Executors.newFixedThreadPool(10);
        while(true) {
            //等待請求到來,轉發產生Socket(系統內部資源)
            final SocketChannel socket = ss.accept();// 阻塞
            threadPool.submit(new Runnable() {
                public void run() {
                    try{
                        //讀取客戶端響應
                        ByteBuffer buffer=ByteBuffer.allocate(1024);

                        ByteArrayOutputStream baos=new ByteArrayOutputStream();//相似stringbuilder,用來暫存用

                        while (true){
                            buffer.clear();
                            int n = socket.read(buffer);
                            if(n==-1) break;
                            buffer.flip();
                            baos.write(buffer.array(),0,n);
                        }
                        System.out.println("服務器收到:"+new String(baos.toByteArray()));

                        //給出客戶端響應

                        ByteBuffer respBuffer = ByteBuffer.wrap((new Date().toLocaleString()).getBytes());
                        socket.write(respBuffer);

                        socket.shutdownOutput();//告知客戶端寫結束
                        //關閉系統資源
                        socket.close();
                    }catch (Exception e){

                    }
                }
            });
        }
    }
}
  1. 通道選擇器
    圖
    在這裏插入圖片描述

NIO核心思想,將網絡編程中全部操做(轉發,讀請求,寫響應)封裝成事件Event,以後用事件形式通知程序進行計算,事件發生則通知程序,這樣處理的IO都是就緒的IO,保證計算資源的充分利用。這時候,引入通道選擇器的概念。 咱們須要把通道註冊到通道選擇器的註冊列表中,由通道選擇器維護註冊列表;一旦咱們所關注的事件發生了,他會把關注的事件封裝到事件處理列表當中;爲了防止處理空或無效IO,每一個事件處理結束後,要從列表中移除;移除不表明取消註冊。只有兩種可能才取消註冊,一種是通道關閉,一種是主動註銷。

相關文章
相關標籤/搜索