一、困境
在应对业务量的快速增长的时候,单机单应用无法抗住压力的情况下,需要进行改造。首先让应用进行了数据库跟应用从单一服务器进行分离,将数据库以及应用部署在不同的服务器上面,然后进行数据库的读写分离、搜索引擎、缓存、读写分离等等缓解数据库压力。在应用方面,把应用从单机做成分布式集群,但是业务复杂度的提升,比如一个交易系统的订单、用户、购物车、地址等模块全部耦合在一个应用里面,不管是复杂度还是可维护性,难度都比较大。
二、应用拆分与服务化带来的问题
应用拆分
因为所有的业务都耦合在单个应用里,所以可以按照业务为维度进行垂直拆分,比如一个系统里面的不同业务,交易、订单、用户等等都是一个单独的业务模块可以进行单业务单应用的拆分。进行拆分之后,如下图。
在上图中,尽管业务的耦合度降低,但是还存在一个新的问题,就是不同的业务还是存在耦合性,存在一些重复的代码。这样会给不同应用的维护带来困难,会造成一致性的问题。比如不同的应用存在相同的代码,A应用改了这部分代码而B应用没同步更改,那么很容易出现问题,为此还需要进行应用拆分的服务化处理。
服务化处理是将在应用层跟底层的中间增加一层服务层,减少最上层的应用层之间的应用交互,将这种交互转向应用层与服务层的交互。如下图所示:
三、服务框架总览
当系统进行服务化之后,很多的调用都会从本地调用变成远端调用。
而远端调用的大体流程应该如下:
- 获取可提供服务列表、确定需要调用的服务机器
- 发送请求
- 解析请求并进行处理
A、获取可提供服务的列表
首先要把自己根据请求的服务获取在线的服务提供机器列表。而在RPC调用中,服务其实就是我们的一个接口,比如:
1 | package com.buzheng.test.api; |
而当发起请求的时候,对目标机器的搜寻是可以将接口的名称作为寻址参数,如果使用负载均衡处于请求方跟服务提供方之间,请求的返回地址实际上是负载均衡或者LVS的地址跟端口,而如果使用名称服务的话返回的就直接是服务提供方的机器地址跟端口。
B、发送请求
发送请求涉及两个问题,请求以什么格式发送以及如何发送的问题。
请求格式:一般使用序列化,其实就是把对象变成二进制数据。好比如类信息可以通过网络传输并被类加载器反序列化实现动态加载。
发送方式:因为是RPC调用,分布式系统上不同机器之间是通过网络连接的,所以可以通过Socket进行发送。
C、解析请求返回结果
当发送到服务提供者机器之后,服务提供者进行反序列化并进行处理返回。
四、服务框架细节
远端通信遇到的问题
当请求方与服务提供方在集群上的不同节点的时候,假设此时集群上请求方以及服务提供方都是一台计算机,那么通过Socket通信,只需要进行固定的IP访问即可,但是实际情况是服务提供方节点背后也是一个集群,那么这个时候如何确定要连接的服务提供方的一台机器呢?
此时自然可以想到的方案就是在请求方跟服务方之间增加一层LVS或者负载均衡设备的方案,但是如果负载均衡设备挂了的话,整个跨进程调用就会出现问题,因此可以采用第二个方案,就是名称服务方案。如下图所示:
名称服务维护了服务提供者的在线信息,当服务提供者上下线的时候可以进行感知。服务提供者的信息一般缓存在调用者本地,但是当服务提供者上下线的时候,名称服务会把可用的服务提供者告知调用者。
流控
为了保证系统的稳定,一般我们需要进行流量控制。一般有2种选择,一个是完全不进行流控,一个是控制每秒的QPS进行限制。
序列化与反序列化的选择
二进制或者XML都是可以考虑的方式。
同步调用以及异步调用
- BIO/NIO 同步调用
- NIO+CallBack 异步调用