RPC was originally a socket-RPC framework to dynamically register dubbo services, service routing, load balancing evolution, rpcdubbo
Order: RPC sends a socket to tell the server which method of the class I want to call and obtain the processing result. Service Registration and routing are called by service consumers through third-party storage media service information.
RPC is a remote process call. There are many implementation methods, such as webservice. The framework is too much to adjust, so if we don't have the passion, we should ask ourselves what the role of these frameworks is to find the original passion.
Generally, the system we write is a standalone system, a web server, and a database service. However, when the processing capacity of a single server is limited by hardware costs, it cannot infinitely improve processing performance. At this time, we use RPC to convert the original local call into calling methods on the remote server, which improves the processing capability and throughput of the system.
The implementation of RPC includes the client and server, that is, the Service caller and the service provider. The service caller sends an rpc request to the service provider. The service provider executes the request method based on the parameters provided by the caller and returns the execution result to the caller. an rpc call is completed.
Let's use socket to implement RPC to see what it looks like.
Discuss with author: http://www.cnblogs.com/intsmaze/p/6058765.html
Supports website development and java development.
Sina Weibo: intsmaze Liu Yang Ge
: Intsmaze
The server code is as follows:
How to provide services on the server
package cn.intsmaze.tcp.two.service;public class SayHelloServiceImpl { public String sayHello(String helloArg) { if(helloArg.equals("intsmaze")) { return "intsmaze"; } else { return "bye bye"; } }}
The port class that the server starts to receive external method requests. After receiving the request data from the client, it creates an object of the specified class using the reflection knowledge and calls the corresponding method, then, return the execution result to the client.
Package cn. intsmaze. tcp. two. service; import java. io. objectInputStream; import java. io. objectOutputStream; import java. lang. reflect. method; import java.net. serverSocket; import java.net. socket; public class Provider {public static void main (String [] args) throws Exception {ServerSocket server = new ServerSocket (1234); while (true) {Socket socket = server. accept (); ObjectInputStream input = new ObjectInputStre Am (socket. getInputStream (); String classname = input. readUTF (); // obtain the class name String methodName = input. readUTF (); // obtain the name of the method Class to be called by the server <?> [] ParameterTypes = (Class <?> []) Input. readObject (); // obtain the parameter type Object [] arguments = (Object []) input of the method to be called by the server. readObject (); // obtain the value of Class serviceclass = Class for each parameter of the method to be called by the server. forName (classname); // create a Class Object = serviceclass. newInstance (); // create object Method = serviceclass. getMethod (methodName, parameterTypes); // obtain the corresponding method of the Class Object result = method. invoke (object, arguments); // This object calls the specified method ObjectOutputStream output = new ObjectOutputStream (socket. getOutputStream (); output. writeObject (result); socket. close ();}}}
Service caller code
To call a service, the client starts a socket and sends data to the service provider. The data is to tell the provider which method of the class to call, the number of parameters that have been called this method, and end the data returned by the server.
Package cn. intsmaze. tcp. two. client; import java. io. objectInputStream; import java. io. objectOutputStream; import java.net. socket; public class consumer {@ SuppressWarnings ({"unused", "rawtypes"}) public static void main (String [] arg) throws Exception {// if you want to call a remote service, you must tell the remote client which class we want to call. Here, you can create an interface locally to obtain the class name, however, we must // ensure that the package name of the interface is the same as that of the remote interface. This method is not good. So we should adopt hard encoding.
// Although webservice is like this, I personally think it is not good. // String interfacename = SayHelloService. class. getName (); String classname = "cn. intsmaze. tcp. two. service. sayHelloServiceImpl "; String method =" sayHello "; Class [] argumentsType = {String. class}; Object [] arguments = {"intsmaze"}; Socket socket = new Socket ("127.0.0.1", 1234); ObjectOutputStream output = new ObjectOutputStream (socket. getOutputStream (); output. writeUTF (classname); output. writeUTF (method); output. writeObject (argumentsType); output. writeObject (arguments); ObjectInputStream input = new ObjectInputStream (socket. getInputStream (); Object result = input. readObject (); System. out. println (result); socket. close ();}}
Of course, for performance considerations, non-blocking I/O is often used to avoid infinite waiting, resulting in consumption of system performance.
The above is just a simple process. When the calls between systems become more complex, this method has the following shortcomings: the service caller Code specifies the information (Class Name, method name) of the called service in a hard-coded manner. After the service provider modifies the code of the provided service, the service caller must modify the code to make adjustments. Otherwise, the Service caller may fail to call a remote method, causing system exceptions. When the service provider goes offline, the Service caller does not know whether the server is alive, access will still be performed, resulting in exceptions.
In a system, there are often not one service provider but multiple service providers. Therefore, it is a problem for service consumers to find the corresponding service from many service providers for RPC, at this time, we cannot hard code in the service caller code to indicate which service address to call and other information, because we can imagine that there is no unified place to manage all services, therefore, we cannot clarify the services and call relationships between services in the complicated system. This is a disaster.
At this time, we need to register the service. A third-party storage medium will be used to write the service-related information to the storage medium through code when the service provider goes online, key-value: Service name: (Class Name, method name, parameter type, parameter, IP address, port ). When the service caller calls the service remotely, the service is obtained from the third-party storage medium based on the service name to be called (Class Name, method name, parameter type, parameter, IP address, port) and then send a call request to the server. In this way, the Code becomes flexible and does not change the global architecture due to a local change. This is because the name of the service is not changed. This method is actually an soa architecture. The service consumer finds the relevant information of the service to be called from many services through the service name, which is called the service routing.
The following uses a static MAP object to simulate the media stored by a third party.
Package cn. intsmaze. tcp. three; import net. sf. json. JSONObject; public class ClassWays {String classname; // Class name String method; // method class [] argumentsType; // String ip address of the parameter type; // int port of the Service ip address; // service port get, set ......}
Third-party storage media, the information of the service provider is fixed here. Ideally, when the service is started, information is automatically added to the map set of this class. However, because the server and the client start two different jvm processes, the client cannot access the data written to the static map set on the server.
package cn.intsmaze.tcp.three;import java.util.HashMap;import java.util.Map;import net.sf.json.JSONObject;public class ServiceRoute { public static Map<String,String> NAME=new HashMap<String, String>(); public ServiceRoute() { ClassWays classWays=new ClassWays(); Class[] argumentsType={String.class}; classWays.setArgumentsType(argumentsType); classWays.setClassname("cn.intsmaze.tcp.three.service.SayHelloServiceImpl"); classWays.setMethod("sayHello"); classWays.setIp("127.0.0.1"); classWays.setPort(1234); JSONObject js=JSONObject.fromObject(classWays); NAME.put("SayHello", js.toString()); } }
Next, let's look at the beautiful faces of the server code.
Package cn. intsmaze. tcp. three. service; public class Provider {// when the service is started, relevant information is assembled and written to the third-party storage mechanism for service callers to obtain public void reallyUse () {ClassWays classWays = new ClassWays (); Class [] argumentsType = {String. class}; classWays. setArgumentsType (argumentsType); classWays. setClassname ("cn. intsmaze. tcp. three. service. sayHelloServiceImpl "); classWays. setMethod ("sayHello"); classWays. setIp ("127.0.0.1"); classWays. SetPort (1234); JSONObject js = JSONObject. fromObject (classWays); // simulate a third-party storage medium, such as redis, mysql, and zookeeper. ServiceRoute. NAME. put ("SayHello", js. toString ();} public static void main (String [] args) throws Exception {ServerSocket server = new ServerSocket (1234); // in practice, the following method should be called here, however, the registration information is hardcoded in the ServiceRoute class because of a simple simulation of service registration. The constructor of this class automatically registers service-related information. // Server. reallyUse (); while (true) {Socket socket = server. accept (); ObjectInputStream input = new ObjectInputStream (socket. getInputStream (); String classname = input. readUTF (); String methodName = input. readUTF (); Class <?> [] ParameterTypes = (Class <?> []) Input. readObject (); Object [] arguments = (Object []) input. readObject (); Class serviceclass = Class. forName (classname); Object object = serviceclass. newInstance (); Method method = serviceclass. getMethod (methodName, parameterTypes); Object result = method. invoke (object, arguments); ObjectOutputStream output = new ObjectOutputStream (socket. getOutputStream (); output. writeObject (result); socket. close ();}}}
Service caller code:
Package cn. intsmaze. tcp. three. client; public class Consumer {public Object reallyUse (String provideName, Object [] arguments) throws Exception {// simulate taking data from a third-party storage medium ServiceRoute serviceRoute = new ServiceRoute (); string js = serviceRoute. NAME. get (provideName); JSONObject obj = new JSONObject (). fromObject (js); ClassWays classWays = (ClassWays) JSONObject. toBean (obj, ClassWays. class); String classname = classWays. getClassname (); String method = classWays. getMethod (); Class [] argumentsType = classWays. getArgumentsType (); Socket socket = new Socket (classWays. getIp (), classWays. getPort (); ObjectOutputStream output = new ObjectOutputStream (socket. getOutputStream (); output. writeUTF (classname); output. writeUTF (method); output. writeObject (argumentsType); output. writeObject (arguments); ObjectInputStream input = new ObjectInputStream (socket. getInputStream (); Object result = input. readObject (); socket. close (); return result ;}@ SuppressWarnings ({"unused", "rawtypes"}) public static void main (String [] arg) throws Exception {Consumer consumer = new Consumer (); Object [] arguments = {"intsmaze"}; Object result = consumer. reallyUse ("SayHello", arguments); System. out. println (result );}}
Back to the problem at the beginning, we now ensure that service callers dynamically control the parameters of service calls and encapsulate them, the service caller only needs to specify the parameter value for each call. However, when the service provider goes offline and the service caller does not know whether the server is alive, the service still accesses the service, causing an exception. How can we solve this problem?
For the rest, I will not write code examples. The code is just a manifestation of the idea, just as the development language has been changing, but the idea remains unchanged.
When a service is deprecated, we should delete the service from a third-party storage, and write code to the service provider for deletion control. That is, before the service is deprecated, access the third party to delete its own services. This certainly won't work, because it won't be said when the service goes down. I am going to go down, and the service provider will go to a third-party storage medium to delete the service information. Therefore, we have to work on a third-party storage medium. For example, the service provider does not directly write service information to a third-party storage medium, but interacts with a third-party system, the third-party system writes the received service information from the service provider to the third-party storage medium, and establishes a heartbeat detection between the service provider and the third-party system, when a third-party system detects that the service provider is down, it automatically deletes the corresponding service information from the third-party media.
In this case, we can select zookeeper as the third-party storage medium. The service will create a temporary directory on zookeeper to store the service information. When the server goes down, zookeeper will automatically delete the folder, which enables dynamic online/offline services.
This is actually a special feature of dubbo: service configuration center-Dynamic Registration and retrieval of service information to manage service names and information about their servers in a unified manner. When a service provider starts, it registers the service name and server address it provides to the service configuration center. The service consumer obtains the machine that needs to call the service through the configuration center. When the server goes down or goes offline, the corresponding machine needs to be dynamically removed from the service configuration center and the corresponding service consumer is notified. In this process, the service consumer only needs to query the service configuration center when calling the service for the first time, and then caches the queried information locally. Subsequent calls directly use the locally cached service address information, you do not need to initiate a new request to the service configuration center to obtain the corresponding service address until the service address list is changed (the machine goes online or offline ).
How does zookeeper know? Zookeeper is actually determined by a heartbeat check directly with the client. The zookeeper function is very simple. You can check the corresponding books by yourself.
With the development of business, the scale of service callers has reached a certain stage, which puts a great deal of pressure on the service providers. At this time, the service providers are no longer a single machine, it is a service cluster.
How can service callers efficiently select a machine in the service provider cluster?
When talking about the cluster, we will all think of reverse proxy nginx, so we will use the nginx configuration file to store all the IP address and port information in the cluster. Then, the service information stored in the third-party storage medium -- key-value: Service name: (Class Name, method name, parameter type, parameter, IP address, Port) change the IP address to the proxy address of the cluster. Then, the service consumer obtains the service information based on the service name, assembles the request, and sends the data to nginx. Then, nginx forwards the request to one of the corresponding service provider clusters.
This is indeed satisfying, but if you are nitpicking, you will find the problems exposed by him!
I. Use nginx for load balancing. Once nginx goes down, all services dependent on it will become invalid. At this time, the service provider will not go down.
2: This is an internal system call. The number of service caller clusters is much smaller than the number of requests from the external system. Therefore, we pass all the requests from the service consumer to the service provider through nginx, unnecessary efficiency overhead.
Improvement Solution: store all the information of the service provider cluster to a third-party system (such as zookeeper) under the corresponding service name, in the form of -- service name: [{machine ip: (Class Name, method Name, parameter type, parameter, IP address, Port)}...]. In this way, the service consumer obtains all the service information (the address list of the service cluster) from a third-party storage system (such as zookeeper ), then the service caller selects an access address from the list based on the server Load balancer algorithm.
In this case, we may think about whether to implement the Server Load balancer Algorithm on a third-party system (such as zookeeper) or on the service caller by referring to nginx? Server Load balancer algorithms are deployed in third-party systems (such as zookeeper). service consumers send service names to third-party systems, the third-party system selects a service consumer from the address information list of the service based on the service name based on the server Load balancer algorithm. After the service consumer obtains the specific information of the called service, send a request directly to the service provider. But as I said, this is just an internal system, and the number of requests is usually not much changed. In addition, to implement it, a middleware should be written as an intermediary before the service consumer directly calls the zookeeper system, too much trouble. We can embed the load balancing algorithm in the service consumer. After the service consumer obtains the service address list, the server Load balancer algorithm selects the request data from the address information list. Furthermore, after the service consumer executes the Server Load balancer Algorithm for the first time, it stores the selected address information in the local cache. After the service consumer accesses the Server Load balancer algorithm again, it directly obtains the address information from the local server and no longer obtains it from a third-party system.
The server Load balancer solution based on a third-party system has been implemented, so let's solve the next problem. How can we tell the service consumers when the service is online or offline to avoid access exceptions from service consumers?
As we mentioned above, service providers can use the features of the zookeeper system to register and delete services. Similarly, we can also allow service consumers to listen to the corresponding service directories on zookeeper, when the service directory changes, the service consumer obtains the new service address information on zookeeper again, and then selects a new service for request by using the algorithm of Server Load balancer.
If you have any clear information, leave a message and I will correct it. Basically, RPC is like this. The rest of the RPC-based frameworks are nothing more than implementing more protocols, as well as consideration and efficiency improvement in some multi-language environments.
I think it's a good recommendation. I spent a day analyzing my knowledge. Thank you. Of course, this is still not well written. I will wait until next week to add more images for improvement. You are welcome to discuss the design of this architecture and grow together.