Remote Procedure Call (RPC) is a communication mechanism that allows different services to communicate and interact over a network.
Through RPC, one service can make a request to another service and receive a response, just like a local call, without developers manually handling the underlying network communication details. The RPC framework encapsulates the underlying network transmission and provides functions such as defining remote service interfaces, serializing and deserializing data.
Distinguishing RPC from HTTP:
HTTP is an application-layer protocol used for transmitting hypermedia. It facilitates communication between clients and servers. It operates on a request-response model, where the client sends an HTTP request to the server, and the server processes the request and returns an appropriate HTTP response. RPC is more similar to an architectural concept; RPC can be implemented using HTTP or TCP.
RPC Process
A simple RPC architecture is shown as follows:
Implementation method:
A simple RPC call chain:
Server implementation based on netty and ZK
According to the figure above, we first need to implement service registration.
Service registration:
Currently, most RPC frameworks support registration through annotations, and the same approach is used here.
This annotation will define the service version, group (to distinguish different interfaces with the same name in the same project), project name, used for service exposure.
Similarly, an annotation is also needed for consumption; an annotation to define the packages to be scanned
Register the service at startup
First, it is necessary to register the annotation with @provider
Get the package that needs to be scanned, then register the annotated bean into Spring
@Override publicvoidregisterBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { //get the scan annotation and the bean package to be scanned String[] scanBasePackages = fetchScanBasePackage(importingClassMetadata); LogUtil.info("scanning packages: [{}]", (Object) scanBasePackages); // //scan the package and register the bean // RpcBeanScanner rpcConsumerBeanScanner = new RpcBeanScanner(registry, RpcConsumer.class); RpcBeanScannerrpcProviderBeanScanner=newRpcBeanScanner(registry, RpcProvider.class); RpcBeanScannerspringBeanScanner=newRpcBeanScanner(registry, Component.class); if (resourceLoader != null) { springBeanScanner.setResourceLoader(resourceLoader); rpcProviderBeanScanner.setResourceLoader(resourceLoader); } intrpcServiceCount= rpcProviderBeanScanner.scan(scanBasePackages); LogUtil.info("rpcServiceScanner扫描的数量 [{}]", rpcServiceCount); LogUtil.info("scanning RpcConsumer annotated beans end"); }
@Override publicvoidsetResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } private String[] fetchScanBasePackage(AnnotationMetadata importingClassMetadata){ AnnotationAttributesannotationAttributes= AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(SimpleRpcApplication.class.getName())); String[] scanBasePackages = newString[0]; if (annotationAttributes != null) { scanBasePackages = annotationAttributes.getStringArray(API_SCAN_PARAM); } //user doesn't specify the package to scan,use the Application base package if (scanBasePackages.length == 0) { scanBasePackages = newString[]{((org.springframework.core.type.StandardAnnotationMetadata) importingClassMetadata).getIntrospectedClass().getPackage().getName()}; } return scanBasePackages; }
}
Register the service and related configurations before the bean initialization to ensure that the service has been successfully registered after Spring starts
Implement the specific method for service registration
To register a service, at least it should include: service provider (IP), service name, and variables in @RpcProvider, so, you can first define an RpcServiceConfig.
@Data @NoArgsConstructor @AllArgsConstructor @Builder publicclassRpcServiceConfig { /** * service version */ privateStringversion="";
/** * target service */ private Object service;
/** * belong to which project */ privateStringproject="";
/** * group */ privateStringgroup="";
/** * generate service name,use to distinguish different service,and * can be split to get the service name * @return */ public String fetchRpcServiceName() { returnthis.getProject() + "*" + this.getGroup() + "*" + this.getServiceName() + "*" + this.getVersion(); }
/** * get the interface name * * @return */ public String getServiceName() { returnthis.service.getClass().getInterfaces()[0].getCanonicalName(); }
}
Provide 2 methods, register services, and get the corresponding bean based on service names
1 2 3 4 5 6 7 8 9 10 11 12 13 14
publicinterfaceRpcServiceRegistryAdapter {
/** * @param rpcServiceConfig rpc service related attributes */ voidregistryService(RpcServiceConfig rpcServiceConfig);
/** * @param rpcClassName rpc class name * @return service object */ Object getService(String rpcClassName);
}
The registration process can be divided into 3 steps:
generate address ->
register service into Zookeeper ->
register into cache.
Here, a ConcurrentHashMap is used to cache services (the method finally calls the Zookeeper API for registration, as it is not closely related to RPC, so it is omitted, and you can refer to the source code directly).
@Override publicvoidregistryService(RpcServiceConfig rpcServiceConfig) { try { // first get address and service StringhostAddress= InetAddress.getLocalHost().getHostAddress(); // add service to zk LogUtil.info("add service to zk,service name{},host:{}", rpcServiceConfig.fetchRpcServiceName(),hostAddress); registerServiceToZk(rpcServiceConfig.fetchRpcServiceName(), newInetSocketAddress(hostAddress, PropertiesFileUtil.readPortFromProperties())); // add service to map cache registerServiceToMap(rpcServiceConfig); } catch (UnknownHostException e) { LogUtil.error("occur exception when getHostAddress", e); thrownewRuntimeException(e); }
}
@Override public Object getService(String rpcServiceName) { Objectservice= serviceMap.get(rpcServiceName); if (null == service) { thrownewRpcException(RpcErrorMessageEnum.SERVICE_CAN_NOT_BE_FOUND.getCode(),"service not found"); } return service; }