潜入浅出Dubbo,认识rpc大致原理

概述

  本门主要以dubbo为切入点,简单讲讲dubbo的简单使用和大致原理。后面再通过给大家简单搭建一个mini版dubbo,通过具体代码来加深大家对dubbo等rpc框架的大致原理。

  有兴趣的同学可以基于本项目进行衍生扩展开发,也可以去dubbo官网深入学习。

开门篇

一、dubbo的常规使用

1、服务提供者通过@service进行服务暴露:

@Service
public class HelloServiceImpl implements HelloService {
    @Override
    public String sayHello(String name) {
        return "hello, " + name;
    }
}

2、服务调用者通过 @Reference来引用服务:

    @Reference
    private HelloService helloService;

    public String sayHello(String name){
        return helloService.sayHello(name);
    }

在了解基本使用后,大家是否会很好奇这中间过程是怎么样的,为什么基于一个注解便可以轻松完成调用了。

能够这么轻松的使用,主要还得感谢dubbo框架封装得很好,让用户通过注解便可以简单使用了。

其dubbo的底层如下:

dubbo架构

别走,本篇文章不会一点点去剖析那张图,只是给大家过过眼,了解一下底层

别走

二、dubbo大致原理

整体流程:

image.png

  1. 服务提供方会向注册中心(zookeeper)注册服务。
    (存入接口全限定类名和服务提供方地址信息等)

image.png

  1. 服务调用方会向注册中心拉取服务列表并缓存到本地。

  2. 服务调用方根据接口信息和服务列表向提供者发起调用请求,并接收数据。

4、若服务列表发生更新,注册中心会去通知调用者去更新服务列表。

5、监视器会记录调用次数、调用时长等信息。

调用方与提供方的交互:

image.png

1、服务消费方(client)以本地调用方式调用服务;
2、client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;
3、client stub找到服务地址,并将消息发送到服务端;
4、server stub收到消息后进行解码;
5、server stub根据解码结果调用本地的服务;
6、本地服务执行并将结果返回给server stub;
7、server stub将返回结果打包成消息并发送至消费方;
8、client stub接收到消息,并进行解码;
9、服务消费方得到最终结果。

总而言之:

(1)调用者会调用接口方法,底层会根据接口方法信息去获取到服务方地址信息,然后向服务方发起调用请求。

(2)提供者接收到调用请求后,会根据信息去执行本地方法,然后将结果值返回给调用者。

插入一句话:

细节只是为了辅助理解,不要过于陷入细节。在理解整体架构后,细节便可由浅及深。

造山篇

ps:本篇将会带你体验创造的乐趣,在创造的过程中去理解rpc框架的大致原理。

本项目的github地址在这里,readme写得比较清晰了,这里主要给大家讲讲整体流程。

项目架构图:

image.png

为了简化项目配置以及降低大家的学习难度,本项目中的注册中心基于本地文件来实现。即提供者的地址信息会写入到文件中,调用者会从本地文件中获取提供者信息。

1-1、服务提供者

  • 提供者首先向本地注册表中注册信息(接口名-实现类),方便后续接收到调用请求时知道该执行哪个实现类方法。
  • 提供者向注册中心注册本机地址信息(map结构:接口名 - List ),方便后期调用者根据接口名获取到服务者地址。
  • 根据配置,启动服务器监听请求。(本项目默认采用基于tomcat的http)。
@Component
public class Provider {

    @Resource
    private LocalRegister localRegister;

    @Resource
    private IRegister remoteRegister;

    @Resource
    private ProtocolFactory protocolFactory;

    public void toStart() {

        //1 本地注册
        //{服务名:实现类}
        localRegister.registLocal(HelloService.class.getName(), HelloServiceImp.class);

        //2 远程注册中心
        //{服务名:list<URL>}
        URL url = new URL("localhost", 8080);
        remoteRegister.registRemote(HelloService.class.getName(), url);

        //3 读取配置,按配置启动服务器
        IProtocol protocol = protocolFactory.getProtocol();
        protocol.start(url);

    }

}

1-2、服务提供者所依赖的dubbo-mini-api

基于工厂模式和配置,protocolFactory.getProtocol()会返回一个HTTPProtocol对象,其
protocol.start(url)主要逻辑为:监听端口,接收http请求,将请求转发给HttpServerHandler

@Component
public class HttpServerHandler {

    @Resource
    private IRegister localRegister;

