目錄
1. TCP注暗、UDP
2. Java Tcp
3. Java Upd
4. 降級的幾種方式
5. 降級的實現(xiàn)方式之一
6. 故障中降級的意義
7. 總結(jié)及展望
1. TCP炼幔、UDP
TCP/IP協(xié)議棧主要分為四層:應用層、傳輸層扫沼、網(wǎng)絡層、數(shù)據(jù)鏈路層,每層都有相應的協(xié)議庄吼,所謂的協(xié)議就是雙方進行數(shù)據(jù)傳輸?shù)囊环N格式。
應用層:用戶進程
傳輸層:TCP严就、UDP
網(wǎng)絡層:ICMP总寻、IP、IGMP
鏈路層:ARP梢为、硬件接口渐行、RARP
-
網(wǎng)絡中,一幀以太網(wǎng)數(shù)據(jù)包的格式如下:
| Ethernet頭 | IP頭 | TCP/UDP頭 | 數(shù)據(jù) |
image在Linux 操作系統(tǒng)中铸董,當我們想發(fā)送數(shù)據(jù)的時候祟印,我們只需要在上層準備好數(shù)據(jù),然后提交給內(nèi)核協(xié)議棧 , 內(nèi)核協(xié)議棧自動添加相應的協(xié)議頭粟害。
1.1. TCP
TCP協(xié)議是面向連接蕴忆、保證高可靠性(數(shù)據(jù)無丟失、數(shù)據(jù)無失序悲幅、數(shù)據(jù)無錯誤套鹅、數(shù)據(jù)無重復到達)傳輸層協(xié)議。
-
TCP頭分析
image
(1)端口號[16bit]
網(wǎng)絡實現(xiàn)的是不同主機的進程間通信汰具。在一個操作系統(tǒng)中卓鹿,有很多進程,當數(shù)據(jù)到來時要提交給哪個進程進行處理呢?這就需要用到端口號留荔。在TCP頭中吟孙,有源端口號(Source Port)和目標端口號(Destination Port)。源端口號標識了發(fā)送主機的進程,目標端口號標識接受方主機的進程。
(2)序號[32bit]
序號分為發(fā)送序號(Sequence Number)和確認序號(Acknowledgment Number)杰妓。
發(fā)送序號:用來標識從 TCP源端向 TCP目的端發(fā)送的數(shù)據(jù)字節(jié)流藻治,它表示在這個報文段中的第一個數(shù)據(jù)字節(jié)的順序號。如果將字節(jié)流看作在兩個應用程序間的單向流動稚失,則 TCP用順序號對每個字節(jié)進行計數(shù)栋艳。序號是 32bit的無符號數(shù),序號到達 2 32- 1后又從 0開始句各。當建立一個新的連接時吸占, SYN標志變 1,順序號字段包含由這個主機選擇的該連接的初始順序號 ISN( Initial Sequence Number)凿宾。
確認序號:包含發(fā)送確認的一端所期望收到的下一個順序號矾屯。因此,確認序號應當是上次已成功收到數(shù)據(jù)字節(jié)順序號加 1初厚。只有 ACK標志為 1時確認序號字段才有效件蚕。 TCP為應用層提供全雙工服務,這意味數(shù)據(jù)能在兩個方向上獨立地進行傳輸产禾。因此排作,連接的每一端必須保持每個方向上的傳輸數(shù)據(jù)順序號。
(3)偏移[4bit]
這里的偏移實際指的是TCP首部的長度亚情,它用來表明TCP首部中32 bit字的數(shù)目妄痪,通過它可以知道一個TCP包它的用戶數(shù)據(jù)是從哪里開始的。這個字段占4bit,如4bit的值是0101,則說明TCP首部長度是5 * 4 = 20字節(jié)楞件。 所以TCP的首部長度最大為15 * 4 = 60字節(jié)衫生。然而沒有可選字段,正常長度為20字節(jié)土浸。
(4)Reserved [6bit]
目前沒有使用罪针,它的值都為0
(5)標志[6bit]
在TCP首部中有6個標志比特。他們中的多個可同時被置為1 黄伊。
URG 緊急指針(urgent pointer)有效
ACK 確認序號有效
PSH 指示接收方應該盡快將這個報文段交給應用層而不用等待緩沖區(qū)裝滿
RST 一般表示斷開一個連接
例如:一個TCP的客戶端向一個沒有監(jiān)聽的端口的服務器端發(fā)起連接,wirshark抓包如下
可以看到host:192.168.63.134向host:192.168.63.132發(fā)起連接請求泪酱,但是host:192.168.63.132并沒有處于監(jiān)聽對應端口的服務器端,這時
host : 192.168.63.132發(fā)一個RST置位的TCP包斷開連接。
SYN 同步序號用來發(fā)起一個連接
FIN 發(fā)送端完成發(fā)送任務(即斷開連接)
(6)窗口大小(window)[16bit]
窗口的大小毅舆,表示源方法最多能接受的字節(jié)數(shù)西篓。。
(7)校驗和[16bit]
校驗和覆蓋了整個的TCP報文段:TCP首部和TCP數(shù)據(jù)憋活。這是一個強制性的字段岂津,一定是由發(fā)端計算和存儲,并由收端進行驗證悦即。
(8)緊急指針[16bit]
只有當URG標志置為1時緊急指針才有效吮成。緊急指針是一個正的偏移量橱乱,和序號字段中的值相加表示緊急數(shù)據(jù)最后一個字節(jié)的序號。TCP的緊急方式是發(fā)送端向另一端發(fā)送緊急數(shù)據(jù)的一種方式粱甫。
(9)TCP選項
是可選的,在后面抓包的時候泳叠,我們在看看它
- ==TCP 三次握手建立連接==
a.請求端(通常稱為客戶)發(fā)送一個SYN段指明客戶打算連接的服務器的端口,以及初始序號(ISN,在這個例子中為1415531521)茶宵。這個SYN段為報文段1危纫。
b.服務器發(fā)回包含服務器的初始序號的SYN報文段(報文段2)作為應答。同時乌庶,將確認序號設(shè)置為客戶的ISN加1以對客戶的SYN報文段進行確認种蝶。一個SYN將占用一個序號
c.客戶必須將確認序號設(shè)置為服務器的ISN加1以對服務器的SYN報文段進行確認(報文段3)
這三個報文段完成連接的建立。這個過程也稱為三次握手(three-way handshake)
- ==TCP 四次揮手斷開連接==
a.現(xiàn)在的網(wǎng)絡通信都是基于socket實現(xiàn)的瞒大,當客戶端將自己的socket進行關(guān)閉時螃征,內(nèi)核協(xié)議棧會向服務器自動發(fā)送一個FIN置位的包,請求斷開連接透敌。我們稱首先發(fā)起斷開請求的一方稱為主動斷開方盯滚。
b.服務器端收到請客端的FIN斷開請求后,內(nèi)核協(xié)議棧會立即發(fā)送一個ACK包作為應答酗电,表示已經(jīng)收到客戶端的請求
c.服務器運行一段時間后魄藕,關(guān)閉了自己的socket。這個時候內(nèi)核協(xié)議棧會向客戶端發(fā)送一個FIN置位的包撵术,請求斷開連接
d.客戶端收到服務端發(fā)來的FIN斷開請求后泼疑,會發(fā)送一個ACK做出應答,表示已經(jīng)收到服務端的請求
- TCP可靠性的保證
TCP采用一種名為“帶重傳功能的肯定確認(positive acknowledge with retransmission)”的技術(shù)作為提供可靠數(shù)據(jù)傳輸服務的基礎(chǔ)荷荤。這項技術(shù)要求接收方收到數(shù)據(jù)之后向源站回送確認信息ACK。發(fā)送方對發(fā)出的每個分組都保存一份記錄移稳,在發(fā)送下一個分組之前等待確認信息蕴纳。發(fā)送方還在送出分組的同時啟動一個定時器,并在定時器的定時期滿而確認信息還沒有到達的情況下个粱,重發(fā)剛才發(fā)出的分組古毛。圖3-5表示帶重傳功能的肯定確認協(xié)議傳輸數(shù)據(jù)的情況,圖3-6表示分組丟失引起超時和重傳都许。為了避免由于網(wǎng)絡延遲引起遲到的確認和重復的確認稻薇,協(xié)議規(guī)定在確認信息中稍帶一個分組的序號,使接收方能正確將分組與確認關(guān)聯(lián)起來胶征。
從圖 3-5可以看出塞椎,雖然網(wǎng)絡具有同時進行雙向通信的能力,但由于在接到前一個分組的確認信息之前必須推遲下一個分組的發(fā)送睛低,簡單的肯定確認協(xié)議浪費了大量寶貴的網(wǎng)絡帶寬案狠。為此服傍, TCP使用滑動窗口的機制來提高網(wǎng)絡吞吐量,同時解決端到端的流量控制骂铁。
- 滑動窗口技術(shù)
滑動窗口技術(shù)是簡單的帶重傳的肯定確認機制的一個更復雜的變形吹零,它允許發(fā)送方在等待一個確認信息之前可以發(fā)送多個分組。如圖 3-7所示拉庵,發(fā)送方要發(fā)送一個分組序列灿椅,滑動窗口協(xié)議在分組序列中放置一個固定長度的窗口,然后將窗口內(nèi)的所有分組都發(fā)送出去钞支;當發(fā)送方收到對窗口內(nèi)第一個分組的確認信息時茫蛹,它可以向后滑動并發(fā)送下一個分組;隨著確認的不斷到達伸辟,窗口也在不斷的向后滑動麻惶。
### 1.2. UDP
-
UDP協(xié)議
UDP協(xié)議也是傳輸層協(xié)議,它是無連接信夫,不保證可靠的傳輸層協(xié)議窃蹋。它的協(xié)議頭比較簡單
2. TCP java實現(xiàn)
- TCPServer
import java.io.*;
import java.net.*;
class TCPServer{
public static void main(String[] args)throws IOException{
ServerSocket listen = new ServerSocket(5050);
Socket server = listen.accept();
InputStream in = server.getInputStream();
OutputStream out = server.getOutputStream();
char c = (char)in.read();
System.out.println("收到:" + c);
out.write('s');
out.close();
in.close();
server.close();
listen.close();
}
}
- TCPClient
import java.io.*;
import java.net.*;
class TCPClient{
public static void main(String[] args)throws IOException{
Socket client = new Socket("127.0.0.1" , 5050);
InputStream in = client.getInputStream();
OutputStream out = client.getOutputStream();
out.write('c');
char c = (char)in.read();
System.out.println("收到:" + c);
out.close();
in.close();
client.close();
}
}
3. UDP java實現(xiàn)
- UDP和TCP有兩個典型的區(qū)別坚芜,一個就是它不需要建立連接墨技,另外就是它在每次收發(fā)的報文都保留了消息的邊界。
-
server端 因為UDP協(xié)議不需要建立連接缔刹,它的過程如下:
- 構(gòu)造DatagramSocket實例振湾,指定本地端口杀迹。
- 通過DatagramSocket實例的receive方法接收DatagramPacket.DatagramPacket中間就包含了通信的內(nèi)容。
- 通過DatagramSocket的send和receive方法來收和發(fā)DatagramPacket.
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
public class UDPSendTest {
public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket();
byte[] buffer = "hello xiaobin".getBytes();
DatagramPacket dp = new DatagramPacket(buffer,buffer.length, InetAddress.getByName("127.0.0.1"),7879);
socket.send(dp);
socket.close();
}
}
-
client端 UDP客戶端的步驟也比較簡單押搪,主要包括下面3步:
- 構(gòu)造DatagramSocket實例树酪。
- 通過DatagramSocket實例的send和receive方法發(fā)送DatagramPacket報文。
- 結(jié)束后大州,調(diào)用DatagramSocket的close方法關(guān)閉续语。
- 因為和TCP不同,UDP發(fā)送報文的時候可以在同一個本地端口隨意發(fā)送給不同的服務器厦画,一般不需要在UDP的DatagramSocket的構(gòu)造函數(shù)中指定目的服務器的地址疮茄。
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UDPReceiveTest {
public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket(7879);
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf,buf.length);
socket.receive(dp);
System.out.println("host : " + dp.getAddress().getHostAddress()+ " : " + dp.getPort() + " : " + dp.getSocketAddress());
System.out.println(new String(dp.getData(),0,dp.getLength()));
socket.close();
}
}
4. 服務降級
- 對于RPC服務來說,核心是遠程調(diào)用根暑,服務降級屬于服務治理方面的內(nèi)容力试。
- 降級方式
- 手動降級:服務超時超過一定比例后,手動將服務關(guān)閉
- 自動降級:根據(jù)服務請求的通過率排嫌,自動處理畸裳。通過率 = 成功的請求 / 總請求
- 服務統(tǒng)計:列表頁的服務在請求時,是按key值進行請求不同的方法躏率,底層去掉用不同的服務獲取數(shù)據(jù)躯畴。因此根據(jù)這個前提條件民鼓,可以實現(xiàn)服務的手動和自動降級
- 手動降級:根據(jù)服務管理平臺的報警,當調(diào)用底層服務發(fā)生超時或異常時蓬抄,判斷是否需要進行手動處理丰嘉,手動屏蔽相關(guān)的調(diào)用key。
- 自動降級:在數(shù)據(jù)返回時嚷缭,根據(jù)是否發(fā)生異常判斷服務的成功或失敗情況饮亏,統(tǒng)計服務的通過率,當?shù)讓臃瞻l(fā)生較多異常時阅爽,會自動屏蔽對該key的調(diào)用路幸。
- 降級實現(xiàn)
- 手動降級,通過控制調(diào)用key來訪問底層的服務獲取數(shù)據(jù)付翁,key可以通過多種方式傳遞到服務入口:
- 通過服務接口
- 通過zk
- 通過發(fā)送udp命令
- 其他方式
- 自動降級简肴,計算服務的通過率可以根據(jù)統(tǒng)計的服務成功量和失敗量來統(tǒng)計,對于降級策略百侧,需要慎重選擇砰识,根據(jù)服務的通過率來設(shè)置一定比例的控制,在自動降級中實現(xiàn)快速失敗佣渴,緩慢升級的原則來處理有問題的服務辫狼。
- 手動降級,通過控制調(diào)用key來訪問底層的服務獲取數(shù)據(jù)付翁,key可以通過多種方式傳遞到服務入口:
5. 降級實現(xiàn)
手動降級:通過增加黑名單監(jiān)聽,在服務進行請求時辛润,先查詢屏蔽名單中是否有該服務膨处,根據(jù)判斷結(jié)果進行處理。服務在啟動時砂竖,同時啟動一個監(jiān)聽線程真椿,監(jiān)聽一個端口,當有需要降級的key時乎澄,可以通過發(fā)送udp命令的方式將key發(fā)送過來瀑粥,啟動對該key的降級,服務重啟后失效三圆。定義發(fā)送的upd命令:list、add避咆、clear
BlackListListener 創(chuàng)建命令接口
public interface BlackListListener {
void addAll(List<String> items);
void removeAll(List<String> items);
void clear();
Set<String> list();
boolean contains(String item);
boolean isEmpty();
}
- CommandServer 接收命令的監(jiān)聽方法舟肉,BlackListListener設(shè)置相關(guān)的屏蔽內(nèi)容,服務啟動時初始化該方法查库,直接貼代碼
public class CommandServer extends Thread{
private static final Logger LOG = LoggerFactory.getLogger(CommandServer.class);
private DatagramSocket serverSocket;
volatile boolean running = true;
private BlackListListener black = BlackListenerService.getInstance();
public void serverStart() throws SocketException {
try {
Config config = new Config(Dict.APP_CONFIG_PROPERTIES);
int port = ConfigUtil.getPropertyCount(config, "WORK.COMMAND.SERVER.PORT", 9991);
LOG.info("start a DatagramSocket with port :" + port);
serverSocket = new DatagramSocket(port);
} catch (IOException e) {
e.printStackTrace();
}
}
public void serverStop(){
this.setDaemon(true);
this.running = false;
if (serverSocket != null){
serverSocket.close();
}
}
public void start(){
try {
this.serverStart();
super.start();
} catch (SocketException e) {
e.printStackTrace();
}
}
public void run(){
while (running){
try {
byte[] receiveBuff = new byte[4096];
DatagramPacket dp = new DatagramPacket(receiveBuff, receiveBuff.length);
serverSocket.receive(dp);
if (dp.getLength() == 0){
continue;
}else{
String receiveStr = new String(dp.getData(), "UTF-8");
receiveStr = receiveStr.replace('\n',' ').replace('\r',' ').trim();
LOG.info(String.format("input[%s], source ip:[%s]", receiveStr, dp.getAddress()));
InetAddress addr = dp.getAddress();
int port = dp.getPort();
DatagramPacket dpc = output(addr, port, parser(receiveStr));
serverSocket.send(dpc);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public String parser(String cmd){
String backString = "Error command!";
if (StringUtils.isEmpty(cmd)){
return backString + "\r\n";
}
cmd = cmd.trim();
if ("list".equals(cmd)) {
backString = "["+Joiner.on(",").join(black.list())+"]";
} else if (cmd.startsWith("add ")) {
cmd = cmd.replaceFirst("add ", "");
String[] cmdArray = cmd.split("\\s");
black.addAll(Arrays.asList(cmdArray));
backString = "["+Joiner.on(",").join(black.list())+"]";
} else if (cmd.startsWith("remove ")) {
cmd = cmd.replaceFirst("remove ", "");
String[] cmdArray = cmd.split("\\s");
black.removeAll(Arrays.asList(cmdArray));
backString = "["+Joiner.on(",").join(black.list())+"]";
} else if (cmd.equalsIgnoreCase("clear")) {
black.clear();
backString = "["+Joiner.on(",").join(black.list())+"]";
} else if(cmd.startsWith("up ")){
cmd = cmd.replaceFirst("up ", "");
String[] cmdArray = cmd.split("\\s");
if (cmdArray.length==0){
backString = "help:ignore name name";
}else{
for(String name: cmdArray){
CrossRateManager.getCrossRateManager().ignore(name, false);
}
backString = CrossRateManager.getCrossRateManager().list();
}
}else if(cmd.startsWith("down ")) {
cmd = cmd.replaceFirst("down ", "");
String[] cmdArray = cmd.split("\\s");
if (cmdArray.length == 0) {
backString = "help:ignore name name";
} else {
for (String name : cmdArray) {
CrossRateManager.getCrossRateManager().ignore(name, true);
}
backString = CrossRateManager.getCrossRateManager().list();
}
}
backString += "\r\n";
return backString;
}
private DatagramPacket output(InetAddress addr, int port, String text){
DatagramPacket dp = new DatagramPacket(text.getBytes(), text.getBytes().length, addr, port);
return dp;
}
public static void main(String[] args) {
CommandServer cs = new CommandServer();
cs.start();
}
}
6. 降級意義
- 根據(jù)線上生產(chǎn)環(huán)境發(fā)生故障的經(jīng)驗路媚,我們對服務進行了統(tǒng)計,按服務的重要性進行分級:基礎(chǔ)信息樊销、擴展信息整慎,在必要時脏款,可以對兩者進行分別降級。
參考: