一个简单的rpc框架实现(待连载优化)

系统长大后为了保证可维护性,拆分系统是一个比较常见的解决方案。系统拆分后,原来的接口直接调用方法不再可行,需要被替换成为远程调用过程。远程调用可以直接使用http协议post 一个请求到服务提供端,然后服务提供端返回一个结果给调用者。这种方案将原本数据service层的DO操作过程上升成为了web服务,我个人并不反感。第二种方案就是使用rmi 实现,但是rmi client 和server的地址耦合到一起,一旦server更换地址client端需要同步修改。 第三种方案是直接读对方的数据库,当然便利性和可维护性更差,需要把server 端的底层DAO业务冗余到client。 最后一种就是rpc 远程调用框架。

rpc框架需要很多组件:调用者,提供者,服务注册中心, 通信总线 和通信协议。 其中每一个组件都有很多技术点要谈。本文在这里只讨论一个最简单的原型:简单分隔符的通信协议,使用socket 实现通信总线。 CS直接耦合绑定在一起,后期再考虑注册中心和服务暴漏的问题。 Socket通信方式也采用最简单的直连接方式,不使用nio, 也不维护连接池。


首先先看一个简单场景

打印接口:

public interface PrintText {
    String print(String text);
}

接口实现:

public class SystemPrint implements PrintText {
    public String print(String text) {
        System.out.println(text);
        return "系统已打印:" + text;
    }
}

业务调用方:

public class SpringClient {

    public static void main(String[] args){
        BeanFactory apx = new ClassPathXmlApplicationContext("classpath:spring-config.xml");
        PrintText pt = (PrintText) apx.getBean("printText");
        System.out.println(pt.print("springClient"));
    }
}

其中图1能够很好的描述这个场景, 虚线A表示业务方并不知道实现者是谁,只知道接口的存在。

简单场景
图1


现在业务系统升级了,负责打印的bean复杂到独立成了一个系统,要从业务系统中剥离出去,该怎么办?我们选择使用socket实现远程调用。 由于控制反转的设计,业务方几乎不需要改动任何代码就能实现升级。远程调用方案如图2所示:

这里写图片描述
图2

接口PrintText的实现被代理成为socket client, 将调用请求通过socket发送出去。 服务提供者接受到请求后,解析完毕,调用具体的实现类SystemPrintText, 然后将返回值发送回socket client。 最后返回给业务方。


下面详细讲一下各个组件的实现方案:

 1. 通讯协议:使用|##|隔开字段(并不推荐这种方式,后面系列进一步讲协议的设计)

   协议设计:
   version:版本号|##|cypher:加密串|##| interfaceName:接口bean名称|##| interface:接口全路径|##|method:调用方法|##|params:参数1的类+参数1对象,单数2类+参数2对象,

   Demo:   `version:1.0.0|##|cypher:default|##|interfaceName:proxyPrintText|##|interface:com.tmall.beans.PrintText|##|method:print|##|params:java.lang.String+演示远程调用`


  2.Client客户端是一个动态代理,截获业务方的接口调用后将调用参数组装成为上述协议,发送给服务端,并将服务端的返回结果返回给业务方。代码实现如下:
public class SocketConsumerProxy implements InvocationHandler {
    private Object target;

    private RemoteDataSource dataSource;

    public SocketConsumerProxy(RemoteDataSource dataSource){
        this.dataSource = dataSource;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        StringBuffer stream = this.buildRpcRequest(proxy, method, args);

        Object object = null;
        try{
            long startTime =  System.currentTimeMillis();
            System.out.println("New rpc client send " + stream.toString() + "  time:" + startTime);
            // socket connect
            Socket socket=new Socket(dataSource.getIp(), dataSource.getPort());

            // request
            PrintWriter os = new PrintWriter(socket.getOutputStream());
            os.println(stream.toString());
            os.flush();

            // read response
            BufferedReader br = new BufferedReader(
                    new InputStreamReader(socket.getInputStream()));
            object = br.readLine();

            long endTime =  System.currentTimeMillis();
            System.out.println("client read from service:" + object + "  time:" + (endTime - startTime));
        }catch (IOException e){
            e.printStackTrace();
            object = e.toString();
        }

        return object;
    }

