3-1-3 (下) 自定义RPC

一、基于Netty自定义RPC

RPC又称远程过程调用,我们所知的远程调用分为两种,现在在服务间通信的方式也基本以这两种为主

  1. 是基于HTTP的restful形式的广义远程调用,以spring could的feign和restTemplate为代表,采用的协议是HTTP的7层调用协议,并且协议的参数和响应序列化基本以JSON格式和XML格式为主。

  2. 是基于TCP的狭义的RPC远程调用,以阿里的Dubbo为代表,主要通过netty来实现4层网络协议,NIO来异步传输,序列化也可以是JSON或者hessian2以及java自带的序列化等,可以配置。

接下来我们主要以第二种的RPC远程调用来自己实现

需求:模仿 dubbo,消费者和提供者约定接口和协议,消费者远程调用提供者,提供者返回一个字符串,消费者打印提供者返回的数据。底层网络通信使用 Netty

步骤:

  1. 创建一个公共的接口项目以及创建接口及方法,用于消费者和提供者之间的约定。 rpc-common
  2. 创建一个提供者,该类需要监听消费者的请求,并按照约定返回数据。 rpc-provider
  3. 创建一个消费者,该类需要透明的调用自己不存在的方法,内部需要使用 Netty 请求提供者返回数据. rpc-consumer

在这里插入图片描述

1.1 、公共模块

1.1.1 工程构建

创建一个maven工程

首先,在公共模块中添加netty的maven依赖

<dependency> 
	<groupId>io.netty</groupId>
	<artifactId>netty-all</artifactId> 
	<version>4.1.16.Final</version> 
 </dependency>

提供者及消费者工程都需依赖公共模块,这样提供者来实现接口并且提供网络调用,消费者直接通过接口来进行TCP通信及一定的协议定制获取提供者的实现返回值

接口的定义

package com.lagou.service;

public interface UserService {
    
    

    String sayHello(String word);

}

只是一个普通的接口,参数是支持序列化的String类型,返回值同理

1.1.2、提供者实现

1.先将 上面的公共模块导入

    <dependencies>
        <dependency>
            <groupId>com.lagou</groupId>
            <artifactId>rpc-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

2.是接口的实现,这一点和普通接口实现是一样的

package com.lagou.service;

import com.lagou.handler.UserServiceHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
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;

public class UserServiceImpl implements UserService {
    
    

    // 将来用户 要远程调用的方法
    @Override
    public String sayHello(String msg) {
    
    

        String word = "客户端 发的数据--->" + msg;
        System.out.println(word);
        return "服务端返回的数据:"+msg;
    }

    // 创建一个方法启动服务器
    public static  void startServer(String ip , int port) throws InterruptedException {
    
    

        // 1、 创建两个线程池对象
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        NioEventLoopGroup workGroup = new NioEventLoopGroup();

        // 2、 创建服务器的启动引导对象
        ServerBootstrap serverBootstrap = new ServerBootstrap();

        // 3、 配置启动引导对象
        serverBootstrap.group(bossGroup,workGroup)
                // 设置通道为Nio
                .channel(NioServerSocketChannel.class)
                // 创建监听
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
    
    
                    @Override
                    protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
    
    
                        // 获取管道对象
                        ChannelPipeline pipeline = nioSocketChannel.pipeline();

                        // 设置编码
                        pipeline.addLast(new StringEncoder());
                        pipeline.addLast(new StringDecoder());

                        // 把我们自定义 一个ChannelHandler 添加到通道中
                        pipeline.addLast(new UserServiceHandler());
                    }
                });


        // 绑定  服务器和端口
        serverBootstrap.bind(ip,port).sync();

    }

}

3.在实现中加入了netty的服务器启动程序,上面的代码中添加了 String类型的编解码 handler,添加了一个自定义
handler

自定义 handler 逻辑如下:

package com.lagou.handler;

import com.lagou.service.UserServiceImpl;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class UserServiceHandler extends ChannelInboundHandlerAdapter {
    
    
    // 当客户端读取数据是,该方法会被调用
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    
    

        // 注意:客户端将来发送请求的时候回传递一个参数  UserService#sayHello#are you ok
        //  判断当时的当前的请求是否 符合规则
        if(msg.toString().startsWith("UserService")){
    
    
            //2.如果符合规则,调用实现类货到一个result
            UserServiceImpl service = new UserServiceImpl();
            String result = service.sayHello(msg.toString().substring(msg.toString().lastIndexOf("#")+1));
            //3.把调用实现类的方法获得的结果写到客户端
            ctx.writeAndFlush(result);
        }

    }
}


4.这里显示判断了是否符合约定(并没有使用复杂的协议,只是一个字符串判断),然后创建一个具体实现类,并调
用方法写回客户端。
还需要一个启动类:

package com.lagou.boot;

import com.lagou.service.UserServiceImpl;

public class ServiceBoot {
    
    

    public static void main(String[] args) throws InterruptedException {
    
    
        UserServiceImpl.startServer("127.0.0.1",8999);
    }

}


关于提供者的代码就写完了,主要就是创建一个 netty 服务端,实现一个自定义的 handler,自定义 handler 判断是否符合之间的约定(协议),如果符合,就创建一个接口的实现类,并调用他的方法返回字符串。

1.1.3 消费者

服务端与客户端梳理
在这里插入图片描述

消费者有一个需要注意的地方,就是调用需要透明,也就是说,框架使用者不用关心底层的网络实现。这里我们可以使用 JDK 的动态代理来实现这个目的。

