1.手写简单的RPC

什么是RPC

RPC是⼀种协议:是⼀种远程过程调⽤(remote procudure call)协议 rpc协议是在应⽤层之上的协议,规定了通信双⽅进⾏通信的数据格式是什么样的,及数据如何传输: 指明调⽤的类或接口 指明调⽤的⽅法及参数

手写RPC

模拟远程调用过程,通过这个过程帮助了解Dubbo的原理

创建功能接口、通用类项目

功能/目的

  • 把功能接口、通用类提取,方便生产者和消费者使用

接口

package myinterface;

/**
 * 定义一个功能接口
 */
public interface HelloWord {
    String helloWord(String parameter);
}

RPC传参对象

package myInvocation;


import java.io.Serializable;

/**
 * 生产者和消费者所用的传参对象
 */
public class MyInvocation implements Serializable {
    //请求的接口名称
    private String interfaceName;
    //请求的方法名称
    private String methodName;
    //请求的参数类型-根据这个匹配具体实现方法
    private Class[] paramTypes;
    //请求的具体参数
    private Object[] params;

    public MyInvocation(String interfaceName, String methodName, Class[] paramTypes, Object[] params) {
        this.interfaceName = interfaceName;
        this.methodName = methodName;
        this.paramTypes = paramTypes;
        this.params = params;
    }

    public MyInvocation() {
    }

    public String getInterfaceName() {
        return interfaceName;
    }

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

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    public Class[] getParamTypes() {
        return paramTypes;
    }

    public void setParamTypes(Class[] paramTypes) {
        this.paramTypes = paramTypes;
    }

    public Object[] getParams() {
        return params;
    }

    public void setParams(Object[] params) {
        this.params = params;
    }
}

Util

ImplRegisterUtil.java

package util;

import java.util.HashMap;
import java.util.Map;

/**
 * 服务实现类注册工具
 */
public class ImplRegisterUtil {
    //接口名与实现类映射关系存储在内存-可用NoSql等其他介质
    private static Map<String, Class> map = new HashMap();

    /**
     * 传入接口名和实现类的Class对象,用于根据接口名获得实现类
     * @param interfaceName 接口名
     * @param implClass 实现类Class对象
     */
    public static void regist(String interfaceName, Class implClass) {
        //注册到服务端内存中
        map.put(interfaceName, implClass);
    }

    public static Class get(String interfaceName) {
        return map.get(interfaceName);
    }
}

ServiceRegisterUtil.java

package util;


import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 服务写入(注册)工具
 */
public class ServiceRegisterUtil {
    private static Map<String, List<ServiceUrl>> SERVICE_REGISTER = new HashMap<String, List<ServiceUrl>>();
    /**
     * 将服务写入(注册)到txt文件中
     * @param interfaceName
     * @param url
     */
    public static void regist(String interfaceName, ServiceUrl url){
        List<ServiceUrl> list = SERVICE_REGISTER.get(interfaceName);
        if (list == null) {
            list = new ArrayList();

        }
        list.add(url);
        SERVICE_REGISTER.put(interfaceName, list);
        //将服务写入写入文件
        saveService();
    }

    public static List<ServiceUrl> get(String interfaceName) {
        SERVICE_REGISTER = getService();
        List<ServiceUrl> list = SERVICE_REGISTER.get(interfaceName);
        return list;
    }

