In the spirit of open source, this project README has been synchronized with the English version. In addition, most of the comments in the project’s source code have been changed to English.

If the access speed is not good, you can put it on the Gitee address: gitee.com/SnailClimb/… . To submit an issue or PR, please submit it on Github: github.com/Snailclimb/… .

Related projects:

  1. Netty: github.com/Snailclimb/…
  2. “Java Learning + Interview Guide” covers the core knowledge that most Java programmers need to master. : github.com/Snailclimb/…

preface

Although the principle of RPC is not difficult, there are many problems in the process of implementation. Guide-rpc-framework currently only realizes the most basic functions of RPC framework, some optimization points are mentioned below, interested friends can improve themselves.

With this simple wheel, you can learn the underlying principles and principles of RPC and various Java coding practices in use.

You can even use guide-Rpc-Framework as your graduation/project experience of choice, which is great! Compared to other candidates whose project experience is all kinds of systems, building wheels is definitely more attractive to the interviewer.

If you are going to use guide-Rpc-Framework as your senior thesis/project experience, I hope you will understand it and not just copy and paste my ideas. You can fork my project and optimize it. If you think the optimization is valuable, you can submit the PR to me, AND I will deal with it as soon as possible.

introduce

Guide-rpc-framework is an RPC framework based on Netty+Kyro+Zookeeper implementation. The code comments are detailed, the structure is clear, and the integration of the Check Style specification code structure is very easy to read and learn.

Due to the limited energy and ability of Guide brother, if you feel that there is any improvement or improvement, welcome to fork this project, clone it to the local, and submit the PR to me after local modification, I will Review your code as soon as possible.

Let’s start with a basic RPC framework design ideas!

A basic RPC framework design ideas

Note: When we say RPC frameworks, we mean frameworks that make it as easy for clients to call server-side methods as to call local methods, such as Dubbo, Motan, gRPC, and so on. If you need to work with the HTTP protocol, parse and encapsulate HTTP requests and responses. Such frameworks are not “RPC frameworks”, such as Feign.

One of the simplest RPC frameworks to use is shown below, which is the current architecture of guide-Rpc-Framework:

The service provider Server registers the service with the registry, and the service consumer Client gets the service information from the registry, and then requests the service provider Server through the network.

The architecture of Dubbo, a leader in the RPC framework field, is shown in the following figure, which is similar to the one we drew above.

Generally speaking, RPC framework should not only provide service discovery function, but also provide load balancing, fault tolerance and other functions, so that RPC framework is truly qualified.

Here are a few ideas for designing a basic RPC framework:

  1. Registries: Registries are a must, and Zookeeper is recommended. A registry is responsible for the registration and lookup of service addresses, which is equivalent to a directory service. When the server starts, it registers the service name and its corresponding address (IP +port) in the registry, and the service consumer finds the corresponding service address according to the service name. With the service address, the service consumer can request the server over the network.
  2. Network transmission: since you want to call a remote method to send a request, the request should at least include you call the class name, method name and related parameters! The NIO-based Netty framework is recommended.
  3. Serialization: Since serialization is involved when it comes to network transport, you can’t just use the JDK’s built-in serialization. The JDK’s native serialization is inefficient and has security holes. So, you also need to consider which serialization protocol to use. The most common ones are Hession2, Kyro, and Protostuff.
  4. Dynamic proxy: In addition, dynamic proxy is also required. Since the main purpose of RPC is to make calling remote methods as easy as calling local methods, using dynamic proxies can mask the details of remote method calls such as network transport. This means that when you call a remote method, you are actually transmitting the network request through a proxy object. Otherwise, how can you call a remote method directly?
  5. Load balancing: Load balancing is also required. Why? For example, we have a service on our system that gets a lot of traffic. We deploy this service on multiple servers. When a client initiates a request, multiple servers can handle the request. Then, choosing the right server to handle the request is critical. If you need a single server to handle requests for the service, the point of deploying the service on multiple servers ceases. Load balancing is to avoid a single server to respond to the same request, easy to cause server downtime, crash and other problems, we can obviously feel its significance from the four words of load balancing.
  6. .

Basic information and optimization points of the project

In order to do this step by step, I used the traditional BIO-based Socket network transfer at first, and then implemented the RPC framework using the SERIalization mechanism of the JDK. Later, I optimized the original version, and the optimization points that have been completed and can be completed are listed below ๐Ÿ‘‡.

Why list the optimizable points? I want to give some ideas to those who want to optimize the RPC framework. You are welcome to fork this repository and optimize it yourself.

  • Use Netty (NIO based) instead of BIO for network transport;

  • Use the open source serialization mechanism Kyro (or others) instead of the JDK’s built-in serialization mechanism;

  • Manage service addresses using Zookeeper

  • Netty reuses channels to avoid repeated connections to the server

  • Using the CompletableFuture wrapper to accept the result returned by the client (the previous implementation was done by binding an AttributeMap to a Channel) see optimizations using the CompletableFuture to accept the result returned by the service provider

  • Add a Netty heartbeat mechanism to ensure that the connection between the client and server is not broken or reconnected.

  • Load balancing when a client invokes a remote service: When a service is invoked, a service address is selected from many service addresses based on the corresponding load balancing algorithm. Ps: At present, only random load balancing algorithm is implemented.

  • Handle multiple class implementations of an interface: To group services, add a group parameter when publishing services.

  • Integration Spring registers services through annotations

  • Add the service version number: You are advised to use a two-digit version, such as 1.0. The version number needs to be upgraded only when the interface is incompatible. Why increase the service version number? For example, adding a method to the service interface or adding a field to the service model can be backward compatible. Deleting a method or field will be incompatible. Adding a field to an enumeration type is also incompatible.

  • The application of SPI mechanism

  • Add configurable methods such as serialization and registry implementation to avoid hard coding: configure through API, and it is recommended to use configuration files for subsequent integration with Spring

  • Use annotations for service consumption