    public void handler(HttpServletRequest req, HttpServletResponse resp) throws IOException, ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        InputStream inputStream = req.getInputStream();
        ObjectInputStream ois = new ObjectInputStream(inputStream);
        // 读取调用信息
        Invocation invocation = (Invocation) ois.readObject();
        Class implClass = localRegister.getLocal(invocation.getInterfaceName());
        Method method = implClass.getMethod(invocation.getMethodName(), invocation.getParamTypes());

        // 调用方法
        String result = (String) method.invoke(implClass.newInstance(), invocation.getParams());

        // 写回结果
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(resp.getOutputStream());
        objectOutputStream.writeObject(result);
        objectOutputStream.flush();
        objectOutputStream.close();

    }
}

上述代码的业务逻辑为:
基于本地注册表,根据接口名找到实现类,再通过反射机制执行本地方法,最后将结果值进行返回。

2-1 服务调用者

  • 首先,基于接口创建代理类;
  • 其次,调用接口方法触发代理类拦截方法;
  • 在拦截方法中进行调用请求,并返回结果。
@Component
public class Consumer {

    public void toUse() {
        // 获取代理类
        HelloService helloService = ProxyFactory.getProxy(HelloService.class);
        // 调用接口方法,获取返回值
        String result = helloService.sayHello("watermelonhit");
        System.out.println("===========>" + result);
    }

}

2-2 调用者所依赖的dubbo-mini-api

当服务调用方调用接口方法时,就会自动执行该方法(动态代理):
(1)在此方法中会获取到调用的接口信息
(2)然后向注册中心(本地文件)拉取服务方的地址信息
(3)基于负载均衡,从服务列表中获取到一个url
(4)其次向服务方发起调用信息
(5)最后向服务调用方返回结果

@Component
public class ProxyFactory {


    private static LoadBalance randomBalance;

    private static IRegister remoteRegister;

    @Autowired
    public ProxyFactory(LoadBalance loadBalance, IRegister remoteRegister) {
        ProxyFactory.randomBalance = loadBalance;
        ProxyFactory.remoteRegister = remoteRegister;
    }

    /**
     * 当服务调用方调用接口方法时,就会自动执行该方法(动态代理)
     * (1)在此方法中会获取到调用的接口信息
     * (2)然后向注册中心(本地文件)拉取服务方的地址信息
     * (3)其次向服务方发起调用信息
     * (4)最后向服务调用方返回结果
     *
     * @param interfaceClass 接口类
     * @param <T>
     * @return
     */
    public static <T> T getProxy(final Class interfaceClass) {
        Object newProxyInstance = Proxy.newProxyInstance(ProxyFactory.class.getClassLoader(), new Class[]{interfaceClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                IProtocol protocol = ProtocolFactory.getProtocol();
                Invocation invocation = new Invocation(interfaceClass.getName(), method.getName(), method.getParameterTypes(), args);
                List<URL> urlList = remoteRegister.getRemote(interfaceClass.getName());
                URL url = randomBalance.getInstance(urlList);
                String result = protocol.send(url, invocation);
                return result;
            }
        });
        return (T) newProxyInstance;
    }
}

测试结果

先运行服务提供方,再运行服务调用方。

@RunWith(SpringRunner.class)
@SpringBootTest()
public class ApiTest {


    @Resource
    private Provider provider;
    @Resource
    private Consumer consumer;

    @Test
    public void start() {
        provider.toStart();
    }

    @Test
    public void consumer() {
        consumer.toUse();
    }

}

运行结果:
调用方操作台会打印:

===========>provider say: watermelonhit hello!

最后,来个小小的总结:

(1)服务提供方:

  • 将接口-实现类信息保存到本地中
  • 向注册中心注册本地地址信息
  • 监听端口,执行本地方法,返回结果值

(2)服务调用者:

  • 创建代理对象
  • 调用接口方法,执行拦截方法,发送调用请求,获取返回值

创建代理对象这一步是否跟dubbo不一样呢?dubbo做了进一步的处理,将代理对象注入到spring容器中,后面直接嗲用代理对象,本质上还是一样的。

最后的最后,希望这篇文章能够对您有所帮助吧🌹


本文到此结束,谢谢阅读。
好久没写博客了,鉴于网上关于手写dubbo版很多但是代码写得都很混乱,逻辑也不是很清晰。故简单的写了这篇文章。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值