基于動(dòng)態(tài)代理 Mock Dubbo 服務(wù)的實(shí)現(xiàn)方案

序言

背景概述

公司目前 Java 項(xiàng)目提供服務(wù)都是基于 Dubbo 框架的萌衬,而且 Dubbo 框架已經(jīng)成為大部分國(guó)內(nèi)互聯(lián)網(wǎng)公司選擇的一個(gè)基礎(chǔ)組件。

在日常項(xiàng)目協(xié)作過程中哑子,其實(shí)會(huì)碰到服務(wù)不穩(wěn)定、不滿足需求場(chǎng)景等情況,很多開發(fā)都會(huì)通過在本地使用 Mocktio 等單測(cè)工具作為自測(cè)輔助炭序。那么,在聯(lián)調(diào)苍日、測(cè)試等協(xié)作過程中怎么處理惭聂?

其實(shí),Dubbo 開發(fā)者估計(jì)也是遇到了這樣的問題相恃,所以提供了一個(gè)提供泛化服務(wù)注冊(cè)的入口辜纲。但是在服務(wù)發(fā)現(xiàn)的時(shí)候有個(gè)弊端,就說通過服務(wù)發(fā)現(xiàn)去請(qǐng)求這個(gè) Mock 服務(wù)的話拦耐,在注冊(cè)中心必須只有一個(gè)服務(wù)有效耕腾,否則消費(fèi)者會(huì)請(qǐng)求到其他非Mock服務(wù)上去。

為了解決這個(gè)問題杀糯,Dubbo 開發(fā)者又提供了泛化調(diào)用的入口扫俺。既支持通過注冊(cè)中心發(fā)現(xiàn)服務(wù),又支持通過 IP+PORT 去直接調(diào)用服務(wù)火脉,這樣就能保證消費(fèi)者調(diào)用的是 Mock 出來的服務(wù)了牵舵。

以上泛化服務(wù)注冊(cè)和泛化服務(wù)調(diào)用結(jié)合起來柒啤,看似已經(jīng)是一個(gè)閉環(huán),可以解決 Dubbo 服務(wù)的 Mock 問題畸颅。但是担巩,結(jié)合日常工作使用時(shí),會(huì)出現(xiàn)一些麻煩的問題:

服務(wù)提供方使用公用的注冊(cè)中心没炒,消費(fèi)方無法準(zhǔn)確調(diào)用

消費(fèi)者不可能更改代碼涛癌,去直連 Mock 服務(wù)

使用私有注冊(cè)中心能解決以上問題,但是 Mock 最小緯度為 Method送火,一個(gè) Service 中被 Mock 的 Method 會(huì)正常處理拳话,沒有被 Mock 的 Method 會(huì)異常,導(dǎo)致服務(wù)方需要 Mock Service 的全部方法

在解決以上麻煩的前提下种吸,為了能快速注冊(cè)一個(gè)需要的 Dubbo 服務(wù)弃衍,提高項(xiàng)目協(xié)作過程中的工作效率,開展了 Mock 工廠的設(shè)計(jì)與實(shí)現(xiàn)坚俗。

功能概述

Mock Dubbo 服務(wù)

單個(gè)服務(wù)器镜盯,支持部署多個(gè)相同和不同的 Service

動(dòng)態(tài)上、下線服務(wù)

非 Mock 的 Method 透?jìng)鞯交A(chǔ)服務(wù)

一猖败、方案探索

1.1 基于 Service Chain 選擇 Mock 服務(wù)的實(shí)現(xiàn)方式

1.1.1 Service Chain 簡(jiǎn)單介紹

在業(yè)務(wù)發(fā)起的源頭添加 Service Chain 標(biāo)識(shí)速缆,這些標(biāo)識(shí)會(huì)在接下來的跨應(yīng)用遠(yuǎn)程調(diào)用中一直透?jìng)鞑⑶一谶@些標(biāo)識(shí)進(jìn)行路由,這樣我們只需要把涉及到需求變更的應(yīng)用的實(shí)例單獨(dú)部署恩闻,并添加到 Service Chain 的數(shù)據(jù)結(jié)構(gòu)定義里面艺糜,就可以虛擬出一個(gè)邏輯鏈路,該鏈路從邏輯上與其他鏈路是完全隔離的幢尚,并且可以共享那些不需要進(jìn)行需求變更的應(yīng)用實(shí)例破停。根據(jù)當(dāng)前調(diào)用的透?jìng)鳂?biāo)識(shí)以及 Service Chain 的基礎(chǔ)元數(shù)據(jù)進(jìn)行路由,路由原則如下:

當(dāng)前調(diào)用包含 Service Chain 標(biāo)識(shí)侠草,則路由到歸屬于該 Service Chain 的任意服務(wù)節(jié)點(diǎn)辱挥,如果沒有歸屬于該

Service Chain 的服務(wù)節(jié)點(diǎn),則排除掉所有隸屬于 Service Chain 的服務(wù)節(jié)點(diǎn)之后路由到任意服務(wù)節(jié)點(diǎn)

當(dāng)前調(diào)用沒有包含 Service Chain 標(biāo)識(shí)边涕,則排除掉所有隸屬于 Service Chain 的服務(wù)節(jié)點(diǎn)之后路由到任意服務(wù)節(jié)點(diǎn)

當(dāng)前調(diào)用包含 Service Chain 標(biāo)識(shí)晤碘,并且當(dāng)前應(yīng)用也屬于某個(gè) Service Chain 時(shí),如果兩者不等則拋出路由異常

以 Dubbo 框架為例功蜓,給出了一個(gè) Service Chain 實(shí)現(xiàn)架構(gòu)圖(下圖來自有贊架構(gòu)團(tuán)隊(duì))

1.1.2 Mock 服務(wù)實(shí)現(xiàn)設(shè)計(jì)方案

方案一园爷、基于 GenericService 生成需要 Mock 接口的泛化實(shí)現(xiàn),并注冊(cè)到 ETCD 上(主要實(shí)現(xiàn)思路如下圖所示)式撼。

方案二童社、使用 Javassist,生成需要mock接口的Proxy實(shí)現(xiàn)著隆,并注冊(cè)到 ETCD 上(主要實(shí)現(xiàn)思路如下圖所示)扰楼。

1.1.3 設(shè)計(jì)方案比較

方案一優(yōu)點(diǎn):實(shí)現(xiàn)簡(jiǎn)單呀癣,能滿足mock需求

繼承 GenericService蝙眶,只要實(shí)現(xiàn)一個(gè)$invoke( String methodName, String[]parameterTypes, Object[]objects )棺妓,可以根據(jù)具體請(qǐng)求參數(shù)做出自定義返回信息。

接口信息只要知道接口名嫂用、protocol 即可蹬竖。

即使該服務(wù)已經(jīng)存在沼沈,也能因?yàn)?generic 字段,讓消費(fèi)者優(yōu)先消費(fèi)該 mock service币厕。

