RPC及Dubbo

2019-07-28  本文已影响0人  软萌白甜Hedy

  开始今天的文章之前,想带大家思考两个情景:
  情景一:自学时自己练手的小Demo 里面的接口,如果自己要使用的话,直接写个实现类实现这个接口就好。
  情景二:如果有一天你在笔记本上写了接口,但另外一个写在台式机上的项目也正好想调用一下,这时怎么办呢?
  都是自己家里的电脑,最直接的想法是在台式机上也写一个一样的接口也就罢了,但如果把这个情景放大到公司,想调用接口的电脑有很多台呢?或者说写的接口很复杂呢?都去重写的话会不会太低效且无意义重复?
  那今天的主题就正好可以帮我们解决这个问题——RPC(Procedure Remote Call),它可以实现接口服务的远程调用。目前比较流行的RPC有:gRPC(google 开源)、Thrift(Facebook开源)、Dubbo(阿里开源)。我们借Dubbo来为大家做RPC的详细介绍:

概念

Dubbo是一个分布式服务框架,致力于个性化和透明化的RPC调用方案。

框架及过程
dubbo.png

  上图是我从dubbo官网下载的一张框架图,我们简要看一下它的过程有哪些?

  1. 启动:服务容器负责启动、加载、服务提供者。
  2. 注册:服务生产者向注册中心注册自己的提供的服务。
  3. 订阅:服务消费者向注册中心订阅自己需要的服务。
  4. 通知:注册中心返回服务提供者的地址列表给服务消费者。
  5. 调用:服务消费者从服务生产者的地址列表里通过负载均衡算法选一台服务提供者,进行调用。
  6. 统计:服务生产者、服务消费者,在内存中累计调用次数和调用时间,定时每分钟发一次统计数据到监控中心。
作用:
  1. 透明化的远程方法调用:服务消费者无需通过外界API入侵,就能远程调用服务生产者的服务。
  2. 服务自动注册与发现:服务提供者与服务消费者之间无需写死服务提供者的地址列表,服务提供者在注册中心配置服务列表,注册中心向服务消费者推送可用的服务提供者的服务列表,实现服务提供者和消费者的动态管理。
  3. 软负载均衡及容错机制:
    Dubbo基于软负载均衡算法,在服务列表中选一台可用的服务提供者进行调用,当某一服务器宕机,会自动切换到另外一台可用的服务提供者,实现容错性和高可用性。
需要思考的点:

  经过以上的介绍,大家对Dubbo有了初步的认识,那我们再沿着它的过程一起思考几个问题?

  1. 服务提供者向注册中心具体注册的是什么?
    服务生产者的URL(IP+端口号)、代表服务的唯一标志。
  2. 注册中心如何保证向服务消费者提供的都是可用的?
    先傻瓜一点想,如果没有注册中心的时候,服务提供者如果有多台时,需要服务消费者手动添加跟管理,如果其中有服务提供者宕机,消费者又需要删掉,这样来回添加和删除会很低效且麻烦。
    注册中心的作用在于它通过“心跳监测”这个功能,定时向各个服务提供者发送一个请求,如果长期没有响应,注册中心就认为该服务提供者已经“挂了”,并将其删除,并通知服务消费者服务提供者地址列表已经发生改变,从而进行更新。
  3. 透明化的远程调用是如何实现的?如何做才能让调用远程服务像调用本地服务一样呢?
    (1) 服务消费者想调用(以下简称Consumer)服务提供者(以下简称Provider)的接口I,首先Consumer端会引入接口I的Jar包,里面有该接口的class字节码文件。
    (2) Consumer为这个接口生成一个唯一的请求ID(requestID,每次请求都会生成)和动态代理类Proxy,并把Consumer想调用的接口名、方法名、参数名、参数类型名及处理结果的回调对象callback封装成一个对象object,并向专门存放调用信息的全局ConcurrentHashMap 里面put(ID, object)。
    requestID在这里的作用主要在于,无论是基于何种连接,Consumer都是异步调用,既然是异步调用,线程发送完请求就干自己的事情去了,如果此时有两个线程,线程A和线程B请求requestA和requestB,若返回结果时,如何得知哪个是线程A的结果,哪个是线程B的结果呢?一般来说,线程发送完请求消息后,就会紧接着执行callback的get()方法试图获取远程返回的结果。在get()内部,则使用synchronized获取回调对象callback的锁,再先检测是否已经获取到结果,如果没有,然后调用callback的wait()方法,释放callback上的锁,让当前线程处于等待状态。Provider接收到请求并处理后,将response结果(此结果中包含了前面的requestID)发送给Consumer,Consumer端socket连接上专门监听消息的线程收到消息,分析结果里的requestID,和ConcurrentHashMap里面get到的requestID,匹配到callback对象,synchronized获取callback上的锁,将方法调用结果设置到callback对象里,再调用callback.notifyAll()唤醒前面处于等待状态的线程。如此,requestID及其唯一性确保了从Provider传过来的线程是属于之前那个请求线程的了。
    (3) Consumer将request ID和打包的方法调用信息封装成一对象connRequest,并且经由Hession序列化,再异步发送出去。
此处再做几点说明:

序列化与反序列化:Hession序列化是为了将对象信息转化成二进制信息,便于传输给Provider;当Provider收到Consumer封装的数据,则需要通过反序列化获悉封装其中的具体的接口名、方法名、参数名、参数类型名,得以知晓Consumer究竟想调用哪个接口的哪个方法;同理,Provider端产生的结果同样需要序列化以传递给Consumer,Consumer收到结果后,需要反序列化拿到具体的数据结果。
通讯:请求消息被序列化之后,下一步要进行网络通信了,通讯的模式包含BIO和NIO两种,IO通信框架为netty连接 。
  有关以上的过程,附上我总结的简要流程图:

Dubbo Model.png
上一篇下一篇

猜你喜欢

热点阅读