Teach you to write an RPC framework hand in hand

Following the previous section, Teach you to write an RPC framework (1) We handwritten a Spring Boot Starter and started the first step of building our own wheels. In this section, let's continue to complete the RPC framework!

V. user defined annotation

For my requirements, when using my RPC framework, the service provider can tell the framework which interfaces can be called by remote procedures by annotation, that is, expose the service; At the same time, the service consumer can also inject the interface of the remote service to be called through annotation, so as to call its methods. Therefore, I decided to write two notes:

  • @ZhonggerRpcServiceProvider: service provider annotation. The interface marked by this annotation can expose services to the outside world
  • @ZhonggerRpcServiceReference: service consumer annotation, which can inject the remote interface marked by @ ZhonggerRpcServiceProvider into the local to make remote calls

@The implementation of ZhonggerRpcServiceProvider is as follows:

package com.zhongger.rpc.annotation;

import org.springframework.stereotype.Component;

import java.lang.annotation.*;

/**
 * The interface marked by @ ZhonggerRpcServiceProvider is responsible for exposing services
 *
 * @author zhongmingyi
 * @date 2021/12/10 8:58 afternoon
 */
@Target(ElementType.TYPE) //The scope is class, interface and Enum
@Retention(RetentionPolicy.RUNTIME) //Annotation retention time: valid at run time
@Component //Can be injected into the Spring container
@Documented //Javadoc tool will include the annotation information of this annotation tag element in Javadoc
public @interface ZhonggerRpcServiceProvider {
}

@The implementation of ZhonggerRpcServiceReference is as follows:

package com.zhongger.rpc.annotation;

import org.springframework.stereotype.Component;

import java.lang.annotation.*;

/**
 * An interface that injects remote services for service consumers
 *
 * @author zhongmingyi
 * @date 2021/12/10 9:37 afternoon
 */
@Target(ElementType.TYPE) //The scope is class, interface and Enum
@Component //Can be injected into the Spring container
@Retention(RetentionPolicy.RUNTIME)//Annotation retention time: valid at run time
@Documented //Javadoc tool will include the annotation information of this annotation tag element in Javadoc
public @interface ZhonggerRpcServiceReference {
}

The definitions of both annotations use three meta annotations:

  • @Target: indicates the Java element type to which the annotation can be applied, where ElementType Type applies to classes, interfaces (including annotation types) and enumerations
  • @Retention: indicates the life cycle of the annotation, where retentionpolicy Runtime indicates that the lifecycle of annotations is runtime
  • @Documented: indicates that the element marked by the annotation can be documented by Javadoc or similar tools

A Spring annotation is used:

  • @Component: indicates that the class marked by the interface can be injected into the Spring container and can be managed by Spring

So far, we have only defined two annotations. The implementation of these two annotation functions will be left behind ~

Vi. Zookeeper Registration Center

Zookeeper is a sub project of Apache Hadoop. It is a tree type directory service that supports change push. It is suitable as the registry of Dubbo service. It has high industrial intensity and can be used in production environment. It is recommended Dubbo official documents

Because excellent RPC frameworks such as Dubbo use Zookeeper as the registry, it can be seen that the stability of Zookeeper can stand the test, so my RPC framework also decides to use Zookeeper as the registry. The installation of Zookeeper is not written here. There are still many tutorials on the Internet. Review the principle of RPC framework. Service providers need to register their IP address, port number and services to be provided in the registry, so that service consumers can find the address of service providers from the registry, so as to send network requests and call corresponding services. So, now let's write the code to connect Zookeeper.

The tool I use to operate Zookeeper is Apache cursor.

<dependency>
	<groupId>org.apache.curator</groupId>
	<artifactId>curator-recipes</artifactId>
	<version>${curator.version}</version>
</dependency>
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-framework</artifactId>
    <version>${curator.version}</version>
</dependency>

First, define an interface RpcServiceRegister (for later expansion, if Zookeeper is not used as the registry center, other implementations can be used)

import com.zhongger.rpc.entity.ServerNode;
/**
 * Service registration
 *
 * @author zhongmingyi
 * @date 2021/12/11 12:13 afternoon
 */
public interface RpcServiceRegister {
    /**
     * Register the service node with the registry
     *
     * @param serverNode
     */
    void register(ServerNode serverNode) throws Exception;

    /**
     * Returns the client used by the registry
     *
     * @return
     */
    Object getRegisterCenterClient();
}

Define ZookeeperRpcServiceRegister class to implement RpcServiceRegister interface:

package com.zhongger.rpc.register.impl;

import com.alibaba.fastjson.JSON;
import com.zhongger.rpc.entity.ServerNode;
import com.zhongger.rpc.register.RpcServiceRegister;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.URLEncoder;

/**
 * @author zhongmingyi
 * @date 2021/12/11 12:20 afternoon
 */
public class ZookeeperRpcServiceRegister implements RpcServiceRegister {
    private static final Logger logger = LoggerFactory.getLogger(ZookeeperRpcServiceRegister.class);

    private CuratorFramework zookeeperClient;