    private StringBuffer buildRpcRequest(Object proxy, Method method, Object[] args) {
        StringBuffer buffer = new StringBuffer();
        buffer.append(String.format("version:%s|##|cypher:%s|##|interfaceName:%s|##|interface:%s|##|method:%s|##|params:"
          , dataSource.getVersion(), dataSource.getCypher(), dataSource.getInterfaceName(), dataSource.getInterfaces(), method.getName()));

        for (Object obj : args){
            buffer.append(obj.getClass().getName() + "+" + obj.toString() + ",");
        }
        return buffer;
    }
}

对于动态代理InvocationHander的介绍这里不再论述。

 3.服务端实现:服务端监听端口,获取客户端访问。 校验数据有效性:是否为空,加密参数,版本号,调用接口,调用方法。 然后通过spring容器找到最终的实现bean,通过反射的方式调用对应方法。 最后将返回值发送给客户端。代码如下:
public class SocketProvider{
    private RemoteDataSource dataSource;

    public SocketProvider(RemoteDataSource dataSource) {
        this.dataSource = dataSource;
    }

    public Object provide() throws Throwable {
        ServerSocket serverSocket = new ServerSocket(dataSource.getPort());
        while (true) {
            Socket socket = null;
            try {
                //接收客户连接,只要客户进行了连接,就会触发accept();从而建立连接
                socket = serverSocket.accept();
                this.getRpcRequest(socket);

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private void getRpcRequest(Socket socket) {
        try {
            System.out.println("rpc client accepted " + socket.getInetAddress() + ":" + socket.getPort() + "   time:" + System.currentTimeMillis());
            // 接收服务器的反馈
            BufferedReader br = new BufferedReader(
                    new InputStreamReader(socket.getInputStream()));

            String msg = br.readLine();

            System.out.println("读到远程调用请求:" + msg);
            Object obj  = this.parseRpcClientRequest(msg);

            // 接收服务器的反馈
            PrintWriter os = new PrintWriter(socket.getOutputStream());
            os.println(obj.toString());
            os.flush();

            System.out.println("rpc server return " + obj + ":" + socket.getPort() + "   time:" + System.currentTimeMillis());
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public Object parseRpcClientRequest(String msg) {
        Object result = "";
        if (StringUtils.isEmpty(msg)){
            return result;
        }

        String[] infos = msg.split("\\|##\\|");
        Map<String, String> infoMap = new HashMap<String, String>();
        for(String info : infos){
            String[] pair = info.split(":");
            infoMap.put(pair[0], pair[1]);
        }
        if (infoMap.isEmpty()){
            return "无调用参数";
        }

        if (!dataSource.getCypher().equals(infoMap.get("cypher"))){
            return "加密串不对";
        }

        if (!dataSource.getVersion().equals(infoMap.get("version"))){
            return "服务版本号不对";
        }

        String interfaces = infoMap.get("interface");
        String interfaceName = infoMap.get("interfaceName");
        String methodName = infoMap.get("method");
        String params = infoMap.get("params");

        if(StringUtils.isEmpty(interfaces) || StringUtils.isEmpty(methodName) || StringUtils.isEmpty(interfaceName)){
            return "无调用接口或者方法 或者接口beanName";
        }

        // bean
        Object obj = RemoteDataSource.beanFactory.getBean(interfaceName);
        if(obj == null){
            return "未找到对应的服务";
        }

        // bean 和 interface对应关系
        boolean isInterfaceRight = false;
        Class<?>[] clazzArray = obj.getClass().getInterfaces();
        for (Class<?> clazz : clazzArray){
            if (clazz.getName().equals(interfaces)){
                isInterfaceRight = true;
                break;
            }
        }
        if (isInterfaceRight == false){
            return "错误的bean Name 和 interface对应关系";
        }


        // 参数对应的类和对象
        String[] paramsArray = params.split(",");
        Class<?>[] paramsClazzArray = new Class<?>[paramsArray.length];
        Object[] paramObjArray = new Object[paramsArray.length];
        try{
            for (int i = 0; i < paramsArray.length; i ++){
                String paramInfo = paramsArray[i];
                if (StringUtils.isEmpty(paramInfo)) {  // 过滤掉多余的逗号
                    continue;
                }

                String[] paramsInfos = paramInfo.split("\\+");

                Class c = Class.forName(paramsInfos[0]);
                paramsClazzArray[i] = c;
                paramObjArray[i] = paramsInfos[1];
            }
        }catch (ClassNotFoundException e){
            e.printStackTrace();
            return "未找到参数对应的类名:" +e.toString();
        }


        try {
            Method method = obj.getClass().getMethod(methodName, paramsClazzArray);
            result = method.invoke(obj, paramObjArray);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
            return "未找到参数对应的方法名称:" +e.toString();
        }
        catch (IllegalAccessException e) {
            e.printStackTrace();
            return "无效的调用:" +e.toString();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
            return "无效的目标地址:" +e.toString();
        }

        result = "我是服务器代理aop, result=" + result;
        return result;
    }

    public static void main(String[] args) throws ClassNotFoundException {
        RemoteDataSource dataSource = new RemoteDataSource();
        dataSource.setInterfaceName("caishengProxyPrintText");
        dataSource.setInterfaces("com.tmall.beans.PrintText");
        dataSource.setVersion("1.0.0");

        SocketProvider provider = new SocketProvider(dataSource);

        StringBuffer buffer = new StringBuffer();
        buffer.append(String.format("version:%s|##|cypher:%s|##|interfaceName:%s|##|interface:%s|##|method:%s|##|params:"
                , dataSource.getVersion(), dataSource.getCypher(), dataSource.getInterfaceName(), dataSource.getInterfaces(), "print"));
        String[] params = new String[1];
        params[0] = "caishengRemoteTest";
        for (Object obj : params){
            buffer.append(obj.getClass().getName() + "+" + obj.toString() + ",");
        }

        Object result = provider.parseRpcClientRequest(buffer.toString());

        System.out.println(result);
    }
}
4.代理通过spring的FactoryBean实现,通过getObject方法能够中转任何接口的调用请求。具体细节请参考spring AOP和动态代理实现方案。Demo版本就非常简单,就是将业务的请求发送给2中的动态代理。 代码如下:
public class CaishengRemoteSimpleConsumerBean implements FactoryBean, InitializingBean{
    private RemoteDataSource remoteDataSource = new RemoteDataSource();  // 远程数据源


    public void afterPropertiesSet() throws Exception {
        remoteDataSource.init();
    }

    public Object getObject() throws Exception {
        return remoteDataSource.getClientObject();
    }

    public Class<?> getObjectType() {
        return remoteDataSource.getClientObject().getClass();
    }

    public boolean isSingleton() {
        return false;
    }

    public void setInterfaces(String interfaces){
        remoteDataSource.setInterfaces(interfaces);
    }

    public void setInterfaceName(String interfaces){
        remoteDataSource.setInterfaceName(interfaces);
    }

    public void setVersion(String version){
        remoteDataSource.setVersion(version);
    }

    public void setRemoteDataSource(RemoteDataSource remoteDataSource){
        this.remoteDataSource = remoteDataSource;
    }
}
 5. RemoteDataSource是远程数据源,耦合了客户端和服务端的ip地址、加密信息以及版本号。代码如下:
public class RemoteDataSource {
    private String ip = "127.0.0.1";
    private int port =  9090;              // 端口
    private String interfaces;             // 接口全路径
    private String interfaceName;          // 接口名
    private String version;
    private String cypher ="default";       // 密码
    private Class<?> interfaceClass;       // 类文件

    private SocketRemotionFactory remotionFactory;

    public void init(){
        remotionFactory = new SocketRemotionFactory(this);

        try {
            interfaceClass = Class.forName(interfaces);
        } catch (ClassNotFoundException e) {

        }
    }

    public Object getClientObject(){
        return remotionFactory.getRemoteClientProxy();
    }

    public String getIp() {
        return ip;
    }

    public void setIp(String ip) {
        this.ip = ip;
    }

    public String getInterfaces() {
        return interfaces;
    }

    public void setInterfaces(String interfaces) {
        this.interfaces = interfaces;
    }

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    public String getCypher() {
        return cypher;
    }

    public void setCypher(String cypher) {
        this.cypher = cypher;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public String getInterfaceName() {
        return interfaceName;
    }

    public void setInterfaceName(String interfaceName) {
        this.interfaceName = interfaceName;
    }

    public Class<?> getInterfaceClass() {
        return interfaceClass;
    }

    public void setInterfaceClass(Class<?> interfaceClass) {
        this.interfaceClass = interfaceClass;
    }
}
 6. remotionFactory是一个简单工厂,生成以PrintText为接口,2中的SocketConsumerProxy的为实现的代理对象给业务方。具体代码如下:
public class SocketRemotionFactory {
    private RemoteDataSource dataSource;
    public SocketRemotionFactory(RemoteDataSource dataSource) {
        this.dataSource = dataSource;
    }

    public Object getRemoteClientProxy(){

        Object result = null;

        Class<?> clazz = dataSource.getInterfaceClass();
        if (clazz == null){
            return "错误的client 代理,无对应的class";
        }

        Class<?>[] clazzArray = new Class[1];
        clazzArray[0] = clazz;
        try{
            result = Proxy.newProxyInstance(clazz.getClassLoader(), clazzArray, new SocketConsumerProxy(dataSource));
        }catch (Exception e){
            e.printStackTrace();
        }

        return result;
    }
}
7.  服务端需要在spring容器加载bean时提供服务,代码如下:
public class CaishengRemoteSimpleProviderBean implements FactoryBean, InitializingBean{
    private RemoteDataSource remoteDataSource = new RemoteDataSource();  // 远程数据源

    private SocketProvider provider;

    public void init() throws Exception {
        this.afterPropertiesSet();
    }

    public void afterPropertiesSet() throws Exception {
        remoteDataSource.init();

        provider = new SocketProvider(remoteDataSource);
    }

    public Object getObject() throws Exception {
        try {
            provider.provide();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return null;
    }

    public Class<?> getObjectType() {
        return null;
    }

    public boolean isSingleton() {
        return false;
    }

    public void setInterfaces(String interfaces){
        remoteDataSource.setInterfaces(interfaces);
    }

    public void setInterfaceName(String interfaces){
        remoteDataSource.setInterfaceName(interfaces);
    }

    public void setVersion(String version){
        remoteDataSource.setVersion(version);
    }

    public void setRemoteDataSource(RemoteDataSource remoteDataSource){
        this.remoteDataSource = remoteDataSource;
    }
}
8.  Spring中bean配置为:

客户端:

<bean id="caishengSimpleRemoteProxyPrintText" class="com.tmall.proxy.remotesimple.CaishengRemoteSimpleConsumerBean">
        <property name="interfaces">
            <value>com.tmall.beans.PrintText</value>
        </property>
        <property name="interfaceName">
            <value>proxyPrintText</value>
        </property>
        <property name="version">
            <value>1.0.0</value>
        </property>
</bean>

服务端:

<bean id="printText" class="com.tmall.beans.impl.SystemPrint"></bean>

<bean id="caishengSimpleRemoteProvider" class="com.tmall.proxy.remotesimple.CaishengRemoteSimpleProviderBean" init-method="init">
        <property name="interfaces">
            <value>com.tmall.beans.PrintText</value>
        </property>
        <property name="interfaceName">
            <value>proxyPrintText</value>
        </property>
        <property name="version">
            <value>1.0.0</value>
        </property>
</bean>
9.  业务方代码:
public class Client {
    public static void main(String[] args){
        BeanFactory apx = new ClassPathXmlApplicationContext("classpath:spring-config.xml");
        PrintText pt = (PrintText) apx.getBean("caishengSimpleRemoteProxyPrintText");
        System.out.println(pt.print("远程调用演示"));
    }
}
好了,一个简单的rpc框架就实现完毕。 一起看一下调用效果吧:

服务方未启动:socket 调用异常

New rpc client send version:1.0.0|##|cypher:default|##|interfaceName:proxyPrintText|##|interface:com.tmall.beans.PrintText|##|method:print|##|params:java.lang.String+远程调用演示,  time:1464108758488
java.net.ConnectException: Connection refused: connect
java.net.ConnectException: Connection refused: connect
    at java.net.DualStackPlainSocketImpl.connect0(Native Method)
    at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:69)
    at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:339)
    at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:200)
    at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:182)
    at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:157)
    at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:391)
    at java.net.Socket.connect(Socket.java:579)
    at java.net.Socket.connect(Socket.java:528)
    at java.net.Socket.<init>(Socket.java:425)
    at java.net.Socket.<init>(Socket.java:208)
    at com.tmall.proxy.remotesimple.socket.SocketConsumerProxy.invoke(SocketConsumerProxy.java:33)
    at com.sun.proxy.$Proxy0.print(Unknown Source)
    at com.tmall.client.simpleRemote.Client.main(Client.java:16)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)

