The preface

Summary of background

The company’s current Java project services are based on the Dubbo framework, and the Dubbo framework has become a basic component of choice for most domestic Internet companies.

In daily project collaboration, where services are unstable and requirements are not met, many developers use a single test tool like Mocktio locally as a self-test aid. So, how to deal with in the process of joint investigation and testing?

In fact, the Dubbo developers probably encountered this problem, so they provided a portal to provide a generic service registration. However, there is a drawback to service discovery. To request the Mock service through service discovery, only one service must be valid in the registry, otherwise consumers will request other non-mock services.

To address this issue, the Dubbo developers also provided a portal for generalization calls. Discovery of the service through the registry and direct invocation of the service through IP+PORT are supported to ensure that the consumer is mocking the service.

The combination of the above generalized service registration and generalized service invocation appears to be a closed loop that solves the Mock problem of the Dubbo service. However, when combined with daily work, there will be some troublesome problems:

  • The service provider uses a common registry that the consumer cannot invoke exactly
  • It is not possible for a consumer to change the code to connect directly to the Mock service
  • Using a private registry can solve this problem, but Mock methods are at least at the latitude of Method. Methods that are mocked in a Service are handled normally, and methods that are not mocked are raised, causing the Service provider to require all methods of the Mock Service

In order to quickly register a needed Dubbo service and improve work efficiency in project collaboration, Mock factory design and implementation were carried out on the premise of solving the above troubles.

Functions overview

  • The Mock Dubbo service
  • Multiple identical and different services can be deployed on a single server
  • Dynamic online and offline services
  • Non-mock methods are passed through to the base service

1. Plan exploration

1.1 Select the Mock Service implementation based on the Service Chain

1.1.1 Service Chain Introduction

Add Service Chain identifiers at the source of business initiation. These identifiers will be transparented and routed based on these identifiers in subsequent cross-application remote calls. In this way, we only need to separately deploy the application instances involved in requirement changes and add them to the data structure definition of Service Chain. You can create a virtual logical link that is logically isolated from other links and can share application instances that do not need to be changed.

Routes are routed based on the transparently transmitted identifier of the current invocation and the basic metadata of the Service Chain. The routing principles are as follows:

  • If the current invocation contains the Service Chain identifier, the route is routed to any Service node belonging to the Service Chain
  • If the Service node of the Service Chain is excluded, the route is routed to any Service node
  • If the current invocation does not contain the Service Chain identifier, all Service nodes belonging to the Service Chain are excluded and routed to any Service node
  • If the current invocation contains the Service Chain identifier and the current application belongs to a Service Chain, a route exception is thrown

Taking the Dubbo framework as an example, a Service Chain implementation architecture diagram is presented.

1.1.2 Mock Service Implementation Design Scheme

Plan a, based on GenericService generated need to Mock the generalization of the interface, and register to the ETCD (main implementation approach as shown in the figure below).

Solution 2: Use Javassist to generate a Proxy implementation that needs the mock interface and register it with ETCD (the main implementation idea is shown in the figure below).

1.1.3 Comparison of design schemes

Advantage of Scheme 1: It is simple to implement and can meet mock requirements

  • $invoke(String methodName, String[] parameterTypes, Object[] Objects);
  • Interface information You need to know the interface name and protocol.
  • Even if the service already exists, the generic fields give consumers priority to consume the Mock Service.

Disadvantages: Conflicts with the company’s service discovery mechanism

Because of the background of praising Service, when using Haunt Service discovery, both the normal Service and the generalized Service with the Service Chain tag will be returned, so there must be two types of services. Causes a consumer with a Service Chain flag to say no available invoke when requesting a generic Service normally. Example: Two helloServices are registered:

  • Normal: generic = false&interface = com. Alia. API. HelloService&methods = doNothing, say, the age
  • Generalization: generic = true&interface = com. Alia. API. HelloService&methods = *

When a Service is discovered, there is a map in the RegistryDirectory that holds the registration information for all services. That is, method=* is saved together with the normal method=doNothing,say,age.

Advantage of Solution 2: Proxy automatically generates a normal Dubbo interface implementation

1.Javassist has a ready-made way to generate interface implementations of bytecode, greatly simplifying dependencies on user code. Such as:

  • Returns String, Json, etc. For a single method mock implementation, the user does not need to upload the implementation class.
  • Pass-through is controlled by the platform. Methods without mock configuration are passed through by default and retain the Service Chain flag.

2. The Mock service registration method information is complete. 3. When the interface Proxy object is generated, it is generated strictly according to the interface definition and the returned data type is guaranteed.