    /**
     * 写入(注册)地址
     */
    private static void saveService() {
        try {
            //写入到文件
            File file =new File("D:\\study\\MyStudy\\dubbo\\register.txt");
            if(!file.exists()){
                file.createNewFile();
            }
            FileOutputStream fileOutputStream = new FileOutputStream(file);
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
            objectOutputStream.writeObject(SERVICE_REGISTER);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获得已经写入(注册)的地址
     * @return
     */
    private static Map<String, List<ServiceUrl>> getService() {
        try {
            File file =new File("D:\\study\\MyStudy\\dubbo\\register.txt");
            FileInputStream fileInputStream = new FileInputStream(file);
            ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
            return (Map<String, List<ServiceUrl>>) objectInputStream.readObject();
        } catch (Exception   e) {
            e.printStackTrace();
        }
        return null;
    }
}

ServiceUrl.java

package util;


import java.io.Serializable;

/**
 * 服务地址
 */
public class ServiceUrl implements Serializable {

    private String hostName;
    private int port;

    public ServiceUrl(String hostName, int port) {
        this.hostName = hostName;
        this.port = port;
    }

    public ServiceUrl() {
    }

    public String getHostName() {
        return hostName;
    }

    public void setHostName(String hostName) {
        this.hostName = hostName;
    }

    public int getPort() {
        return port;
    }

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

完成后通过maven install 打包进仓库

创建服务提供项目(生产者)

功能/目的

  • 实现具体功能
  • 启动时将服务注册到注册中心

POM

<!--引入上面创建的接口和工具类jar(命名有点偏差,最好应该是common***,且工具和接口分两个jar)-->
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>rpc-interface</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

启动类

添加@ServletComponentScan

@SpringBootApplication
@ServletComponentScan //开启servlet扫描
public class RpcApplication {

    public static void main(String[] args) {
        SpringApplication.run(RpcApplication.class, args);
    }

}

接口实现方法

package com.example.rpc.impl;

import myinterface.HelloWord;

/**
 * 对接口的实现
 */
public class HelloWordImpl implements HelloWord {
    @Override
    public String helloWord(String s) {
        return "HelloWordImpl:"+s;
    }
}

启动服务注册Bean

package com.example.rpc.config;

import com.example.rpc.impl.HelloWordImpl;
import myinterface.HelloWord;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import util.ImplRegisterUtil;
import util.ServiceRegisterUtil;
import util.ServiceUrl;

/**
 * 注册服务到本地的一个txt文件中(dubbo就是把服务注册到zk或redis或nacos)
 */
@Configuration
public class RegistService {
    @Bean
    void regist(){
        //配置自己的ip和端口
        ServiceUrl url = new ServiceUrl("localhost",8080);
        //将自己的ip和端口注册到.txt文件
        ServiceRegisterUtil.regist(HelloWord.class.getName(), url);
        //指明服务的实现类
        ImplRegisterUtil.regist(HelloWord.class.getName(), HelloWordImpl.class);
    }
}

RPC调用时执行的方法

package com.example.rpc.common;

import myInvocation.MyInvocation;
import org.apache.commons.io.IOUtils;
import util.ImplRegisterUtil;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.lang.reflect.Method;

/**
 * 远程调用具体执行方法
 */
@WebServlet(name = "myServlet", urlPatterns = "/myServlet/*")  //标记为servlet,以便启动器扫描。
public class MyServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws  IOException {
        try {
            ObjectInputStream obj = new ObjectInputStream(req.getInputStream());
            //接到消费端传入的请求参数
            MyInvocation invocation = (MyInvocation)obj.readObject();
            //获得要请求的接口名
            String interfaceName = invocation.getInterfaceName();
            //根据接口名从内存中获得生产者启动时注册的实现类Class对象
            Class implClass = ImplRegisterUtil.get(interfaceName);
            //根据消费端传入方法名和参数类型,通过反射获得对应的方法
            Method method = implClass.getMethod(invocation.getMethodName(), invocation.getParamTypes());
            //传入消费端请求参数,返回结果
            String result = (String) method.invoke(implClass.newInstance(), invocation.getParams());
            //结果返回
            IOUtils.write(result.getBytes(), resp.getOutputStream());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

创建服务调用项目(消费者)

功能/目的

  • 通过RPC调用上面创建的项目

POM

<!--引入上面创建的接口和工具类jar(命名有点偏差,最好应该是common***,且工具和接口分两个jar)-->
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>rpc-interface</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

负载均衡类

注:只为模拟RPC调用,实际这个应该也抽取出来

package com.example.demo;

import util.ServiceUrl;

import java.util.List;
import java.util.Random;

/**
 * 负载均衡
 */
public class LoadBalance {
    public static ServiceUrl random(List<ServiceUrl> list) {
        Random random =new Random();
        int n = random.nextInt(list.size());
        return list.get(n);
    }
}

请求代理类

职责:负责发送RPC请求

package com.example.demo;

import myInvocation.MyInvocation;
import org.aopalliance.intercept.Invocation;
import org.apache.commons.io.IOUtils;
import org.apache.tomcat.util.net.openssl.ciphers.Protocol;
import util.ServiceRegisterUtil;
import util.ServiceUrl;

import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;

/**
 * 代理
 * @param <T>
 */
public class ProxyFactory<T> {

    @SuppressWarnings("unchecked")
    public static <T> T getProxy(final Class interfaceClassName) {
        return (T)Proxy.newProxyInstance(interfaceClassName.getClassLoader(), new Class[]{interfaceClassName}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) {
                //组装RPC调用的参数                           接口名                    接口方法            入参类型                   具体参数
                MyInvocation invocation = new MyInvocation(interfaceClassName.getName(), method.getName(), method.getParameterTypes(), args);
                //去文件中找到有哪些服务提供者(注册中心)
                List<ServiceUrl> urlList = ServiceRegisterUtil.get(interfaceClassName.getName());
                //如果文件中记录了多个生产者,进行负载均衡选择
                ServiceUrl serviceUrl = LoadBalance.random(urlList);
                try {
                    //请求生产者。封装http调用过程,此处通讯协议可用其他
                    URL url = new URL("http", serviceUrl.getHostName(), serviceUrl.getPort(), "/myServlet");
                    HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                    connection.setRequestMethod("POST");
                    connection.setDoOutput(true);
                    OutputStream os = connection.getOutputStream();
                    ObjectOutputStream oos = new ObjectOutputStream(os);
                    oos.writeObject(invocation);
                    oos.flush();
                    oos.close();
                    //获得结果
                    InputStream inputStream = connection.getInputStream();
                    String result = IOUtils.toString(inputStream);
                    return result;
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return "-1";
            }
        });
    }

}

发起调用

    @Test
    void contextLoads() {
        //获得代理对象
        HelloWord obj = ProxyFactory.getProxy(HelloWord.class);
        //发送请求,实际执行的是代理对象方法
        String rb = obj.helloWord("RB");
        System.out.println(rb);
    }

完整启动流程

  1. install 接口、通用类项目,生成jar
  2. 启动服务提供者
    • 将服务注册到txt文件中
  3. 调用服务消费者

此RPC执行流程

  • 消费者

    调用 obj.helloWord("RB") 方法
    ​ ↓
    进入ProxyFactory.getProxy方法中
    ​ ↓
    组装RPC请求参数
    ​ ↓
    进行获得已注册的服务列表
    ​ ↓
    进行负载均衡
    ​ ↓
    发送请求


  • 生产者

    进入自定义的Servlet

    ​ ↓

    获得请求参数

    ​ ↓

    获得对应实现类Class对象

    ​ ↓

    使用反射调用实际业务方法

    ​ ↓

    返回结果

代码下载

下载

posted @ 2022-04-05 17:04  RollBack2010  阅读(77)  评论(0编辑  收藏  举报