Java使用Netty實現簡單的RPC
造一個輪子,實現RPC調用
在寫了一個Netty實現通信的簡單例子後,萌發了自己實現RPC調用的想法,於是就開始進行了Netty-Rpc的工作,實現了一個簡單的RPC調用工程。
如果也有興趣動手造輪子的同學,可以先看看之前寫的 使用Java實現Netty通信 這篇博客。
本文源地址:造一個RPC的輪子
準備
首先,你需要明白下列知識。
Netty
處理服務之間的通信。
Zookeeper
服務註冊與發現。
SpringBoot
目前單單只是作為啟動項目。
Cglib Proxy & Jdk Reflect
使用代理實例化暴露的接口,通過反射調用接口方法的實現。
RPC處理流程
- 服務提供者通過Netty進行對應的端口暴露。
- 同時提供者將需要暴露的服務信息註冊到Zookeeper,Zookeeper註冊的節點信息為接口的類路徑,註冊的Data為暴露的端口,並且需要將對應的接口實現類在ApplicationContext上下文中進行保存。
- 此時消費者啟動後,會根據對應接口的類路徑在Zookeeper進行Discover。
- 根據對應接口的類路徑在Zookeeper中通過ReadData獲取到對應暴露的端口信息。
- 拿到了端口信息,通過Netty發起請求。
- Netty發起請求接收後,通過之前的ApplicationContext上下文調用對應的接口方法。
- ApplicationContext調用成功後,Netty將響應的結果返回。
- 服務消費者得到響應結果,RPC流程結束。
動手
在 使用Java實現Netty通信 這篇文章的基礎上,我們新建下列工程信息。
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="//www.springframework.org/schema/beans"
xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="//www.springframework.org/schema/beans //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過程,相對於Dubbo RPC會更加直觀的看明白,希望能對大家有所幫助作用,也歡迎大家和我一起造輪子。😝
本項目Github地址:Netty-RPC