Disadvantages:

  • No preferential consumption selection function.
  • Background bytecode generation is not conducive to troubleshooting problems in the generated Proxy.

1.1.4 Selection results

As a platform, we not only need to meet the mock requirements, but also need to reduce user operations and support the existing service architecture of the company, so we choose design plan 2.

1.2 Dynamic proxy based on ServiceConfig dynamic up and down services

1.2.1 Introduction to Dubbo exposure service process

The figure above (from the Dubbo developer documentation) exposes the service sequence diagram: First, the ServiceConfig class gets the actual class ref that provides the service. StudentInfoServiceImpl), and use ref to generate an instance of AbstractProxyInvoker using the getInvoker method of the ProxyFactory class. This step completes the transformation of the specific service to Invoker. Next comes the conversion of Invoker to my Exporter, which exposes the service to a URL. From the Dubbo source, Dubbo extends its configuration support through the Schema extensibility mechanism provided by the Spring framework. Dubbo – Container starts the Spring context by wrapping the Spring container. It then parses the Spring bean configuration file (the Spring XML configuration file). When parsing the Dubbo :service tag, Dubbo custom BeanDefinitionParser is used for parsing. Dubbo BeanDefinitonParser for DubboBeanDefinitionParser. Spring. Handlers file: 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 configuration tag parsed, and generate the corresponding Javabean, eventually to register the Spring Ioc container. The ServiceBean is registered with the Implements InitializingBean interface. After the bean is registered, the afterPropertiesSet() method is called, which calls Export () to complete the service registration. The doExport() method in ServiceConfig validates the parameters of the service. 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; }Copy the code

The type of the implementation class is determined during registration. If the GenericService interface is implemented, the generic is set to true and the exposure method is *. If not, the service is added as normal. This is where we can implement the Mock, using Javassist to write an implementation class class file based on custom Mock information and generate an instance to inject into ServiceConfig. The generated class instance is shown below, exactly as a normal implementation class, and the registered service is exactly as a normal service.

	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() {}
       }
Copy the code

Use ServiceConfig to inject and register the custom implementation class as follows:

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 "exported successfully: ${sc}-${service.interface}"} else {log.error "${sc}-${service.interface}"}}Copy the code

GenericService (service.setref) is used to implement class injection, and service.export() is used to register the service. The value of ref has been stuffed in with the ServiceChain tag stored in the Service’s paramters. The conversion of a specific service to an Invoker and the conversion of an Invoker to an Exporter and a Exporter to a URL are registered to the registry with the ServiceChain tag.

1.2.2 Generate the implementation class design scheme

Option 1: Support designationString (or Json)Mock a single method.

Function description: Generates a proxy object according to the input parameter String or Json. Get the unique method definition from methodName and methodParams. (referring to support for a single method mock). When the consumer requests the corresponding Mock Method to the Mock service, the Mock service converts the saved data into the corresponding return type and returns.

Option 2: Support designationString (or Json)Mock multiple methods.

Function description: Generates a proxy object according to the input parameter String or Json. The mock data corresponding to method is specified by methodMockMap, and methodName gets the unique method definition, so the mock interface cannot have overloaded methods (only multiple different method mocks are supported). When the consumer requests the corresponding Mock method to the Mock service, the Mock service converts the saved data into the corresponding return type and returns.

Plan three, in useThe implementation class (Impl)Supports passing in a specified method to mock.

Function description: according to the implementation class of the input parameter, generate proxy object. Get the unique method definition from methodName and methodParams. Mock a method is supported. When the consumer requests the corresponding Mock method to the Mock service, the Mock service invokes the corresponding method of the implementation class and returns.

Plan 4. In useThe implementation class (Impl)Supports passing in multiple methods to mock.

Function description: according to the implementation class of the input parameter, generate proxy object. MethodName gets a unique method definition, so the mock interface cannot have overloaded methods (only one implementation class can mock multiple methods). When the consumer requests the corresponding Mock method to the Mock service, the Mock service invokes the corresponding method of the implementation class and returns.

Plan 5. UseCustom ReferenceMock multiple methods.

Function Description: Generate a proxy object based on the ServiceReference input parameter. The custom ServiceReference corresponding to method is specified by methodMockMap, and methodName gets the unique method definition, so the mock interface cannot have overloaded methods (only multiple different method mocks are supported). When the consumer requests the corresponding Mock method of the Mock service, the Mock service proactively requests the custom Dubbo service.

1.2.3 Selection of design scheme

The above five scenarios are essentially iterations of the Mock factory implementation. In each scheme tried, the drawbacks were discovered and the next scheme emerged. At present, after combining various application scenarios, plan 2 and Plan 5 are selected.

