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);
    }
完整启动流程
- install 接口、通用类项目,生成jar
- 启动服务提供者
- 将服务注册到txt文件中
 
- 调用服务消费者
此RPC执行流程
- 
消费者 调用 obj.helloWord("RB") 方法 
  ↓
 进入ProxyFactory.getProxy方法中
  ↓
 组装RPC请求参数
  ↓
 进行获得已注册的服务列表
  ↓
 进行负载均衡
  ↓
 发送请求
 
- 
生产者 进入自定义的Servlet  ↓ 获得请求参数  ↓ 获得对应实现类Class对象  ↓ 使用反射调用实际业务方法  ↓ 返回结果 
 
                     
                    
                 
                    
                
 
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号