1, the RPC
1.1 RPC definition
The system of Internet companies consists of thousands of large and small services, which are deployed on different machines. The invocation between services needs network communication, and the service consumer needs to write a pile of code related to network communication for each invocation of a service, which is not only complex but also prone to error. Also consider how the old service is called when the new service depends on it, and how the new service is published when other services depend on the new service for others to call. How to solve this problem? The industry generally adopts RPC remote call to implement.
RPC:
Remote Procedure Call Protocol (RPC) allows us to Call a Remote service as if it were a local service, without the caller being aware of the details of network traffic. Such as the service consumer in the execution helloWorldService. SayHello (” sowhat “), in essence is the remote service call. This method is actually RPC, which is widely used in various Internet companies, such as Alibaba’s Dubbo, Dangdang’s Dubbox, Facebook Thrift, Google’s GRPC, Twitter’s Finagle and so on.
1.2 RPC demo
Having said that, let’s implement a simplified version of RPC demo.
1.2.1 Public Interface
public interface SoWhatService {
String sayHello(String name);
}
Copy the code
1.2.2 Service Providers
Interface class implementation
Public class SoWhatServiceImpl implements SoWhatService {@override public String sayHello(String name) {return "Hello" + name; }}Copy the code
The service registers external providers
Public class ServiceFramework {public static void export(Object service, int port) throws Exception { ServerSocket server = new ServerSocket(port); while (true) { Socket socket = server.accept(); New Thread(() -> {try {// deserialize ObjectInputStream input = new ObjectInputStream(socket.getinputStream ()); String methodName =(String) input.readObject(); // Parameter type Class<? >[] parameterTypes = (Class<? >[]) input.readObject(); // Arguments Object[] arguments = (Object[]) input.readObject(); Method = service.getClass().getMethod(methodName, parameterTypes); Object result = method.invoke(service, arguments); ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream()); output.writeObject(result); } catch (Exception e) { e.printStackTrace(); } }).start(); }}}Copy the code
Service operation
Public class ServerMain {public static void main(String[] args) {// Service provider exposed interface SoWhatService service = new SoWhatServiceImpl(); try { ServiceFramework.export(service, 1412); } catch (Exception e) { e.printStackTrace(); }}}Copy the code
1.2.3 Service caller
Dynamic proxies invoke remote services
Public class RpcFunction {public static <T> T refer(class <T> interfaceClass, String host, int port) throws Exception { return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class<? >[]{interfaceClass}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] arguments) throws Throwable {// Specify the PROVIDER'S IP and port. ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream()); WriteObject (method.getName()); WriteObject (method.getparameterTypes ()); output.writeObject(method.getparameterTypes ()); WriteObject (arguments); ObjectInputStream input = new ObjectInputStream(socket.getInputStream()); Object result = input.readObject(); return result; }}); }}Copy the code
A method is called
Public class RunMain {public static void main(String[] args) {try {// The service caller needs to set the dependency SoWhatService = RpcFunction. Refer (SoWhatService. Class, "127.0.0.1", 1412); System.out.println(service.sayHello(" sowhat1412")); } catch (Exception e) { e.printStackTrace(); }}}Copy the code
2. Dubbo frame design
2.1 introduction of Dubbo
Dubbo is an open source tool developed by Alibaba, which is mainly divided into 2.6.x and 2.7.x versions. Is a distributed, high-performance, transparent RPC service framework, providing automatic service registration, automatic discovery and other efficient service governance solutions, can be seamlessly integrated with the Spring framework, it provides six core capabilities:
1. High-performance RPC calls for interface proxy
2. Intelligent fault tolerance and load balancing
3. Automatic service registration and discovery
4. Highly scalable
5. Traffic scheduling during runtime
6. Visual service governance and operation and maintenance
Call procedure:
The service Provider Provider starts and registers the services it can provide with Registry.
The service Consumer subscribes to Registry for the required service, parses the meta information provided by Registry, and selects Provider calls from the service through load balancing.
Registry pushes changes to the Provider metadata to the Consumer to ensure that the Consumer has the latest available information.
Note:
The Provider and Consumer record the number and time of calls in memory, and periodically send statistics to the Monitor, which is short-connected.
Monitor and Registry are optional and can be written directly in the configuration file. Provider and Consumer are directly connected.
If the Monitor and Registry are suspended, the Consumer caches the Provider information locally.
The Consumer calls the Provider directly without going through Registry. There is a long connection between Provider and Consumer and Registry.
2.2 Dubbo Framework layering
Overall, Dubbo is divided into three layers, as shown above.
Busines layer: The user provides the interface and implementation and some configuration information.
RPC layer: the real RPC call core layer, encapsulating the whole RPC call process, load balancing, cluster fault tolerance, proxy.
Remoting layer: Encapsulates network transport protocols and data transformations.
If each layer is subdivided further, there are ten layers.
- Interface Service layer: This layer is related to business logic and designs corresponding interfaces and implementations according to the services of Provider and consumer.
- Configuration layer (Config) : External configuration interface, with ServiceConfig and ReferenceConfig as the center for initial configuration.
- Service Proxy layer: transparent Proxy of service interface. Provider and Consumer generate Proxy classes to make service interface transparent, and the Proxy layer implements service invocation and result return.
- Service Registration layer (Registry) : encapsulates the registration and discovery of service addresses, centered on service urls.
- Routing layer (Cluster) : encapsulates routing and load balancing for multiple providers and Bridges registries, centered on Invoker, with extended interfaces for Cluster, Directory, Router, and LoadBlancce.
- Monitoring layer (Monitor) : Monitors the number and time of RPC calls. It centers on Statistics and extends interfaces to MonitorFactory, Monitor, and MonitorService.
- Protocal (Protocal) : Encapsulates RPC calls with Invocation and Result centered and extends interfaces to Protocal, Invoker, and Exporter.
- Information Exchange layer (Exchange) : encapsulates the request response pattern, synchronous to asynchronous. Based on Request and Response, the expansion interfaces are Exchanger, ExchangeChannel, ExchangeClient and ExchangeServer.
- Network Transport Layer (Transport) : Abstract MINA and Netty as the unified interface, Message as the center, extended interfaces are Channel, Transporter, Client, Server and Codec.
- Serialize: Reusable tools with Serialization, ObjectInput, ObjectOutput, and ThreadPool extensions.
The call relationship between them can be directly seen in the following official website figure.
3. Dubbo SPI mechanism
Dubbo uses microkernel design + SPI extension technology to build the core framework while meeting user customization needs. So let’s focus on SPI.
3.1 the microkernel
Operating system level microkernels and macrokernels:
Microkernel: A design architecture of the kernel, which consists of as few programs as possible, in order to achieve the most basic functions required by an operating system, including low-level addressing space management, thread management, and interprocess communication. Success stories are QNX systems, such as the blackberry and car market.
Monolithic macro kernel: Implement process management, memory management, file system, process communication, and other functions as the kernel, while the microkernel only retains the most basic functions. Linux is the macro kernel architecture.
Generalized microkernels in Dubbo:
The idea is core system + plug-in, to put it bluntly, is to abstract out the unchanged functions as the core, the change of functions as plug-ins to expand, in line with the open and closed principle, easier to expand, maintenance. For example, the body itself as a core system in the Small Overlord game machine, the game is a plug-in. Vscode, Idea, chrome, etc. are all products of the microkernel.
Microkernel architecture is always the idea of architecture, which can be the framework level or a module design. Its essence is to abstract the changing part into plug-ins, so that it can quickly and easily meet various needs without affecting the overall stability.
3.2 SPI meaning
Mainstream databases include MySQL, Oracle, DB2, etc. These databases are developed by different companies, and their underlying protocols are different. How can they be restricted? General is to customize unified interface, no matter the specific implementation, anyway, for the same interface programming. Just use a concrete implementation class when you actually use it, the question is where do you find that implementation class? At this point, the agreed rules are used to write the implementation class to the specified location.
SPI, also known as the Service Provider Interface, is a Service discovery mechanism. It will look for files in the META-INF/services folder of the ClassPath path and automatically load the classes defined in the files.
3.3 SPI demo
Interface:
package com.example.demo.spi;
public interface SPIService {
void execute();
}
Copy the code
Implementation class 1:
public class SpiImpl1 implements SPIService{
@Override
public void execute() {
System.out.println("SpiImpl1.execute()");
}
}
Copy the code
Implementation class 2:
public class SpiImpl2 implements SPIService{
@Override
public void execute() {
System.out.println("SpiImpl2.execute()");
}
}
Copy the code
Configure paths
Call load class
package com.example.demo.spi; import sun.misc.Service; import java.util.Iterator; import java.util.ServiceLoader; public class Test { public static void main(String[] args) { Iterator<SPIService> providers = Service.providers(SPIService.class); ServiceLoader<SPIService> load = ServiceLoader.load(SPIService.class); while(providers.hasNext()) { SPIService ser = providers.next(); ser.execute(); } System.out.println("--------------------------------"); Iterator<SPIService> iterator = load.iterator(); while(iterator.hasNext()) { SPIService ser = iterator.next(); ser.execute(); }}}Copy the code
3.4 SPI source tracking
Serviceloader. load(SPIService. Class)
Iterator.hasnext () and iterator.next() call the following:
3.5 Disadvantages of the Java SPI
- Instead of loading on demand, the Java SPI loads all available extension points at once, many of which are not needed and waste system resources.
- The method of obtaining an implementation class is not flexible. The method can only be obtained in the form of Iterator. The corresponding implementation class cannot be obtained according to a parameter.
- Without SUPPORT for AOP and dependency injection, the JAVA SPI may lose information about loading extension point exceptions, making tracing problems difficult.
3.6 Dubbo SPI
Dubbo implements an SPI that instantiates specified implementation classes by name and implements IOC, AOP, and adaptive extension SPIs.
key = com.sowhat.value
Copy the code
Dubbo’s convention for configuration file directories differs from the Java SPI in that Dubbo has three categories of directories.
Meta-inf /services/ : SPI configuration files in this directory are used for compatibility with Java SPI.
Meta-inf /dubbo/ : This directory stores user-defined SPI configuration files.
Meta-inf /dubbo/internal/ : This directory stores SPI configuration files used internally by dubbo.
Use it is very simple to introduce a dependency, and then baidu tutorial.
@Test void sowhat() { ExtensionLoader<SPIService> spiService = ExtensionLoader.getExtensionLoader(SPIService.class); SPIService Demo1 = SPIService. GetExtension ("SpiImpl1"); demo1.execute(); }Copy the code
3.7 Dubbo SPI source tracing
ExtensionLoader. The whole idea is to find the cache of getExtension method exists, does not exist, it reads the SPI files, create classes through reflection, and then set di these things, a wrapper class packing, the implementation process as shown in the figure below:
Four important parts:
- injectExtension IOC
Find the set method, according to the parameters to find the dependent object is injected.
- WrapperClass AOP
Dubbo automatically wraps classes for you. You only need an extension class whose constructor has one argument and is of the extension interface type to be considered a wrapped class.
- Activate
Active has three attributes, group indicating which end the modifier is on, provider or consumer, value indicating that the modifier is activated only when it appears in the URL parameter, and Order indicating the order in which the class is implemented.
3.8 Adaptive Adaptive expansion
Requirement: SPI extensions are loaded according to the configuration and do not want the extensions to be loaded at startup. You want to dynamically select the corresponding extensions according to the parameters at request time. Implementation: Dubbo implements adaptive extensions using the proxy mechanism, generating a proxy class for the interface the user wants to extend through JDK or Javassist compilation, and then creating instances through reflection. Instance will be upon the request of the original method, the parameters that need to extend classes, and then through ExtensionLoader. GetExtensionLoader (type. The class). GetExtension (name) to get the real instance to call, look at a website for the sample.
public interface WheelMaker { Wheel makeWheel(URL url); } public class AdaptiveWheelMaker implements WheelMaker {public Wheel makeWheel(URL URL) {if (url == null) { throw new IllegalArgumentException("url == null"); } // 1. Call url getXXX to getParameter value String wheelMakerName = url.getparameter (" wheel.maker "); if (wheelMakerName == null) { throw new IllegalArgumentException("wheelMakerName == null"); } // 2. Call ExtensionLoader getExtensionLoader to get the loader // 3. The getExtension call to ExtensionLoader loads the implementation class WheelMaker WheelMaker = based on the parameters obtained from the URL as the class name ExtensionLoader.getExtensionLoader(WheelMaker.class).getExtension(wheelMakerName); // 4. Call the concrete method of the implementation class to implement the call. return wheelMaker.makeWheel(URL url); }}Copy the code
The Adaptive annotations can be applied to classes or methods, but they have different implementation logic on classes or methods.
7.8.1 Adaptive annotation is on the class
When Adaptive annotations are on a class, Dubbo will not generate a proxy class for the class. Adaptive annotations are rarely on a class. In Dubbo, only two classes are annotated by Adaptive. AdaptiveCompiler and AdaptiveExtensionFactory respectively, meaning that the extended loading logic is manually coded, which is not our focus.
7.8.2 Adaptive annotation is in the method
When Adaptive annotation is in the method, Dubbo will generate proxy logic for the method, indicating that the extended loading logic needs to be automatically generated by the framework. The general implementation mechanism is as follows:
-
Load an interface annotated with @adaptive annotation. If it does not exist, the Adaptive mechanism is not supported.
-
Generate subclass code from a template for the target interface, compile the generated code, and then generate objects for that class through reflection;
-
In combination with the generated object instance, the configuration of the specified key is obtained through the passed URL object, and then the corresponding class object of the key is loaded. Finally, the call is delegated to the class object.
@SPI(“apple”) public interface FruitGranter { Fruit grant(); @Adaptive String watering(URL url); }
FruitGranter {@override public Fruit grant() {return new Apple(); } @Override public String watering(URL url) { System.out.println(“watering apple”); return “watering finished”; }}
FruitGranter {@override public Fruit grant() {return new Banana(); } @Override public String watering(URL url) { System.out.println(“watering banana”); return “watering success”; }}
Call method implementation:
Public class ExtensionLoaderTest {@test public void testGetExtensionLoader() {// first create a mock URL object URL = URL. The valueOf (" dubbo: / / 192.168.0.1:1412? fruit.granter=apple"); / / a FruitGranter object obtained through ExtensionLoader FruitGranter granter = ExtensionLoader. GetExtensionLoader (FruitGranter. Class) .getAdaptiveExtension(); // Use this FruitGranter to call its "self-adapted labeled" method, and obtain the call result. String result = Granter.watering (URL); System.out.println(result); }}Copy the code
Generate an inner class as described above. The general call process is as follows:
4. Dubbo service exposure process
4.1 Overview of Service Exposure
Dubbo framework is the URL for the bus, run all state in the process of data and information could be obtained via the URL, such as what current system USES serialization, use what communication, use the information, such as what kind of load balancing is presented in the URL parameter, so in the process of framework, operation need data corresponding to a certain stage, Can be obtained from the parameter list of the URL using the corresponding Key. URL parameters are as follows:
Protocol: indicates various protocols in dubbo, such as: dubbo thrift HTTP username/password: indicates the username or password. Host /port: indicates the host or port. Path: indicates the interface name
protocol://username:password@host:port/path? k=vCopy the code
Service exposure is divided into three parts in terms of code flow:
Check the configuration and finally assemble the URL.
Expose service to local service and remote service.
Services are registered to the registry.
Service exposure is divided into two steps from the object build transformation:
Encapsulate the service as Invoker.
Convert Invoker to Exporter by contract.
4.2 Service exposure source tracing
- After the container is started and Spring IOC is refreshed, onApplicationEvent is called to enable service exposure, ServiceBean.
- Export and doExport are splicing to construct URL, so as to shield the details of call, an executable is uniformly exposed, and invoker is obtained through ProxyFactory.
- Calling the specific Protocol converts the wrapped Invoker to EXPORTER, where SPI is used.
- Then start the server Server, listen on the port, and use NettyServer to create a listen server.
- The PROVIDER information is made available to consumers by registering the URL to the registry with the RegistryProtocol.
5. Dubbo service reference process
An executable in Dubbo is an Invoker, so both providers and consumers should look like invokers. As you can see from the demo above, in order to call the remote interface without feeling it, you need a proxy class wrapped around invoker.
There are two opportunities for service introduction:
- The hungry type:
By implementing the afterPropertiesSet method in Spring’s InitializingBean interface, the container introduces the service by calling the afterPropertiesSet method of the ReferenceBean.
- Lazy (default) :
Lazy is to start the import process only when the service is injected into another class.
There are three ways to reference a service:
Local import: The service is exposed locally to avoid network call overhead.
Direct connection To introduce remote services: Do not start the registry, directly write the remote Provider address for direct connection.
Introducing remote services through the registry: The registry decides how to load balance the invocation of remote services.
Service reference process:
Check the configuration to build map, map to build URL, through the protocol on the URL using adaptive extension mechanism to call the corresponding protocol.refer to get the corresponding invoker, here
To register with the registry, subscribe to the registry, obtain the PROVIDER IP address and other information, and connect to the registry through the shared Netty client.
When there are multiple urls, the invoker is iterated and then encapsulated by a StaticDirectory, and then merged through the cluster to expose only one invoker.
It then builds the proxy that encapsulates the invoker return service reference, which Comsumer then calls.
** Call method ** :
- Oneway: does not care whether the request is sent successfully.
- Async Async call: Dubbo is naturally asynchronous. When the client calls the request, the ResponseFuture returned is stored in the context. The user can call future.get at any time to get the result. An asynchronous invocation identifies the request with a unique ID.
- Sync call: Future. get was called in Dubbo source code, and the user felt that the method was blocked and had to wait for the result to return.
6. Dubbo calls the overall process
Here are some things you might want to consider before invoking:
- The consumer and provider agree on communication protocols. Dubbo supports multiple protocols, such as Dubbo, RMI, Hessian, HTTP, and WebService. By default, duBBO protocol is used. The connection belongs to a single long connection and NIO asynchronous communication. It is applicable to transfer a small amount of data (less than 100KB for a single request), but high concurrency.
- Convention serialization patterns can be broadly divided into two categories, one is character type (XML or JSON is human-readable but inefficient), and the other is binary stream (data compact and machine friendly). Hessian2 is used by default as the serialization protocol.
- When the consumer invokes the provider, it provides the interface, method name, parameter type, parameter value, and version number.
- The provider list provides services externally. The load balancer selects a provider to provide services.
- The consumer and provider periodically send messages to the Monitor.
General call process:
- The client makes a request to invoke the interface, which invokes the generated proxy class. The proxy class generates the RpcInvocation and then invokes the invoke method.
- ClusterInvoker gets the list of services in the registry and gives an available Invoker through load balancing.
- Serialization and deserialization of network transmission data. Call the network service through netttyServer.
- The server business thread pool receives parsed data and invokes an Invoker from exportMap.
- Call the real Impl to get the results and return.
Call method:
- Oneway: does not care whether the request is sent successfully and consumes the least amount.
- Sync call: Future. get was called in Dubbo source code, and the user felt that the method was blocked and had to wait for the result to return.
- Async Async call: Dubbo is naturally asynchronous. When the client calls the request, the ResponseFuture returned is stored in the context. The user can call future.get at any time to get the result. An asynchronous invocation identifies the request with a unique ID.
7. Dubbo cluster fault tolerant load balancing
Dubbo introduces Cluster, Directory, Router, LoadBalance, and Invoker modules to ensure the robustness of Dubbo system. Their relationship is shown as follows:
- Service discovery puts multiple remote calls into Directory and then encapsulates them with Cluster into an Invoker that provides fault tolerance.
- The consumer substitute gets an available Invoker from Directory through load balancing and then invokes it.
- You can think of Cluster in Dubbo as a big encapsulation of this, with all sorts of robust features.
7.1 Cluster fault Tolerance
Cluster fault tolerance is implemented on the consumer side through Cluster subclasses. The Cluster interface has 10 implementation classes, and each Cluster implementation class creates a corresponding ClusterInvoker object. The core idea is to let the user selectively call the Cluster middle layer, shielding the implementation details behind.
7.2 Intelligent fault-tolerant load balancing
There are generally four load balancing policies in Dubbo.
- RandomLoadBalance: weighted randomness, its algorithm idea is simple. Assume that there is A set of servers = [A, B, C], corresponding weights = [5, 3, 2], the total weight is 10. Now, these weight values are tiled on the one-dimensional coordinate values. The interval [0, 5) belongs to server A, the interval [5, 8) belongs to server B, and the interval [8, 10) belongs to server C. Then generate a random number in the range of [0, 10) through the random number generator, and calculate which range this random number will fall on. Default implementation.
- LeastActiveLoadBalance: A minimum number of active load balancing, the provider of choice now calls for active at least call, active call number indicating that it was easy, now less and active number from 0 to add up to a request to the active number of + 1, a request processing is completed the active number 1, so the number of active less can also be a disguised form of processing.
- RoundRobinLoadBalance: Weighted polling load balancing. For example, there are two servers A and B, and the call sequence of the polling is A, B, A, and B. If the weight of A is 2:1, the call sequence is A, A, B, A, and B.
- ConsistentHashLoadBalance: Consistent Hash Load balancing: Generates a Hash value for the IP address of the server, projects the Hash value to the ring as a node, and searches for the first node with a Hash value greater than or equal to the key clockwise when the key is searched. Generally speaking, virtual nodes are also introduced to make the data more scattered, so as to avoid data tilt to overwhelm a node. Dubbo has 160 virtual nodes by default.
7.3 Intelligent Fault Tolerance Service catalog
You can think of Directory as a collection of the same service Invoker, with the RegistryDirectory class at its core. It has three functions.
Get the invoker list from the registry.
Monitor changes in the registry invoker, invoker up and down.
Refresh the Invokers list to the services directory.
7.4 Intelligent fault tolerant service routing
A service route is a routing rule that specifies which service providers a service consumer can invoke. Conditional routing rules consist of two conditions for matching service consumers and providers, respectively. For example, here’s a rule:
Host = 10.20.153.14 => host = 10.20.153.12Copy the code
This rule indicates that a service consumer with IP 10.20.153.14 can only invoke the service on the machine with IP 10.20.153.12, but not on other machines. Conditional routing rules are in the following format:
[Service consumer matching condition] => [Service provider matching condition]Copy the code
If the service consumer match condition is empty, the service consumer is not restricted. If the service provider match condition is empty, the service is disabled for some service consumers.
8. Design RPC
After reading through the general implementation of Dubbo, you can see that an RPC framework needs the following:
- Service registration and discovery of the same, you can use ZooKeeper or Redis to implement.
- Then when the consumer makes a request, your interface programming uses a dynamic proxy to make the call.
- Multiple providers provide the same service.
- Finally choose a machine after you agree on a good communication protocol ah, how to serialize and deserialize?
- Bottom layer with ready-made high performance Netty framework NIO mode to achieve bai.
- There is monitor after the service is started.
Three things to watch ❤️
If you find this article helpful, I’d like to invite you to do three small favors for me:
-
Like, forward, have your “like and comment”, is the motivation of my creation.
-
Follow the public account “Java rotten pigskin” and share original knowledge from time to time.
-
Also look forward to the follow-up article ing🚀
-
[666] Scan the code to obtain the learning materials package