The main reason for the exclusion of scheme 3 and Scheme 4 is that Dubbo saves the ClassLoader of the implementation class to the published Service. Once the class with the same className is registered successfully, the ClassLoader of the implementation class will be saved in the memory, which is difficult to delete. Therefore, to use these two solutions, you need to change the className of the implementation class frequently, greatly reducing the ease of use of a tool. Use the custom Dubbo service (Plan 5) to replace the custom implementation class, but the user needs to set up a Dubbo service and inform THE IP+PORT.

Scheme 1 is actually a complement to scheme 2 and supports Mock Service overloading methods. The user needs to pass in the signature information of the Method, which increases the operation cost. Because of the company’s internal guarantee that a Service cannot have overloaded methods, the scheme is not open for efficiency. Later, if there is such a case of overloaded methods, and then open.

1.2.4 Pits encountered

The underlying data types require special handling

Using Javassist to write a class file that implements a class based on the interface class, one of the most frustrating things you encounter is method signatures and return values. If the signature and return value of a method are underlying data types, special processing is required during parameter passing and return. I use the dumbest enumeration method in the platform, so if you are a Javassist expert, please feel free to give me some good advice. The code is as follows:

* Basic types include: * Real numbers: double, float * Integers: byte, short, int, and long * Characters: char * Boolean values: 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) } }Copy the code

1.3 Non-mock methods are passed through to the base service

1.3.1 Introduction to Dubbo service consumption process

On the consumer side: Spring parsing dubbo: reference, dubbo first use com. Alibaba. Dubbo. Config. Spring. The schema. The NamespaceHandler registered parser, These parsers are called when Spring parses the XML configuration file to generate the corresponding BeanDefinition for Spring to manage. Spring initializes the IOC container using the BeanDefinitionParser parse method registered here to get the BeanDefinition instance of the ReferenceBean. Since the ReferenceBean implements the InitializingBean interface, the afterPropertiesSet method is called after all properties of the Bean are set. GetObject in the afterPropertiesSet method calls the init method of its parent ReferenceConfig class to complete the assembly. The init method of the ReferenceConfig class calls the refer method of Protocol to generate the Invoker instance, which is key to service consumption. Next, convert Invoker to the interface that the client needs (for example, StudentInfoService). From ReferenceConfig, Dubbo’s generalization call is used through API, and the code is as follows:

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) / / statement for generalization interface / / using com. Alibaba. Dubbo. RPC. Service. GenericService can replace all interface GenericService reference 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 }Copy the code

As shown in the code above, the DTO type of specific business, the result of the generalization call not only contains the result data, but also contains the class information of the DTO. The result needs special processing, and the required result is extracted and returned.

1.3.2 Recording the Dubbo Service Request Design

Scheme 1: Capture request information

The service provider and service consumer invoke procedural interception, which is based on this extension point for most of Dubbo’s own functionality, and is executed each time a remote method is executed. Provider provides a chain of calls in the ProtocolFilterWrapper buildInvokerChain, which implements a Filter that has group= Provider in its annotations. Sort by order, The final call order is EchoFilter – > ClassLoaderFilter – > GenericFilter – > ContextFilter – > ExceptionFilter – > TimeoutFilter – > MonitorFilter – > TraceFilter. The function of EchoFilter is to check whether it is an echo test request. If it is, it directly returns the content. Echo test is used to check whether the service is available. Echo test is performed according to the normal request flow to test whether the whole call is smooth and can be used for monitoring. ClassLoaderFilter simply adds functionality to the main function to change the current thread’s ClassLoader.

AbstractInterfaceConfig inherits AbstractInterfaceConfig from ServiceConfig with the filter property. From this entry point, add a filter to each Mock service to record each Dubbo service request (interface, method, input parameter, return, response time).

Plan 2: Record the request information

Request information is kept in memory, with each Mock method on an interface holding records nearly 10 times. The cache code is as follows:

@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 DAYS of unrequested interface, cache collection.build ()}Copy the code

As shown in the code above, an Object in the level 2 cache is wasted memory space, but because I can’t think of a better solution, I leave the design on hold for now.

1.3.3 Pits encountered

Parameter object conversion when generalization calls

ReferenceConfig is used for direct service calls, bypassing the verification of the signature of an interface method, so the biggest problem in the generalization calls is the parameter types in Object[]. Every time when encountered data type problems, I will only use the most stupid way, enumeration to solve. The code is as follows:

* Basic types include: * Real numbers: double, float * Integers: byte, short, int, and long * Characters: char * Boolean values: 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) // convert JSONObject return JSONObject}}Copy the code