    public ZookeeperRpcServiceRegister(String zookeeperAddress) {
        zookeeperClient = CuratorFrameworkFactory.
                builder().
                connectString(zookeeperAddress).
                sessionTimeoutMs(10000).
                retryPolicy(new RetryNTimes(3, 5000)).
                namespace("rpc-register-center").
                build();
        logger.info("init zookeeper client success {}", zookeeperAddress);
    }


    @Override
    public void register(ServerNode serverNode) throws Exception {
        logger.info("register server node info is {}", serverNode);
        String uri = JSON.toJSONString(serverNode);
        uri = URLEncoder.encode(uri, "UTF-8");

        String servicePath = "/com/zhongger/rpc/" + serverNode.getServerName() + "/service";
        // Create permanent node
        if (zookeeperClient.checkExists().forPath(servicePath) == null) {
            logger.info("service path {} not exist create persistent node ", servicePath);
            zookeeperClient.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath(servicePath);
        }

        //Create temporary node
        String uriPath = servicePath + "/" + uri;
        logger.info("uri path is {}", uriPath);
        if (zookeeperClient.checkExists().forPath(uriPath) != null) {
            zookeeperClient.delete().forPath(uriPath);
        }
        zookeeperClient.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(uriPath);
    }

    @Override
    public Object getRegisterCenterClient() {
        return zookeeperClient;
    }
}

In the initialization of Zookeeper connection class, there are the following points:

  • The IP address and port number of the connection are: zookeeperAddress
  • The Session timeout is 10s
  • The retry policy is 35000
  • Namespace: RPC register center

Write a @ ConfigurationProperties class. The class marked by @ ConfigurationProperties can use application Properties file.

package com.zhongger.rpc.properties;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * RPC Service information configuration class, corresponding to application Configuration in YML
 *
 * @author zhongmingyi
 * @date 2021/12/10 9:54 afternoon
 */
@ConfigurationProperties(prefix = "com.zhongger.rpc")
@Data
@Component //Indicates that this is a Spring component
public class ZhonggerRpcProperties {
    //Service name
    private String applicationName;
    //Zookeeper registration center address
    private String zookeeperAddress;
    //Port number exposed by RPC service
    private Integer servicePort;
}

The ZhonggerRpcProperties class needs to be managed by the Spring container, so write it into the configuration class AutoConfiguration:

package com.zhongger.rpc.config;

import com.zhongger.rpc.properties.ZhonggerRpcProperties;
import com.zhongger.rpc.register.impl.ZookeeperRpcServiceRegister;
import com.zhongger.rpc.service.StarterDemoService;
import com.zhongger.rpc.service.StarterDemoServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author zhongmingyi
 * @date 2021/12/7 6:40 afternoon
 */
@Configuration
public class AutoConfiguration {

    /**
     * The configuration file is bound to class properties
     * @return
     */
    @Bean
    public ZhonggerRpcProperties getZhonggerRpcProperties() {
        return new ZhonggerRpcProperties();
    }
	/**
     * Return to the Zookeeper registry, where the Ip+Port of Zookeeper is dynamically configured by the configuration file and bound to the zookeeperAddress property of ZhonggerRpcProperties class
     * As a parameter of the construction method of ZookeeperRpcServiceRegister
     *
     * @param properties
     * @return
     */
    @Bean
    public ZookeeperRpcServiceRegister getZookeeperRpcServiceRegister(@Autowired ZhonggerRpcProperties properties) {
        return new ZookeeperRpcServiceRegister(properties.getZookeeperAddress());
    }

}

OK, the Zookeeper of the registry has been written. Now let's use the project zhongger RPC consumer to test:

  • First, in application Configure the address of Zookeeper in properties
com.zhongger.rpc.zookeeper-address=127.0.0.1:2181
  • Inject ZookeeperRpcServiceRegister into the Controller, call its method, and start the project
package com.zhongger.rpc.consumer.controller;

import com.zhongger.rpc.properties.ZhonggerRpcProperties;
import com.zhongger.rpc.register.impl.ZookeeperRpcServiceRegister;
import com.zhongger.rpc.service.StarterDemoService;
import org.apache.curator.framework.CuratorFramework;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author zhongmingyi
 * @date 2021/12/7 7:52 afternoon
 */
@RestController
public class StarterDemoController {
    @Autowired
    private ZookeeperRpcServiceRegister zookeeperRpcServiceRegister;
    @GetMapping("/testZookeeper")
    public String testZookeeper() {
        CuratorFramework zkClient = (CuratorFramework) zookeeperRpcServiceRegister.getRegisterCenterClient();
        return "zookeeper init success namespace is " + zkClient.getNamespace();
    }
}

  • Browser access: http://localhost:8080/testZookeeper , it is found that the namespace of Zookeeper is consistent with that during initialization: RPC register center, which indicates that the connection is successful!

Well, today's article is written here. If you like this series, don't forget to click three times! See you next time!

Keywords: Java Spring rpc

Added by zymosan on Sun, 12 Dec 2021 06:36:23 +0200