Day13.高效能RPC設計 學習筆記1
一、引言
- 系統架構演變
隨著網際網路的發展,網站應用的規模不斷擴大,常規的垂直應用(MVC)架構已無法應對,分散式服務架構以及流動計算架構(伸縮性)勢在必行,亟需一個治理系統確保架構有條不紊的演進。
- 單一架構:例如早期servlet/jsp - ORM(物件關係對映) Hibernate|MyBatis
- 垂直架構:將一個應用分層,實現協同開發,便於後期專案升級維護 - MVC Struts1/2|SpringMVC
- 分散式服務:提升服務整體效能,將一些模組獨立出去(SOA service orented architecture),獨立執行,隨著業務發展,不可避免需要排程第三方服務(物流資訊、第三支付)。RPC
- 流動式計算架構:要求服務提供方能夠根據負載情況,動態增加或者減少。同時實現服務視覺化,簡化服務運維,提升系統資源的利用率。 - SOA治理框架
微服務
- 對服務進行集中管理調控。
-
高併發網際網路設計原則 ——
高負載
、高儲存
、高容錯
也稱AKF拆分原則
X軸
:服務水平擴充套件
服務具有可複製性 ,水平分割
,解決一些併發問題
(水平拓展,比效能的垂直提升成本低-摩爾定律)
Y軸
:服務間各司其職
弱耦合,垂直分割
,物理節點物盡其用
(泳道設計)
Z軸
x,y
打包,進行物理隔離。
X中有Y,Y中有X
x和y互相轉換
y中有x:對y軸做垂直拆分,層與層間獨立,為了保證每一層的可用性,每一層做水平分割 -
高併發的場景問題 —— 優化思路
如何讓服務支援x軸的水平擴充套件,利用分散式的思維解決高併發;如何做y中的垂直拆分,是應用之間儘可能隔離,弱化服務件耦合度;結合兩個維度來解決高併發的場景問題。(Socket + IO)
二、網路基礎(TCP/IP)
-
網路程式設計:通過編碼的方式,讓不同計算機之間相互通訊(資料的傳遞)。
-
兩個核心問題:
定址問題:ip地址+port埠號
協議問題:資料傳輸格式 -
OSI 七層模型(標準,瞭解就好)
-
OSI 五層模型(七層的轉變)
-
協議
http協議:應用層協議,超文字傳輸協議,不同計算機間傳輸文字的協議(字串),底層tcp協議
tcp/ip協議:傳輸層協議
tcp協議:傳輸安全,效率低
udp協議:傳輸不安全,效率高
- 瞭解
應用層(Http協議:WebService,tomcat協議)——普通程式設計師
傳輸層(TCP/IP UDP:基於tcp/ip封裝的ftp、smtp/pop3、websocket…定製協議rpc協議)——架構程式設計師 也是程式碼最終觸及到的地方
物理層(嵌入式開發)
三、傳統 BIO 網路程式設計模型(TCP/IP)
- IO模型分類
-
BIO 同步阻塞IO
傳統IO 或者 Blocking IO
特點:面向流Input | Output
【每個執行緒只能處理一個channel(同步的,該執行緒和該channel繫結)。
執行緒發起IO請求,不管核心是否準備好IO操作,從發起請求起,執行緒一直阻塞,直到操作完成。】 -
NIO 同步非阻塞IO
New IO 或者Non Blocking IO
特點:面向緩衝區Buffer(基於通道)
【每個執行緒可以處理多個channel(非同步)。
執行緒發起IO請求,立即返回;核心在做好IO操作的準備之後,通過呼叫註冊的回撥函式通知執行緒做IO操作,執行緒開始阻塞,直到操作完成 。】 -
AIO(Async Non Blocking IO) 非同步非阻塞
【執行緒發起IO請求,立即返回;記憶體做好IO操作的準備之後,做IO操作,直到操作完成或者失敗,通過呼叫註冊的回撥函式通知執行緒做IO操作完成或者失敗 。】
-
BIO
方向—— 輸入、輸出流(InputStream | OutputStream)
型別—— 位元組、字元流(Reader | Writer)
功能—— 節點、過濾流(BufferedInputStream | BufferedOutputStream ) -
BIO網路程式設計
-
服務端:ServerSocket 【接收和響應,
請求轉發
-ServerSocket
、請求響應
-Socket
】
①初始化伺服器ServerSocket,繫結監聽埠
②等待客戶端連線serverSocket.accept();
③處理請求/響應sockect.getInputStream(); / socket.getOutputStream();
④關閉資源 -
客戶端:Socket 【傳送和接收,傳送請求|接收響應:
Socket
】
①初始化客戶端Socket,繫結伺服器IP/埠
②發起請求/獲取響應socket.getOutputStream(); / socket.getInputStream();
③關閉資源
- 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代表一個系統的隨機埠,用來在服務和客戶端之間建立連線。
- BIO網路程式設計問題
通過將相應處理和轉發進行分離,但是在轉發的時候,伺服器是通過先開啟執行緒資源(計算),然後在計算中動用IO操作,IO存在匯流排佔用問題,導致當前執行緒操作的IO資源沒有就緒,導致系統計算資源的浪費。如果要解決該問題,必須先考慮讓IO就緒,然後在開啟執行緒處理或者理解為讓執行緒去處理一些非阻塞的IO操作。
BIO程式設計詬病 :多執行緒模型解決服務端併發,但是無法判斷當前處理的IO狀態是否就緒,此時就會導致執行緒在做無畏等待,導致系統資源利用率不高。 先開執行緒 -> 線上程等待IO就緒->處理IO->執行緒資源釋放
四、NIO程式設計模型
- Channel(FileChannel-讀寫檔案、ServerSocketChannel-請求轉發、SocketChannel-響應處理)
- ByteBuffer (位元組緩衝區)
- 建立FileChannel
//可讀的FileChannel
FileChannel fr=new FileInputStream("xxx路徑").getChannel();
//可寫的FileChannel
FileChannel fw=new FileOutputStream("xxx路徑").getChannel();
檔案操作
//讀檔案件
fr.read(緩衝區)
//寫檔案
fw.write(緩衝區)
- 位元組緩衝區
flip:pos賦值給limit,post歸0
clear:恢復變數為初始狀態
- 一個基於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();
}
}
- “掛羊頭,賣狗肉” —— 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){
}
}
});
}
}
}
- 通道選擇器
NIO核心思想,將網路程式設計中所有操作(轉發,讀請求,寫響應)封裝成事件Event,之後用事件形式通知程式進行計算,事件發生則通知程式,這樣處理的IO都是就緒的IO,保證計算資源的充分利用。這時候,引入通道選擇器
的概念。
我們需要把通道註冊到通道選擇器的註冊列表中,由通道選擇器維護註冊列表;一旦我們所關注的事件發生了,他會把關注的事件封裝到事件處理列表當中;為了防止處理空或無效IO,每個事件處理結束後,要從列表中移除;移除不代表取消註冊。只有兩種可能才取消註冊,一種是通道關閉,一種是主動登出。