Client – server communication protocol (packet structure) redesign

, can be the original

RpcRequest
Copy the code

and

RpcReuqest
Copy the code

Object as the body of the message, and then add the following fields (can refer to: Netty getting Started manual and Dubbo framework for this design) :

  • Magic number: Usually 4 bytes. The magic number is aimed to filter packets from the server to have the magic number, after the server out the front four bytes than in the first place, can be identified in the first time the packet is not follow the custom protocol, which is invalid packets, for the sake of safety can be directly closed connection to conserve resources.

  • Serializer number: Identifies the method of serialization, such as using Java, json, kyro, etc.

  • Message body length: calculated at run time.

  • .

  • Writing tests provides confidence for refactoring code

Project module overview

Run the project

Import the project

Fork project to warehouse, then cloning projects to their own local: git clone [email protected]: username/guide xml-rpc – framework. Git, use the IDEA of open, waiting for the project initialization is complete.

Initialize Git hooks

The main purpose of this step is to run the Check Style before committing the code, so that the code is formatted properly and cannot be committed if there is a problem.

For Mac/Linux, the Windows user needs to manually copy the pre-commit file from the config/git-hooks directory to the.git/hooks/ directory of the project.

Execute the following commands:

โžœ guide-rpc-framework git:(master) qualify chmod +x./init.sh โžœ guide-rpc-framework git:(master) qualify./init.shCopy the code

This script copies the Git commit hook to the.git/hooks/ directory of your project, so that it will be executed every time you commit.

CheckStyle plugin download and configuration

IntelliJ IDEA-> Preferences->Plugins-> Search Download the CheckStyle plugin and configure as follows.

Once configured, use the plug-in as follows!

Download and run ZooKeeper

Docker is used here to download and install.

Download:

Docker pull zookeeper: 3.5.8Copy the code

Run:

Docker run -d --name zooKeeper -p 2181:2181 ZooKeeper :3.5.8Copy the code

use

Service provider

Implementation interface:

@Slf4j
@RpcService(group = "test1", version = "version1")
public class HelloServiceImpl implements HelloService {
    static {
        System.out.println("HelloServiceImpl created");
    }

    @Override
    public String hello(Hello hello) {
        log.info("HelloServiceImpl received: {}.", hello.getMessage());
        String result = "Hello description is " + hello.getDescription();
        log.info("HelloServiceImpl returns: {}.", result);
        returnresult; }}@Slf4j
public class HelloServiceImpl2 implements HelloService {

    static {
        System.out.println("HelloServiceImpl2 created");
    }

    @Override
    public String hello(Hello hello) {
        log.info("HelloServiceImpl2 Received: {}.", hello.getMessage());
        String result = "Hello description is " + hello.getDescription();
        log.info("HelloServiceImpl2 returns: {}.", result);
        returnresult; }}Copy the code

Publish service (transport using Netty) :

/**
 * Server: Automatic registration service via @RpcService annotation
 *
 * @author shuang.kou
 * @createTime 2020ๅนด05ๆœˆ10ๆ—ฅ 07:25:00
 */
@RpcScan(basePackage = {"github.javaguide.serviceimpl"})
public class NettyServerMain {
    public static void main(String[] args) {
        // Register service via annotation
        new AnnotationConfigApplicationContext(NettyServerMain.class);
        NettyServer nettyServer = new NettyServer();
        // Register service manually
        HelloService helloService2 = new HelloServiceImpl2();
        RpcServiceProperties rpcServiceProperties = RpcServiceProperties.builder()
                .group("test2").version("version2").build(); nettyServer.registerService(helloService2, rpcServiceProperties); nettyServer.start(); }}Copy the code

Service consumer

ClientTransport rpcClient = new NettyClientTransport();
RpcServiceProperties rpcServiceProperties = RpcServiceProperties.builder()
  .group("test1").version("version1").build();
RpcClientProxy rpcClientProxy = new RpcClientProxy(rpcClient, rpcServiceProperties);
HelloService helloService = rpcClientProxy.getProxy(HelloService.class);
String hello = helloService.hello(new Hello("111"."222"));
Copy the code

Issues related to

Why was this wheel built? Doesn’t Dubbo smell good?

The RPC framework was written primarily to learn by building wheels and to test the application of the knowledge you have learned.

Implementing a simple RPC framework is actually relatively easy, but a bit harder than writing AOP and IoC, if you understand the fundamentals of RPC.

I previously shared how to implement an RPC from a theoretical level on my knowledge planet. But the theory is just support, you understand the theory may only fool the interviewer. Hands-on skills are still the most important thing in a programmer’s field, even if you’re an architect. When you put your hands to work on something, when you put theory into practice, you will find a lot of holes waiting for you.

In actual projects, we should try to build fewer wheels and use them as soon as possible after we have excellent frames. Dubbo has done a good job in all aspects.

What I need to know in advance if I want to write it myself

Java:

  1. Dynamic proxy mechanism;
  2. Serialization mechanisms and comparisons of various serialization frameworks such as Hession2, Kyro, Protostuff.
  3. Use of thread pools;
  4. CompletableFutureThe use of
  5. .

Netty:

  1. Use Netty for network transmission;
  2. ByteBufintroduce
  3. Netty Sticking and unpacking packets
  4. Netty Long connection and heartbeat mechanism

Zookeeper :

  1. Basic concept;
  2. Data structure;
  3. How to use Open source ZooKeeper client framework of Netflix to add, delete, modify and check;