当前位置 : 主页 > 大数据 > 区块链 >

RPC客户端如何实现-KRPC源码解析

来源:互联网 收集:自由互联 发布时间:2021-06-22
1.前言 这篇文章主要结合KRPC(我自己开源的一个RPC框)代码详细分析一下RPC客户端具体实现。在一篇文章了解RPC框架原理文中,我们主要讲述了一次调用RPC调用中各流程,这篇文章就结

1.前言

这篇文章主要结合KRPC(我自己开源的一个RPC框)代码详细分析一下RPC客户端具体实现。在一篇文章了解RPC框架原理文中,我们主要讲述了一次调用RPC调用中各流程,这篇文章就结合KRPC的代码仔细讲解一下

开始前,我先说一下KRPC的网络传输中的内容:
1.服务实现名字。server端需要你服务实现的名字,才能知道你调用的是哪个实现的方法,跟web项目中的controller写的路径一样。

2.方法名字。知道了调用的是哪个类,接下来就需要调用的哪个方法了,无需多言。

3.方法参数名字。KRPC会获取每个方法参数的class的全路径(原因在下面展开讲)

4.方法中传入的值。

2.源码分析

2.1 客户端端初始化

KRPC在调用时,必须要先执行

KRPC.init("/opt/krpc/client/client.xml");

该方法内部解析该配置文件,把配置文件中的服务及其地址解析后并放入内存缓存中,供后面TCP请求时,快速获取到服务地址。

2.2 动态代理

可以看到,我们调用的都是接口,并且我们没有引入接口的实现包,调用后怎么能获取到server端的数据呢?

这就引入了动态代理,由代理类替我们处理接口调用的动作。这里使用的是jdk的动态代理实现方式。

调用者接口代理获取方法

UserService service = ProxyFactory.create(UserService.class, "user", "userService");

ProxyFactory#create方法

public static <T> T  create(Class<?> type, String serviceName,String serviceImpleName) {
    ProxyHandler handler = new ProxyHandler(serviceName,serviceImpleName);
    return (T) handler.bind(new Class<?>[]{type});

ProxyHandler.java

@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        // 构造请求request
        Request request = new Request();
        request.setMethodName(method.getName());
        request.setServiceImplName(serviceImplName);
        request.setParamsValues(Arrays.asList(args));
        Class[] sourceTypes = method.getParameterTypes();
        List<String> paramsTypeName = new ArrayList<String>();

        for (int i = 0; i < args.length; i++) {
            paramsTypeName.add(sourceTypes[i].getName());
        }
        request.setParamsTypesName(paramsTypeName);

        Class returnClass = method.getReturnType();

        return RequestHandler.request(serviceName, request, returnClass);
    }

上面放了3段代码,是动态代理相关的全部代理,是的,就这么简单。通过ProxyFactory将接口绑定,获取接口代理,这样调用接口中的方法,直接交给ProxyHandler#invoke方法来执行。

invoke中主要是构建网络请求的参数Request类,然后调用请求控制类request。

我们在前言中有讲到Request的内容。这里说明一下对于参数Class为什么传递类的全路径名字,而不是Class类。

1.因为服务端的类是通过URLClassLoader动态加载进来的,客户端直接传递客户端这边的Class会报ClassNotFound异常

2.传递起类路径数据长度更小

服务端只需要根据类名,通过Class.forname加载进来就ok了。

2.3. 序列化

序列化是RPC框架中重要的一个环结,KRPC采用了Hessian序列化方式,common中的序列化工具

因为在服务端的各service的类都是动态加载进来的

public static Object deserialize(byte[] by, ClassLoader classLoader) throws IOException {
        if (by == null)
            throw new NullPointerException();

        ByteArrayInputStream is = new ByteArrayInputStream(by);
        ClassLoader old = null;
        if (classLoader != null) {
            old = Thread.currentThread().getContextClassLoader();
            // 切换当前线程classloader,保证动态加载的类不会报CNF
            Thread.currentThread().setContextClassLoader(classLoader);
        }
        HessianInput hi = new HessianInput(is);
        Object obj = hi.readObject();

        if (classLoader != null) {
            Thread.currentThread().setContextClassLoader(old);
        }
        return obj;
    }

所以在凡序列中,server端需要传入动态加载的classLoader。
Hessian在凡序列化时,会获取当前线程的ClassLoader,所以我们在外面修改了当前线程的classloader(这也是迭代的目标,这样做有些不稳妥)。

同时我也引入了压缩功能,这样让传输的字节更少。

2.4.TCP请求

传输的数据准备好了,也从配置文件中获取到服务的地址,接下来就要进行TCP请求了。

public static byte[] send(byte[] sendData,String host,int port,int timeout) throws UnknownHostException, IOException {
        Socket socket = new Socket(host,port);
        socket.setSoTimeout(timeout);

        OutputStream os = socket.getOutputStream();
        InputStream is = socket.getInputStream();
        byte resultArray[] = null;
        try {
            os.write(sendData);
            os.flush();
            socket.shutdownOutput();
            resultArray = IOUtils.toByteArray(is);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            os.close();
            is.close();
            socket.close();
        }
        return resultArray;
    }

嗯。。这个TCP请求写的还是相当简单的。以后会迭代这一块的,可能会采用netty的方式实现客户端,可以能用连接池,这个我会深入对比,选择一个好的方案,到时候在来更新博客。

2.5.数据反序列化

通过TCP请求,获取的服务端返回的字节,这时候使用Hessian凡序列就行。
因为客户端会引入服务的接口包,使用AppClassloader加载,所以在客户端无需修改当前线程的classloaer。

3.总结

对于写一个RPC框架,主要是先构造出网络传输的数据格式(协议),客户端的难点主要在TCP请求这一块把。因为用户会引入接口包,所以序列化这一块客户端比较好实现。

对于KRPC在高并发压测下,表现还有要改进的地方,我也会持续迭代更新,争取能让期用于生产环境。关于改进的点,我也会在博客中持续更新。

欢迎对RPC框架原理感兴趣的同学与我交流。https://github.com/yangzhenkun/krpc/ KRPC源码地址,欢迎star,issues.

网友评论