RPC 基本介绍
-
RPC(Remote Procedure Call)— 远程过程调用,是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程
-
两个或多个应用程序都分布在不同的服务器上,它们之间的调用都像是本地方法调用一样(如图)
-
常见的 RPC 框架有: 比较知名的如阿里的Dubbo、google的gRPC、Go语言的rpcx、Apache的thrift,Spring 旗下的 Spring Cloud
RPC 调用流程图
PRC 调用流程说明
- 服务消费方(client)以本地调用方式调用服务
- client stub 接收到调用后负责将方法、参数等封装成能够进行网络传输的消息体
- client stub 将消息进行编码并发送到服务端
- server stub 收到消息后进行解码
- server stub 根据解码结果调用本地的服务
- 本地服务执行并将结果返回给 server stub
- server stub 将返回导入结果进行编码并发送至消费方
- client stub 接收到消息并进行解码
- 服务消费方(client)得到结果
小结:RPC 的目标就是将 2-8 这些步骤都封装起来,用户无需关心这些细节,可以像调用本地方法一样即可完成远程服务调用
自己实现 dubbo RPC(基于 Netty)
需求说明
- dubbo 底层使用了 Netty 作为网络通讯框架,要求用 Netty 实现一个简单的 RPC 框架
- 模仿 dubbo,消费者和提供者约定接口和协议,消费者远程调用提供者的服务,提供者返回一个字符串,消费 者打印提供者返回的数据。
设计说明
-
创建一个接口,定义抽象方法。用于消费者和提供者之间的约定
-
创建一个提供者,该类需要监听消费者的请求,并按照约定返回数据
-
创建一个消费者,该类需要透明的调用自己不存在的方法,内部需要使用 Netty 请求提供者返回数据
-
开发的分析图
-
目录结构
publicInterface(代码)
IHelloService
public interface IHelloService {
String hello(String msg);
}
provider(代码)
HelloServiceImpl
public class HelloServerImpl implements IHelloService {
/**
* @Description 当消费方调用该方法时,就返回一个结果
* @date 2020/7/25 16:32
* @param msg
* @return java.lang.String
*/
@Override
public String hello(String msg) {
System.out.println("收到客户端消息:"+msg);
//根据msg,返回不同的结果
if(msg!=null){
return "你好客户端,我已经收到你的消息["+msg+"]";
}else{
return "大妹子,你没发送信息呀";
}
}
}
ServerBootStrap
public class ServerBootStrap {
public static void main(String[] args) {
//启动服务
NettyServer.startServer("127.0.0.1",50000);
}
}
netty
NettyServer
public class NettyServer {
public static void startServer(String host,int port){
startServer0(host,port);
}
private static void startServer0(String host,Integer port){
EventLoopGroup bossGroup=new NioEventLoopGroup();
EventLoopGroup workerGroup=new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap=new ServerBootstrap();
serverBootstrap.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG,128)
.childOption(ChannelOption.SO_KEEPALIVE,true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
ChannelPipeline pipeline = sc.pipeline();
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
//业务处理器
pipeline.addLast(new NettyServerHandler());
}
});
ChannelFuture channelFuture = serverBootstrap.bind(new InetSocketAddress(port)).sync();
//进行监听
channelFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
System.out.println(Thread.currentThread().getName());
if(future.isSuccess()){
System.out.println("服务提供方开始 提供服务成功");
}else{
System.out.println("服务提供方开始 提供服务失败");
}
}
});
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
NettyServerHandler
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//获取客户端发送的消息,并调用我们的服务
System.out.println(Thread.currentThread().getName()+" msg="+msg);
//客户端在调用服务器的api时,我们需要定义一个协议
//比如我们要求 每次发送消息都是必须以某个字符串开头"HelloService#hello#"
if(msg.toString().startsWith("HelloService#hello#")){
String result= new HelloServerImpl().hello(msg.toString().substring(msg.toString().lastIndexOf('#')+1));
ctx.writeAndFlush(result);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("异常关闭"+cause.getMessage());
ctx.close();
}
}
NettyClient
public class NettyClient {
/**
* 创建线程池
*/
//不推荐
// private static ExecutorService executor=Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
private static ThreadFactory threadFactory=new DefaultThreadFactory("netty-client");
private static ExecutorService executor=new ThreadPoolExecutor(10,10,
60L, TimeUnit.SECONDS,new ArrayBlockingQueue<>(10),threadFactory);
//使用guava提供的ThreadFactoryBuilder来创建线程池(推荐)
// private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
// .setNameFormat("demo-pool-%d").build();
private static NettyClientHandler clientHandler;
public Object getBean(final Class<?> serviceClass,final String providerName){
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),new Class<?>[]{serviceClass},
((proxy, method, args) -> {
if(clientHandler==null){
initClient();
}
//设置要发给服务器端的信息
//协议头+调用远程api的参数
clientHandler.setParam(providerName+args[0]);
return executor.submit(clientHandler).get();
}));
}
private static void initClient(){
clientHandler=new NettyClientHandler();
//创建EventLoopGroup
EventLoopGroup group=new NioEventLoopGroup();
try {
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(clientHandler);
}
});
ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress("127.0.0.1", 50000)).sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
NettyClientHandler
public class NettyClientHandler extends ChannelInboundHandlerAdapter implements Callable {
/**
* 上下文
*/
private ChannelHandlerContext context;
/**
* 返回的结果
*/
private String result;
/**
* 客户端调用方法时,传入的参数
*/
private String param;
/**
* @Description 与服务器的连接创建后,方法会被调用(1)
* @date 2020/7/25 17:07
* @param ctx
* @return void
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//我在其他方法会用到ctx
this.context=ctx;
}
/**
* @Description 收到服务器的数据后,方法会被调用(4)
* @date 2020/7/25 17:08
* @param ctx
* @param msg
* @return void
*/
@Override
public synchronized void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("从服务端获取消息 "+Thread.currentThread().getName());
this.result=msg.toString();
//唤醒等待的线程
notify();
}
/**
* @Description 被代理对象调用,真正发送数据给服务器-->wait-->等待被唤醒,获取结果(3)-->(5)
* @date 2020/7/25 17:12
* @return java.lang.Object
*/
@Override
public synchronized Object call() throws Exception {
System.out.println("代理发送消息 "+Thread.currentThread().getName());
context.writeAndFlush(param);
//进行wait,等待channelRead方法获取服务器的结果后,唤醒
wait();
//返回服务方返回的结果
return this.result;
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
/**
* @Description 设置参数(2)
* @date 2020/7/25 17:20
* @param param
* @return void
*/
public void setParam(String param){
this.param=param;
}
}
consumer
ClientBootStrap
public class ClientBootStrap {
private static final String providerName="HelloService#hello#";
public static void main(String[] args) {
//创建一个消费者
NettyClient consumer = new NettyClient();
//创建一个代理对象
IHelloService helloService=(IHelloService)consumer.getBean(IHelloService.class,providerName);
//调用服务提供者的方法
String res = helloService.hello("你好呀");
System.out.println("调用的结果res="+res);
}
}