缺點(diǎn):與公司的服務(wù)發(fā)現(xiàn)機(jī)制沖突

由于有贊服務(wù)背景列另,在使用 Haunt 服務(wù)發(fā)現(xiàn)時(shí),是會(huì)同時(shí)返回正常服務(wù)和帶有 Service Chain 標(biāo)記的泛化服務(wù)旦装,所以必然存在兩種類型的服務(wù)页衙。導(dǎo)致帶有 Service Chain 標(biāo)記的消費(fèi)者在正常請(qǐng)求泛化服務(wù)時(shí)報(bào) no available invoke。

例:注冊(cè)了 2個(gè) HelloService:

正常的 :generic=false&interface=com.alia.api.HelloService&methods=doNothing,say,age

泛化的:generic=true&interface=com.alia.api.HelloService&methods=*

在服務(wù)發(fā)現(xiàn)的時(shí)候同辣,RegistryDirectory 中有個(gè) map拷姿,保存了所有 Service 的注冊(cè)信息。也就是說旱函, method=* 和正常 method=doNothing,say,age 被保存在了一起。

客戶端請(qǐng)求服務(wù)的時(shí)候描滔,優(yōu)先匹配到正常的服務(wù)的 method棒妨,而不會(huì)去調(diào)用泛化服務(wù)。

導(dǎo)致結(jié)果:訪問時(shí)含长,會(huì)跳過 genericFilter券腔,報(bào) no available invoke。

方案二優(yōu)點(diǎn):Proxy 實(shí)現(xiàn)拘泞,自動(dòng)生成一個(gè)正常的 Dubbo 接口實(shí)現(xiàn)

1.Javassist 有現(xiàn)成的方法生成接口實(shí)現(xiàn)字節(jié)碼纷纫,大大簡(jiǎn)化了對(duì)用戶代碼依賴。例如:

返回 String陪腌、Json 等辱魁,對(duì)單 method 的 mock 實(shí)現(xiàn),都無需用戶上傳實(shí)現(xiàn)類诗鸭。

透?jìng)鲿r(shí)統(tǒng)一由平臺(tái)控制染簇,不配置 mock 的方法默認(rèn)就會(huì)進(jìn)行透?jìng)鳎冶A?Service Chain 標(biāo)記强岸。

2.Mock 服務(wù)注冊(cè) method 信息完整锻弓。

3.生成接口 Proxy 對(duì)象時(shí),嚴(yán)格按照接口定義進(jìn)行生成蝌箍,返回?cái)?shù)據(jù)類型有保障青灼。

缺點(diǎn):

無優(yōu)先消費(fèi)選擇功能暴心。

字節(jié)碼后臺(tái)生成,不利于排查生成的 Proxy 中存在問題杂拨。

1.1.4 選擇結(jié)果

由于做為平臺(tái)专普,不僅僅需要滿足 mock 需求,還需要減少用戶操作扳躬,以及支持現(xiàn)有公司服務(wù)架構(gòu)體系脆诉,所以選擇設(shè)計(jì)方案二。

1.2 基于動(dòng)態(tài)代理結(jié)合 ServiceConfig 實(shí)現(xiàn)動(dòng)態(tài)上贷币、下線服務(wù)

1.2.1 Dubbo 暴露服務(wù)的過程介紹

上圖(來自 dubbo 開發(fā)者文檔)暴露服務(wù)時(shí)序圖: 首先 ServiceConfig 類拿到對(duì)外提供服務(wù)的實(shí)際類 ref(如:StudentInfoServiceImpl),然后通過 ProxyFactory 類的 getInvoker 方法使用 ref 生成一個(gè) AbstractProxyInvoker 實(shí)例击胜。到這一步就完成具體服務(wù)到 Invoker 的轉(zhuǎn)化。接下來就是 Invoker 轉(zhuǎn)換到 Exporter 的過程,Exporter 會(huì)通過轉(zhuǎn)化為 URL 的方式暴露服務(wù)役纹。 從 dubbo 源碼來看偶摔,dubbo 通過 Spring 框架提供的 Schema 可擴(kuò)展機(jī)制,擴(kuò)展了自己的配置支持促脉。dubbo-container 通過封裝 Spring 容器辰斋,來啟動(dòng)了 Spring 上下文,此時(shí)它會(huì)去解析 Spring 的 bean 配置文件(Spring 的 xml 配置文件)瘸味,當(dāng)解析 dubbo:service 標(biāo)簽時(shí)宫仗,會(huì)用 dubbo 自定義 BeanDefinitionParser 進(jìn)行解析。dubbo 的 BeanDefinitonParser 實(shí)現(xiàn)為 DubboBeanDefinitionParser旁仿。?

Spring.handlers 文件:http://code.alibabatech.com/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler


public

class

DubboNamespaceHandler

extends

NamespaceHandlerSupport

{


public

DubboNamespaceHandler

() {

? ? ?}


public

void

init() {


this

.registerBeanDefinitionParser(

"application"

,

new

DubboBeanDefinitionParser

(

ApplicationConfig

.

class

,

true

));


this

.registerBeanDefinitionParser(

"module"

,

new

DubboBeanDefinitionParser

(

ModuleConfig

.

class

,

true

));


this

.registerBeanDefinitionParser(

"registry"

,

new

DubboBeanDefinitionParser

(

RegistryConfig

.

class

,

true

));


this

.registerBeanDefinitionParser(

"monitor"

,

new

DubboBeanDefinitionParser

(

MonitorConfig

.

class

,

true

));


this

.registerBeanDefinitionParser(

"provider"

,

new

DubboBeanDefinitionParser

(

ProviderConfig

.

class

,

true

));


this

.registerBeanDefinitionParser(

"consumer"

,

new

DubboBeanDefinitionParser

(

ConsumerConfig

.

class

,

true

));


this

.registerBeanDefinitionParser(

"protocol"

,

new

DubboBeanDefinitionParser

(

ProtocolConfig

.

class

,

true

));


this

.registerBeanDefinitionParser(

"service"

,

new

DubboBeanDefinitionParser

(

ServiceBean

.

class

,

true

));


this

.registerBeanDefinitionParser(

"reference"

,

new

DubboBeanDefinitionParser

(

ReferenceBean

.

class

,

false

));


this

.registerBeanDefinitionParser(

"annotation"

,

new

DubboBeanDefinitionParser

(

AnnotationBean

.

class

,

true

));

? ? ?}


static

{


Version

.checkDuplicate(

DubboNamespaceHandler

.

class

);

? ? ?}

? ? }

DubboBeanDefinitionParser

會(huì)將配置標(biāo)簽進(jìn)行解析,并生成對(duì)應(yīng)的

Javabean

藕夫,最終注冊(cè)到

Spring

Ioc

容器中。 對(duì)

