仿照dubbo手写一个RPC框架

dubbo介绍:

dubbo: Dubbo是一款高性能、轻量级的开源 Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。

目的: 实现调用远程服务像调用本地服务一样,将调用过程进行封装。在消费者端只需要一个要调用服务的接口,不需要实现,dubbo对该接口进行动态代理。并且支持多种调用协议/服务器。

框架实现:

  • 动态代理接口(核心):动态代理要调用服务的接口,在这里完成了对方法的远程调用,传递一个inovocation。
public static <T> T getProxy(final Class interfaceClass){
        /*动态代理,代理接口*/
        return (T)Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                String mock = System.getProperty("mock");
                if(mock != null && mock.startsWith("return:")){
                    return mock.replace("return","");    // 测试用,不调用远程服务
                }

                Invocation invocation = new Invocation(interfaceClass.getName(), method.getName(),method.getParameterTypes(), args);

                List<URL> urlList = RemoteMapRegister.get(interfaceClass.getName());    // 从注册中心获取到对应的URLs(可能有多台服务器提供服务)

                /*负载均衡*/
                URL url = LoadBalance.random(urlList);    
                Protocol protocol = ProtocolFactory.getProtocol();
                return protocol.send(url,invocation);

            }
        });
    }
  • invocation类(通信的信息载体):包含要调用的接口名,方法名,参数类型,参数。
public class Invocation implements Serializable {

    /*调用接口名*/
    private String interfaceName;
    /*调用方法名*/
    private String methodName;
    /*参数类型*/
    private Class[] paramType;
    /*参数*/
    private Object[] params;
  • 服务器与通信协议接口(通信的方式):包含两个方法,启动服务器与发送消息
/**
 * 将多个协议抽象为一个Protocol
 */
public interface Protocol {
    /*启动服务器*/
    void start(URL url);

    /**
     * 发送数据
     * @param url 发送的url
     * @param invocation 发送的数据,包装为要给invocation
     * @return
     */
    String send(URL url, Invocation invocation);
}

  • 使用工厂模式来获取需要的Protocol:
public class ProtocolFactory {
    public static Protocol getProtocol() throws ProtocolNotFoundException {

        /*通过命令行参数来决定通信使用dubbo还是http*/
        String name = System.getProperty("protocolName");

        /*默认为http请求*/
        if(name == null || name.equals("")) name = "http";

        switch(name) {
            case "http":
                return new HttpProtocol();
            case "dubbo":
                return new DubboProtocol();
            default:
                throw new ProtocolNotFoundException("此协议暂不支持");
        }
    }
}
  • 注册中心:

    记录:<接口名,对应提供者的URI>

    1. 使用一个本地文件模拟

      提供者注册时写入文件,消费者使用时读取文件即可

    2. 使用zookeeper存储

      • 使用curator操作zookeeper:

        client = CuratorFrameworkFactory.newClient("localhost:2181", new RetryNTimes(3, 1000));
        client.start();
        
      • 注册方法:

        String result = client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(String.format("/dubbo/service/%s/%s", interfaceName, JSONObject.toJSONString(url)), null);
                    System.out.println(result);
        
      • 获取方法:

        public static List<URL> get(String interfaceName) {
                List<URL> urlList = new ArrayList<>();
        
                try {
                    List<String> result = client.getChildren().forPath(String.format("/dubbo/service/%s", interfaceName));
                    for (String urlstr : result) {
                        urlList.add(JSONObject.parseObject(urlstr, URL.class));
                    }
        
                    REGISTER.put(interfaceName, urlList);
                } catch (Exception e) {
                    e.printStackTrace();
                }
        
                return urlList;
            }
        

执行流程:

提供者:

  1. 暴露服务

    URL url = new URL("localhost",8080);  // 这个url是自己封装的一个对象,只包括ip和端口
    
    /* 将<对应接口,url>注册到远程(redis/zookeeper)*/
    RemoteMapRegister.regist(IHelloService.class.getName(),url);
    
    /* 服务器本地注册:<接口,对应实现类> */
    LocalRegister.regist(IHelloService.class.getName(), HelloServiceImpl.class);
    
  2. 开启服务器,接受请求

    // 开启服务器,使用工厂获得服务器
    Protocol protocol = ProtocolFactory.getProtocol();
    protocol.start(url);    // 开启服务器,并且将服务器阻塞接受请求
    
  3. 处理接受到的请求( 展示使用 tomcat & Http协议

    // java 13:获取req中内容,将InputStream转换为Invocation对象
    // Invocation invocation = JSONObject.parseObject(req.getInputStream(),Invocation.class);
    
    // java8:使用URL发送请求,获得InputStream,转换为ObjectInputStream,读取对象
    InputStream inStream = req.getInputStream();
    ObjectInputStream objectInputStream = new ObjectInputStream(inStream);
    Invocation invocation = (Invocation) objectInputStream.readObject();
    
    
    String interfaceName = invocation.getInterfaceName();
    /*通过接口获得对应实现类*/
    Class implClass = LocalRegister.get(interfaceName);
    /*通过方法名和参数类型获得唯一的方法*/
    Method method = implClass.getMethod(invocation.getMethodName(),invocation.getParamType());
    /*反射机制method.invoke进行方法调用*/
    String result = (String) method.invoke(implClass.newInstance(),invocation.getParams());
    
    // 打印结果,并且将结果写入到输出流
    System.out.println("tomcat:" + result);
    IOUtils.write(result,resp.getOutputStream());
    

消费者:

  1. 获取代理对象,使用代理对象直接调用方法

    public static void main(String[] args) {
        IHelloService helloService = ProxyFactory.getProxy(IHelloService.class);    // 获取要调用接口的代理对象
    
        String alan = helloService.sayHello("alan");     // 直接调用方法,底层进行方法调用
        System.out.println(alan);
    }
    
  2. 代理对象开启服务器,并且发送请求( 展示使用 tomcat & Http 协议 ):

    // 使用JDK13的Java.net发送请求
    // var request = HttpRequest.newBuilder()
    //    .uri(new URI("http", null, hostname, port, "/", null, null))
    //    .POST(HttpRequest.BodyPublishers.ofString(JSONObject.toJSONString(invocation)))
    //    .build();
    // var client = java.net.http.HttpClient.newHttpClient();
    //
    // HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
    //
    // String result = response.body();
    
    // 使用 JDK1.8的url进行发送网络请求
    /*创建一个URL对象*/
    URL url = new URL("http", hostname, port, "/");
    /*建立连接*/
    HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
    /*设置连接参数*/
    httpURLConnection.setRequestMethod("POST");
    httpURLConnection.setDoOutput(true);
    /*输出流*/
    OutputStream outputStream = httpURLConnection.getOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(outputStream);
    oos.writeObject(invocation);
    oos.flush();
    oos.close();
    
    /*发送请求,获得结果*/
    InputStream inputStream = httpURLConnection.getInputStream();
    String result = IOUtils.toString(inputStream);
    
    return result;
    

运行验证:

首先启动服务提供者,然后启动服务消费者:

  • 提供者打印:
    在这里插入图片描述

  • 消费者打印:

在这里插入图片描述

这里测试的只是一个消费者与一个提供者。

当使用到提供者集群时,也是可以的,本框架消费端提供了负载均衡功能(最简单的 随机负载均衡 )

一个建议的dubbo框架就这样完成了,虽然简单,但是五脏俱全!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值