基于 Netty 手写 RPC

栏目: Java · 发布时间: 5年前

内容简介:RPC(Remote Procedure Call),即远程过程调用,它是一种通过网络从远程计算机程序 上请求服务,而不需要了解底层网络实现的技术。常见的RPC 框架有: 源自阿里的Dubbo, Spring 旗下的Spring Cloud,Google 出品的grpc 等等。将上面的12个步骤整理为下面9个步骤:RPC 的目标就是将2-8 这些步骤都封装起来,用户无需关心这些细节,可以像调用本地 方法一样即可完成远程服务调用。接下来我们基于Netty 自己动手搞定一个RPC。

RPC(Remote Procedure Call),即远程过程调用,它是一种通过网络从远程计算机程序 上请求服务,而不需要了解底层网络实现的技术。常见的RPC 框架有: 源自阿里的Dubbo, Spring 旗下的Spring Cloud,Google 出品的grpc 等等。

基于 Netty 手写 RPC

将上面的12个步骤整理为下面9个步骤:

1,服务消费方(Client)以本地调用方式调用服务
2. client stub 接收到调用后负责将方法、参数等封装成能够进行网络传输的消息体
3. client stub 将消息进行编码并发送到服务端
4. server stub 收到消息后进行解码
5. server stub 根据解码结果调用本地的服务
6. 本地服务执行并将结果返回给server stub
7. server stub 将返回导入结果进行编码并发送至消费方
8. client stub 接收到消息并进行解码
9. 服务消费方(client)得到结果
复制代码

RPC 的目标就是将2-8 这些步骤都封装起来,用户无需关心这些细节,可以像调用本地 方法一样即可完成远程服务调用。接下来我们基于Netty 自己动手搞定一个RPC。

设计和实现

基于 Netty 手写 RPC
Client(服务的调用方): 两个接口+ 一个包含main 方法的测试类
 Client Sub: 一个客户端代理类+ 一个客户端业务处理类
 Server(服务的提供方): 两个接口+ 两个实现类
 Server Sub: 一个网络处理服务器+ 一个服务器业务处理类
 注意:服务调用方的接口必须跟服务提供方的接口保持一致(包路径可以不一致),最终要实现的目标是:在TestNettyRPC 中远程调用HelloRPCImpl 或HelloNettyImpl 中的方法
复制代码

代码实现

Server(服务的提供方)

public interface HelloNetty {
    String hello();
}

public class HelloNettyImpl implements HelloNetty {
    @Override
    public String hello() {
        return "----> hello,netty <---";
    }
}

public interface HelloRPC {

    String hello(String name);
}

public class HelloRpcImpl implements HelloRPC {
    @Override
    public String hello(String name) {
        return "hello," + name;
    }
}
复制代码

上述代码作为服务的提供方,我们分别编写了两个接口和两个实现类,供消费方远程调用

Server Sub部分

public class ClassInfo implements Serializable {

    private static final long serialVersionUID = -7821682294197810003L;

    private String className;//类名

    private String methodName;//返回值

    private Class<?>[] types; //参数类型

    private Object[] objects; //参数列表
    
        // ..getter
        // ..setter
    }
复制代码

上述代码作为实体类用来封装消费方发起远程调用时传给服务方的数据

服务器端业务处理类

public class InvokeHandler extends ChannelInboundHandlerAdapter {

    //得到某接口下某个实现类的名字
    private String getImplClassName(ClassInfo classInfo) throws ClassNotFoundException {
        //服务方接口和实现类所在的包路径
        String interfacePath = "cn.haoxiaoyong.record.rpc.server";
        int lastDot = classInfo.getClassName().lastIndexOf(".");
        String interfaceName = classInfo.getClassName().substring(lastDot);
        Class<?> superClass = Class.forName(interfacePath + interfaceName);
        Reflections reflections = new Reflections(interfacePath);
        //得到某接口下的所有实现类
        Set<Class<?>> ImplClassSet = (Set<Class<?>>) reflections.getSubTypesOf(superClass);
        if (ImplClassSet.size() == 0) {
            System.out.println("未找到实现类");
            return null;
        } else if (ImplClassSet.size() > 1) {
            System.out.println("找到多个实现类,未明确使用哪一个");
            return null;
        } else {
            //把集合转换为数组
            Class[] classes = ImplClassSet.toArray(new Class[0]);
            return classes[0].getName();//得到实现类的名字
        }
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ClassInfo classInfo = (ClassInfo) msg;
        Object clazz = Class.forName(getImplClassName(classInfo)).newInstance();
        Method method = clazz.getClass().getMethod(classInfo.getMethodName(), classInfo.getTypes());
        //通过反射调用实现类的方法
        Object result = method.invoke(clazz, classInfo.getObjects());
        ctx.writeAndFlush(result);
    }
}
复制代码

