RPC(Remote Procedure Call)— 远程过程调用,是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。两个或多个应用程序分布在不同的服务器上,它们之间的调用都像是本地方法调用一样,常见的 RPC 框架有: 比较知名的如阿里的Dubbo、google的gRPC、Go语言的rpcx、Apache的thrift, Spring 旗下的 Spring Cloud。今天我们就利用netty框架,手撸一款简易的RPC框架(不考虑服务注册中心)。
1.服务消费方(client)以本地调用方式调用服务
2.client stub (客户端根程序)接收到调用后负责将方法、参数等封装成能够进行网络传输的消息体
3.client stub 将消息进行编码并发送到服务端
4.server stub (服务端根程序) 收到消息后进行解码
5.server stub 根据解码结果调用本地的服务
6.本地服务执行并将结果返回给 server stub
7server stub 将返回导入结果进行编码并发送至消费方
8.client stub 接收到消息并进行解码
9.服务消费方(client)得到结果
RPC 的目标就是将 2-8 步骤都封装起来,用户无需关心这些细节,可以像调用本地方法一样完成远程服务调用。其中编码和解码需要我们留意一下:在TCP进行数据传输的过程中,出站(消息发出)前需要进行编码操作,入站(消息接收)前需要进行解码操作,编码解码器则成为了必不可少的内容,否则会引发粘包和拆包问题,关于粘包和拆包,不明白的朋友可以自行百度,在此不做赘述。
1.创建一个接口,定义抽象方法。用于消费者和提供者之间的约定。
2.创建一个提供者,该类需要监听消费者的请求,并按照约定返回数据。
3.创建一个消费者,该类需要透明的调用自己不存在的方法,内部需要使用 Netty 请求提供者返回数据
我们把协议定义的简单一些:只有以 myRPC# 开头的消息发送到server,才会对消息进行处理并返回给client。
pom.xml引入相关依赖:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>nettygroupId>
<artifactId>nettyDemoartifactId>
<version>1.0-SNAPSHOTversion>
<dependencies>
<dependency>
<groupId>io.nettygroupId>
<artifactId>netty-allartifactId>
<version>4.1.20.Finalversion>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<configuration>
<source>1.8source>
<target>1.8target>
<encoding>utf-8encoding>
configuration>
plugin>
plugins>
build>
project>
创建公共接口,这里我们也做的简单一些,如下:HelloService.java
package rpc.publicinterface;
//公共接口,服务提供方和服务消费方都需要
public interface HelloService {
String hello(String mes);
}
公共接口实现类(供提供者调用为消费者返回结果):HelloServiceImpl.java
package rpc.provider;
import rpc.publicinterface.HelloService;
public class HelloServiceImpl implements HelloService {
@Override
public String hello(String mes) {
//根据mes 返回不同的结果
if (mes != null) {
return "你好客户端, 我已经收到你的消息 [" + mes + "] ";
} else {
return "你好客户端, 我已经收到你的消息 ";
}
}
}
服务提供方实现:NettyServer.java
package rpc.netty;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
public class NettyServer {
public static void startServer(String hostName, int port) {
startServer0(hostName,port);
}
//编写一个方法,完成对NettyServer的初始化和启动
private static void startServer0(String hostname, int port) {
//处理accecpt
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
//处理具体的业务逻辑
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//启动类
ServerBootstrap serverBootstrap = new ServerBootstrap();
//设置启动参数
serverBootstrap.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
pipeline.addLast(new NettyServerHandler()); //业务处理器
}
}
);
ChannelFuture channelFuture = serverBootstrap.bind(hostname, port).sync();
System.out.println("服务提供方开始提供服务~~");
channelFuture.channel().closeFuture().sync();
}catch (Exception e) {
e.printStackTrace();
}
finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
服务提供方业务处理类实现:NettyServerHandler.java
package rpc.netty;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import rpc.customer.ClientBootstrap;
import rpc.provider.HelloServiceImpl;
//服务器这边handler比较简单
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//获取客户端发送的消息,并调用服务
System.out.println("msg=" + msg);
//客户端在调用服务器的api 时,我们需要定义一个协议
//比如我们要求 每次发消息是都必须以某个字符串开头 如:"myRPC#你好"
if(msg.toString().startsWith(ClientBootstrap.providerName)) {
String result = new HelloServiceImpl().hello(msg.toString().substring(msg.toString().lastIndexOf("#") + 1));
ctx.writeAndFlush(result);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
服务提供方启动类实现:ServerBootstrap.java
package rpc.provider;
import rpc.netty.NettyServer;
//ServerBootstrap 会启动一个服务提供者,就是 NettyServer
public class ServerBootstrap {
public static void main(String[] args) {
NettyServer.startServer("127.0.0.1", 7000);
}
}
服务消费方业务处理类实现:NettyClientHandler.java
package rpc.netty;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.util.concurrent.Callable;
public class NettyClientHandler extends ChannelInboundHandlerAdapter implements Callable {
private ChannelHandlerContext context;//上下文
private String result; //返回的结果
private String para; //客户端调用方法时,传入的参数
//与服务器的连接创建后,就会被调用, 这个方法是第一个被调用(1)
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
context = ctx; //因为我们在其它方法会使用到 ctx
}
//收到服务器的数据后,调用方法 (4)
@Override
public synchronized void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
result = msg.toString();
notify(); //唤醒等待的线程
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
//被代理对象调用, 发送数据给服务器,-> wait -> 等待被唤醒(channelRead) -> 返回结果 (3)-》5
@Override
public synchronized Object call() throws Exception {
context.writeAndFlush(para);
//进行wait
wait(); //等待channelRead 方法获取到服务器的结果后,唤醒
return result; //服务方返回的结果
}
void setPara(String para) {
this.para = para;
}
}
在消费者业务处理类中使用了线程的等待唤醒机制,当向服务端发送请求之后,进入等待状态,直到读取到服务端返回的数据才唤醒睡眠的线程,然后返回服务端响应的结果,此处并没有考虑超时等因素。
服务提供方实现:NettyClient.java
package rpc.netty;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import java.lang.reflect.Proxy;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class NettyClient {
//创建线程池用于执行请求服务端的任务
private static ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
private static NettyClientHandler client;
//编写方法使用代理模式,获取一个代理对象
public Object getBean(final Class<?> serivceClass, final String providerName) {
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class<?>[]{
serivceClass}, (proxy, method, args) -> {
//客户端每调用一次 hello, 就会进入到该代码
if (client == null) {
initClient();
}
//设置要发给服务器端的信息
//providerName 协议头 args[0] 就是客户端调用api hello(???), 参数
client.setPara(providerName + args[0]);
//执行任务,调用call方法,携带数据向服务端发送请求
return executor.submit(client).get();
});
}
//初始化客户端
private static void initClient() {
client = new NettyClientHandler();
//创建EventLoopGroup
NioEventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(
new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
pipeline.addLast(client);
}
}
);
try {
bootstrap.connect("127.0.0.1", 7000).sync();
} catch (Exception e) {
e.printStackTrace();
}
}
}
服务提供方的getBean方法使用了jdk的动态代理,通过调用getBean可以获得其代理对象,发送请求给服务端。
服务端启动类:ClientBootstrap.java
package rpc.customer;
import rpc.netty.NettyClient;
import rpc.publicinterface.HelloService;
public class ClientBootstrap {
//这里定义协议头
public static final String providerName = "myRPC#";
public static void main(String[] args) throws Exception {
//创建一个消费者
NettyClient customer = new NettyClient();
//创建代理对象
HelloService service = (HelloService) customer.getBean(HelloService.class, providerName);
//通过代理对象调用服务提供者的方法(服务)
String res = service.hello("你好 ~");
System.out.println("调用的结果 res= " + res);
}
}
启动服务端,运行ServerBootstrap:
启动消费端,运行ClientBootstrap:
服务端同样也受到消费端发送的消息:
测试成功,一个简易RPC框架创建完毕。
此篇博客讲解了RPC的基本概念,设计思路以及编码实现,其中主要用到了线程的等待唤醒机制,线程池异步提交任务获取结果和JDK动态代理,这都需要我们完全理解并熟练掌握。同时我们也要感谢netty的开发者们,没有他们,NIO编程将变得极其繁琐复杂。
实际的RPC框架将远比我们案例要复杂的多,但是通过此案例想必也会让你有一定的收获和感悟,干这一行就是一个不断学习的过程,学的越多,懂的越少,学海无涯,与君共勉,2020,加油!