Java implements a simple RPC framework

Java implements simple RPC framework and understanding

  • consumer
  • RPC framework
    • Registration center
      • Service registration
        • Local service registration
        • Remote service registration
      • service discovery
      • service call
        • dynamic proxy
        • load balancing
        • Retry mechanism
        • Service distribution
  • service provider
    • interface
    • Implementation class

Consumer

As shown in the following code, consumers who want to call HelloService and ByeService of remote services only need to pass parameters and method calls according to the interface definition, and then specify the required service version number, and use the dynamic proxy function implemented by the rpc framework. Make remote method calls as if they were local methods

public class Consumer {<!-- -->
    public static void main(String[] args) {<!-- -->
        HelloService helloService = ProxyFactory.getProxy(HelloService.class, "1.0");
        String res1 = helloService.sayHello("lujx");

        HelloService helloService2 = ProxyFactory.getProxy(HelloService.class, "2.0");
        String res2 = helloService2.sayHello("lujx");

        ByeService byeService = ProxyFactory.getProxy(ByeService.class, "1.0");
        String res3 = byeService.sayBye("lujx");

        log.info("\\
" + res1 + "\\
" + res2 + "\\
" + res3);
    }

}

RPC framework

Registration Center

The core function of the rpc framework is the registration center. The three elements of the registration center are service registration, service discovery, and service invocation.

Service registration

The registration center needs to implement a sharing mechanism and cannot be a resource unique to a certain thread. Therefore, you can choose reids or zookeeper as the data management middleware of the registration center.

 /**
* Manual registration, automatic registration can be achieved through annotations
* */
    public void afterPropertiesSet() throws Exception {<!-- -->
        String host = "localhost";

        //Local registration
        LocalRegister.register(HelloService.class.getName(), "1.0", HelloServiceImpl.class);
        LocalRegister.register(HelloService.class.getName(), "2.0", HelloServiceImpl2.class);
        LocalRegister.register(ByeService.class.getName(), "1.0", ByeServiceImpl.class);

        //Registration center registration
        URL url = new URL("http", host, port, "/inner");
        MapRemoteRegister.register(HelloService.class.getName(), "1.0", url);
        MapRemoteRegister.register(HelloService.class.getName(), "2.0", url);
        MapRemoteRegister.register(ByeService.class.getName(), "1.0", url);

        MapRemoteRegister.serviceMap.forEach((key, value) -> JedisUtil.put("Registration Center", key, JSON.toJSONString(value)));
    }
Local service registration
  • The service node is responsible for registering all externally provided services locally.
  • When a consumer requests a service from the service node through a remote service call, the service provider can quickly find the specified service class through the local service registration center.
 /**
* Local service registration value stores class information, and remote access url information
* */
    public static void register(String interfaceName, String version, Class<?> clazz) {<!-- -->
        List<Class<?>> clazzs = serviceMap.get(interfaceName + version);
        if (clazzs == null) {<!-- -->
            serviceMap.put(interfaceName + version, Collections.singletonList(clazz));
        } else {<!-- -->
            clazzs.add(clazz);
            serviceMap.put(interfaceName + version, clazzs);
        }
    }
    
Remote service registration
  • Responsible for registering in the registration center all services provided by the service node.

Service discovery

Data format for service registration to the registration center

  • key: registration center
  • field: interface name + version number
  • value: url information

When a dynamic proxy makes a service call, it only needs to use the above format to obtain the service node in redis.

Service call

Dynamic proxy
public class ProxyFactory {<!-- -->
    public static <T> T getProxy(Class<?> interfaceClass, String version) {<!-- -->
        Object o = Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{<!-- -->interfaceClass},
                (proxy, method, args) -> {<!-- -->
                    Invocation invocation = new Invocation(interfaceClass.getName(), version, method.getName(),
                            method.getParameterTypes(), args);

                    //Service discovery
                    List<URL> urls = RegisterUtil.getServiceUrl(interfaceClass.getName(), version);

                    //Load balancing
                    URL url = LoadBalanceStrategy.loadBalance(urls);

                    //service call
                    RestTemplate restTemplate = new RestTemplate();
                    HttpHeaders headers = new HttpHeaders();
                    //Set request headers
                    headers.setContentType(MediaType.MULTIPART_FORM_DATA);
                    HttpEntity httpEntity = new HttpEntity(ByteUtil.getByteArray(invocation), headers);
                    ResponseEntity<String> entity = restTemplate.postForEntity(url.toURI(), httpEntity, String.class);

                    return entity.getBody();
        });
        return (T) o;
    }
}
Load balancing

When the dynamic proxy completes service discovery, it obtains the service node URL information. If there is more than one node at this time, a load balancing strategy can be designed to select the appropriate node for service requests.

  • What the author implements is a simple random strategy
Retry mechanism

If the service call fails, consider implementing a retry mechanism

  • Sleep for n seconds and continue calling
  • Set the maximum number of retries
  • Select other service nodes to implement the load balancing strategy again, and then make service calls.
Service distribution

When the dynamic proxy sends a request to the service node through the http protocol, the service node needs to implement a servlet that specifically handles internal node services. If it is a node that specifically handles internal services, it can filter out other illegal requests.
The author borrows the server started by SpringBoot. If it is started directly using the Tomcat package, the following annotations cannot be used.

  • The /inner path represents the internal service
  • @WebServlet
    • Intercept service requests at the specified path
    • Used in conjunction with @ServletComponentScan, configured on the SpringBoot startup class
@WebServlet(urlPatterns = {<!-- -->"/inner"})
public class MyServlet extends HttpServlet {<!-- -->
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) {<!-- -->
        //echo test
        new EchoTestHandler().handler(req, resp);
        //Heartbeat request
        new HeartServerHandler().handler(req, resp);
        //rest interface normal request processing
        new HttpServerHandler().handler(req, resp);
    }
}

Service Provider

Interface

public interface ByeService {<!-- -->
    String sayBye(String name);
}

Implementation class

public class ByeServiceImpl implements ByeService {<!-- -->
    @Override
    public String sayBye(String name) {<!-- -->
        return name + "says bye";
    }
}

ps: time flies so fast