前言
业务规模不断扩大,对稳定性、扩展性的要求不断提高,推动了后台架构技术的不断革新。面对日益复杂的需求,分布式系统的理念也逐渐深入到后台开发者的骨髓。2013年,借着手游热潮我对分布式系统开始尝试。在近三年的摸爬滚打中,踩过不少坑,也从业界技术发展中吸取一些经验,逐渐形成了目前的设计思路。这里和大家分享点心得,不敢奢谈有多大参考价值,权当抛砖引玉吧。
1. 失败的首次尝试
这种架构提供了三个基本组件:
Client API, 服务请求者API:
从 Cluster Center Server 获取服务提供者地址
向Server集群内所有实例注册,注册成功则认为可用
通过负载均衡算法,选择一个Server实例通信
检测Server集群内各实例的运行状态
向 Cluster Center Server 上报自己的状态、访问地址等
接收 Client API 的注册,并提供服务
向已经注册成功的Client定时汇报状态
接收 Server Cluster 上报,确定服务集群的结构,以及各实例的状态
接收 Client Cluster 的请求,返回可用服务集群列表
对架构的层次、模块划分没有作出很好的规划,比如通信底层、服务发现、集群探测与保活等等没有清晰定义接口,导致相互耦合,替换、维护较为困难。
上述问题,归根结底还是眼界狭窄,自己闷头造轮子没跟上业界技术发展的步伐。近几年微服务架构发展迅速,相比传统面向服务架构不再过分强调企业服务总线,而是深入到单个业务系统内部的组件化。这里我介绍下自己的调研结果。
统一命名 对服务以及其中的节点,进行集中式、统一命名,便于相互区分和访问。
监控 确定服务的可用性和状态,当服务状态变化时,关注者要有途径获知。
访问策略 服务通常包含多个节点,以集群形式存在,Client在每次请求时需要策略确定通信节点,策略目标可能是多样的,比如 负载均衡 ,稳定映射 等等。
可用性 容灾处理,动态扩容。
2.2 消息中间件
2.3 通信协议数据格式
通用性 是否支持跨平台、跨语言;业界是否广泛流行或者支持
可读性 文本流有天然优势,纯粹二进制流如果没有便捷可视化工具,调试将会异常痛苦
性能 空间开销——存储空间的占用;时间开销——序列化/反序列化的快慢
可扩展性 业务的不变之道就是——一直在变,必须具有处理新旧数据之间的兼容性的能力
允许高延迟比如100ms以上,内容变更频繁,且复杂的业务,可以考虑基于XML的SOAP协议。
基于Web browser的Ajax,以及Mobile app与服务端之间的通讯;对于性能要求不太高,或者以动态类型语言为主的场景,JSON可以考虑。
对性能和简洁性有极高要求的场景,Protobuf,Thrift,Avro都差不多。
对于Terabyte级别数据持久化应用场景,Protobuf和Avro是首要选择。持久化后的数据若存储在Hadoop子项目里,或以动态类型语言为主,Avro会是更好的选择;非Hadoop项目,以静态类型语言为主,首选Protobuf。
不想造 RPC 的轮子,Thrift可以考虑。
如果序列化之后需要支持不同的传输层协议,或者需要跨防火墙访问的高性能场景,Protobuf可以优先考虑。
系统拆分、解耦,清晰定义系统间接口,隐藏系统内部实现
大框架尽可能通用,子系统可在不同场景替换
下面首先对服务定义,然后介绍整体框架和服务内部拆分。
Service Cluster 服务集群,由功能相同的实例组成,作为整体对外服务,是一个集合。比如 Lobby 提供大厅服务,Battle 提供战斗服务,Club 提供工会服务,Trade 提供交易服务。
Service Instance 服务实例,提供某种服务功能的最细粒度,以进程形式存在。比如Club 集群中有两个实例 3.2.6.1 和 3.2.6.2 ,功能一致。
Service Node 服务节点,是服务发现组件管理的基本单元,可以是集群、实例、层次关系或者业务关心的含义。
Service Key 服务节点的Key,全局唯一的身份标记。key的设计需要能够体现出层级关系,至少要能够体现出 Cluster 和 Instance 的包含关系。etcd和zookeeper均支持key层次化的组织关系,类似文件系统的树形结构。etcd有mkdir直接建立目录,zookeeper则通过路径描述父子关系。但不管怎么都可以在概念层次使用路径结构 。
集群路径一定是其中各个实例的父路径
从功能完整性而言,集群是服务的基本粒度
相同功能的集群在不同前缀路径下含义不同,服务目标也可以不同,比如:
/Example/wechat/android/w_1/g_1/Lobby 和/Example/wechat/android/w_3/g_2/Lobby 功能上均表示大厅服务,但一个为大区1分组1服务,一个为大区3分组2服务
3.2 服务发现基本流程
Create 在服务发现组件中创建 Key 对应的 Service Node,指定全局唯一的标记。
Delete 在服务发现组件中删除 Key 对应的节点。
Set 设置 Key 对应的 Value, 安全访问策略或者节点基础属性等。
Get 根据 Key 获取对应节点的数据,如果是父节点可以获取其子节点列表。
Watch 对节点设置监视器,当该节点自身,以及嵌套子节点数据发生变更时,服务发现组件将变更事件主动通知给监视者。
生成自己的 Service Path,注意这是服务实例的路径。
以 Service Path 为key,通过 Create 方法生成节点,Set 数据:对外开放的地址、安全访问策略等。
生成需要访问的服务集群的 Service Path,通过 Get 方法获取集群数据,如果找不到说明该服务不存在;如果可以找到分两种情况:
该路径下没有子节点。说明当前不存在可用的服务实例,对集群路径设置watcher,等待新的可用实例。
该路径下有子节点。那么 Get 所有子节点列表,并进一步 Get 子节点访问方式和其它数据。同时设置 watcher 到集群路径,检测集群是否存在变化,比如新增或减少实例等。
通过 Delete 方法删除自己对应的节点。有些服务发现组件可以在实例生命周期结束时自行删除,比如zookeeper的临时节点。对于etcd的目录,或者zookeeper的父路径,如果非空,是无法删除的。
3.3 服务架构
关于Serialize/DeSerialize, APP业务的选择自由度较高,下面介绍其它Layer的具体实现:
REQ/REP 一应一答,有请求必须等待回应
PUB/SUB 发布订阅
PUSH/PULL 流水线式处理,上游推数据,下游拉数据
DEALER/ROUTER 全双工异步通信
DEALER/ROUTER 是传统异步模式,一方connect,一方bind。前端如果要连接多个后端就得建立多个socket。在前面描述的集群服务模式下,一个节点既会作为Client也会作为Server,会有多条入边(被动接收连接)和出边(主动发起连接)。这正好就是路由的概念,一个ROUTER socket可以建立多条通路,并对每条通路发送或者接收消息。
PUB/SUB 注重的是扩展性和规模,按照ZeroMQ作者的意思当每秒钟需要向上千的节点广播百万条消息时,你应该考虑使用 PUB/SUB 。好吧,可预见的将来业务规模恐怕还到达不到这种程度,现在先把简单放在第一位吧。
3.3.2 DMS Protocol
协议命令字
通信流程——建立连接
服务发现虽然可以反映节点是否存活,但一般有延迟,所以从服务发现获取的节点仅仅是候选节点。
网络底层机制差异较大,有些基于连接,比如raw socket,有些没有连接,比如shared memory。最好在高层协议中解决连接是否成功。这就好比声纳,投石问路,有回应说明可以连接,没有回应说明目前连接不可用。
通信流程——业务消息发送
普通消息 若 PIDF 表示对端实例和当前进程直接连接,那么发送消息
路由消息 若 PIDF 表示对端实例和当前进程没有直接连接,那么可以通过直连的实例转发。路由机制 后文会介绍
广播消息 若 PIDF InstanceID为负数,则向指定集群内所有实例广播
通信流程——保活机制
通信流程——连接断开
3.3.3 DMS Kernel
SERVICE MANAGER
self 确定自身 服务路径,实现服务注册,以及与目标通信链路的注册,供路由表使用
targets 获取并监控目标服务的数据以及运行状态
ACL 访问控制管理
对服务发现层接口进行封装,不同的 SERVICE DISCOVERY 功能可能有所不同
ROUTER MANAGER
Updater 用于向路由表中添加边,删除边,设置边的属性(比如权重),并对边的变化进行监控
Calculator 根据邻接边形成的 图结构 计算路由,出发点是当前实例,给定目标点判断目标是否可达,如果可达确定路径并传输给下一个节点转发。默认选择 Dijkstra 算法,业务可以定制。
CONNECTION MANAGER
Sentinel 对前端发起的连接,通过 READY 协议,可以获取该连接的失活标准,并通过前端主动包来判断进入连接是否存活。如果失活,将该连接置为断开状态,不再向对应前端主动发包。
Prober 对后端服务进行连接建立和连接保活。
Dispatcher 消息发送时用于确定通信对端实例。连接是基于实例的,但是业务一般都是面向服务集群的,所以Dispathcer 需要实现一定的分配机制,将消息转发给 服务集群中的某个 具体实例 。注意这里仅只存在直接连接的单播。分配时应考虑 负载均衡 默认使用一致性哈希算法,业务完全可以根据具体应用场景自定义。
3.3.4 DMS Interface
3.4 应用场景
无Broker通信
Broker通信
Broker级联通信