公司目前 Java 项目提供服务都是基于 Dubbo 框架的,并且 Dubbo 框架已经成为大部分国内互联网公司选择的一个基础组件。java
在平常项目协做过程当中,其实会碰到服务不稳定、不知足需求场景等状况,不少开发都会经过在本地使用 Mocktio 等单测工具做为自测辅助。那么,在联调、测试等协做过程当中怎么处理?spring
其实,Dubbo 开发者估计也是遇到了这样的问题,因此提供了一个提供泛化服务注册的入口。可是在服务发现的时候有个弊端,就说经过服务发现去请求这个 Mock 服务的话,在注册中心必须只有一个服务有效,不然消费者会请求到其余非Mock服务上去。json
为了解决这个问题,Dubbo 开发者又提供了泛化调用的入口。既支持经过注册中心发现服务,又支持经过 IP+PORT 去直接调用服务,这样就能保证消费者调用的是 Mock 出来的服务了。设计模式
以上泛化服务注册和泛化服务调用结合起来,看似已是一个闭环,能够解决 Dubbo 服务的 Mock 问题。可是,结合平常工做使用时,会出现一些麻烦的问题:api
在解决以上麻烦的前提下,为了能快速注册一个须要的 Dubbo 服务,提升项目协做过程当中的工做效率,开展了 Mock 工厂的设计与实现。缓存
在业务发起的源头添加 Service Chain 标识,这些标识会在接下来的跨应用远程调用中一直透传而且基于这些标识进行路由,这样咱们只须要把涉及到需求变动的应用的实例单独部署,并添加到 Service Chain 的数据结构定义里面,就能够虚拟出一个逻辑链路,该链路从逻辑上与其余链路是彻底隔离的,而且能够共享那些不须要进行需求变动的应用实例。服务器
根据当前调用的透传标识以及 Service Chain 的基础元数据进行路由,路由原则以下:数据结构
以 Dubbo 框架为例,给出了一个 Service Chain 实现架构图(下图来自有赞架构团队)架构
方案1、基于 GenericService 生成须要 Mock 接口的泛化实现,并注册到 ETCD 上(主要实现思路以下图所示)。 app
方案2、使用 Javassist,生成须要mock接口的Proxy实现,并注册到 ETCD 上(主要实现思路以下图所示)。
方案一优势:实现简单,能知足mock需求
缺点:与公司的服务发现机制冲突
因为有赞服务背景,在使用 Haunt 服务发现时,是会同时返回正常服务和带有 Service Chain 标记的泛化服务,因此必然存在两种类型的服务。致使带有 Service Chain 标记的消费者在正常请求泛化服务时报 no available invoke。 例:注册了 2个 HelloService:
在服务发现的时候,RegistryDirectory 中有个 map,保存了全部 Service 的注册信息。也就是说, method=* 和正常 method=doNothing,say,age 被保存在了一块儿。
方案二优势:Proxy 实现,自动生成一个正常的 Dubbo 接口实现
1.Javassist 有现成的方法生成接口实现字节码,大大简化了对用户代码依赖。例如:
2.Mock 服务注册 method 信息完整。 3.生成接口 Proxy 对象时,严格按照接口定义进行生成,返回数据类型有保障。
缺点:
因为作为平台,不只仅须要知足 mock 需求,还须要减小用户操做,以及支持现有公司服务架构体系,因此选择设计方案二。
上图(来自 dubbo 开发者文档)暴露服务时序图: 首先 ServiceConfig 类拿到对外提供服务的实际类 ref(如:StudentInfoServiceImpl),而后经过 ProxyFactory 类的 getInvoker 方法使用 ref 生成一个 AbstractProxyInvoker 实例。到这一步就完成具体服务到 Invoker 的转化。接下来就是 Invoker 转换到 Exporter 的过程,Exporter 会经过转化为 URL 的方式暴露服务。 从 dubbo 源码来看,dubbo 经过 Spring 框架提供的 Schema 可扩展机制,扩展了本身的配置支持。dubbo-container 经过封装 Spring 容器,来启动了 Spring 上下文,此时它会去解析 Spring 的 bean 配置文件(Spring 的 xml 配置文件),当解析 dubbo:service 标签时,会用 dubbo 自定义 BeanDefinitionParser 进行解析。dubbo 的 BeanDefinitonParser 实现为 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 会将配置标签进行解析,并生成对应的 Javabean,最终注册到 Spring Ioc 容器中。 对 ServiceBean 进行注册时,其 implements InitializingBean 接口,当 bean 完成注册后,会调用 afterPropertiesSet() 方法,该方法中调用 export() 完成服务的注册。在 ServiceConfig 中的 doExport() 方法中,会对服务的各个参数进行校验。
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;
}
复制代码
注册过程当中会进行判断该实现类的类型。其中若是实现了 GenericService 接口,那么会在暴露服务信息时,将 generic 设置为 true,暴露方法就为*。若是不是,就会按正常服务进行添加服务的方法。此处就是咱们能够实现 Mock 的切入点,使用 Javassist 根据自定义的 Mock 信息,写一个实现类的 class 文件并生成一个实例注入到 ServiceConfig 中。生成 class 实例以下所示,与一个正常的实现类彻底一致,以及注册的服务跟正常服务也彻底一致。
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[])null);
}
public void findInfo(String var1, long var2) {
this.service.reference("findInfo", "java.lang.String,long", new Object[]{var1, new Long(var2)});
}
public StudentInfoServiceImpl() {}
}
复制代码
使用 ServiceConfig 将自定义的实现类注入,并完成注册,实现以下:
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 "发布成功 : ${sc}-${service.interface}"
} else {
log.error "发布失败 : ${sc}-${service.interface}"
}
}
复制代码
经过service.setRef(genericService)
完成实现类的注入,最终经过service.export()
完成服务注册。ref 的值已经被塞进来,并附带 ServiceChain 标记保存至 service 的 paramters 中。具体服务到 Invoker 的转化以及 Invoker 转换到 Exporter,Exporter 到 URL 的转换都会附带上 ServiceChain 标记注册到注册中心。
功能介绍:根据入参 String or Json,生成代理对象。由 methodName 和 methodParams 获取惟一 method 定义。(指支持单个方法mock)。消费者请求到Mock服务的对应Mock Method时,Mock服务将保存的数据转成对应的返回类型,并返回。
功能介绍:根据入参 String or Json,生成代理对象。method 对应的 mock 数据由 methodMockMap 指定,由 methodName 获取惟一 method 定义,因此被 mock 接口不能有重载方法(只支持多个不一样方法 mock)。消费者请求到 Mock 服务的对应 mock method 时,Mock 服务将保存的数据转成对应的返回类型,并返回。
功能介绍:根据入参的实现类,生成代理对象。由 methodName 和 methodParams 获取惟一 method 定义。(支持 mock 一个方法)。消费者请求到 Mock 服务的对应 mock method 时,Mock 服务调用该实现类的对应方法,并返回。
功能介绍:根据入参的实现类,生成代理对象。由 methodName 获取惟一 method 定义,因此被 mock 接口不能有重载方法(只支持一个实现类 mock 多个方法)。消费者请求到 Mock 服务的对应 mock method 时,Mock 服务调用该实现类的对应方法,并返回。
功能介绍:根据入参 ServiceReference,生成代理对象。method 对应的自定义 ServiceReference 由 methodMockMap 指定,由 methodName 获取惟一method定义,因此被 mock 接口不能有重载方法(只支持多个不一样方法 mock)。消费者请求到 Mock 服务的对应 mock method 时,Mock 服务会主动请求自定义的 Dubbo 服务。
以上五种方案,其实就是整个 Mock 工厂实现的一个迭代过程。在每一个方案的尝试中,发现各自的弊端而后出现了下一种方案。目前,在结合各类使用场景后,选择了方案2、方案五。
方案3、方案四被排除的主要缘由:Dubbo 对已经发布的 Service 保存了实现类的 ClassLoader,相同 className 的类一旦注册成功后,会将实现类的 ClassLoader 保存到内存中,很难被删除。因此想要使用这两种方案的话,须要频繁变动实现类的 className,大大下降了一个工具的易用性。改用自定义 Dubbo 服务(方案五),替代自定义实现类,可是须要使用者本身起一个 Dubbo 服务,并告知 IP+PORT。
方案一实际上是方案二的补集,能支持 Service 重载方法的 Mock。因为在使用时,须要传入具体 Method 的签名信息,增长了用户操做成本。因为公司内部保证一个 Service 不可能有重载方法,且为了提升使用效率,不开放该方案。后期若是出现这样的有重载方法的状况,再进行开放。
使用 Javassist 根据接口 class 写一个实现类的 class 文件,遇到最让人头疼的就是方法签名和返回值。若是方法的签名和返回值为基础数据类型时,那在传参和返回时须要作特殊处理。平台中本人使用了最笨的枚举处理方法,若是有使用 Javassist 的高手,有好的建议麻烦不吝赐教。代码以下:
/** 参数存在基本数据类型时,默认使用基本数据类型
* 基本类型包含:
* 实数:double、float
* 整数: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)
}
}
复制代码
在消费端:Spring 解析 dubbo:reference 时,Dubbo 首先使用 com.alibaba.dubbo.config.spring.schema.NamespaceHandler 注册解析器,当 Spring 解析 xml 配置文件时就会调用这些解析器生成对应的 BeanDefinition 交给 Spring 管理。Spring 在初始化 IOC 容器时会利用这里注册的 BeanDefinitionParser 的 parse 方法获取对应的 ReferenceBean 的 BeanDefinition 实例,因为 ReferenceBean 实现了 InitializingBean 接口,在设置了 Bean 的全部属性后会调用 afterPropertiesSet 方法。afterPropertiesSet 方法中的 getObject 会调用父类 ReferenceConfig 的 init 方法完成组装。ReferenceConfig 类的 init 方法调用 Protocol 的 refer 方法生成 Invoker 实例,这是服务消费的关键。接下来把 Invoker 转换为客户端须要的接口(如:StudentInfoService)。由 ReferenceConfig 切入,经过 API 方式使用 Dubbo 的泛化调用,代码以下:
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 != null && 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 = null
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
}
复制代码
如上代码所示,具体业务 DTO 类型,泛化调用结果非仅结果数据,还包含 DTO 的 class 信息,须要特殊处理结果,取出须要的结果进行返回。
服务提供方和服务消费方调用过程拦截,Dubbo 自己的大多功能均基于此扩展点实现,每次远程方法执行,该拦截都会被执行。Provider 提供的调用链,具体的调用链代码是在 ProtocolFilterWrapper 的 buildInvokerChain 完成的,具体是将注解中含有 group=provider 的 Filter 实现,按照 order 排序,最后的调用顺序是 EchoFilter->ClassLoaderFilter->GenericFilter->ContextFilter->ExceptionFilter->TimeoutFilter->MonitorFilter->TraceFilter。 其中:EchoFilter 的做用是判断是不是回声测试请求,是的话直接返回内容。回声测试用于检测服务是否可用,回声测试按照正常请求流程执行,可以测试整个调用是否通畅,可用于监控。ClassLoaderFilter 则只是在主功能上添加了功能,更改当前线程的 ClassLoader。
在 ServiceConfig 继承 AbstractInterfaceConfig,中有 filter 属性。以此为切入点,给每一个 Mock 服务添加 filter,记录每次 dubbo 服务请求信息(接口、方法、入参、返回、响应时长)。
将请求信息保存在内存中,一个接口的每一个被 Mock 的方法保存近 10次 记录信息。使用二级缓存保存,缓存代码以下:
@Singleton(lazy = true)
class CacheUtil {
private static final Object PRESENT = new Object()
private int maxInterfaceSize = 10000 // 最大接口缓存数量
private int maxRequestSize = 10 // 最大请求缓存数量
private Cache<String, Cache<RequestDO, Object>> caches = CacheBuilder.newBuilder()
.maximumSize(maxInterfaceSize)
.expireAfterAccess(7, TimeUnit.DAYS) // 7天未被请求的接口,缓存回收
.build()
}
复制代码
如上代码所示,二级缓存中的一个 Object 是被浪费的内存空间,可是因为想不到其余更好的方案,因此暂时保留该设计。
使用 ReferenceConfig 进行服务直接调用,绕过了对一个接口方法签名的校验,因此在进行泛化调用时,最大的问题就是 Object[] 内的参数类型了。每次当遇到数据类型问题时,本人只会用最笨的办法,枚举解决。代码以下:
/** 参数存在基本数据类型时,默认使用基本数据类型
* 基本类型包含:
* 实数:double、float
* 整数: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) // 转成JSONObject
return jsonObject
}
}
复制代码
如以上代码所示,是将传入参数转成对应的包装类型。当接口的签名若是为 int,那么入参对象是 Integer 也是能够的。由于 $invoke(String methodName, String[] paramsTypes, Object[] objects),是由 paramsTypes 检查方法签名,而后再将 objects 传入具体服务中进行调用。
使用泛化调用发起远程 Dubbo 服务请求,在发起 invoke 前,有 GenericService genericService = referenceConfig.get() 操做。当 Dubbo 服务没有起来,此时首次发起调用后,进行 ref 初始化操做。ReferenceConfig 初始化 ref 代码以下:
private void init() {
if (initialized) {
return;
}
initialized = true;
if (interfaceName == null || interfaceName.length() == 0) {
throw new IllegalStateException("<dubbo:reference interface=\"\" /> interface not allow null!");
}
// 获取消费者全局配置
checkDefault();
appendProperties(this);
if (getGeneric() == null && getConsumer() != null) {
setGeneric(getConsumer().getGeneric());
}
...
}
复制代码
结果致使:因为第一次初始化的时候,先把 initialize 设置为 true,可是后面未获取到有效的 genericService,致使后面即便 Dubbo 服务起来后,也会泛化调用失败。
解决方案:泛化调用就是使用 genericService 执行 invoke 调用,因此每次请求都使用一个新的 ReferenceConfig,当初始化进行 get() 操做时报异常或返回为 null 时,不保存;直到初始化进行 get() 操做时获取到有效的 genericService 时,将该 genericService 保存起来。实现代码以下:
synchronized (hasInit) {
if (!hasInit) {
ReferenceConfig referenceConfig = new ReferenceConfig();
// set interface name
referenceConfig.setInterface(serviceInfoDO.interfaceName)
referenceConfig.setApplication(serviceInfoDO.applicationConfig)
// set version
if (serviceInfoDO.version != null && 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 (null != genericService) {
hasInit = true
}
}
}
复制代码
根据需求,须要解决两个问题:1.服务器运行过程当中,外部API的Jar包加载问题;2.注册多个相同接口服务时,名称相同的问题。
方案1、为外部 Jar 包生成单独的 URLClassLoader,而后在泛化注册时使用保存的 ClassLoader,在回调时进行切换 currentThread 的 ClassLoader,进行相同 API 接口不一样版本的 Mock。
不可用缘由: JavassistProxyFactory 中 final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type); wapper 获取的时候,使用的 makeWrapper 中默认使用的是ClassHelper.getClassLoader(c);
致使一直会使用 AppClassLoader。API 信息会保存在一个 WapperMap 中,当消费者请求过来的时候,会优先取这个 Map 找对应的 API 信息。
致使结果:
解决方案:
不可用缘由:
在 Mock 终端部署时,使用 -Djava.system.class.loader 设置 ClassLoader 时,JVM 启动参数不可用。由于,TestPlatformClassLoader 不存在于当前 JVM 中,而是在工程代码中。详细参数以下:
-Djava.system.class.loader=com.youzan.test.mocker.internal.classloader.TestPlatformClassLoader
解决方案:(由架构师汪兴提供)
应用启动流程,以下图所示(下图来自有赞架构团队)
Java 的类加载遵循双亲委派的设计模式,从 AppClassLoader 开始自底向上寻找,并自顶向下加载,因此在没有自定义 ClassLoader 时,应用的启动是经过 AppClassLoader 去加载 Main 启动类去运行。
自定义 ClassLoader 后,系统 ClassLoader 将被设置成容器自定义的 ClassLoader,自定义 ClassLoader 从新去加载 Main 启动类运行,此时后续全部的类加载都会先去自定义的 ClassLoader 里查找。
难点:应用默认系统类加载器是 AppClassLoader,在 New 对象时不会通过自定义的 ClassLoader。
巧妙之处:Main 函数启动时,AppClassLoader 加载 Main 和容器,容器获取到 Main class,用自定义 ClassLoader 从新加载Main,设置系统类加载器为自定义类加载器,此时 New 对象都会通过自定义的 ClassLoader。
以上三个方案,实际上是实践过程当中的一个迭代。最终结果:
使用 Javassist 生成的 Class,每一个 Class 有单独的 ClassName 以 Service Chain + className 组成。在从新生成相同名字的 class 时,即便使用 new ClassPool()
也不能彻底隔离。由于生成 Class 的时候 Class<?> clazz = ctClass.toClass()
默认使用的是同一个 ClassLoader,因此会报“attempted duplicate class definition for name:****”。
解决方案:基于 ClassName 不是随机生成的,因此只能基于以前的 ClassLoader 生成一个新的 SecureClassLoader(ClassLoader parent) 加载新的 class,旧的 ClassLoader 靠 Java 自动 GC。代码以下:
Class<?> clazz = ctClass.toClass(new SecureClassLoader(clz.classLoader))
PS:该方案目前没有作过压测,不知道会不会致使内存溢出。
上图所示为基本元素组成,相关名词解释以下:
消费者从注册中心获取到 Base 环境服务的 IP+PORT,直接请求 Base 环境的服务。
消费者从注册中心获取到两个地址:1.Base 环境服务的 IP+PORT;2.带 Service Chain 标记服务(Mock服务)的 IP+PORT。根据 Service Chain 调用路由,去请求 Mock 服务中的该方法,并返回 Mock 数据。
消费者从注册中心获取到两个地址:1.Base 环境服务的 IP+PORT;2.带 Service Chain 标记服务(Mock 服务)的 IP+PORT。根据 Service Chain 调用路由,去请求 Mock 服务。因为 Mock 服务中该方法是默认服务透传,因此由 Mock 服务直接泛化调用 Base 服务,并返回数据。
消费者从注册中心获取到两个地址:1.Base 环境服务的 IP+PORT;2.带 Service Chain 标记服务(Mock 服务)的 IP+PORT。根据 Service Chain 调用路由,去请求Mock服务。因为 Mock 服务中该方法是自定义服务(CF),因此由 Mock 服务调用用户的 dubbo 服务,并返回数据。
消费者调用 InterfaceA 的 Method3 时,从注册中心获取到两个地址:1.Base 环境服务的 IP+PORT;2.带 Service Chain 标记服务(Mock 服务)的 IP+PORT。根据 Service Chain 调用路由,去请求 InterfaceA 的 Mock 服务。因为 Mock 服务中该方法是默认服务透传,因此由 Mock 服务直接泛化调用 InterfaceA 的 Base 服务的Method3。
可是,因为 InterfaceA 的 Method3 是调用 InterfaceB 的 Method2,从注册中心获取到两个地址:1.Base 环境服务的 IP+PORT;2.带 Service Chain 标记服务(Mock 服务)的 IP+PORT。因为 Service Chain 标识在整个请求链路中是一直被保留的,因此根据Service Chain调用路由,最终请求到 InterfaceB 的 Mock 服务,并返回数据。
因为不能同时存在两个相同的 Service Chain 服务,因此须要降原先的 Service Chain 服务进行只订阅、不注册的操做。而后将Mock服务的透传地址,配置为原 Service Chain 服务(即订阅)。 消费者在进行请求时,只会从 ETCD 发现 Mock 服务,其余同场景二、三、四、5。
Mock平台实践过程当中,遇到不少的难题,此处须要特别感谢架构组何炜龙、汪兴的友情支持。后续还有不少须要完善的,但愿你们能多提宝贵意见(邮箱:zhongyingying@youzan.com)。