【RPC高性能框架总结】9.手写rpc框架-代码实现(二)

接上一篇《8.手写rpc框架-代码实现(上)》
上一篇我们编写了框架层的rpc-framework父级工程以及rpc-common公共(编码解码)工具类工程。本篇我们继续来编写rpc-client工程。
注:代码参考http://git.oschina.net/huangyong/rpc(作者:黄勇)

对于rpc-client工程我们要实现的是,使用Netty封装一个客户端网络层(RpcClient),以及rpc的服务动态代理(RpcProxy),即用来进行远程服务类加载和方法调用的代理类。
在MyEclipse新建名为rpc-client的maven工程:
【RPC高性能框架总结】9.手写rpc框架-代码实现(二)_第1张图片
【RPC高性能框架总结】9.手写rpc框架-代码实现(二)_第2张图片
【RPC高性能框架总结】9.手写rpc框架-代码实现(二)_第3张图片
新建成功之后,因为我们需要rpc-common工程进行编码解码,然后还需要注入即将要编写的rpc-registry注册中心模块(这里是zookeeper)。我们引入以下依赖:




    4.0.0

    
      com.xxx.rpc
      rpc-framework
      0.0.1-SNAPSHOT
    

    rpc-client

    
        
        
            com.xxx.rpc
            rpc-common
            ${project.version}
        
        
        
    

因为我们这里还没有编写rpc-registry-zookeeper,所以可以暂时将其注释。
然后我们开始编写代码,首先在src/main/java中创建com.xxx.rpc.client包下的RpcClient和RpcProxy类,其中RpcClient是利用Netty进行tcp请求发送的类,RpcProxy是封装rpc信息,并调用RpcClient客户端对象,将rpc请求信息发送至服务地址对应的服务器中。
首先是RpcClient客户端:

package com.xxx.rpc.client;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.xxx.rpc.common.bean.RpcRequest;
import com.xxx.rpc.common.bean.RpcResponse;
import com.xxx.rpc.common.codec.RpcDecoder;
import com.xxx.rpc.common.codec.RpcEncoder;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

//RPC 客户端,用于发送rpc请求
//继承SimpleChannelInboundHandler,将自己作为一个InboundHandler
public class RpcClient extends SimpleChannelInboundHandler{
    //日志对象
    private static final Logger LOGGER = LoggerFactory.getLogger(RpcClient.class);
    //传输信息的服务端的ip和端口
    private final String host;
    private final int port;
    //服务端反馈的response信息对象
    private RpcResponse response;
    //构造方法,用于传输服务端的信息
    public RpcClient(String host,int port){
        this.host = host;
        this.port = port;
    }
    
    //处理管道读取反馈的response对象,这里只需获取response对象即可。
    @Override
    protected void channelRead0(ChannelHandlerContext arg0, RpcResponse arg1) throws Exception {
        this.response = response;
    }

    //异常处理
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        LOGGER.error("api caught exception",cause);//日志记录异常原因
        ctx.close();//关闭上下文对象
    }

    //使用Netty发送rpc请求
    public RpcResponse send(RpcRequest request) throws Exception{
        EventLoopGroup group = new NioEventLoopGroup();
        
        try {
            //创建并初始化Netty客户端Bootstrap对象
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group);
            bootstrap.channel(NioSocketChannel.class);
            bootstrap.handler(new ChannelInitializer(){

                @Override
                protected void initChannel(SocketChannel channel) throws Exception {
                    ChannelPipeline pipeline = channel.pipeline();
                    pipeline.addLast(new RpcEncoder(RpcRequest.class));//注册编码器
                    pipeline.addLast(new RpcDecoder(RpcResponse.class));//注册解码器
                    pipeline.addLast(RpcClient.this);//注册客户端处理对象
                }
            });
            //设置无延迟操作
            bootstrap.option(ChannelOption.TCP_NODELAY, true);
            //连接RPC服务器
            ChannelFuture future = bootstrap.connect(host, port).sync();
            //写入RPC请求数据并关闭连接
            Channel channel = future.channel();
            channel.writeAndFlush(request).sync();
            channel.closeFuture().sync();
            return response;
        }finally{
            group.shutdownGracefully();
        }
    }
}

这里RpcClient继承了SimpleChannelInboundHandler,将自己作为一个InboundHandler,类的成员变量有LOGGER日志对象、服务端的ip和端口,服务端反馈的response信息对象。然后在构造方法中传入传输服务端的信息(ip/端口),然后在重写channelRead0和exceptionCaught进行反馈信息接收和异常处理。最后编写核心方法----send,该方法初始化了Netty客户端的Bootstrap对象,设置了事件队列EventLoopGroup、通道类型,处理器handler中,分别在管道处理器链上注册了编码器RpcEncoder、解码器RpcDecoder和客户端处理对象RpcClient本身,最后连接主机,发送请求对象并获取反馈对象,作为方法的返回值。然后是RpcProxy类:

package com.xxx.rpc.client;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.UUID;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.xxx.rpc.common.bean.RpcRequest;
import com.xxx.rpc.common.bean.RpcResponse;
import com.xxx.rpc.common.utils.StringUtil;

//RPC 代理,用于创建 RPC 服务代理
//使用Netty客户端发送rpc请求,并获取反馈信息,拿到相关的服务调用类的相关方法调用结果
public class RpcProxy {
    //日志对象
    private static final Logger LOGGER = LoggerFactory.getLogger(RpcProxy.class);
    //服务地址
    private String serviceAddress;
    //服务地址发现类,该类由注册中心实现
    private ServiceDiscovery serviceDiscovery;
    //当不需要注册中心时,直接传入服务地址即可
    public RpcProxy(String serviceAddress) {
        this.serviceAddress = serviceAddress;
    }
    //当需要注册中心时,传入注册中心的服务地址发现类对象
    public RpcProxy(ServiceDiscovery serviceDiscovery) {
        this.serviceDiscovery = serviceDiscovery;
    }
    
    //创建类方法
    @SuppressWarnings("unchecked")
    public  T create(final Class interfaceClass) {
        return create(interfaceClass, "");
    }
    
    //创建类方法,带有服务版本参数
    @SuppressWarnings("unchecked")
    public  T create(final Class interfaceClass, final String serviceVersion) {
        //创建动态代理对象
        return (T) Proxy.newProxyInstance(
                interfaceClass.getClassLoader(), //类加载器
                new Class[]{interfaceClass}, //代理类的类型
                new InvocationHandler(){//代理的处理类
                    //具体的代理方法
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                         // 创建 RPC 请求对象并设置请求属性
                        RpcRequest request = new RpcRequest();
                        request.setRequestId(UUID.randomUUID().toString());//唯一的请求ID
                        request.setInterfaceName(method.getDeclaringClass().getName());//要调用的方法名
                        request.setServiceVersion(serviceVersion);//服务版本
                        request.setMethodName(method.getName());//要调用的方法名称
                        request.setParamterTypes(method.getParameterTypes());//设置调用方法的参数类型
                        request.setParameters(args);//设置调用方法的参数
                        //获取RPC服务地址
                        if(serviceDiscovery!=null){
                            //当serviceDiscovery对象不为空时,说明需要从注册中心获取服务地址
                            String serviceName = interfaceClass.getName();
                            if (StringUtil.isNotEmpty(serviceVersion)) {
                                serviceName += "-" + serviceVersion;//服务名称加版本号
                            }
                            serviceAddress = serviceDiscovery.discover(serviceName);//远程获取服务地址
                            LOGGER.debug("discover service: {} => {}", serviceName, serviceAddress);
                        }
                        //如果服务地址为空,就报错
                        if (StringUtil.isEmpty(serviceAddress)) {
                            throw new RuntimeException("server address is empty");
                        }
                        // 从 RPC 服务地址中解析主机名与端口号
                        String[] array = StringUtils.split(serviceAddress,":");
                        String host = array[0];
                        int port = Integer.parseInt(array[1]);
                        // 创建 RPC 客户端对象并发送 RPC 请求
                        RpcClient client = new RpcClient(host, port);
                        long time = System.currentTimeMillis();
                        RpcResponse response = client.send(request);//获取rpc请求的反馈对象
                        LOGGER.debug("time: {}ms", System.currentTimeMillis() - time);//日志打印请求处理时间
                        if(response==null){//如果反馈对象为空,则报错
                            throw new RuntimeException("response is null");
                        }
                        // 返回 RPC 响应结果
                        if (response.hasException()) {//是否有异常
                            throw response.getException();//反馈的异常对象
                        } else {
                            return response.getResult();//调用远程方法返回的具体对象
                        }
                    }
                });
    }
    
    //注册中心服务发现对象,还没有实现,这里先放一个空的
    class ServiceDiscovery{

        public String discover(String serviceName) {
            // TODO Auto-generated method stub
            return null;
        }
        
    }
}

该类的核心就是,利用create方法中传递的类的类型信息,创建代理对象,替用户获取这个类的实现类,调用相关方法,并获取方法调用返回的具体对象。
这里要注意的是ServiceDiscovery是需要在注册中心工程rpc-registry-zookeeper中实现的,但是这里因为还没有开始写rpc-registry-zookeeper工程,所以在下面先放置了一个空对象。

至此,rpc-client工程编写完成,下一篇我们来编写服务端工程rpc-server,根据客户端发送的方法调用请求,调用相关方法并反馈方法返回的对象。
转载请注明出处:https://blog.csdn.net/acmman/article/details/88777930

你可能感兴趣的:(RPC,手写RPC框架)