Java RPC framework based on Netty/Zookeeper implementation
A small RPC framework based on Spring Boot Starter. This RPC framework is not written to repeat the wheel, but for the purpose of learning, by writing a RPC framework to achieve knowledge learning and application purposes. Simple RPC framework (Danran-RPC), the bottom layer uses Netty for network communication, using Zookeeper as the registry. This project can be packaged with Maven and run directly with other projects. Another warehouse:
https://gitee.com/lengdanran/danran-rpc-debug
https://github.com/lengdanran/danran-rpc-debug
Integrated debugging warehouse for the project, in which you can debug danran-RPC source code
Quick learning
Example code: gitee.com/lengdanran/…
https://gitee.com/lengdanran/danran-rpc.git
https://github.com/lengdanran/danran-rpc.git
Generate local Maven dependencies
Clone the code locally from the above git repository address, then go to the project poM directory, and run the Maven installation command:
mvn clean install
Copy the code
The service provider-consumer introduces this Maven dependency simultaneously
Importing packaged maven dependencies, because the RPC framework uses a logging framework that conflicts with Springboot itself, is a good ideadanran-rpc
Log dependencies are removed to avoid program failure to start.
<dependency>
<groupId>danran.rpc</groupId>
<artifactId>danran-rpc-spring-boot-starter</artifactId>
<version>1.0.1</version>
<! -- Remove log conflicts -->
<exclusions>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
Copy the code
Configure registries for both service providers and consumers (replace with your own ZK address)
Add the following configuration to your project’s application.properties
danran.rpc.register-address=121.4.195.203:2181 # Zk address
danran.rpc.protocol=danran
danran.rpc.server-port=6666
Copy the code
Service provider usage examples
package provider.service;
import common.api.BookService;
import common.entity.Book;
import danran.rpc.annotation.RPCService;
import org.springframework.beans.factory.annotation.Autowired;
import provider.mapper.BookMapper;
import java.util.List;
/ * * *@Classname BookServiceImpl
* @Description TODO
* @Date2021/8/24 now *@Created by ASUS
*/
@RPCService
public class BookServiceImpl implements BookService {
@Autowired
private BookMapper bookMapper;
/ * * *@returnAll book information */
@Override
public List<Book> getAllBooks(a) {
returnbookMapper.getAllBooks(); }}Copy the code
By adding the @rpcService annotation to a specific service implementation class, the class can be registered with ZooKeeper as a service provider so that the consumer can discover the service. After adding the annotation, it will be injected into the Spring container as a Component of Spring. (No longer need to add @Component annotation)
Service Consumer Example
package consumer.controller;
import common.api.BookService;
import common.entity.Book;
import danran.rpc.annotation.InjectService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/ * * *@Classname ConsumerController
* @Description TODO
* @Date 2021/8/24 19:04
* @Created by ASUS
*/
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
@InjectService
private BookService bookService;
@GetMapping("/get_all_books")
public List<Book> getAllBooks(a) {
returnbookService.getAllBooks(); }}Copy the code
Using the @InjectService annotation, you can start service discovery and then automatically inject proxy objects for remote services.
Sample run results
Start two Springboot Web services, the Provider provides the implementation of querying the database, and the consumer remotely invokes the service.
- Provider Startup Logs
- Consumer Startup log
Start the Postman interface debugging tool and access the service interface:
Interface returns data successfully, check program log:
RPC principle
define
Remote Procedure Call (RPC), simply understood, means that a local service is required, and the specific service is provided by another independent server. We can notify the server to execute the corresponding service through the network or other means, and then return the information we care about.
RPC: Call to check the results
Local: you
Remote service: your child’s homeroom teacher
Now you want to know the result of your child’s final exam, and the result is in the homeroom teacher, you call the homeroom teacher, and the homeroom teacher checks and tells you what the result is, which is an RPC process.
Computer 1 want to invoke a service of 2, make an RPC call to the communication middleware, after communication middleware will ask marshalling to request is sent to the computer 2 through the network communication middleware, reads the request packets from the network communication middleware, solutions group, then the computer 2 internal local call, after the call, A return data is obtained, and the communication middleware marshals the response data and sends it to computer 1 over the network.
There is a problem: how does computer 1 (the consumer) know the address of the remote service?
Service discovery
To solve this problem, you can use a third party, that is, the person who manages the service, once there is a service request, tell the other party the address of the service you requested.
Therefore, the current popular RPC framework Dubbo adopts the following architecture:
Mainly include consumers, registry, the provider tripartite structure character, consumers through the service registry to subscribe to their care, registry will be registered service address and relevant information is returned to the consumers, consumers through specific protocol sends the data to the socket in the finally sent to the network by the network card, finally get the service provider’s response data.
Danran xml-rpc implementation
File structure
- Annotation: Contains the definition of InjectService and RPCService annotations, which are used to inject and publish services
- Client: Implementation of client service discovery, service proxy, and network communication
- Common: The common module of the framework that contains the definition of the protocol, serialization, and common Entity
- Config: automatic configuration class for Spring container startup
- Properties: user-defined parameter
- Server: Registration, publishing, and startup of RPC services
Implementation component
The implementation of Danran-RPC refers to the implementation of Dubbo, mainly using the following components:
- Network communication — Netty
- Registries – Zookeeper (different registries should be implemented later: Redis, Nacos, Eureka, etc.)
- Proxy – Java dynamic proxy
- The IOC – Spring
The overall architecture
Start the process
Client package implementation
According to the principle of RPC, the client needs to group and send the request data through the network communication middleware and get the corresponding response data.
NetClient
The interface defines the network request client, defines the network request specification, can be implemented by different network communication framework, users can customize the implementation class, can be integrated into the Danran-RPC framework. Netty is used here.
public byte[] sendRequest(byte[] data, Service service) throws InterruptedException {
String[] ip_port = service.getAddress().split(":");
String ip = ip_port[0];
int port = Integer.parseInt(ip_port[1]);
SendHandler sendHandler = new SendHandler(data);
byte[] rsp;
// Configure the client
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
// Initialize the channel
bootstrap.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true).handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(sendHandler); }});// Start the connection
bootstrap.connect(ip, port).sync();
rsp = (byte[]) sendHandler.rspData();
logger.info("Send Request and get the response: {}", rsp);
} finally {
// Release thread group resources
group.shutdownGracefully();
}
return rsp;
}
Copy the code
ClientProxyFactory
This is the client proxy factory, a proxy class for creating remote services that encapsulates the marshalling and unmarshalling of request and response data. The factory class contains the service discovery instance object and the concrete network layer implementation, which can be used with the service discovery instance to discover services and send data. The getProxy(Class
clazz) method returns the clazz proxy object. The core code is:
return (T) this.objectCache.computeIfAbsent(
clazz,
cls -> newProxyInstance(
cls.getClassLoader(),
newClass<? >[]{cls},new ClientInvocationHandler(cls)));
Copy the code
This code builds a proxy object based on the Clazz Class, which is implemented as ClientInvocationHandler. JDK dynamic proxy is used to achieve the InvocationHandler interface.
The key is the implementation of the invoke() method. Call a method of a proxied object by reflection.
First, it will get the proxied object’s full name through the proxied object clazz, and the service discovery instance will go to the service registry to find the corresponding remote service with the obtained full name.
List<Service> services = serviceDiscovery.getServices(serviceName);
Copy the code
After obtaining the specific remote Service, a RequestWrap class is generated that encapsulates the Service name, proxied method name, proxied method parameter type, and proxied method parameter. After that, the encapsulated request will be marshalled according to the protocol to get data, and finally the request will be sent through NetClient at the network layer. Finally, the response data is obtained from the network layer implementation, the ResponseWrap is obtained from the ungroup response, and the specific response data is obtained from the response encapsulation.
ServiceDiscovery — ServiceDiscovery
Service discovery will query the remote service registration information from the registry, and then return. In Danran-RPC, Zookeeper is currently implemented as the registry, so the specific implementation class is ZkDiscovery. The specific implementation process of service discovery is as follows:
The basis for service discovery is exactly the same as the basis for service registration, ensuring that registered services will be discovered.
- Concatenate the ZNode path based on the provided service name (default is full class name)
- Get the names of all the children in this path from Zookeeper (the names of the children are JSON strings of the specific remote service information following URLEncode)
- Decoded to the service provider and returned based on the received service offering information
Server package implementation
This package is the implementation of RPC service registration and service injection package, including the register module and service startup module.
The register module
This module provides a ServiceRegister interface at the top, which is used to define service registration specifications. Users can implement this interface, customize the registration specifications and logic, Danran xml-rpc concrete implementation class for ZookeeperExportServiceRegister, provide a Zookeeper registry implementation.
ZookeeperExportServiceRegister
This class (hereafter referred to as ZESR) implements the registration interface ServiceRegister and provides the Zookeeper registration implementation.
ZESR starts a zkClient based on the Zookeeper address provided by the user.
Service register — register()
- Gets the host address of the service provider
- A user provides a service port, port, from which the service is exposed
- Encapsulating service wrapper classes
Service
- Invoking service exposure
exportService()
Service exposure — exportService()
- Get the Service name (full class name of the Service implementation interface) from the Service
- Service address concatenation (logically consistent with service discovery)
- Concrete services (including the service address of the service provider, as well as the service implementation interface)
{
"address":"192.168.25.1:6666"."name":"common.api.BookService"."protocol":"danran"
}
Copy the code
- Register the service with Zookeeper – create the node
/*** * service exposed **@paramService Specifies the exposed service */
private void exportService(Service service) {
String serviceName = service.getName();
String uri = JSON.toJSONString(service);
try {
uri = URLEncoder.encode(uri, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
// Abstract services locate nodes
String servicePath = "/rpc/" + serviceName + "/service";
logger.info("Abstract service [" + serviceName + "] registered address ==" + servicePath);
if(! zkClient.exists(servicePath)) { zkClient.createPersistent(servicePath,true);
}
// Address of a specific service instance
String uriPath = servicePath + "/" + uri;
logger.info("Specific service instance registered node ==" + uriPath);
if (zkClient.exists(uriPath)) zkClient.delete(uriPath);// If so, delete the update
zkClient.createEphemeral(uriPath);// Create a transient node
}
Copy the code
RPC handler — DefaultRpcProcessor
This class defines the specific process of RPC Service startup, implements Spring ApplicationListener, supports Service startup exposure and automatic Service injection.
Internal contains three members:
This class does two things:
- The user to use
@RPCService
Service publication of the tag - The user to use
@InjectService
Service injection of the tag
The SPRing-supplied IOC container is used here to fetch beans from the container for operation.
RPC Service Startup
- Pull everything from the Spring container
@RPCService
Mark the bean - Iterate over the resulting list of beans, each of which is an instance of the provider object of the actual service
- Get the bean’s Class object, Clazz, and use Clazz to get its implemented interface (which is consistent with the client interface to associate the client with the server).
- The actual service provider is wrapped in the parent class (interface), and then the service registry module is invoked to register with Zookeeper
- Start the RPC service implementation
With services
Inject the service that the user marked with @InjectService.
The Spring container is also used to obtain beans in the container, and then determine whether each bean has a service that needs to be injected. Once an attribute is found, the attribute is injected into a proxy object through reflection. Once the client initiates a service call, the invoke() method of the proxy object will be triggered to execute the proxy execution. Get the required return data.