从服务框架开始

服务拆分在项目中是有帮助的,但是有帮助的「度」也存在,因为还有诸如「不同系统中存在代码冗余」等问题。等好的方式应该是服务化,就是将系统分层。

服务化就是之前提到的概念,存在多层,比如:顶层 Web 系统,中间服务中心,底层数据库

1. 远程调用

服务化带来了各种各样的问题,内部系统依赖变得错综复杂,我们需要解决它所带来的问题。

没有服务化之前,我们通过「本地调用」的方式使用其他组件,服务化以后我们需要使用的是「远程调用」,这里我们需要关注提高易用性降低性能损失两个问题。

因此一个通用的服务框架,要先完成 RPC 功能,为服务使用者提供相应的客户端。多机之间,我们需要对请求进行编码,传到远程节点,解码后进行真正的调用。

  • 寻址路由:「调用方」确定哪个实例被调用。
  • 实例地位:「被调用方」找到对应的实例进行调用。

2. 服务框架原型

上面的图只是一个从一端到另外一端的请求,服务框架是一个既包含调用端逻辑又包含服务端逻辑的一个是实现

在远程调用中,一般有如下的「请求流程」:

  1. 获取可用服务地址列表;
  2. 确定调用的目标机器; // 这两步完成「寻址路由」,方式很多,规则服务器、名称服务等等。
  3. 建立连接;
  4. 请求序列化;
  5. 发送请求;
  6. 接收结果;
  7. 解析结果。

而对于「服务端」而言,也要加入请求的处理:接收比特流,「实例定位」,返回结果。

3. 消费者设计

假设用 Spring 作为一个服务的框架,在 Bean 的定义中,需要指定这些 Bean 属性:「interfaceName」、「version」、「group」。

在本地通过「动态代理」的方式实现方法的调用。

  1. 首先需要「interfaceName」确认被调用接口是哪一个,显然使用的是全限定名;
  2. 接着「version」主要用以升级的场景,让版本存在隔离;
  3. 最后「group」,假设同一个服务有很多机器,我们可以把这些服务的机器归组,选择不同的组来调用。

「服务框架」可以作为:Web 应用的扩展(jar 包)、Web 容器的一部分(Web 应用)、本身作为容器。这些都可行,在开发中应该根据不同的场景来进行选择。

3.1 通信问题

我们的假想中调用的服务是一台的,但实际中一定是多台的集群:

处理方式多种,可以采用「透明代理」或者「直连」的方式,主流采用「直连」的方式:

引入一个「注册中心」来支持查找,让客户端拿到服务提供者列表,然后就是「路由」的问题了。

3.2 路由问题

「基于接口、方法、参数的路由」也是我们要解决的问题。下面两个调用者可能都调用同一个服务:

假设现在接口 A 的方法 1 是比较慢的方法,可能会陷入不断的等待:

我们会发现执行过程中很容易陷入 IA.m1 方法的等待队列,简单处理可以「增加资源保证系统能力」,另一种一种可行的方式「隔离资源」。

现在只有接口 A 的方法 2 会收到接口 A 方法 1 的影响了,那么我们可以对路由更加细致一点,可以基于「具体方法进行路由」,而不是「基于接口进行路由」,沿着这个思路,还有「基于参数进行路由」的选择。

3.3 多机房场景

如果在「服务注册中心」和「调用者」、「提供者」之间加上「机房」的概念。我们可以在「路由」中下文章:

给不同机房的调用者相同的服务提供者列表,然后根据「虚拟机房」等策略完成路由,一般是基于接口的路由(服务框架进行集中配置管理)。

另外流控是为了保证系统的稳定性,序列化是为了传输对象,网络一般采用 NIO ,这些都是老生常谈了。

3.4 多种异步调用

  • Oneway
    • 只管发送不关心结果的方式。只需要把发送的数据放入数据队列,然后继续处理后续任务;而 IO 线程也只需要从数据队列中读到数据,然后通过 Socket 连接发送出去即可。它是不保证可靠传达的通知。

  • Callback
    • 请求发送方发送请求后会继续执行自己的操作,等对方有响应时进行一个回调。

  • Future
    • 先把 Future 放入队列,然后把数据放入队列,接着就在线程中进行处理,等到请求线程在其他工作处理结束后,就通过 Future 来获取通信结果并直接控制超时。

  • 可靠异步
    • 一般通过消息中间件完成。

4. 提供者设计

在服务端我们也要像调用端一样设置特殊字段,以此暴露出远程服务的具体接口。

具体的请求流程从「网络通信层」一直延续到调用「具体服务」:

4.2 不同服务的线程隔离

值得注意的是,服务端线程池不是一个,定位服务以后,根据名称、方法、参数等信息来决定具体执行服务调用的线程池是哪一个,不会出现争抢线程资源的情况:

4.3 分布式环境合并请求

我们知道,合并请求的线程流程如下:

但是在分布式环境中,会有新的问题,需要使用「分布式锁」或者「路由」:

具体场景需要具体分析。

5. 总结

服务框架为我们从集中式系统转向分布式系统提供了基础支持:

完成服务框架,还需要针对自身环境完成工作,以助于开发中定位和发现问题。