用socket实现RPC

什么是RPC:

RPC其全程为Remote Process Call,即为远程过程调用,相对于本地过程调用来说的。

说起RPC,就不能不提到分布式,这个促使RPC诞生的领域。

假设你有一个计算器接口,Calculator,以及它的实现类CalculatorImpl,那么在系统还是单体应用时,你要调用Calculator的add方法来执行一个加运算,直接new一个CalculatorImpl,然后调用add方法就行了,这其实就是非常普通的本地函数调用,因为在同一个地址空间,或者说在同一块内存,所以通过方法栈和参数栈就可以实现。

现在,基于高性能和高可靠等因素的考虑,你决定将系统改造为分布式应用,将很多可以共享的功能都单独拎出来,比如上面说到的计算器,你单独把它放到一个服务里头,让别的服务去调用它,这就需要远程过程调用。

RPC解决的问题:

  1. 解决分布式系统中,服务之间的调用问题。
  2. 远程调用时,要能够像本地调用一样方便,让调用者感知不到远程调用的逻辑。

如何实现一个RPC

实际情况下,RPC很少用到http协议来进行数据传输,毕竟我只是想传输一下数据而已,何必动用到一个文本传输的应用层协议呢,我为什么不直接使用二进制传输?比如直接用Java的Socket协议进行传输?

不管你用何种协议进行数据传输,一个完整的RPC过程,都可以用下面这张图来描述

以左边的Client端为例,Application就是rpc的调用方,Client Stub就是代理对象,也就是那个看起来像是Calculator的实现类,其实内部是通过rpc方式来进行远程调用的代理对象,至于Client Run-time Library,则是实现远程调用的工具包,比如jdk的Socket,最后通过底层网络实现实现数据的传输。

这个过程中最重要的就是序列化反序列化了,因为数据传输的数据包必须是二进制的,你直接丢一个Java对象过去,人家可不认识,你必须把Java对象序列化为二进制格式,传给Server端,Server端接收到之后,再反序列化为Java对象。

这里RPC框架,进程间的通讯是使用java的套接字socket。这里实现的简易RPC框架,并没有处理负载均衡,服务的路由,服务的监控等,服务的注册等等,仅仅是实现服务的调用。 

1.服务端

package com.king.guaixiong.rpc;

import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author Administrator
 * @date 2018年12月26日 下午2:16:34
 * description:生产者暴露服务
 */
public class Provider {
	
    public static void export(final Object service, int port) throws Exception {
        //服务校验
        if (service == null) {
            throw new IllegalArgumentException("service must not be null");
        }
        //端口校验
        if (port <= 0 || port > 65535) {
            throw new IllegalArgumentException("Invalid port:" + port + "a valid port must between 0 and 65535");
        }
        
        
        //向操作系统注册服务
        ServerSocket serverSocket = new ServerSocket(port);
        
        //循环启动监听
        while (true) {
        	System.out.println("等待新客户端连接...");
            Socket socket = serverSocket.accept();
            //开启独立的线程处理服务调用
            new ServerThread(socket, service).start();
            System.out.println("创建异步线程并启动成功...");
        }
    }

    public static void main(String[] args) throws Exception {
        HelloService service = new HelloServiceImpl();
        export(service, 1234);
    }
}

处理服务调用的线程:

package com.king.guaixiong.rpc;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.net.Socket;

/**
 * description: 单独处理调用的服务
 */
public class ServerThread extends Thread {
    private Socket socket;
    private Object service;

    public ServerThread(Socket socket, Object service) {
        this.socket = socket;
        this.service = service;
    }

