搜索關注微信公眾號"捉蟲大師",后端技術分享,架構設計、性能優化、源碼閱讀、問題排查、踩坑實踐。
協議
協議
通俗易懂地解釋就是通信雙方需要遵循的約定。
我們了解的常見的網絡傳輸協議有tcp、udp、http等。再到我們常用的基礎組件,一般來說client端與server端也有相應的協議,如redis、mysql、zookeeper等都是各自約定的私有協議,同樣今天標題中的dubbo協議也是一種私有協議,他們都是應用層協議,基於tcp或udp設計。
通常應用層協議都是基於tcp和udp,可靠傳輸通常使用tcp,如大多數的基礎組件,如redis、mysql。只有能容忍丟失且需要很高的性能時使用udp協議,比如metric上報等場景。
這里介紹幾種基於tcp的應用協議。
redis協議
redis協議足夠簡單,所以先介紹一下。redis協議基於tcp設計,客戶端和服務器發送的命令一律使用\r\n
(CRLF)結尾。他的格式如下
*<參數數量> CRLF
$<參數1 字節數量> CRLF
<參數1的數據> CRLF
...
$<參數n 字節數量> CRLF
<參數n的數據> CRLF
舉個例子,client向server端發送命令 set mykey myvalue
*3 CRLF
$3 CRLF
SET CRLF
$5 CRLF
mykey CRLF
$7 CRLF
myvalue CRLF
也就是 *3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$7\r\nmyvalue\r\n
關於redis協議更詳細信息可以看這個鏈接:
http://redisdoc.com/topic/protocol.html
http協議
http協議是我們最常見的協議,它的請求報文格式是由三部分組成:
- 請求行:包括method、url、version,由空格分隔,\r\n結尾
- 請求頭:多行,每行是key:value的格式,以\r\n結尾
- 請求體:請求頭與請求體直接由一個空白行分隔,請求體的長度在請求頭中由
content-length
給出
redis和http協議的處理方式截然不同。他們都是基於tcp,而tcp協議傳輸的數據是流式的,通俗地說就是它就像水流,不斷地發送字節,tcp保證不重復,不丟包。而接收端要拿到想要的數據必須得從流式的數據中“判斷出數據包的邊界”,這就是tcp的粘包
問題,解決它通常有三種方法:
- 發送固定長度的消息
- 使用特殊標記區分消息間隔
- 將消息的尺寸和消息一起發送
redis協議使用了第2種,http和接下來要介紹的dubbo協議使用了第3種,固定長度的消息比較理想,在實際中很少遇到。
dubbo協議
由於dubbo支持的協議很多,本文提到的dubbo協議
特指dubbo框架的默認協議,也就是dubbo的私有協議。它的格式如下:
- 0-15: 魔數,判斷是否是dubbo協議
- 16: 判斷是請求還是返回
- 17: 判斷是否期望返回
- 18: 判斷是否為事件消息,如心跳事件
- 19-23: 序列化標志
- 24-31: 標志響應狀態(類似http status)
- 32-63: 請求id
- 64-95: 內容長度(字節)
- 96-?: 序列化后的內容(換行符分隔)
常用的attachments在dubbo協議的哪里?
dubbo的attachments,我們通常將他類比為http協議的header,可以攜帶一些隱式
的參數信息(不用編碼到請求對象中),如壓測標志等。從他的類型
private Map<String, String> attachments;
基本可以推斷出attachments存在於dubbo協議的96字節之后的內容中,因為前面頭的根本放不下這個map。
從dubbo的實現中可以看出,dubbo的一個請求被封裝為一個DecodeableRpcInvocation
對象,里面包含了methodName
、parameterTypes
、arguments
、attachments
等,將該對象序列化后填入dubbo協議的96字節后的內容中發送出去。
使用時,consumer端:
RpcContext.getContext().setAttachment("hello", "from_consumer");
provider端:
RpcContext.getContext().getAttachment("hello");
這里能看出dubbo協議相比較http協議來說設計的還是有所欠缺的,想要拿到一些隱式參數,或者想要知道請求發往哪里,必須得把請求體解析出來才可以,這也是dubbo協議往mesh方向發展的一個絆腳石。
dubbo協議支持在返回值中帶回attachments嗎?
consumer端向provider端發送請求可以在頭部攜帶隱式參數,那么返回時也可以從provider端帶回到consumer端嗎?
比如provider回傳給consumer它自身的處理耗時,consumer計算出請求的響應時間,兩者相減即可得到網絡耗時,此時provider端最好是將耗時放在attachments中隱式地傳回。
dubbo的協議是請求和回復都是相同格式,理論上consumer可以帶隱式參數到provider端,則provider端肯定也可以回傳。
從dubbo的返回對象DecodeableRpcResult
中可以看到是存在attachments
的,但從實際的測試來看,2.7.x版本是不支持的,但2.6.x(>=2.6.3)版本是支持的。
provider端設置:
RpcContext.getServerContext().setAttachment("hello", "from_provider");
consumer端獲取:
RpcContext.getServerContext().getAttachment("hello")
github上相關的issue鏈接如下:
https://github.com/apache/dubbo/pull/1843
協議和序列化有什么區別?
我們可能會經常聽到這樣的說法,dubbo除了dubbo協議外還支持rest、thrift、grpc等協議,也支持hessian、json序列化。協議與序列化是什么關系?
通過剛剛介紹的dubbo協議格式或許就能明白,dubbo協議是如上的格式包含了頭和內容,其中96字節之后的內容是序列化后的內容,默認使用hessian2序列化,也可以修改為fastjson、gson、jdk等。只需要配置即可修改協議
<dubbo:protocol name="dubbo" serialization="fastjson"/>
如果非要做個類比的話,就是你不僅可以通過http協議傳輸json格式的數據,也可以傳輸xml格式的數據。http就是協議,json和xml就是序列化。
最后
dubbo協議的設計雖然有所欠缺,但依然不能阻止它成為dubbo使用最廣泛的協議。
搜索關注微信公眾號"捉蟲大師",后端技術分享,架構設計、性能優化、源碼閱讀、問題排查、踩坑實踐。