1.微服務(wù)改造成Dubbo項(xiàng)目
改造成Dubbo項(xiàng)目,有幾件事情要做:
- 添加dubbo核心依賴
dubbo-spring-boot-starter - 添加要使用的注冊(cè)中心依賴
dubbo-registry-zookeeper - 添加要使用的協(xié)議的依賴
dubbo-rpc-dubbo - 配置dubbo相關(guān)的基本信息
dubbo.application.name=provider-application - 配置注冊(cè)中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181 - 配置所使用的協(xié)議
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880 - 改造服務(wù)
抽象出服務(wù)接口UserService润梯;
要把Spring中的@Service注解替換成Dubbo中的@DubboService注解鸦难;
加上@EnableDubbo(scanBasePackages = "com.zhouyu.service")迅矛,表示Dubbo會(huì)去掃描某個(gè)路徑下的@DubboService榜跌,從而對(duì)外提供該Dubbo服務(wù)兽埃。(有dubbo-spring-boot-starter包板丽,可以不加該注解) - 抽出一個(gè)common模塊,包括
User類刹勃;
UserService公共接口;
服務(wù)提供者和服務(wù)調(diào)用者都引入該common模塊嚎尤。 - 服務(wù)提供者改造時(shí)荔仁,需要兼容原來(lái)的調(diào)用方式(服務(wù)調(diào)用者還可以使用原來(lái)的方式調(diào)用,比如RestTemplate)
dubbo.protocols.p1.name=dubbo
dubbo.protocols.p1.port=20880
dubbo.protocols.p2.name=rest
dubbo.protocols.p2.port=8082
引入dubbo-rpc-dubbo包
在UserServiceImpl上加注解@Path("/user")以及@Produces芽死;
在方法getUser上加注解
@GET乏梁,@Path("/{uid}")以及@Produces(MediaType.APPLICATION_JSON),入?yún)⑿枰由螥PathParam("uid")关贵; - 將服務(wù)調(diào)用者改造為Dubbo方式遇骑。
1)添加依賴:dubbo-spring-boot-starter、dubbo-registry-zookeeper揖曾、dubbo-rpc-dubbo落萎、dubbo-rpc-rest;
2)配置:dubbo.registry.address=zookeeper://127.0.0.1:2181
3)引入服務(wù)@DubboReference UserService炭剪;
4)@EnableDubbo
5)如果還要支持REST協(xié)議练链,需要將@Path @Produces等注解加到接口模塊上。
2.Http1.1奴拦、Dubbo和Triple協(xié)議對(duì)比
Http1.1請(qǐng)求為什么相對(duì)于Dubbo慢媒鼓?
- 一個(gè)HTTP請(qǐng)求有請(qǐng)求行、請(qǐng)求頭错妖、請(qǐng)求體绿鸣,通過(guò)字符(ASCII碼)發(fā)出去。有效數(shù)據(jù)Invocation放在請(qǐng)求體中暂氯,數(shù)據(jù)的有效使用率比較低潮模。(HTTP1.x協(xié)議中,多余無(wú)用的字符太多了株旷,比如回車符再登、換行符尔邓,這每一個(gè)字符都會(huì)占用一個(gè)字節(jié),這些字節(jié)占用了網(wǎng)絡(luò)帶寬锉矢,降低了網(wǎng)絡(luò)IO的效率)
- Dubbo協(xié)議梯嗽,相對(duì)于HTTP協(xié)議,精簡(jiǎn)了很多(Dubbo用一個(gè)bit位event就可以標(biāo)識(shí)序列化方式沽损,而Http需要在請(qǐng)求頭里面加入一個(gè)鍵值對(duì)來(lái)說(shuō)明灯节,有效性差距很大)。
- Http1.1不能通過(guò)一個(gè)Socket連接绵估,連續(xù)發(fā)送請(qǐng)求炎疆,只能發(fā)送一個(gè)請(qǐng)求,接收到響應(yīng)后国裳,再發(fā)送下一個(gè)請(qǐng)求形入。因?yàn)镠TTP沒(méi)有請(qǐng)求ID,如果多發(fā)幾個(gè)缝左,響應(yīng)回來(lái)后沒(méi)法對(duì)應(yīng)起來(lái)亿遂。而Dubbo有了ID,所以可以連續(xù)發(fā)送渺杉,這樣性能更好蛇数。(HTTP1.x協(xié)議中,一條Socket連接是越,一次只能發(fā)送一個(gè)HTTP請(qǐng)求耳舅,因?yàn)槿绻B續(xù)發(fā)送兩個(gè)HTTP請(qǐng)求,然后收到了一個(gè)響應(yīng)倚评,那怎么知道這個(gè)響應(yīng)對(duì)應(yīng)的是哪個(gè)請(qǐng)求呢浦徊,這樣導(dǎo)致Socket連接的利用低,并發(fā)天梧、吞吐量低辑畦。)
有了Dubbo協(xié)議,為什么還要引入Triple協(xié)議腿倚?
- Dubbo不夠通用纯出,dubbo協(xié)議一旦涉及到跨RPC框架,比如一個(gè)Dubbo服務(wù)要調(diào)用gPRC服務(wù)敷燎,就比較麻煩了暂筝,因?yàn)榘l(fā)一個(gè)dubbo協(xié)議的請(qǐng)求給一個(gè)gPRC服務(wù),gPRC服務(wù)只會(huì)按照gRPC的格式來(lái)解析字節(jié)流硬贯,最終肯定會(huì)解析不成功的焕襟。
- 所以這就出現(xiàn)了Triple協(xié)議,Triple協(xié)議是基于HTTP2饭豹,沒(méi)有性能問(wèn)題鸵赖,另外HTTP協(xié)議非常通用务漩,全世界都認(rèn)它,兼容起來(lái)也比較簡(jiǎn)單它褪,而且還有很多額外的功能饵骨,比如流式調(diào)用。
大概對(duì)比一下triple茫打、dubbo居触、rest這三個(gè)協(xié)議
- triple協(xié)議基于的是HTTP2,rest協(xié)議目前基于的是HTTP1老赤,都可以做到跨語(yǔ)言轮洋。
- triple協(xié)議兼容了gPRC(Triple服務(wù)可以直接調(diào)用gRPC服務(wù),反過(guò)來(lái)也可以)抬旺,rest協(xié)議不行
- triple協(xié)議支持流式調(diào)用弊予,rest協(xié)議不行
- rest協(xié)議更方便瀏覽器、客戶端直接調(diào)用开财,triple協(xié)議不行(原理上支持块促,當(dāng)?shù)脤?duì)triple協(xié)議的底層實(shí)現(xiàn)比較熟悉才行,得知道具體的請(qǐng)求頭床未、請(qǐng)求體是怎么生成的)
- dubbo協(xié)議是Dubbo3.0之前的默認(rèn)協(xié)議,triple協(xié)議是Dubbo3.0之后的默認(rèn)協(xié)議振坚,優(yōu)先用Triple協(xié)議
- dubbo協(xié)議不是基于的HTTP薇搁,不夠通用,triple協(xié)議底層基于HTTP所以更通用(比如跨語(yǔ)言渡八、跨異構(gòu)系統(tǒng)實(shí)現(xiàn)起來(lái)比較方便)
dubbo協(xié)議不支持流式調(diào)用
Http1 vs Http2
- Http1的缺點(diǎn)
1)額外占用了很多字節(jié)啃洋,比如眾多的回車符、換行符屎鳍,它們都是字符宏娄,都需要一個(gè)字節(jié);
2)大頭兒子逮壁,通常一個(gè)HTTP1的請(qǐng)求孵坚,都會(huì)攜帶各種請(qǐng)求頭;
3)Request-Repsonse模式窥淆,一次只能發(fā)送一個(gè)HTTP請(qǐng)求卖宠,接收到響應(yīng)后才能發(fā)送下一個(gè)請(qǐng)求;(瀏覽器通過(guò)創(chuàng)建多個(gè)Socket連接來(lái)提高并發(fā)量忧饭。) - Http2的改進(jìn)
1)設(shè)計(jì)了幀扛伍,通過(guò)這種設(shè)計(jì),就可以來(lái)壓縮請(qǐng)求頭了词裤,比如如果幀的類型是HEADERS 刺洒,那就進(jìn)行壓縮鳖宾,當(dāng)然壓縮算法是固定的HPACK算法,不能更換逆航;
2)支持Stream鼎文。每個(gè)幀里有一個(gè)流標(biāo)識(shí)符,表示Stream ID纸泡,這是HTTP2的新特性漂问,表示一個(gè)“虛擬流”,達(dá)到的效果是女揭,我們可以在一個(gè)TCP連接中蚤假,同時(shí)維護(hù)多個(gè)Stream,每一個(gè)幀都是屬于某一個(gè)Stream吧兔。極大的提高了并發(fā)磷仰。
HTTP1協(xié)議:
HTTP2協(xié)議:
- 幀長(zhǎng)度,用三個(gè)字節(jié)來(lái)存一個(gè)數(shù)字,這個(gè)數(shù)字表示當(dāng)前幀的實(shí)際傳輸?shù)臄?shù)據(jù)的大小,3個(gè)字節(jié)表示的最大數(shù)字是2的24次方(16M)砂客,所以一個(gè)幀最大為9字節(jié)+16M览闰。
- 幀類型,占一個(gè)字節(jié)推捐,可以分為數(shù)據(jù)幀和控制幀
?數(shù)據(jù)幀又分為:HEADERS 幀和 DATA 幀,用來(lái)傳輸請(qǐng)求頭、請(qǐng)求體的
?控制幀又分為:SETTINGS瞒爬、PING、PRIORITY沟堡,用來(lái)進(jìn)行管理的 - 標(biāo)志位侧但,占一個(gè)字節(jié),可以用來(lái)表示當(dāng)前幀是整個(gè)請(qǐng)求里的最后一幀航罗,方便服務(wù)端解析
- 流標(biāo)識(shí)符禀横,占4個(gè)字節(jié),在Java中也就是一個(gè)int粥血,不過(guò)最高位保留不用柏锄,表示Stream ID,這也是HTTP2的一個(gè)重要設(shè)計(jì)
- 實(shí)際傳輸?shù)臄?shù)據(jù)Payload复亏,如果幀類型是HEADERS绢彤,那么這里存的就是請(qǐng)求頭,如果幀類型是DATA 蜓耻,那么這里存的就是請(qǐng)求體
在利用HTTP2發(fā)送一個(gè)請(qǐng)求時(shí)茫舶,首先:
- 1)新建一個(gè)TCP連接(三次握手)
- 2)新建一個(gè)Stream,生成一個(gè)新的StreamID刹淌,生成一個(gè)控制幀饶氏,幀里記錄了前面生成出來(lái)的StreamID讥耗,通過(guò)TCP連接發(fā)送出去
- 3)生成一個(gè)要發(fā)送的請(qǐng)求對(duì)應(yīng)的HEADERS 幀,用來(lái)發(fā)送請(qǐng)求頭疹启,也是key:value的格式古程,先利用ascii進(jìn)行編碼,然后利用HPACK算法進(jìn)行壓縮喊崖,最終把壓縮之后的字節(jié)存在幀中的Payload區(qū)域挣磨,記錄好StreamID,最后通過(guò)TCP連接把這個(gè)HEADERS 幀發(fā)送出去
- 4)最后把要發(fā)送的請(qǐng)求體數(shù)據(jù)按指定的壓縮算法(請(qǐng)求中所指定的壓縮算法荤懂,比如gzip)進(jìn)行壓縮茁裙,把壓縮之后的字節(jié)生成DATA 幀,記錄好StreamID节仿,通過(guò)TCP連接把DATA 幀發(fā)送出去晤锥。
對(duì)于服務(wù)端而言:
- 1)會(huì)不斷的從TCP連接接收到某些幀
- 2)當(dāng)接收到一個(gè)控制幀時(shí),表示客戶端要和服務(wù)端新建一個(gè)Stream廊宪,在服務(wù)端記錄一下StreamID矾瘾,比如在Dubbo3.0的源碼中會(huì)生成一個(gè)ServerStreamObserver的對(duì)象
- 3)當(dāng)接收到一個(gè)HEADERS 幀,取出StreamID箭启,找到對(duì)應(yīng)的ServerStreamObserver對(duì)象壕翩,并解壓得到請(qǐng)求頭,把請(qǐng)求頭信息保存在ServerStreamObserver對(duì)象中
- 4)當(dāng)接收到一個(gè)DATA 幀時(shí)傅寡,取出StreamID放妈,找到對(duì)應(yīng)的ServerStreamObserver對(duì)象,根據(jù)請(qǐng)求頭的信息看如何解壓請(qǐng)求體赏僧,解壓之后就得到了原生了請(qǐng)求體數(shù)據(jù),然后按業(yè)務(wù)邏輯處理請(qǐng)求體
- 5)處理完了之后扭倾,就把結(jié)果也生成HEADERS 幀和DATA 幀時(shí)發(fā)送客戶端淀零,客戶端此時(shí)就變成了服務(wù)端,來(lái)處理響應(yīng)結(jié)果膛壹。
- 6)客戶端接收到響應(yīng)結(jié)果的HEADERS 幀驾中,是也先解壓得到響應(yīng)頭,記錄響應(yīng)體的解壓方式
- 7)然后繼續(xù)接收到響應(yīng)結(jié)果的DATA 幀模聋,解壓響應(yīng)體肩民,得到原生的響應(yīng)體,處理響應(yīng)體
3.Triple協(xié)議的流式調(diào)用(分批發(fā)送链方、處理)
幾種調(diào)用方式:
- UNARY持痰,發(fā)送一次,返回一次祟蚀。就是最普通的工窍,服務(wù)端只有在接收到完請(qǐng)求包括的所有的HEADERS幀和DATA幀之后(通過(guò)調(diào)用onCompleted()發(fā)送最后一個(gè)DATA幀)割卖,才會(huì)處理數(shù)據(jù),客戶端也只有接收完響應(yīng)包括的所有的HEADERS幀和DATA幀之后患雏,才會(huì)處理響應(yīng)結(jié)果鹏溯。
- SERVER_STREAM,發(fā)送一次請(qǐng)求淹仑,返回多次響應(yīng)丙挽。服務(wù)端流,特殊的地方在于匀借,服務(wù)端在接收完請(qǐng)求包括的所有的DATA幀之后颜阐,才會(huì)處理數(shù)據(jù),不過(guò)在處理數(shù)據(jù)的過(guò)程中怀吻,可以多次發(fā)送響應(yīng)DATA幀(第一個(gè)DATA幀發(fā)送之前會(huì)發(fā)送一個(gè)HEADERS幀)瞬浓,客戶端每接收到一個(gè)響應(yīng)DATA幀就可以直接處理該響應(yīng)DATA 幀,這個(gè)模式下蓬坡,客戶端只能發(fā)一次數(shù)據(jù)猿棉,但能多次處理響應(yīng)DATA幀。(目前有Bug屑咳,gRPC的效果是正確的萨赁,Dubbo3.0需要異步進(jìn)行發(fā)送)。
- CLIENT_STREAM / BI_STREAM兆龙,客戶端可以發(fā)送多次數(shù)據(jù)杖爽,也可以接收多次響應(yīng)。雙端流紫皇,或者客戶端流慰安,特殊的地方在于,客戶端可以控制發(fā)送多個(gè)請(qǐng)求DATA幀(第一個(gè)DATA幀發(fā)送之前會(huì)發(fā)送一個(gè)HEADERS幀)聪铺,服務(wù)端會(huì)不斷的接收到請(qǐng)求DATA幀并進(jìn)行處理化焕,并且及時(shí)的把處理結(jié)果作為響應(yīng)DATA幀發(fā)送給客戶端(第一個(gè)DATA幀發(fā)送之前會(huì)發(fā)送一個(gè)HEADERS幀),而客戶端每接收到一個(gè)響應(yīng)結(jié)果DATA幀也會(huì)直接處理铃剔,這種模式下撒桨,客戶端和服務(wù)端都在不斷的接收和發(fā)送DATA幀并進(jìn)行處理,注意請(qǐng)求HEADER幀和響應(yīng)HEADERS幀都只發(fā)了一個(gè)键兜。
public interface UserService {
// UNARY
String sayHello(String name);
// SERVER_STREAM
default void sayHelloServerStream(String name, StreamObserver<String> response) {
}
// CLIENT_STREAM / BI_STREAM
default StreamObserver<String> sayHelloStream(StreamObserver<String> response) {
return response;
}
}
中間Thread.sleep()后凤类,服務(wù)調(diào)用者還是一下子收到了所有數(shù)據(jù),這是個(gè)Bug普气。但是gRPC是可以實(shí)現(xiàn)睡眠接收到谜疤,而不是一下子接收到所有數(shù)據(jù)。
4.Dubbo3.0跨語(yǔ)言調(diào)用(Triple協(xié)議)
在工作中,我們用Java語(yǔ)言通過(guò)Dubbo提供了一個(gè)服務(wù)茎截,另外一個(gè)應(yīng)用(也就是消費(fèi)者)想要使用這個(gè)服務(wù)苇侵,如果消費(fèi)者應(yīng)用也是用Java語(yǔ)言開(kāi)發(fā)的,那沒(méi)什么好說(shuō)的企锌,直接在消費(fèi)者應(yīng)用引入Dubbo和服務(wù)接口相關(guān)的依賴即可榆浓。
但是,如果消費(fèi)者應(yīng)用不是用Java語(yǔ)言寫的呢撕攒,比如是通過(guò)python或者go語(yǔ)言實(shí)現(xiàn)的陡鹃,那就至少需要滿足兩個(gè)條件才能調(diào)用Java實(shí)現(xiàn)的Dubbo服務(wù):
- Dubbo一開(kāi)始是用Java語(yǔ)言實(shí)現(xiàn)的,那現(xiàn)在就需要一個(gè)go語(yǔ)言實(shí)現(xiàn)的Dubbo框架抖坪,也就是現(xiàn)在的dubbo-go萍鲸,然后在go項(xiàng)目中引入dubbo-go,從而可以在go項(xiàng)目中使用dubbo擦俐,比如使用go語(yǔ)言去暴露和使用Dubbo服務(wù)脊阴。
- 我們?cè)谑褂肑ava語(yǔ)言開(kāi)發(fā)一個(gè)Dubbo服務(wù)時(shí),會(huì)把服務(wù)接口和相關(guān)類蚯瞧,單獨(dú)抽象成為一個(gè)Maven項(xiàng)目嘿期,實(shí)際上就相當(dāng)于一個(gè)單獨(dú)的jar包,這個(gè)jar能被Java項(xiàng)目所使用埋合,但不能被go項(xiàng)目所使用备徐,所以go項(xiàng)目中該如何使用Java語(yǔ)言所定義的接口呢?直接用是不太可能的甚颂,只能通過(guò)間接的方式來(lái)解決這個(gè)問(wèn)題蜜猾,除開(kāi)Java語(yǔ)言之外,那有沒(méi)有其他技術(shù)也能定義接口呢振诬?并且該技術(shù)也是Java和go都支持蹭睡,這就是protobuf。
5.Triple與gRPC互通
Triple與gRPC互通之所以能夠互通赶么,是因?yàn)閠ri兼容了grpc肩豁,兼容的意思是,tri協(xié)議在發(fā)送請(qǐng)求和發(fā)送響應(yīng)時(shí)禽绪,都是按照grpc的格式來(lái)發(fā)送的蓖救,比如在請(qǐng)求頭和響應(yīng)頭中設(shè)置grpc能識(shí)別的信息洪规。
6.Dubbo3.0與Spring Cloud互通
目前Dubbo3.0和Spring Cloud之間的互通印屁,還沒(méi)做到特別方便,比如我們知道SpringCloud在使用的過(guò)程中斩例,需要程序員知道某個(gè)服務(wù)的controller訪問(wèn)路徑雄人,就算用openFeign也避免不了。
需要調(diào)用一個(gè)SpringCloud的微服務(wù)時(shí),得在消費(fèi)端應(yīng)用中自己去確定要調(diào)用的應(yīng)用名础钠,以及具體的controller路徑恰力。
要調(diào)用Spring Cloud的服務(wù),得用http協(xié)議旗吁,那tri協(xié)議行不行呢踩萎?原理上行,但是我們?cè)谟胻ri協(xié)議去調(diào)用另外一個(gè)服務(wù)時(shí)很钓,并不能去指定controller地址香府,得用rest協(xié)議,底層也是http協(xié)議码倦。