ServiceBean

進(jìn)行注冊(cè)時(shí)枯冈,其

implements

InitializingBean

接口毅贮,當(dāng) bean 完成注冊(cè)后,會(huì)調(diào)用 afterPropertiesSet() 方法尘奏,該方法中調(diào)用

export

() 完成服務(wù)的注冊(cè)滩褥。在

ServiceConfig

中的 doExport() 方法中,會(huì)對(duì)服務(wù)的各個(gè)參數(shù)進(jìn)行校驗(yàn)炫加。


if

(

this

.

ref

instanceof

GenericService

) {


this

.interfaceClass =

GenericService

.

class

;


this

.

generic

=

true

;

? ?}

else

{


try

{


this

.interfaceClass =

Class

.forName(

this

.interfaceName,

true

,

Thread

.currentThread().getContextClassLoader());

? ? ? ?}

catch

(

ClassNotFoundException

var5) {


throw

new

IllegalStateException

(var5.getMessage(), var5);

? ? ? ?}


this

.checkInterfaceAndMethods(

this

.interfaceClass,

this

.methods);


this

.checkRef();


this

.

generic

=

false

;

? ?}

注冊(cè)過程中會(huì)進(jìn)行判斷該實(shí)現(xiàn)類的類型瑰煎。其中如果實(shí)現(xiàn)了 GenericService 接口,那么會(huì)在暴露服務(wù)信息時(shí)琢感,將 generic 設(shè)置為 true丢间,暴露方法就為*。如果不是驹针,就會(huì)按正常服務(wù)進(jìn)行添加服務(wù)的方法烘挫。此處就是我們可以實(shí)現(xiàn) Mock 的切入點(diǎn),使用 Javassist 根據(jù)自定義的 Mock 信息,寫一個(gè)實(shí)現(xiàn)類的 class 文件并生成一個(gè)實(shí)例注入到 ServiceConfig 中饮六。生成 class 實(shí)例如下所示其垄,與一個(gè)正常的實(shí)現(xiàn)類完全一致,以及注冊(cè)的服務(wù)跟正常服務(wù)也完全一致卤橄。


package

123.com

.youzan.api;


import

com.youzan.api.

StudentInfoService

;


import

com.youzan.pojo.

Pojo

;


import

com.youzan.test.mocker.

internal

.common.reference.

ServiceReference

;


public

class

StudentInfoServiceImpl

implements

StudentInfoService

{


private

Pojo

getNoValue0;


private

Pojo

getNoValue1;


private

ServiceReference

service;


public

void

setgetNoValue0(

Pojo

var1) {


this

.getNoValue0 = var1;

? ? ? ?}


public

void

setgetNoValue1(

Pojo

var1) {


this

.getNoValue1 = var1;

? ? ? ?}


public

Pojo

getNo(

int

var1) {


return

var1 ==

1

?

this

.getNoValue0 :

this

.getNoValue1;

? ? ? ?}


public

void

setService(

ServiceReference

var1) {


this

.service = var1;

? ? ? ?}


public

double

say() {


return

(

Double

)

this

.service.reference(

"say"

,

""

, (

Object

[])

);

? ? ? ?}


public

void

findInfo(

String

var1,

long

var2) {


this

.service.reference(

"findInfo"

,

"java.lang.String,long"

,

new

Object

[]{var1,

new

Long

(var2)});

? ? ? ?}


public

StudentInfoServiceImpl

() {}

? ? ? }

使用 ServiceConfig 將自定義的實(shí)現(xiàn)類注入绿满,并完成注冊(cè),實(shí)現(xiàn)如下:


void

registry(

Object

T,

String

sc) {

? ? ? ?service.setFilter(

"request"

)

? ? ? ?service.setRef(T)

? ? ? ?service.setParameters(

new

HashMap

<

String

,

String

>())

? ? ? ?service.getParameters().put(

Constants

.SERVICE_CONFIG_PARAMETER_SERVICE_CHAIN_NAME, sc)

? ? ? ?service.

export

()


if

(service.isExported()) {

? ? ? ? ? ?log.warn

"發(fā)布成功 : ${sc}-${service.interface}"

? ? ? ?}

else

{

? ? ? ? ? ?log.error

"發(fā)布失敗 : ${sc}-${service.interface}"

? ? ? ?}

? ?}

通過 service.setRef(genericService)完成實(shí)現(xiàn)類的注入窟扑,最終通過 service.export()完成服務(wù)注冊(cè)喇颁。ref 的值已經(jīng)被塞進(jìn)來,并附帶 ServiceChain 標(biāo)記保存至 service 的 paramters 中嚎货。具體服務(wù)到 Invoker 的轉(zhuǎn)化以及 Invoker 轉(zhuǎn)換到 Exporter橘霎,Exporter 到 URL 的轉(zhuǎn)換都會(huì)附帶上 ServiceChain 標(biāo)記注冊(cè)到注冊(cè)中心。

1.2.2 生成實(shí)現(xiàn)類設(shè)計(jì)方案

方案一殖属、 支持指定?String(或 Json)?對(duì)單個(gè) method 進(jìn)行 mock姐叁。

功能介紹:根據(jù)入?yún)?String or Json,生成代理對(duì)象洗显。由 methodName 和 methodParams 獲取唯一 method 定義外潜。(指支持單個(gè)方法mock)。消費(fèi)者請(qǐng)求到Mock服務(wù)的對(duì)應(yīng)Mock Method時(shí)挠唆,Mock服務(wù)將保存的數(shù)據(jù)轉(zhuǎn)成對(duì)應(yīng)的返回類型处窥,并返回。

方案二玄组、 支持指定?String(或 Json)?對(duì)多個(gè) method生成 mock碧库。

功能介紹:根據(jù)入?yún)?String or Json,生成代理對(duì)象巧勤。method 對(duì)應(yīng)的 mock 數(shù)據(jù)由 methodMockMap 指定,由 methodName 獲取唯一 method 定義弄匕,所以被 mock 接口不能有重載方法(只支持多個(gè)不同方法 mock)颅悉。消費(fèi)者請(qǐng)求到 Mock 服務(wù)的對(duì)應(yīng) mock method 時(shí),Mock 服務(wù)將保存的數(shù)據(jù)轉(zhuǎn)成對(duì)應(yīng)的返回類型迁匠,并返回剩瓶。

方案三、 在使用?實(shí)現(xiàn)類(Impl)?的情況下城丧,支持傳入一個(gè)指定的 method 進(jìn)行 mock延曙。

功能介紹:根據(jù)入?yún)⒌膶?shí)現(xiàn)類,生成代理對(duì)象亡哄。由 methodName 和 methodParams 獲取唯一 method 定義枝缔。(支持 mock 一個(gè)方法)。消費(fèi)者請(qǐng)求到 Mock 服務(wù)的對(duì)應(yīng) mock method 時(shí),Mock 服務(wù)調(diào)用該實(shí)現(xiàn)類的對(duì)應(yīng)方法愿卸,并返回灵临。