思路:
客户端调用代理方法,返回一个实现了 HelloService 接口的代理对象,调用代理对象的方法,返回结果。我们需要在代理中做手脚,当调用代理方法的时候,我们需要初始化 Netty 客户端,还需要向服务端请求数据,并返回数据。

1.先将 上面的公共模块导入

    <dependencies>
        <dependency>
            <groupId>com.lagou</groupId>
            <artifactId>rpc-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

2.创建代理相关的类:

package com.lagou.consumer;

import com.lagou.handler.UserClientHandler;
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.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

import java.lang.reflect.Proxy;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

//消费者
public class RPCConsumer {
    
    

    // 一、创建一个线程池对象 --- 它要处理 我们自定义的事件
    private static ExecutorService executorService =
            Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    // 二、声明一个 自动应以事件处理器 userClientHandler
    private static UserClientHandler userClientHandler ;

    // 三、 编写方法,初始化客户端 (创建连接池 bootStrap 设置bootStrap ,连接服务器)
    public static void initClient() throws InterruptedException {
    
    

        // 1) 初始化UserClientHandler
       userClientHandler = new UserClientHandler();

        // 2)创建连接池对象
        NioEventLoopGroup group = new NioEventLoopGroup();

        // 3)创建客户端 的引导对象
        Bootstrap bootstrap = new Bootstrap();

        // 4)配置引导对象
        bootstrap.group(group)
                //设置通道为NIO
                .channel(NioSocketChannel.class)
                //设置请求协议为TCP
                .option(ChannelOption.TCP_NODELAY,true)
                //监听channel 并初始化
                .handler(new ChannelInitializer<NioSocketChannel>() {
    
    
                    @Override
                    protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
    
    

                        ChannelPipeline pipeline = nioSocketChannel.pipeline();

                        // 设置编码
                        pipeline.addLast(new StringEncoder());
                        pipeline.addLast(new StringDecoder());

                        // 添加自定义事件 处理器
                        pipeline.addLast(userClientHandler);

                    }
                });

        //5)连接服务端
        bootstrap.connect("127.0.0.1",8999).sync();
    }

    // 四、编写一个 方法 ,事件JDK 的动态代理创建对象
    // serviceClass 接口类型,根据 那个接口生成子类代理对象 ;   providerParam :  "UserService#sayHello#"
    public static  Object createProxy(Class<?> serviceClass, final String providerParam){
    
    

        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                new Class[]{
    
    serviceClass}, new InvocationHandler() {
    
    
                    @Override
                    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
    
    

                        //1) 初始化 客户端
                        if( userClientHandler == null){
    
    
                            initClient();
                        }
                        //2)给UserClientHandler 设置param参数
                        userClientHandler.setParam(providerParam+objects[0]);

                        //3).使用线程池,开启一个线程处理处理call() 写操作,并返回结果
                        Object result = executorService.submit(userClientHandler).get();

                        return result;
                    }
                });
    }


}


该类有 2 个方法,创建代理和初始化客户端

创建代理逻辑:使用 JDK 的动态代理技术,代理对象中的 invoke 方法实现如下: 如果 client 没有初始化,则初始化 client,这个 client 既是 handler ,也是一个 Callback。将参数设置进 client ,使用线程池调用 client 的 call 方法并阻塞等待数据返回

初始化客户端逻辑: 创建一个 Netty 的客户端,并连接提供者,并设置一个自定义 handler,和一些 String 类型的序列化方式。

UserClientHandler 的实现:

package com.lagou.handler;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

import java.util.concurrent.Callable;

public class UserClientHandler extends ChannelInboundHandlerAdapter implements Callable {
    
    

 	// 1、定义成员变量
    private ChannelHandlerContext context; // 事件处理器上下文对象(存储handler信息,写操作)

    private String result;    // 记录服务器返回的数据
    private String param;     // 记录将要传送给 服务器的数据

    // 2、 实现channelActive 客户端和服务区连接时 ,该方法自动执行
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
    
    

        this.context = ctx;
    }

    // 3、实现channelRead 当我们读到服务器数据,该方法自动执行
    @Override
    public synchronized void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    
    

        result = msg.toString();

        notify();

    }

    // 4、将客户端的数据写的服务器
    public synchronized Object call() throws InterruptedException {
    
    
        context.writeAndFlush(param);
        wait();
        return result;
    }

    //5.设置参数的方法
    public void setParam(String param){
    
    
        this.param = param;
    }
   

}


该类缓存了 ChannelHandlerContext,用于下次使用,有两个属性:返回结果和请求参数。
当成功连接后,缓存 ChannelHandlerContext,当调用 call 方法的时候,将请求参数发送到服务端,等待。当服务端收到并返回数据后,调用 channelRead 方法,将返回值赋值个 result,并唤醒等待在 call 方法上的线程。此时,代理对象返回数据。

再看看消费者调用方式,一般的TCP的RPC只需要这样调用即可,无需关心具体的协议和通信方式:


package com.lagou.boot;


import com.lagou.consumer.RPCConsumer;
import com.lagou.service.UserService;

public class ConsumerBoot {
    
    

    //参数定义 的规则
    private static final String PROVIDER_NAME = "UserService#sayHello#";

    public static void main(String[] args) throws InterruptedException {
    
    
        //1.创建代理对象
        UserService service = (UserService) RPCConsumer.createProxy(UserService.class, PROVIDER_NAME);

        //2.循环给服务器写数据
        while (true){
    
    
            String result = service.sayHello("are you ok !!");
            System.out.println(result);
            Thread.sleep(2000);
        }

    }

}



猜你喜欢

转载自blog.csdn.net/qq_42082278/article/details/113260978