# RPC与异步设计

\[TOC]

## RPC

![](https://github.com/Nixum/Java-Note/raw/master/picture/RPC%E7%AE%80%E5%8D%95%E6%A1%86%E6%9E%B6.png)

一个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原理](https://blog.csdn.net/u010900754/article/details/78081428)

[RPC原理及RPC实例分析](http://www.importnew.com/22003.html)

## 异步通信

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

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

### 定时轮询

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

### 回调

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

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

### 发布订阅

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

### 事件驱动 + 状态机

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

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

#### 事件驱动模型

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

![](https://github.com/Nixum/Java-Note/raw/master/picture/%E4%BA%8B%E4%BB%B6%E9%A9%B1%E5%8A%A8%E5%9F%BA%E6%9C%AC%E6%A1%86%E6%9E%B6.png)

这里的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会麻烦一些，需要完善的日志调用链来补充

## 参考