方案四、 在使用?實(shí)現(xiàn)類(Impl)?的情況下趴荸,支持傳入多個(gè) method 進(jìn)行 mock儒溉。

功能介紹:根據(jù)入?yún)⒌膶?shí)現(xiàn)類,生成代理對(duì)象发钝。由 methodName 獲取唯一 method 定義顿涣,所以被 mock 接口不能有重載方法(只支持一個(gè)實(shí)現(xiàn)類 mock 多個(gè)方法)。消費(fèi)者請(qǐng)求到 Mock 服務(wù)的對(duì)應(yīng) mock method 時(shí)酝豪,Mock 服務(wù)調(diào)用該實(shí)現(xiàn)類的對(duì)應(yīng)方法涛碑,并返回。

方案五寓调、 使用?Custom Reference?對(duì)多個(gè) method 進(jìn)行 mock锌唾。

功能介紹:根據(jù)入?yún)?ServiceReference,生成代理對(duì)象夺英。method 對(duì)應(yīng)的自定義 ServiceReference 由 methodMockMap 指定晌涕,由 methodName 獲取唯一method定義,所以被 mock 接口不能有重載方法(只支持多個(gè)不同方法 mock)痛悯。消費(fèi)者請(qǐng)求到 Mock 服務(wù)的對(duì)應(yīng) mock method 時(shí)余黎,Mock 服務(wù)會(huì)主動(dòng)請(qǐng)求自定義的 Dubbo 服務(wù)。

1.2.3 設(shè)計(jì)方案選擇

以上五種方案载萌,其實(shí)就是整個(gè) Mock 工廠實(shí)現(xiàn)的一個(gè)迭代過程惧财。在每個(gè)方案的嘗試中,發(fā)現(xiàn)各自的弊端然后出現(xiàn)了下一種方案扭仁。目前垮衷,在結(jié)合各種使用場(chǎng)景后,選擇了方案二乖坠、方案五搀突。

方案三、方案四被排除的主要原因:Dubbo 對(duì)已經(jīng)發(fā)布的 Service 保存了實(shí)現(xiàn)類的 ClassLoader熊泵,相同 className 的類一旦注冊(cè)成功后仰迁,會(huì)將實(shí)現(xiàn)類的 ClassLoader 保存到內(nèi)存中,很難被刪除顽分。所以想要使用這兩種方案的話徐许,需要頻繁變更實(shí)現(xiàn)類的 className,大大降低了一個(gè)工具的易用性卒蘸。改用自定義 Dubbo 服務(wù)(方案五)雌隅,替代自定義實(shí)現(xiàn)類,但是需要使用者自己起一個(gè) Dubbo 服務(wù),并告知 IP+PORT澄步。

方案一其實(shí)是方案二的補(bǔ)集冰蘑,能支持 Service 重載方法的 Mock。由于在使用時(shí)村缸,需要傳入具體 Method 的簽名信息祠肥,增加了用戶操作成本。由于公司內(nèi)部保證一個(gè) Service 不可能有重載方法梯皿,且為了提高使用效率仇箱,不開放該方案。后期如果出現(xiàn)這樣的有重載方法的情況东羹,再進(jìn)行開放剂桥。

1.2.4 遇到的坑

基礎(chǔ)數(shù)據(jù)類型需要特殊處理

使用 Javassist 根據(jù)接口 class 寫一個(gè)實(shí)現(xiàn)類的 class 文件,遇到最讓人頭疼的就是方法簽名和返回值属提。如果方法的簽名和返回值為基礎(chǔ)數(shù)據(jù)類型時(shí)权逗,那在傳參和返回時(shí)需要做特殊處理。平臺(tái)中本人使用了最笨的枚舉處理方法冤议,如果有使用 Javassist 的高手斟薇,有好的建議麻煩不吝賜教。代碼如下:


/** 參數(shù)存在基本數(shù)據(jù)類型時(shí)恕酸,默認(rèn)使用基本數(shù)據(jù)類型

? ? * 基本類型包含:

? ? * 實(shí)數(shù):double堪滨、float

? ? * 整數(shù):byte、short蕊温、int袱箱、long

? ? * 字符:char

? ? * 布爾值:boolean

? ? * */


private

static

CtClass

getParamType(

ClassPool

classPool,

String

paramType) {


switch

(paramType) {


case

"char"

:


return

CtClass

.charType


case

"byte"

:


return

CtClass

.byteType


case

"short"

:


return

CtClass

.shortType


case

"int"

:


return

CtClass

.intType


case

"long"

:


return

CtClass

.longType


case

"float"

:


return

CtClass

.floatType


case

"double"

:


return

CtClass

.doubleType


case

"boolean"

:


return

CtClass

.booleanType


default

:


return

classPool.

get

(paramType)

? ? ? ?}

? ?}

1.3 非 Mock 的 Method 透?jìng)鞯交A(chǔ)服務(wù)

1.3.1 Dubbo 服務(wù)消費(fèi)的過程介紹

在消費(fèi)端:Spring 解析 dubbo:reference 時(shí),Dubbo 首先使用?com.alibaba.dubbo.config.spring.schema.NamespaceHandler?注冊(cè)解析器义矛,當(dāng) Spring 解析 xml 配置文件時(shí)就會(huì)調(diào)用這些解析器生成對(duì)應(yīng)的 BeanDefinition 交給 Spring 管理发笔。Spring 在初始化 IOC 容器時(shí)會(huì)利用這里注冊(cè)的 BeanDefinitionParser 的 parse 方法獲取對(duì)應(yīng)的 ReferenceBean 的 BeanDefinition 實(shí)例,由于 ReferenceBean 實(shí)現(xiàn)了 InitializingBean 接口凉翻,在設(shè)置了 Bean 的所有屬性后會(huì)調(diào)用 afterPropertiesSet 方法筐咧。afterPropertiesSet 方法中的 getObject 會(huì)調(diào)用父類 ReferenceConfig 的 init 方法完成組裝。ReferenceConfig 類的 init 方法調(diào)用 Protocol 的 refer 方法生成 Invoker 實(shí)例噪矛,這是服務(wù)消費(fèi)的關(guān)鍵。接下來把 Invoker 轉(zhuǎn)換為客戶端需要的接口(如:StudentInfoService)铺罢。由 ReferenceConfig 切入艇挨,通過 API 方式使用 Dubbo 的泛化調(diào)用,代碼如下:

Object