    @Override
    public void run() {
        try {
            ObjectInputStream input = new ObjectInputStream(socket.getInputStream());
            //获取服务调用的方法
            String methodName = input.readUTF();
            //获取服务调用的参数类型
            Class<?>[] parameterTypes = (Class<?>[]) input.readObject();
            //获取服务调用的参数
            Object[] arguments = (Object[]) input.readObject();
            //获取输出响应结果流
            ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
            try {
                Method method;
                //利用反射调用服务
                method = service.getClass().getMethod(methodName, parameterTypes);
                Object result = method.invoke(service, arguments);
                //返回调用结果
                output.writeObject(result);
            } catch (Exception e) {
                e.printStackTrace();
            }

            input.close();
        } catch (IOException e) {

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            //关闭连接
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

2.客户端:

package com.king.guaixiong.rpc;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.Socket;

/**
 * @author Administrator
 * @date 2018年12月26日 下午2:13:02
 * Description:消费者,调用服务
 * http://javatar.iteye.com/blog/1123915
 */
public class Consumer {
	
	
    @SuppressWarnings("unchecked")
	public static <T> T refer(final Class<T> interfaceClass,final String host, final int port) throws Exception {
        if (interfaceClass == null)
            throw new IllegalArgumentException("Interface class == null");
        if (!interfaceClass.isInterface())
            throw new IllegalArgumentException("The " + interfaceClass.getName() + " must be interface class!");
        if (host == null || host.length() == 0)
            throw new IllegalArgumentException("Host == null!");
        if (port <= 0 || port > 65535)
            throw new IllegalArgumentException("Invalid port " + port);
        
        System.out.println("Get remote service " + interfaceClass.getName() + " from server " + host + ":" + port);
        
        
        //生成动态代理对象
        return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class<?>[]{interfaceClass}, new InvocationHandler() {
        	
            public Object invoke(Object proxy, Method method, Object[] arguments) throws Throwable {
                Socket socket = new Socket(host, port);
                try {
                    ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
                    try {
                        output.writeUTF(method.getName());
                        output.writeObject(method.getParameterTypes());
                        output.writeObject(arguments);
                        ObjectInputStream input = new ObjectInputStream(socket.getInputStream());
                        try {
                            Object result = input.readObject();
                            if (result instanceof Throwable) {
                                throw (Throwable) result;
                            }
                            return result;
                        } finally {
                            input.close();
                        }
                    } finally {
                        output.close();
                    }
                } finally {
                    socket.close();
                }
            }
        });
    }

    public static void main(String[] args) throws Exception {
        //此处返回的是动态代理对象
        HelloService service = refer(HelloService.class, "10.188.32.120", 1234);
        System.out.println("获取服务..");
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            //调用hello方法时会调用代理对象的invoke方法
            String hello = service.hello("世界" + i);
            System.out.println(hello);
            Thread.sleep(1000);
        }
    }
}

3.接口

package com.king.guaixiong.rpc;

public interface HelloService {
	
	String hello(String name);

}

实现类

package com.king.guaixiong.rpc;

public class HelloServiceImpl implements HelloService {

	@Override
	public String hello(String name) {
		
		return "hello:" + name;
	}

}

至此,一个简易的,基于socket的RPC代码已经完成...

RPC没那么简单

要实现一个RPC不算难,难的是实现一个高性能高可靠的RPC框架。

比如,既然是分布式了,那么一个服务可能有多个实例,你在调用时,要如何获取这些实例的地址呢?

这时候就需要一个服务注册中心,比如在Dubbo里头,就可以使用Zookeeper作为注册中心,在调用时,从Zookeeper获取服务的实例列表,再从中选择一个进行调用。

那么选哪个调用好呢?这时候就需要负载均衡了,于是你又得考虑如何实现复杂均衡,比如Dubbo就提供了好几种负载均衡策略。

这还没完,总不能每次调用时都去注册中心查询实例列表吧,这样效率多低呀,于是又有了缓存,有了缓存,就要考虑缓存的更新问题,blablabla......

你以为就这样结束了,没呢,还有这些:

  • 客户端总不能每次调用完都干等着服务端返回数据吧,于是就要支持异步调用
  • 服务端的接口修改了,老的接口还有人在用,怎么办?总不能让他们都改了吧?这就需要版本控制了;
  • 服务端总不能每次接到请求都马上启动一个线程去处理吧?于是就需要线程池
  • 服务端关闭时,还没处理完的请求怎么办?是直接结束呢,还是等全部请求处理完再关闭呢?
  • ......

如此种种,都是一个优秀的RPC框架需要考虑的问题。

 

 

 

参考文章:

RPC框架几行代码就够

使用Java实现简易RPC框架

如何给老婆解释什么是RPC

如何实现一个简单的RPC

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值