一、前言
在上一篇文章中我们模仿着Dubbo初步搭建了一个RPC框架,虽然目前什么都没有实现,但是万丈高楼平地起,接下来我们会一步步完善我们的框架。
二、一次RPC的流程是什么样的
在正式编写代码之前我们需要了解的是一次完整的RPC调用是怎么样的。这里我们以ZK为注册中心,UserService作为服务提供方,OrderService做为消费方为例来聊一聊一次完整的RPC调用。
- 首先在服务启动的初期服务提供方(UserService)需要将提供的接口注册到ZK上(假设该接口为getUserInfo(Long userId))
- 消费方(OrderService)想要获取用户信息所以需要调用UserService。那么就至少需要知道UserSerivce的地址信息(IP+端口),所以OrderService会到ZK中查找对应的信息。
- 拿到信息后就要调用UserService对应的方法,在UserService没有提供Http接口的前提下我们怎么调用呢?显然就要用到RPC调用。OrderService通过网络发送对应的请求,UserService收到请求后返回对应的响应,这就是一个简单的RPC调用过程。接下来我们要讨论的是如这个网络请求是如何发送的。
三、在RPC调用中怎么发送网络请求
Java天生是支持网络编程的,想要发送一个网络请求也是比较容易的,下面就是几个方案。
1、最容易想到的就是采用Socket实现BIO模型的网络通信,每次调用之前服务端和消费端都要建立一个Socket,然后通信结束后释放链接,这么做的优缺点都非常明显,优点:模型简单,代码易懂。缺点:效率很低,浪费资源。一个线程处理一个链接。
2、NIO模型,Jdk1.4之后提供了NIO相关API(这里不细谈NIO)。这个方案弥补了方案1缺点,但是NIO的代码相对晦涩难懂。
3、基于Netty实现网络通信,Netty是一个基于事件驱动的NIO框架,提供了相对简单的API屏蔽了Jdk自带NIO晦涩难懂的概念。
综上我们采用Netty作为我们RPC框架的网络模块框架。既然选中了Netty作为网络框架,那么整个网络发送的流程是怎么样的?
- 首先我们要知道,作为网络通信一定是有一定的规则的,这个规则我们称之为"通信协议"。所谓的协议,可以认为是一种约定,即服务端和客户端定义好数据是如何解析的。由于在网络传输的过程都是比特流(01010101),所以双方需要约定好如何解读这些数据。
- 我们发送的请求在网络中传输的过程中是比特流,所以我们需要将参数进行序列化操作。
这两个是必须的,当然我们也可以在此基础上做一些额外的操作,例如记录日志,压缩等等。下面我们以图的形式来展示。
四、来一次简单的网络通信
1、服务端代码
public class ServerTest {
public static void main(String[] args) throws InterruptedException {
ServerBootstrap serverBootstrap = new ServerBootstrap();
//boss 只负责接收请求
final NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
//worker 负责具体业务
final NioEventLoopGroup workerGroup = new NioEventLoopGroup();
final ChannelFuture channelFuture = serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(final SocketChannel channel) throws Exception {
//服务器初始化
channel.writeAndFlush(Unpooled.copiedBuffer("Hello I am Server".getBytes(StandardCharsets.UTF_8)));
}
}).bind(8888);
channelFuture.sync();
channelFuture.channel().closeFuture().sync();
}
}
2、客户端代码
public class ConsumerTest {
public static void main(String[] args) throws InterruptedException {
Bootstrap bootstrap = new Bootstrap();
EventLoopGroup group = new NioEventLoopGroup();
bootstrap.group(group).remoteAddress("127.0.0.1",8888).channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(final SocketChannel socketChannel) throws Exception {
System.out.println("客户端初始化handler");
socketChannel.pipeline().addLast(new SimpleChannelInboundHandler<ByteBuf>() {
@Override
protected void channelRead0(final ChannelHandlerContext channelHandlerContext,
final ByteBuf byteBuf)
throws Exception {
System.out.println("收到服务端的消息:"+byteBuf.toString(Charset.defaultCharset()));
}
});
}
});
final ChannelFuture connect = bootstrap.connect();
final ChannelFuture channelFuture = connect.sync();
channelFuture.channel().writeAndFlush(Unpooled.copiedBuffer("hello world".getBytes(StandardCharsets.UTF_8)));
//阻塞,等待接收关闭消息
channelFuture.channel().closeFuture().sync();
}
}
3、结果
至此我们已经完成了一次最简单的客户端和服务端的通信,那么接下来要考虑的是如何完成我们的RPC请求呢?这一部分我们放到下一篇中完成