reference(

String

s,

String

paramStr,

Object

[] objects) {


if

(

StringUtils

.isEmpty(serviceInfoDO.interfaceName) || serviceInfoDO.interfaceName.length() <=

0

) {


throw

new

NullPointerException

(

"The 'interfaceName' should not be ${serviceInfoDO.interfaceName}, please make sure you have the correct 'interfaceName' passed in"

)

? ?}


// set interface name

? ?referenceConfig.setInterface(serviceInfoDO.interfaceName)

? ?referenceConfig.setApplication(serviceInfoDO.applicationConfig)


// set version


if

(serviceInfoDO.version !=

&& serviceInfoDO.version !=

""

&& serviceInfoDO.version.length() >

0

) {

? ? ? ?referenceConfig.setVersion(serviceInfoDO.version)

? ?}


if

(

StringUtils

.isEmpty(serviceInfoDO.refUrl) || serviceInfoDO.refUrl.length() <=

0

) {


throw

new

NullPointerException

(

"The 'refUrl' should not be ${serviceInfoDO.refUrl} , please make sure you have the correct 'refUrl' passed in"

)

? ?}


//set refUrl

? ?referenceConfig.setUrl(serviceInfoDO.refUrl)

? ?reference.setGeneric(

true

)

// 聲明為泛化接口


//使用com.alibaba.dubbo.rpc.service.GenericService可以代替所有接口引用


GenericService

genericService = reference.

get

()


String

[] strs =


if

(paramStr !=

""

){

? ? ? ?strs = paramStr.split(

","

)

? ?}


Object

result = genericService.$invoke(s, strs, objects)


// 返回值類型不定韭赘,需要做特殊處理


if

(result.getClass().isAssignableFrom(

HashMap

.

class

)) {


Class

dtoClass =

Class

.forName(result.

get

(

"class"

))

? ? ? ?result.remove(

"class"

)


String

resultJson = JSON.toJSONString(result)


return

JSON.parseObject(resultJson, dtoClass)

? ?}


return

result

}

如上代碼所示缩滨,具體業(yè)務(wù) DTO 類型,泛化調(diào)用結(jié)果非僅結(jié)果數(shù)據(jù),還包含 DTO 的 class 信息脉漏,需要特殊處理結(jié)果苞冯,取出需要的結(jié)果進(jìn)行返回。

1.3.2 記錄dubbo服務(wù)請(qǐng)求設(shè)計(jì)方案

方案一侧巨、捕獲請(qǐng)求信息

服務(wù)提供方和服務(wù)消費(fèi)方調(diào)用過程攔截舅锄,Dubbo 本身的大多功能均基于此擴(kuò)展點(diǎn)實(shí)現(xiàn),每次遠(yuǎn)程方法執(zhí)行司忱,該攔截都會(huì)被執(zhí)行皇忿。Provider 提供的調(diào)用鏈,具體的調(diào)用鏈代碼是在 ProtocolFilterWrapper 的 buildInvokerChain 完成的坦仍,具體是將注解中含有 group=provider 的 Filter 實(shí)現(xiàn)鳍烁,按照 order 排序,最后的調(diào)用順序是 EchoFilter->ClassLoaderFilter->GenericFilter->ContextFilter->ExceptionFilter->TimeoutFilter->MonitorFilter->TraceFilter繁扎。 其中:EchoFilter 的作用是判斷是否是回聲測(cè)試請(qǐng)求幔荒,是的話直接返回內(nèi)容∈崦担回聲測(cè)試用于檢測(cè)服務(wù)是否可用爹梁,回聲測(cè)試按照正常請(qǐng)求流程執(zhí)行,能夠測(cè)試整個(gè)調(diào)用是否通暢汽纠,可用于監(jiān)控卫键。ClassLoaderFilter 則只是在主功能上添加了功能,更改當(dāng)前線程的 ClassLoader虱朵。

在 ServiceConfig 繼承 AbstractInterfaceConfig莉炉,中有 filter 屬性。以此為切入點(diǎn)碴犬,給每個(gè) Mock 服務(wù)添加 filter,記錄每次 dubbo 服務(wù)請(qǐng)求信息(接口絮宁、方法、入?yún)⒎⒎祷厣馨骸㈨憫?yīng)時(shí)長(zhǎng))。

方案二偿荷、記錄請(qǐng)求信息

將請(qǐng)求信息保存在內(nèi)存中窘游,一個(gè)接口的每個(gè)被 Mock 的方法保存近 10次 記錄信息。使用二級(jí)緩存保存跳纳,緩存代碼如下:


@Singleton

(lazy =

true

)


class

CacheUtil

{


private

static

final

Object

PRESENT =

new

Object

()


private

int

maxInterfaceSize =

10000


// 最大接口緩存數(shù)量


private

int

maxRequestSize =

10


// 最大請(qǐng)求緩存數(shù)量


private

Cache

<

String

,

Cache

<

RequestDO

,

Object

>> caches =

CacheBuilder

.newBuilder()

? ? ? ? ? ? ? ?.maximumSize(maxInterfaceSize)

? ? ? ? ? ? ? ?.expireAfterAccess(

7

,

TimeUnit

.DAYS) ? ?

// 7天未被請(qǐng)求的接口忍饰,緩存回收

? ? ? ? ? ? ? ?.build()

? ?} ?

如上代碼所示,二級(jí)緩存中的一個(gè) Object 是被浪費(fèi)的內(nèi)存空間寺庄,但是由于想不到其他更好的方案艾蓝,所以暫時(shí)保留該設(shè)計(jì)力崇。

1.3.3 遇到的坑

泛化調(diào)用時(shí)參數(shù)對(duì)象轉(zhuǎn)換

使用 ReferenceConfig 進(jìn)行服務(wù)直接調(diào)用,繞過了對(duì)一個(gè)接口方法簽名的校驗(yàn)赢织,所以在進(jìn)行泛化調(diào)用時(shí)亮靴,最大的問題就是 Object[] 內(nèi)的參數(shù)類型了。每次當(dāng)遇到數(shù)據(jù)類型問題時(shí)于置,本人只會(huì)用最笨的辦法茧吊,枚舉解決。代碼如下:


/** 參數(shù)存在基本數(shù)據(jù)類型時(shí)俱两,默認(rèn)使用基本數(shù)據(jù)類型

? ? * 基本類型包含:

? ? * 實(shí)數(shù):double饱狂、float

? ? * 整數(shù):byte、short宪彩、int休讳、long

? ? * 字符:char

? ? * 布爾值:boolean

? ? * */


private

Object

getInstance(

String

paramType,

String

value) {


switch

(paramType) {


case

"java.lang.String"

:


return

value


case

"byte"

:


case

"java.lang.Byte"

:


return

Byte

.parseByte(value)


case

"short"

:


return

Short

.parseShort(value)


case

"int"

:


case

"java.lang.Integer"

:


return

Integer

.parseInt(value)


case

"long"

:


case

"java.lang.Long"

:


return

Long

.parseLong(value)


case

"float"

:


case

"java.lang.Float"

:


return

Float

.parseFloat(value)


case

"double"

:


case

"java.lang.Double"

:


return

Double

.parseDouble(value)


case

"boolean"

:


case

"java.lang.Boolean"

:


return

Boolean

.parseBoolean(value)


default

:


JSONObject

jsonObject = JSON.parseObject(value)

// 轉(zhuǎn)成JSONObject


return

jsonObject

? ? ? ?}

? ?}