As shown in the code above, the passed parameter is converted to the corresponding wrapper type. If the signature of the interface is int, then the input object is Integer. Because $invoke(String methodName, String[] paramsTypes, Object[] objects) is checked by paramsTypes for the method signature, and then objects are passed into the concrete service for invocation.

ReferenceConfig Initialization preference Setting Initialize to true

Invoke a remote Dubbo service request using a generalization call. GenericService GenericService = Referenceconfig.get () before invoking invoke. When the Dubbo service is not up, the ref initialization is performed after the first call. ReferenceConfig initializes the ref code as follows:

private void init() { if (initialized) { return; } initialized = true; if (interfaceName == null || interfaceName.length() == 0) { throw new IllegalStateException("<dubbo:reference interface=\"\" /> interface not allow null!" ); } // Get the consumer global configuration checkDefault(); appendProperties(this); if (getGeneric() == null && getConsumer() ! = null) { setGeneric(getConsumer().getGeneric()); }... }Copy the code

As a result, the initial initialization set initialize to true, but no valid genericService was obtained, and the subsequent generalization failed even after the Dubbo service was started.

Solution: A generalized call is an invoke call using genericService, so each request uses a new ReferenceConfig, which is not saved when an exception is reported or null is returned by the initialization of get(); Save genericService until a valid genericService is retrieved during initialization of get(). The implementation code is as follows:

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) / / statement for generalization interface genericService = referenceConfig. The get () if (null! = genericService) { hasInit = true } } }Copy the code

1.4 Multiple Identical and different Services can be deployed on a single server

According to the requirements, you need to solve two problems: 1. During server running, Jar packages of external apis are loaded. 2. The same name occurs when multiple services of the same interface are registered.

1.4.1 Dynamic external Jar package loading design scheme

Generate a separate URLClassLoader for the external Jar package, then use the saved ClassLoader during the generalization registration, switch currentThread’s ClassLoader during the callback, and Mock different versions of the same API.

JavassistProxyFactory final Wrapper Wrapper = wrapper.getwrapper (proxy.getClass().getName().indexof (‘$’) < 0? proxy.getClass() : type); In the makeWrapper wapper access, use the default use ClassHelper. GetClassLoader (c); The AppClassLoader will always be used. The API information is stored in a WapperMap, and when the consumer requests it, the Map will be used to find the corresponding API information.

Lead to:

  • 1. The class is not in the AppClassLoader because of the generic registration. CurrentThread ClassLoader does not take effect.
  • 2. Dubbo stores only one Map of API information, so only one SET of APIS can be published.

Solution:

  • Use custom ClassLoader to load API information in external Jar packages.
  • A Mock terminal stores a set of API information, and the server needs to be restarted to update the API.
Solution 2: Use custom TestPlatformClassLoader when the program starts. ApiClassLoader is generated for each Jar package, which is managed by TestPlatformClassLoader.

Unavailable reasons:

In the Mock terminal deployment, use – Djava. System. Class. Set this loader, the JVM startup parameter is not available. The TestPlatformClassLoader does not exist in the current JVM, but in the project code. Detailed parameters are as follows:

-Djava.system.class.loader=com.youzan.test.mocker.internal.classloader.TestPlatformClassLoader

Solution :(provided by architect wang xing)

  • Use custom Runnable() to save the ClassLoader, startup parameters, and mainClass information required for application startup.
  • When the program starts, a new Thread is created, the custom Runnable() is passed in, and the Thread is started.
Solution 3. Start the service using a user-defined container

The app startup process is shown below (from the liked Architecture team)

Java class loading follows the design pattern of parental delegation, starting from AppClassLoader to look up and load from the top down, so when there is no custom ClassLoader, the application is started by AppClassLoader to load the Main startup class to run.

After a custom ClassLoader is defined, the system ClassLoader will be set to a custom container ClassLoader, and the custom ClassLoader will reload the Main start class to run. In this case, all subsequent class loads will be searched in the custom ClassLoader first.

Difficulty: the default system ClassLoader is AppClassLoader, which does not go through a custom ClassLoader when New objects.

Clever: When Main starts, AppClassLoader loads Main and container. The container obtains Main class and reloads Main with the custom ClassLoader. The system ClassLoader is set to the custom ClassLoader. The New object will pass through a custom ClassLoader.

1.4.2 Design scheme selection

