Back

RPC与异步设计

RPC原理、异步通信方案

[TOC]

RPC

一个RPC框架的基本实现,高性能网络传输、序列化和反序列化、服务注册和发现,如果是实现客户端级别的服务注册和发现,还可以在SDK中提供容错、负载均衡、熔断、降级等功能。

客户端发起RPC调用,实际上是调用该RPC方法的桩,它和服务端提供的RPC方法有相同的方法签名,或者说实现了相同的接口,只是这个桩在客户端承担的是请求转发的功能,向客户端屏蔽调用细节(比如向发现与注册中心查询要请求的服务方的url),使其像在使用本地方法一样;服务端在收到请求后,由其RPC框架解析出服务名和请求参数,调用在RPC框架中注册的该接口的真正实现者,最后将结果返回给客户端。

一个简单的RPC实现可以由三部分组成:规定远程的接口和其实现,服务端提供接口注册和IO连接,客户端IO连接和接口代理

  1. 首先是定义要提供的远程接口和其实现类

  2. 服务端使用线程池处理IO,实现多路复用,使用socket去循环accept(),每个请求建立一个线程

    线程里注册远程接口实例,使用InputStream接收客户端发送的参数,如接口的字节文件,判断是哪个接口,哪个方法,什么参数;接收后反射调用接口方法,将结果通过OutputStream发送回客户端

    客户端在发送参数可以做一个封装,加入id,服务端处理得到结果后也加入此id,返回回去,表示此次调用完成

  3. 客户端使用接口,动态代理的方式调用方法,在动态代理的实现里使用IO连接服务端,将远程接口字节码、方法参数这些东西做一个封装发送给服务端,等待返回结果,IO接收是阻塞的

参考【Java】java实现的远程调用例子 rpc原理

RPC原理及RPC实例分析

异步通信

优点:解耦,减少服务间的依赖,获得更大的吞吐量,削峰,把抖动的吞吐量变得均匀。

缺点:业务处理变得复杂,比如引入新的中间件,意味着要维护多一套东西,有时可能还得保证消息顺序,失败重传,幂等等处理,比较麻烦;异步也导致了debug的时候比较麻烦;

定时轮询

发送方请求接收方进行业务处理,接收方先直接返回,之后接收方在自己处理,最后将结果保存起来,发送方定时轮询接收方,获取处理结果。

回调

发送方请求接收方进行业务处理时,带上发送方结果回调的url,接收方接收到请求后先立刻返回,之后接收方在自己处理,当处理结果出来时,调用发送方带过来的回调url,将处理结果发送给发送方。

同理在于服务内部的异步回调,也是如此,只是把url换成了callback方法,比如Java中的Future类+Callable类。

发布订阅

主要靠消息队列实现,不过比较适合发送方不太care处理结果的,如果care处理结果,可以再通过一条队列将结果传递下去,执行后面的处理。

事件驱动 + 状态机

可以依靠消息队列,本质还是发布订阅那一套,只是将触发的条件换成事件,消费者根据不同的事件触发不同的逻辑,然后再通过状态机保证处理事件顺序。

比较常见的场景是电商业务中围绕订单服务的一系列业务处理,比如订单创建完成后,订单服务发出订单创建的事件,对应库存服务,收到该事件,就会进行锁库操作等

事件驱动模型

实际上是使用了观察者模式和状态模式来实现的,比较直观的例子就是android的EventBus,chrome的V8引擎都有用到此模型,这里仅总结并进行简单介绍

这里的demo是项目中使用到的组件的一个简化,真实的组件要比这个复杂的多,这里只简单罗列出基本的原理和优缺点。

原理:

  1. 事件驱动-状态机的异步模型,本质上是底层controller维护一个阻塞队列,将外部请求转化为事件,通过事件在内部传递。
  2. controller接收到请求,从对象复用池中获取一个上下文context并init,然后将事件交由context处理。context内有一套状态的扭转的控制流程,在不同的状态接收事件对业务逻辑进行处理,最后将处理结果交由注册的回调函数异步或者同步返回。
  3. 每一个状态在处理完当前逻辑操作后将发送事件给阻塞队列,并扭转为下一个状态,等待下一个事件的到来。
  4. 由于controller是单线程的,各个状态在处理的时候要求速率尽可能的快,以至于不会阻塞主线程,因此在controller内部还维护了一个延迟队列,用来接收延迟事件,状态通常在进行业务处理前会起一个定时器,如果超时将发送延迟事件给到延迟队列,来避免当前操作过长导致阻塞主线程,定时器由下一个状态来取消。
  5. 一般会为每个请求分配id,每个id对应一个上下文context,上下文一般使用id + Map来实现同一个请求下的上下文切换、保存和恢复,使用对象复用池来避免上下文对象频繁初始化
  6. 这套模型一般应用在中间件的设计上,当然也可以抽成通用框架,在写的时候就会发现,其实变化最多的是状态流程那一块,所以完全可以把这块抽出来 + netty进行网络通信就能搭出一套web框架出来了

优点:

  • 是一个单线程的模型,本身就是线程安全的。

  • 理论上一套业务逻辑拆分成小逻辑,交由不同的状态操作,各个状态的操作时间要求尽可能的短,不然会阻塞主线程

  • 各个状态在进行逻辑操作时,如果处理的时间过长,一般会使用线程池+回调+事件的方式处理

  • 状态机模式对应业务逻辑流程有比较强的控制,各个状态对应不同的职责

  • 对CPU的利用率比较高,吞吐量比较高,因为可以一次处理多种业务请求,每个业务请求都能进行拆分进行异步处理,速率比较快,因此性能会比常用的Spring全家桶好很多吧,至少在项目使用中的感受是这样

缺点:

  • 处理请求时会初始化一个上下文context来处理,所有异步操作的结果会暂存在上下文中,对内存的占用会比较高,对上下文里的参数存储也需要一套规范
  • 模型相对来讲还是比较复杂,运用多种设计模式,包含了一些回调,需要有一定的设计模式基础,容易劝退
  • 对象复用池,对象回收,线程池的操作,一不小心会造成内存泄漏;还要注意不能阻塞主线程,因此需要配合定时器进行处理
  • 异步处理导致debug会麻烦一些,需要完善的日志调用链来补充

参考

Licensed under CC BY-NC-SA 4.0
Last updated on Nov 14, 2021 00:00 UTC
Built with Hugo
Theme Stack designed by Jimmy