如以上代碼所示,是將傳入?yún)?shù)轉(zhuǎn)成對(duì)應(yīng)的包裝類型尿孔。當(dāng)接口的簽名如果為 int,那么入?yún)?duì)象是 Integer 也是可以的俊柔。因?yàn)?invoke(StringmethodName,String[]paramsTypes,Object[]objects),是由 paramsTypes 檢查方法簽名活合,然后再將 objects 傳入具體服務(wù)中進(jìn)行調(diào)用雏婶。

ReferenceConfig 初始化優(yōu)先設(shè)置 initialize 為 true

使用泛化調(diào)用發(fā)起遠(yuǎn)程 Dubbo 服務(wù)請(qǐng)求,在發(fā)起 invoke 前白指,有GenericServicegenericService=referenceConfig.get()操作留晚。當(dāng) Dubbo 服務(wù)沒有起來,此時(shí)首次發(fā)起調(diào)用后告嘲,進(jìn)行 ref 初始化操作错维。ReferenceConfig 初始化 ref 代碼如下:


private

void

init() {


if

(initialized) {


return

;

? ? ? ?}

? ? ? ?initialized =

true

;


if

(interfaceName ==

|| interfaceName.length() ==

0

) {


throw

new

IllegalStateException

(

"<dubbo:reference interface=\"\" /> interface not allow null!"

);

? ? ? ?}


// 獲取消費(fèi)者全局配置

? ? ? ?checkDefault();

? ? ? ?appendProperties(

this

);


if

(getGeneric() ==

&& getConsumer() !=

) {

? ? ? ? ? ?setGeneric(getConsumer().getGeneric());

? ? ? ?}

? ? ? ?...

? ?}

結(jié)果導(dǎo)致:由于第一次初始化的時(shí)候,先把 initialize 設(shè)置為 true橄唬,但是后面未獲取到有效的 genericService赋焕,導(dǎo)致后面即使 Dubbo 服務(wù)起來后,也會(huì)泛化調(diào)用失敗仰楚。

解決方案:泛化調(diào)用就是使用 genericService 執(zhí)行 invoke 調(diào)用隆判,所以每次請(qǐng)求都使用一個(gè)新的 ReferenceConfig,當(dāng)初始化進(jìn)行 get() 操作時(shí)報(bào)異成纾或返回為 null 時(shí)侨嘀,不保存;直到初始化進(jìn)行 get() 操作時(shí)獲取到有效的 genericService 時(shí)捂襟,將該 genericService 保存起來飒炎。實(shí)現(xiàn)代碼如下:


synchronized

(hasInit) {


if

(!hasInit) {


ReferenceConfig

referenceConfig =

new

ReferenceConfig

();


// set interface name

? ? ? ? ? ?referenceConfig.setInterface(serviceInfoDO.interfaceName)

? ? ? ? ? ?referenceConfig.setApplication(serviceInfoDO.applicationConfig)


// set version


if

(serviceInfoDO.version !=

&& serviceInfoDO.version !=

""

&& serviceInfoDO.version.length() >

0

) {

? ? ? ? ? ? ? ?referenceConfig.setVersion(serviceInfoDO.version)

? ? ? ? ? ?}


if

(

StringUtils

.isEmpty(serviceInfoDO.refUrl) || serviceInfoDO.refUrl.length() <=

0

) {


throw

new

NullPointerException

(

"The 'refUrl' should not be ${serviceInfoDO.refUrl} , please make sure you have the correct 'refUrl' passed in"

)

? ? ? ? ? ?}

? ? ? ? ? ?referenceConfig.setUrl(serviceInfoDO.refUrl)

? ? ? ? ? ?referenceConfig.setGeneric(

true

)

// 聲明為泛化接口

? ? ? ? ? ?genericService = referenceConfig.

get

()


if

(

!= genericService) {

? ? ? ? ? ? ? ?hasInit =

true

? ? ? ? ? ?}

? ? ? ?}

? ?}

1.4 單個(gè)服務(wù)器,支持部署多個(gè)相同和不同的Service

根據(jù)需求笆豁,需要解決兩個(gè)問題:1.服務(wù)器運(yùn)行過程中郎汪,外部API的Jar包加載問題;2.注冊(cè)多個(gè)相同接口服務(wù)時(shí)闯狱,名稱相同的問題煞赢。

1.4.1 動(dòng)態(tài)外部Jar包加載的設(shè)計(jì)方案

方案一、為外部 Jar 包生成單獨(dú)的 URLClassLoader,然后在泛化注冊(cè)時(shí)使用保存的 ClassLoader哄孤,在回調(diào)時(shí)進(jìn)行切換 currentThread 的 ClassLoader照筑,進(jìn)行相同 API 接口不同版本的 Mock。

不可用原因:

JavassistProxyFactory 中finalWrapperwrapper=Wrapper.getWrapper(proxy.getClass().getName().indexOf('$')<0?proxy.getClass():type);wapper 獲取的時(shí)候瘦陈,使用的 makeWrapper 中默認(rèn)使用的是 ClassHelper.getClassLoader(c);導(dǎo)致一直會(huì)使用 AppClassLoader凝危。API 信息會(huì)保存在一個(gè) WapperMap 中,當(dāng)消費(fèi)者請(qǐng)求過來的時(shí)候晨逝,會(huì)優(yōu)先取這個(gè) Map 找對(duì)應(yīng)的 API 信息蛾默。

導(dǎo)致結(jié)果:

1.由于使用泛化注冊(cè),所以 class 不在 AppClassLoader 中捉貌。設(shè)置了 currentThread 的 ClassLoader 不生效支鸡。

2.由于 dubbo 保存 API 信息只有一個(gè) Map,所以導(dǎo)致發(fā)布的服務(wù)的 API 也只能有一套趁窃。

解決方案:

使用自定義 ClassLoader 進(jìn)行加載外部 Jar 包中的 API 信息牧挣。

一臺(tái) Mock 終端存一套 API 信息,更新 API 時(shí)需要重啟服務(wù)器醒陆。

方案二瀑构、在程序啟動(dòng)時(shí),使用自定義 TestPlatformClassLoader刨摩。還是給每個(gè) Jar 包生成對(duì)應(yīng)的 ApiClassLoader寺晌,由 TestPlatformClassLoader 統(tǒng)一管理。

不可用原因:

在 Mock 終端部署時(shí)码邻,使用 -Djava.system.class.loader設(shè)置 ClassLoader 時(shí)折剃,JVM 啟動(dòng)參數(shù)不可用。因?yàn)橄裎荩琓estPlatformClassLoader 不存在于當(dāng)前 JVM 中怕犁,而是在工程代碼中。詳細(xì)參數(shù)如下:?-Djava.system.class.loader= com.youzan.test.mocker.internal.classloader.TestPlatformClassLoader

解決方案:(由架構(gòu)師汪興提供)

使用自定義 Runnable()己莺,保存程序啟動(dòng)需要的 ClassLoader奏甫、啟動(dòng)參數(shù)、mainClass 信息凌受。

在程序啟動(dòng)時(shí)阵子,新起一個(gè) Thread,傳入自定義 Runnable()胜蛉,然后將該線程啟動(dòng)挠进。

方案三色乾、使用自定義容器啟動(dòng)服務(wù)

應(yīng)用啟動(dòng)流程,如下圖所示(下圖來自有贊架構(gòu)團(tuán)隊(duì))

Java 的類加載遵循雙親委派的設(shè)計(jì)模式领突,從 AppClassLoader 開始自底向上尋找暖璧,并自頂向下加載,所以在沒有自定義 ClassLoader 時(shí)君旦,應(yīng)用的啟動(dòng)是通過 AppClassLoader 去加載 Main 啟動(dòng)類去運(yùn)行澎办。

自定義 ClassLoader 后,系統(tǒng) ClassLoader 將被設(shè)置成容器自定義的 ClassLoader金砍,自定義 ClassLoader 重新去加載 Main 啟動(dòng)類運(yùn)行局蚀,此時(shí)后續(xù)所有的類加載都會(huì)先去自定義的 ClassLoader 里查找。

難點(diǎn):應(yīng)用默認(rèn)系統(tǒng)類加載器是 AppClassLoader恕稠,在 New 對(duì)象時(shí)不會(huì)經(jīng)過自定義的 ClassLoader琅绅。

巧妙之處:Main 函數(shù)啟動(dòng)時(shí),AppClassLoader 加載 Main 和容器谱俭,容器獲取到 Main class奉件,用自定義 ClassLoader 重新加載Main,設(shè)置系統(tǒng)類加載器為自定義類加載器昆著,此時(shí) New 對(duì)象都會(huì)經(jīng)過自定義的 ClassLoader县貌。

1.4.2 設(shè)計(jì)方案選擇

以上三個(gè)方案,其實(shí)是實(shí)踐過程中的一個(gè)迭代凑懂。最終結(jié)果:

方案一煤痕、保留為外部Jar包生成單獨(dú)的 URLClassLoader。

方案二接谨、保留自定義 TestPlatformClassLoader摆碉,使用 TestPlatformClassLoader 保存每個(gè) Jar 包中 API 與其 ClassLoader 的對(duì)應(yīng)關(guān)系。

方案三脓豪、采用自定義容器啟動(dòng)巷帝,新起一個(gè)線程,并設(shè)置其 concurrentThreadClassLoader 為 TestPlatformClassLoader扫夜,用該線程啟動(dòng) Main.class楞泼。

1.4.3 遇到的坑

使用 Javassist 生成的 Class 名稱相同

使用 Javassist 生成的 Class,每個(gè) Class 有單獨(dú)的 ClassName 以 Service Chain + className 組成笤闯。在重新生成相同名字的 class 時(shí)堕阔,即使使用 newClassPool()也不能完全隔離。因?yàn)樯?Class 的時(shí)候 Class<?>clazz=ctClass.toClass()默認(rèn)使用的是同一個(gè) ClassLoader颗味,所以會(huì)報(bào)“attempted duplicate class definition for name:**”超陆。

解決方案:基于 ClassName 不是隨機(jī)生成的,所以只能基于之前的 ClassLoader 生成一個(gè)新的 SecureClassLoader(ClassLoader parent) 加載新的 class浦马,舊的 ClassLoader 靠 Java 自動(dòng) GC时呀。代碼如下:?Class<?>clazz=ctClass.toClass(newSecureClassLoader(clz.classLoader))

PS:該方案目前沒有做過壓測(cè)张漂,不知道會(huì)不會(huì)導(dǎo)致內(nèi)存溢出。

二谨娜、方案實(shí)現(xiàn)

2.1 Mock 工廠整體設(shè)計(jì)架構(gòu)

2.2 Mocker 容器設(shè)計(jì)圖

2.3 二方包管理時(shí)序圖

2.4 Mocker 容器服務(wù)注冊(cè)時(shí)序圖

三鹃锈、支持場(chǎng)景

3.1 元素及名詞解釋

上圖所示為基本元素組成,相關(guān)名詞解釋如下:

消費(fèi)者:調(diào)用方發(fā)起 DubboRequest

Base 服務(wù):不帶 Service Chain 標(biāo)識(shí)的正常服務(wù)

Mock 服務(wù):通過 Mock 工廠生成的 dubbo 服務(wù)

ETCD:注冊(cè)中心瞧预,此處同時(shí)注冊(cè)著 Base 服務(wù)和 Mock 服務(wù)

默認(rèn)服務(wù)透?jìng)鳎簩?duì)接口中不需要 Mock 的方法,直接泛化調(diào)用 Base 服務(wù)

自定義服務(wù)(CF):用戶自己起一個(gè)泛化 dubbo 服務(wù)(PS:不需要注冊(cè)到注冊(cè)中心仅政,也不需要 Service Chain 標(biāo)識(shí))

3.2 支持場(chǎng)景簡(jiǎn)述

場(chǎng)景1:不帶 Service Chain 請(qǐng)求(不使用 Mock 服務(wù)時(shí))

消費(fèi)者從注冊(cè)中心獲取到 Base 環(huán)境服務(wù)的 IP+PORT垢油,直接請(qǐng)求 Base 環(huán)境的服務(wù)。

場(chǎng)景2圆丹、帶 Service Chain 請(qǐng)求滩愁、Mock 服務(wù)采用 JSON 返回實(shí)現(xiàn)

消費(fèi)者從注冊(cè)中心獲取到兩個(gè)地址:1.Base 環(huán)境服務(wù)的 IP+PORT;2.帶 Service Chain 標(biāo)記服務(wù)(Mock服務(wù))的 IP+PORT辫封。根據(jù) Service Chain 調(diào)用路由硝枉,去請(qǐng)求 Mock 服務(wù)中的該方法,并返回 Mock 數(shù)據(jù)倦微。

場(chǎng)景3妻味、帶 Service Chain 請(qǐng)求、Mock 服務(wù)沒有該方法實(shí)現(xiàn)

消費(fèi)者從注冊(cè)中心獲取到兩個(gè)地址:1.Base 環(huán)境服務(wù)的 IP+PORT欣福;2.帶 Service Chain 標(biāo)記服務(wù)(Mock 服務(wù))的 IP+PORT责球。根據(jù) Service Chain 調(diào)用路由,去請(qǐng)求 Mock 服務(wù)拓劝。由于 Mock 服務(wù)中該方法是默認(rèn)服務(wù)透?jìng)鞒猓杂?Mock 服務(wù)直接泛化調(diào)用 Base 服務(wù),并返回?cái)?shù)據(jù)郑临。