Process finished with exit code 0

服务方启动:

rpc client accepted /127.0.0.1:54730   time:1464108803359
读到远程调用请求:version:1.0.0|##|cypher:default|##|interfaceName:proxyPrintText|##|interface:com.tmall.beans.PrintText|##|method:print|##|params:java.lang.String+远程调用演示,
远程调用演示
rpc server return 我是服务器代理aop, result=系统已打印:远程调用演示:54730   time:1464108803380

服务方日志:

New rpc client send version:1.0.0|##|cypher:default|##|interfaceName:proxyPrintText|##|interface:com.tmall.beans.PrintText|##|method:print|##|params:java.lang.String+远程调用演示,  time:1464108803353
client read from service:我是服务器代理aop, result=系统已打印:远程调用演示  time:27
我是服务器代理aop, result=系统已打印:远程调用演示

Process finished with exit code 0

客户端日志:用时38ms, 很多地方需要优化。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
介绍RCP的实现原理 目录 1. 前言 2 2. 基本概念 3 2.1. IDL 3 2.2. 代理(Proxy) 3 2.3. 存根(Stub) 4 3. 三要素 4 3.1. 网络通讯 4 3.2. 消息编解码 5 3.3. IDL编译器 5 4. flex和bison 5 4.1. 准备概念 5 4.1.1. 正则表达式(regex/regexp) 6 4.1.2. 符号∈ 6 4.1.3. 终结符/非终结符/产生式 6 4.1.4. 记号(Token) 6 4.1.5. 形式文法 7 4.1.6. 上下文无关文法(CFG) 7 4.1.7. BNF 8 4.1.8. 推导 8 4.1.9. 语法树 8 4.1.10. LL(k) 9 4.1.11. LR(k) 9 4.1.12. LALR(k) 9 4.1.13. GLR 9 4.1.14. 移进/归约 9 4.2. flex和bison文件格式 9 4.2.1. 定义部分 10 4.2.2. 规则部分 10 4.2.3. 用户子例程部分 10 4.3. flex基础 10 4.3.1. flex文件格式 11 4.3.2. 选项 11 4.3.3. 名字定义 11 4.3.4. 词法规则 12 4.3.5. 匹配规则 12 4.3.6. %option 13 4.3.7. 全局变量yytext 13 4.3.8. 全局变量yyval 13 4.3.9. 全局变量yyleng 13 4.3.10. 全局函数yylex 13 4.3.11. 全局函数yywrap 13 4.4. bison基础 14 4.4.1. bison文件格式 14 4.4.2. %union 14 4.4.3. %token 15 4.4.4. 全局函数yyerror() 15 4.4.5. 全局函数yyparse() 15 4.5. 例1:单词计数 15 4.5.1. 目的 15 4.5.2. flex词法文件wc.l 16 4.5.3. Makefile 16 4.6. 例2:表达式 17 4.6.1. 目的 17 4.6.2. flex词法exp.l 17 4.6.3. bison语法exp.y 17 4.6.4. Makefile 19 4.6.5. 代码集成 19 4.7. 例3:函数 20 4.7.1. 目的 20 4.7.2. func.h 20 4.7.3. func.c 21 4.7.4. IDL代码func.idl 22 4.7.5. flex词法func.l 22 4.7.6. bison语法func.y 24 4.7.7. Makefile 27 5. 进阶 27 5.1. 客户端函数实现 27 5.2. 服务端函数实现 28 5.2.1. Stub部分实现 28 5.2.2. 用户部分实现 29 6. 参考资料 29

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值