The above three schemes are actually an iteration in the process of practice. Final result:

  • Option 1: Keep generating a separate URLClassLoader for external Jar packages.
  • Plan 2. Retain the customized TestPlatformClassLoader and use TestPlatformClassLoader to save the mapping between apis in each Jar package and its ClassLoader.
  • Plan 3, using a custom container startup, new up a single thread, and set its concurrentThreadClassLoader TestPlatformClassLoader,. Use this thread to start the Main class.

1.4.3 Pits encountered

The Class name generated using Javassist is the same

Using classes generated by Javassist, each Class has a separate ClassName consisting of Service Chain + ClassName. When recreating classes with the same name, even using new ClassPool() does not completely isolate. Because Class
clazz = ctclass.toclass () uses the same ClassLoader by default, so” runtime duplicate class definition for name:**** “is reported.

Solution: ClassName is not randomly generated, so only a new SecureClassLoader(ClassLoader parent) can be generated based on the previous ClassLoader to load a new class. Older Classloaders rely on Java for automatic GC. The code is as follows:

Class<? > clazz = ctClass.toClass(new SecureClassLoader(clz.classLoader))

PS: The scheme has not been overtested at present, so I don’t know whether it will cause memory overflow.

2. Implementation of the scheme

2.1 Mock Factory overall design architecture

2.2 Mocker container design diagram

2.3 Timing diagram of two-party package management

2.4 Mocker Container Service registration sequence diagram

3. Support scenarios

3.1 Interpretation of elements and nouns

The basic elements are shown in the figure above, and the related terms are explained as follows:

  • Consumer: The caller initiates a DubboRequest
  • Base Service: a normal Service without the Service Chain identifier
  • Mock service: A Dubbo service generated through a Mock factory
  • ETCD: Registry where both the Base service and Mock service are registered
  • Default service passthrough: The docking port does not need Mock methods and directly generalizes the Base service
  • Custom Service (CF) : a user can create a generic Dubbo Service (PS: no registry required, no Service Chain identification required)

3.2 Supported Scenarios

Scenario 1: Request without Service Chain (without Mock services)

The consumer obtains the IP address and PORT of the Base environment service from the registry and requests the Base environment service directly.

Scenario 2. The Mock Service with Service Chain request is implemented using JSON return

The consumer gets two addresses from the registry: 1. The IP+PORT of the Base environment service; 2. IP+PORT of the Mock Service with Service Chain. Invoke the route according to the Service Chain to request the method in the Mock Service and return the Mock data.

Scenario 3, Mock Service with Service Chain request without this method implementation

The consumer gets two addresses from the registry: 1. The IP+PORT of the Base environment service; 2. IP+PORT of the Mock Service with Service Chain. Invoke the route according to the Service Chain to request the Mock Service. Since this method is the default service passthrough in the Mock service, the Mock service directly generalizes the Base service and returns data.

Scenario 4: The Mock Service with the Service Chain request header is implemented using a custom Service (CR)

The consumer gets two addresses from the registry: 1. The IP+PORT of the Base environment service; 2. IP+PORT of the Mock Service with Service Chain. Invoke the route according to the Service Chain to request the Mock Service. Since the method in the Mock service is a custom service (CF), the Mock service calls the user’s Dubbo service and returns data.

Scenario 5. Request header with Service Chain, Mock Service does not implement this method, and this method calls InterfaceB method with Service Chain

When a consumer calls InterfaceA’s Method3, it gets two addresses from the registry: 1. The IP+PORT of the Base environment service; 2. IP+PORT of the Mock Service with Service Chain. Invoke the route according to the Service Chain to request InterfaceA’s Mock Service. Because this method is the default service passthrough in the Mock service, the Mock service directly generalizes Method3 of the Base service that calls InterfaceA.

However, since InterfaceA’s Method3 is the Method2 that calls InterfaceB, two addresses are retrieved from the registry: 1. The IP+PORT of the Base environment service; 2. IP+PORT of the Mock Service with Service Chain. Since the Service Chain identifier is retained throughout the request link, the route is invoked based on the Service Chain, which ultimately requests the Mock Service of InterfaceB and returns data.

Scenario 6 Mock an existing Service Chain Service with a Service Chain request header

Because two same Service Chain services cannot exist at the same time, you need to lower the original Service Chain Service to subscribe only. Then configure the passthrough address of the Mock Service to the original Service Chain Service (subscription). The Mock service will only be discovered from ETCD when the consumer makes the request, as in scenarios 2, 3, 4, 5.

Fourth, concluding remarks

During the practice of the Mock platform, I encountered many difficulties. I would like to thank Weilong He and Xing Wang of the architecture team for their friendly support. There is still a lot to be improved in the follow-up, and I hope you can give more valuable suggestions (email: [email protected]).