Java使用Netty實現簡單的RPC

嚴陣以待發表於2020-05-29

造一個輪子,實現RPC呼叫

在寫了一個Netty實現通訊的簡單例子後,萌發了自己實現RPC呼叫的想法,於是就開始進行了Netty-Rpc的工作,實現了一個簡單的RPC呼叫工程。


如果也有興趣動手造輪子的同學,可以先看看之前寫的 使用Java實現Netty通訊 這篇部落格。


本文源地址:造一個RPC的輪子


準備

首先,你需要明白下列知識。

Netty

處理服務之間的通訊。

Zookeeper

服務註冊與發現。

SpringBoot

目前單單只是作為啟動專案。

Cglib Proxy & Jdk Reflect

使用代理例項化暴露的介面,通過反射呼叫介面方法的實現。


RPC處理流程

Rpc process.jpg

  1. 服務提供者通過Netty進行對應的埠暴露。

  2. 同時提供者將需要暴露的服務資訊註冊到Zookeeper,Zookeeper註冊的節點資訊為介面的類路徑,註冊的Data為暴露的埠,並且需要將對應的介面實現類在ApplicationContext上下文中進行儲存。

  3. 此時消費者啟動後,會根據對應介面的類路徑在Zookeeper進行Discover。

  4. 根據對應介面的類路徑在Zookeeper中通過ReadData獲取到對應暴露的埠資訊。

  5. 拿到了埠資訊,通過Netty發起請求。

  6. Netty發起請求接收後,通過之前的ApplicationContext上下文呼叫對應的介面方法。

  7. ApplicationContext呼叫成功後,Netty將響應的結果返回。

  8. 服務消費者得到響應結果,RPC流程結束。

動手

使用Java實現Netty通訊 這篇文章的基礎上,我們新建下列工程資訊。

netty-rpc-sample.png

rpc-sample-api

依賴資訊

<dependency>
    <groupId>com.yanzhenyidai</groupId>
    <artifactId>netty-rpc-common</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

程式碼

只需要定義一個HiService介面。

public interface HiService {

    public String hi(String msg);
}

rpc-sample-server

依賴資訊

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.0.3.RELEASE</version>
</dependency>

<dependency>
    <groupId>com.yanzhenyidai</groupId>
    <artifactId>rpc-sample-api</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

<dependency>
    <groupId>com.yanzhenyidai</groupId>
    <artifactId>netty-rpc-server</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

application.yaml

register.address: 127.0.0.1:3000

程式碼

  • 首先實現HiService介面。
@RpcServer(cls = HiService.class)
public class HiServiceImpl implements HiService {

    public String hi(String msg) {
        return "hello, I'm Rpc, I want say : " + msg;
    }
}
  • Server類。
@Component
public class Server implements ApplicationContextAware {

    private static final Logger logger = LoggerFactory.getLogger(Server.class);

    @Value("${register.address}")
    private String registerAddress;

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Map<String, Object> serviceBean = new HashMap<String, Object>();

        Map<String, Object> objectMap = applicationContext.getBeansWithAnnotation(RpcServer.class);

        for (Object object : objectMap.values()) {
            try {
                RpcServer annotation = object.getClass().getAnnotation(RpcServer.class);

                serviceBean.put("/yanzhenyidai/" + annotation.cls().getName(), object);

                String[] split = registerAddress.split(":");

                new NettyServer(split[0], Integer.valueOf(split[1])).server(serviceBean);
            } catch (Exception e) {
                logger.error("[server-start] fail ", e);
            }
        }
    }
}

實現 ApplicationContextAware 以獲取到Spring上下文,通過掃描RpcServer註解,得到本次需要暴露的服務資訊,並且開啟NettyServer的埠服務暴露及Zookeeper註冊。

  • Application啟動類
@SpringBootApplication
public class RpcServerApplication {

    public static void main(String[] args) {
        new SpringApplicationBuilder(RpcServerApplication.class)
                .web(WebApplicationType.NONE)
                .run(args);
    }
}

開啟SpringBoot無Web啟動。

rpc-sample-client

依賴

<dependency>
    <groupId>com.yanzhenyidai</groupId>
    <artifactId>netty-rpc-client</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

<dependency>
    <groupId>com.yanzhenyidai</groupId>
    <artifactId>rpc-sample-api</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.2</version>
</dependency>

<dependency>
    <groupId>asm</groupId>
    <artifactId>asm</artifactId>
    <version>3.3.1</version>
</dependency>

程式碼

  • Client
public class Client {

    public <T> T create(final Class<?> cls) {

        return (T) Proxy.newProxyInstance(cls.getClassLoader(), new Class<?>[]{cls}, new InvocationHandler() {
            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {

                Request request = new Request();
                request.setInterfaceName("/yanzhenyidai/" + cls.getName());
                request.setRequestId(UUID.randomUUID().toString());
                request.setParameter(objects);
                request.setMethodName(method.getName());
                request.setParameterTypes(method.getParameterTypes());

                Response response = new NettyClient().client(request);
                return response.getResult();
            }
        });
    }
}

使用Cglib動態代理先例項化介面資訊,在呼叫的時候,通過NettyClient傳送請求到NettyServer,由NettyServer處理發現Zookeeper節點以及反射呼叫介面實現方法。

  • context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean class="com.yanzhenyidai.config.Client"></bean>

</beans>

注入Client的bean物件資訊。

  • Application
public class RpcClientApplication {

    public static void main(String[] args) {

        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("context.xml");

        Client client = context.getBean(Client.class);

        HiService hiService = client.create(HiService.class);
        String msg = hiService.hi("msg");
        System.out.println(msg);

    }
}

執行結果

rpc-sample-result.png


總結

以上只是一個簡單的RPC過程,相對於Dubbo RPC會更加直觀的看明白,希望能對大家有所幫助作用,也歡迎大家和我一起造輪子。?

本專案Github地址:Netty-RPC

相關文章