場(chǎng)景4栖博、帶 Service Chain 請(qǐng)求頭、Mock 服務(wù)采用自定義服務(wù)(CR)實(shí)現(xiàn)

消費(fèi)者從注冊(cè)中心獲取到兩個(gè)地址:1.Base 環(huán)境服務(wù)的 IP+PORT;2.帶 Service Chain 標(biāo)記服務(wù)(Mock 服務(wù))的 IP+PORT。根據(jù) Service Chain 調(diào)用路由昌妹,去請(qǐng)求Mock服務(wù)廓鞠。由于 Mock 服務(wù)中該方法是自定義服務(wù)(CF),所以由 Mock 服務(wù)調(diào)用用戶的 dubbo 服務(wù)唆阿,并返回?cái)?shù)據(jù)。

場(chǎng)景5、帶 Service Chain 請(qǐng)求頭蠢正、Mock 服務(wù)沒有該方法實(shí)現(xiàn)、該方法又調(diào)用帶 Service Chain 的 InterfaceB 的方法

消費(fèi)者調(diào)用 InterfaceA 的 Method3 時(shí)省店,從注冊(cè)中心獲取到兩個(gè)地址:1.Base 環(huán)境服務(wù)的 IP+PORT嚣崭;2.帶 Service Chain 標(biāo)記服務(wù)(Mock 服務(wù))的 IP+PORT笨触。根據(jù) Service Chain 調(diào)用路由,去請(qǐng)求 InterfaceA 的 Mock 服務(wù)雹舀。由于 Mock 服務(wù)中該方法是默認(rèn)服務(wù)透?jìng)髀樱杂?Mock 服務(wù)直接泛化調(diào)用 InterfaceA 的 Base 服務(wù)的Method3。

但是说榆,由于 InterfaceA 的 Method3 是調(diào)用 InterfaceB 的 Method2虚吟,從注冊(cè)中心獲取到兩個(gè)地址:1.Base 環(huán)境服務(wù)的 IP+PORT;2.帶 Service Chain 標(biāo)記服務(wù)(Mock 服務(wù))的 IP+PORT签财。由于 Service Chain 標(biāo)識(shí)在整個(gè)請(qǐng)求鏈路中是一直被保留的串慰,所以根據(jù)Service Chain調(diào)用路由,最終請(qǐng)求到 InterfaceB 的 Mock 服務(wù)唱蒸,并返回?cái)?shù)據(jù)邦鲫。

場(chǎng)景6、帶 Service Chain 請(qǐng)求頭神汹、Mock已經(jīng)存在的 Service Chain 服務(wù)

由于不能同時(shí)存在兩個(gè)相同的 Service Chain 服務(wù)庆捺,所以需要降原先的 Service Chain 服務(wù)進(jìn)行只訂閱、不注冊(cè)的操作屁魏。然后將Mock服務(wù)的透?jìng)鞯刂诽弦裕渲脼樵?Service Chain 服務(wù)(即訂閱)。

消費(fèi)者在進(jìn)行請(qǐng)求時(shí)蚁堤,只會(huì)從 ETCD 發(fā)現(xiàn) Mock 服務(wù)醉者,其他同場(chǎng)景2、3披诗、4撬即、5。

四呈队、結(jié)束語

Mock平臺(tái)實(shí)踐過程中剥槐,遇到很多的難題.

歡迎工作一到五年的Java工程師朋友們加入Java程序員開發(fā): 854393687

群內(nèi)提供免費(fèi)的Java架構(gòu)學(xué)習(xí)資料(里面有高可用、高并發(fā)宪摧、高性能及分布式粒竖、Jvm性能調(diào)優(yōu)、Spring源碼几于,MyBatis蕊苗,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個(gè)知識(shí)點(diǎn)的架構(gòu)資料)合理利用自己每一分每一秒的時(shí)間來學(xué)習(xí)提升自己,不要再用"沒有時(shí)間“來掩飾自己思想上的懶惰沿彭!趁年輕朽砰,使勁拼,給未來的自己一個(gè)交代!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末瞧柔,一起剝皮案震驚了整個(gè)濱河市漆弄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌造锅,老刑警劉巖撼唾,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異哥蔚,居然都是意外死亡倒谷,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門糙箍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來恨锚,“玉大人,你說我怎么就攤上這事倍靡。” “怎么了课舍?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵塌西,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我筝尾,道長(zhǎng)捡需,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任筹淫,我火速辦了婚禮站辉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘损姜。我一直安慰自己饰剥,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布摧阅。 她就那樣靜靜地躺著汰蓉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪棒卷。 梳的紋絲不亂的頭發(fā)上顾孽,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音比规,去河邊找鬼若厚。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蜒什,可吹牛的內(nèi)容都是我干的测秸。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼乞封!你這毒婦竟也來了做裙?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤肃晚,失蹤者是張志新(化名)和其女友劉穎锚贱,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體关串,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡拧廊,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了晋修。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吧碾。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖墓卦,靈堂內(nèi)的尸體忽然破棺而出倦春,到底是詐尸還是另有隱情,我是刑警寧澤落剪,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布睁本,位于F島的核電站,受9級(jí)特大地震影響忠怖,放射性物質(zhì)發(fā)生泄漏呢堰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一凡泣、第九天 我趴在偏房一處隱蔽的房頂上張望枉疼。 院中可真熱鬧,春花似錦鞋拟、人聲如沸骂维。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽席舍。三九已至,卻和暖如春哮笆,著一層夾襖步出監(jiān)牢的瞬間来颤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工稠肘, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留福铅,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓项阴,卻偏偏與公主長(zhǎng)得像滑黔,于是被迫代替她去往敵國(guó)和親笆包。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理庵佣,服務(wù)發(fā)現(xiàn),斷路器汛兜,智...
    卡卡羅2017閱讀 134,599評(píng)論 18 139
  • dubbo.xsd 文件說明 當(dāng)我們要使用Dubbo進(jìn)行 配置時(shí)巴粪,需要像下面那樣引入dubbo.xsd這個(gè)文件. ...
    Mis_Gtw閱讀 1,096評(píng)論 0 1
  • 先看官網(wǎng)兩張圖【引用來自官網(wǎng)】:image.png 官網(wǎng)說明: 1.首先 ReferenceConfig 類的 i...
    致慮閱讀 1,021評(píng)論 0 2
  • 人心不足徒心焦 不忍微利夢(mèng)斗金 眾人醒悟平中樂 可笑遙望名利場(chǎng)
    wupeihuo閱讀 86評(píng)論 0 1
  • 111
    _______M閱讀 161評(píng)論 0 0