上述代码作为业务处理类,读取消费方发来的数据,并根据得到的数据进行本地调用,然后把结果返回给消费方.

网络处理服务器

public class NettyRpcServer {
    private int port;

    public NettyRpcServer(int port) {
        this.port = port;
    }

    public void start() {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup wprkGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, wprkGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    .localAddress(port)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            //编码器
                            pipeline.addLast("encoder", new ObjectEncoder())
                                    //解码器
                                    .addLast("decoder", new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)))
                                    .addLast(new InvokeHandler());
                        }
                    });
            ChannelFuture future = serverBootstrap.bind(port).sync();
            System.out.println("......server is ready......");
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            bossGroup.shutdownGracefully();
            wprkGroup.shutdownGracefully();
        }
    }
    public static void main(String[] args) {
       new NettyRpcServer(9999).start();
    }
}
复制代码

上述代码是用Netty实现的网络服务器,采用Netty自带的ObjectEncoder 和ObjectDecoder 作为编码解码(为了降低复杂度,这里并没有使用 第三方的编码解码器),当然实际开发中也可以采用JSON或XML.

Client Sub部门(客户端业务处理类)

public class ResultHandler extends ChannelInboundHandlerAdapter {

    private Object response;

    public Object getResponse() {
        return response;
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        response = msg;
        ctx.close();
    }
}
复制代码

上述代码作为客户端的业务处理类读取远程调用返回的数据

客户端代理类

public class NettyRpcProxy {

    //根据结构创建代理对象
    public static Object create(Class target) {
        return Proxy.newProxyInstance(target.getClassLoader(), new Class[]{target}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //封装ClassInfo
                ClassInfo classInfo = new ClassInfo();
                classInfo.setClassName(target.getName());
                classInfo.setMethodName(method.getName());
                classInfo.setObjects(args);
                classInfo.setTypes(method.getParameterTypes());
                //开始用Netty发送数据
                EventLoopGroup group = new NioEventLoopGroup();
                ResultHandler resultHandler = new ResultHandler();
                try {
                    Bootstrap bootstrap = new Bootstrap();
                    bootstrap.group(group)
                            .channel(NioSocketChannel.class)
                            .handler(new ChannelInitializer<SocketChannel>() {

                                @Override
                                protected void initChannel(SocketChannel ch) throws Exception {
                                    ChannelPipeline pipeline = ch.pipeline();
                                    //编码器
                                    pipeline.addLast("encoder", new ObjectEncoder())
                                            //解码器,构造方法第一个参数设置二进制的最大字节数,第二个参数设置具体使用哪个类解析器
                                            .addLast("decoder", new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)))
                                            //客户端业务处理类
                                            .addLast("handler", resultHandler);
                                }
                            });
                    ChannelFuture future = bootstrap.connect("127.0.0.1", 9999).sync();
                    future.channel().writeAndFlush(classInfo).sync();
                    future.channel().closeFuture().sync();
                } finally {
                    group.shutdownGracefully();
                }
                return resultHandler.getResponse();
            }
        });
    }
}

复制代码

上述代码是用Netty 实现的客户端代理类,采用Netty 自带的ObjectEncoder 和ObjectDecoder 作为编解码器(为了降低复杂度,这里并没有使用第三方的编解码器),当然实际开发时也 可以采用JSON 或XML。

Client

public interface HelloNetty {

    String hello();
}

public interface HelloRPC {

    String hello(String name);
}
复制代码

上述代码定义了两个接口作为服务的调用方

Client(服务的调用方-消费方)

public class TestNettyRpc {
    public static void main(String[] args) {
        //第一次远程调用
        HelloNetty helloNetty = (HelloNetty) NettyRpcProxy.create(HelloNetty.class);
        System.out.println(helloNetty.hello());

        //第二次远程调用
        HelloRPC helloRPC = (HelloRPC) NettyRpcProxy.create(HelloRPC.class);
        System.out.println(helloRPC.hello("RPC"));
    }
}
复制代码

消费方不需要知道底层的网络实现细节,就像调用本地方法一样成功发起了两次远程调用。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

数据挖掘导论

数据挖掘导论

(美)Pang-Ning Tan、Michael Steinbach、Vipin Kumar / 机械工业出版社 / 2010-9 / 59.00元

本书全面介绍了数据挖掘的理论和方法,着重介绍如何用数据挖掘知识解决各种实际问题,涉及学科领域众多,适用面广。 书中涵盖5个主题:数据、分类、关联分析、聚类和异常检测。除异常检测外,每个主题都包含两章:前面一章讲述基本概念、代表性算法和评估技术,后面一章较深入地讨论高级概念和算法。目的是使读者在透彻地理解数据挖掘基础的同时,还能了解更多重要的高级主题。 本书特色 ·包含大量的图表、......一起来看看 《数据挖掘导论》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

html转js在线工具
html转js在线工具

html转js在线工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具