分布式微服务架构设计原理
分布式微服务架构设计原理
应用架构演进
- 传统垂直架构
- 垂直应用架构介绍
- MVC架构
- MVC全名是Model View Controller,是模型-视图-控制器的缩写,是一种软件设计典范。
- 他是用一种业务逻辑、数据域界面显示分离的方法来组织代码,将众多的业务逻辑聚集到一个部件里面,在需要改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑,达到减少编码的时间
- MVC的三层结构
- 最前端的是View,主要用于前端页面展示,使用jsp,js,html+css等
- 中间为调度控制层Control,主要是用于前端web请求的分发,然后调度后台的逻辑执行,可以通过Struts2或者SpringMVC来实现
- 第三层为应用模型层Model,模型是应用程序上的字体部分,模型代表了业务逻辑和业务数据
- 垂直应用架构面临的挑战
- 复杂应用开发的维护成本很高,部署效率低
- 团队合作弱,多个项目或者同一个项目的公共模块重复开发,增加了代码的冗余量
- 系统可靠性降低,随业务的不断增加,访问量增大,网络流量增大,数据库连接增多,这都是将要面临的问题
- 维护困难,业务代码不断加大,功能不断加多,在这种垂直的架构中无法对已有的服务拆分,牵一发而动全身,改一处地方,其他地方会受到影响
- 新功能上线周期变长
- RPC架构
- RPC架构原理
- 客户端(Client):服务调用方
- 客户端存根(Client Stub):将客户端的请求参数打包成网络消息,再通过网络发送给服务方
- 服务端存根(Server Stub):接受客户端发送过来的消息并解包,再调用本地服务
- 服务端(Server):真正的服务提供者
- 调用原理
- 服务消费方调用以本地调用方式调用服务
- Client Stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体
- Client Stub找到服务地址,并将消息发送到服务端
- Server Stub收到消息后进行解码
- Server Stub根据解码结果调用本地的服务
- 本地服务之星并将结果返回给Server Stub
- Server Stub根据解码结果调用本地的服务
- Server Stub将返回结果打包成消息并发送至消费方
- Client Stub接收到消息,并进行解码
- 服务消费方得到最终结果
- RPC架构的核心技术
- 远程服务提供者需要以某种形式提供服务调用相关的信息
- 远程代理对象
- 通信
- 序列化
- 常用的RPC框架
- Thrift
- Hadoop的Avro-RPC
- Hessian
- gRPC
- RPC架构面临的挑战
- 随着服务越来越多,服务间的依赖关系变得错综复杂,服务的调用量越来越大,服务的容量问题变的越来越显著
- 将业务服务化之后,随之而来的就是服务治理问题,当应用大规模服务化后会面临许多服务治理方面的挑战,需要解决这些问题,必须通过服务框架+服务治理来完成,单凭RPC架构就比较吃力了
- SOA服务架构
- SOA是一种粗理服,松耦合的以服务为中心的架构,接口间通过定义明确的协议和接口进行通信。它可以更加从容地应对复杂企业系统集成和需求的快速变化
- 面向服务的设计原则
- 服务和复用
- 服务共享一个标准的契约,比如说一个接口
- 服务间是松耦合的
- 服务是底层逻辑的抽象
- 服务是可以组合的
- 服务室自治的,不依赖其他服务
- 服务是可以被自动发现的
- 服务可以是无状态的
- 微服务架构(MSA)
- 微服务的主要特征
- 原子服务:专注做一件事情,与面向对象中的"单一职责原则"类似,实现高内聚,低耦合
- 高密度部署:重要的服务可以独立进程部署,非核心业务可以独立打包,合并设置到统一的进程中去,服务被高密度部署
- 敏捷交付:服务由小研发团队负责设计、开发、测试、部署、线上治理运维整个服务的生命周期
- 微治理:服务足够小,功能单一,可以独立打包、部署、升级。不依赖其他的服务,实现了局部自治
- 微服务架构与SOA对比
- 服务拆分粒度:SOA首要解决的是异构系统的服务化,微服务专注服务的拆分,原子服务
- 服务依赖:SOA主要处理已有系统,重用已有的资产,存在大量的服务间依赖,微服务强调服务自治,原子性,避免依赖耦合的产生
- 服务规模:SOA服务粒度大,大多数将多个服务合并打包,因此服务实例数有限,微服务强调自治,服务独立部署,导致规模膨胀,对服务治理有挑战
- 架构差异:无法业务员通常是去中心化的,SOA通常是基于ESB(实总线的)
- 服务治理:微服务的动态治理,实施管控,而SOA通常是静态配制治理
- 交付:微服务的小团队作战
分布式服务框架
- 分布式服务框架诞生
- 应用从集中式走向分布式
- 随着业务的发展,应用的功能越来越多,打包、部署和新特性上线周期也变得越来越困难,大流量、高并发的用户访问,,传统的架构模式开发,整个流程变长,效率变低,后台服务的压力变大,只能通过硬件扩容来暂时缓解压力,但是解决不了根本性问题
- 应用规模变大,开发维护成本变高,部署效率降低
- 代码复用:原来是本地API调用,导致一些公用功能按需开发,不统一
- 交付面临困难:业务变得复杂,新登修改测试变得困难,拉长整个流程
- 解决方案
- 拆分:大系统拆小系统,独立扩展和伸缩,具体分为横向拆分和纵向拆分
- 纵向拆分:分业务模块
- 横向拆分:提炼核心能力,公共业务
- 迫切需要服务治理
- 大拆小,核心服务提炼后,服务的数量变多,而且需要一些运行态的管控,这时候就需要服务治理
- 服务生命周期管理
- 服务容量规划
- 运行期治理
- 服务安全
- 业界分布式服务框架
- 阿里Dubbo
- Dubbo是阿里巴巴内部的SOA服务化治理方案的核心框架
- Dubbo架构特点:
- 连通性
- 健壮性
- 伸缩性
- 升级性
- 淘宝HSF
- OSGi容器
- 服务治理
- 应用层
- 多协议接入层
- 核心服务层
- 容器接入
- 亚马逊Coral Service
- 架构原理
- RPC层:底层的通讯框架,通讯协议,序列化和反序列化
- 服务发布订阅
- 服务治理
- 功能特性
- 服务订阅发布
- 配制话发布和引用服务 -- 支持通过XML配置的方式发布和导入服务,降低对业务代码的入侵
- 服务自动发现机制 -- 支持服务实时自动发现,由注册中心推送服务地址,消费者不需要配制服务提供者地址,服务地址透明化
- 服务在线注册和去注册 -- 支持运行状态注册新服务,也支持运行态取消某个服务的注册
- 服务路由
- 默认提供随机路由、轮询、基于权重的路由策略等 -- 默认提供常用的路由策略,避免每个框架使用者都重复开发
- 粘滞连接 -- 总是向同一个提供方发起请求,除非此提供方挂掉,再切换到下一台
- 路由定制 -- 支持用户自定义路由策略,扩展平台的功能
- 集群容错
- Failover -- 失败自动切换,当出现失败,重试其他服务器,通常用于读操作,也可用于幂等性写操作
- Failback -- 失败自动回复,后台记录失败请求,定时重发,通常用于消息通知操作
- Failfast -- 快速失败,直发器一次调用,失败立即报错,通常用于非幂等性的写操作
- 服务调用
- 同步调用 -- 消费者在发起服务调用之后,同步阻塞等待服务端响应
- 异步调用 -- 消费者发起服务调用之后,不阻塞立刻返回,由服务端返回应答后异步通知消费者
- 并行调用 -- 消费者同时对多个服务提供者批量发起服务调用请求,批量发起请求后,集中等待应答
- 多协议
- 私有协议 -- 支持二进制等私有协议,支持私有协议定制和扩展
- 公有协议 -- 提供Web Service等公有协议,用于外部服务对接
- 序列化方式
- 二进制类序列化 -- 支持Thrift、Protocol Buffeer等二进制协议,提升序列化性能
- 文本类序列化 -- 支持JSON,XML等文本类型的序列化方式,提升通用性和可读性
- 统一配置
- 本地静态配置 -- 安装部署修改一次,运行态不修改的配制,可以存放到本地配制文件中
- 基于配制中心的动态配制 -- 运行态需要调整的参数,统一放到配制中心(服务注册中心),修改之后统一下发,实时生效
- 性能特性
- 高性能 -- 在同等资源占用下,但服务提供者的TPS要尽量高
- 低时延 -- 在同等资源占用的情况下,服务调用时延要尽量低
- 性能线性增长 -- 扩展服务提供者,性能要能够线性增长
- 可靠性
- 服务注册中心
- 服务健康状态监测 -- 注册中心通过心跳检测服务提供者的存在,服务提供者宕机,服务注册中心将立刻推送事件通知消费者
- 故障切换 -- 注册中心对等集群,任意一台宕掉后,将自动切换到另一台
- 高HA -- 注册中心全部宕掉后,服务提供者和服务消费者荣然能够通过本地缓存通信
- 清除单点故障
- 服务无状态 -- 服务提供者无状态,任意一台宕掉后,不影响使用
- 服务集群容错 -- 只要及群众有一台机器的服务提供者可用,业务就不会中断
- 链路健壮性
- 心跳检测 -- 链路空闲时没有业务消息,通过定时心跳检测链路是否可用
- 断连重连机制 -- 链路断连之后,根据客户端配制上的重连策略定时重连,重连成功之前消息不会路由到断连的服务提供者上
- 服务治理
- 服务运行态管控
- 服务路由 -- 业务高峰期,通过动态修改路由策略实现导流
- 服务限流 -- 资源成为瓶颈时,服务端和消费者的动态流控
- 服务迁入迁出 -- 实现资源的动态分配
- 服务降级 -- 服务提供者故障时或者业务高峰期,进行服务强制或者容错降级,执行本地降级逻辑,保证系统平稳运行
- 服务超时控制 -- 动态调整超时时间,在业务高峰期保障业务调用成功率
- 服务监控
- 性能统计 -- 统计项包括服务调用时延、成功率、调用次数等
- 统计报表 -- 提供多维度,实时和历史数据报表,阿比,环比等性能对比数据,供运维、运营等使用
- 告警 -- 指标异常,根据告警策略发出告警,包括但不限于短信,E-mail,记录日志等
- 服务生命周期管理
- 上线审批 -- 服务提供者不能随意线上发布服务,需要通过正规的审批流程批准之后才能上线
- 下线通知 -- 服务提供者在下线某个服务之前一段时间,需要根据SLA策略,通知消费者
- 服务灰度发布 -- 灰度环境划分原则、接口前向兼容性策略,以及消费者如何路由,都需要灰度发布引擎负责管理
- 故障快速定界定位
- 分布式日志采集 -- 支持在大规模分布式环境中实时采集容器、中间件和应用的各种日志
- 海量日志在线检索 -- 支持分布式环境海量日志的在线检索,支持多维度索引和模糊查询
- 调用链可视化展示 -- 通过分布式消息跟踪系统输出调用链,可视化、快速地进行故障定界
- 运行日志故障定位 -- 通过调用链的故障关键字,在日志检索界面快速检索故障日志,用于故障的精确定位
- 服务安全
- 敏感服务的授权策略 -- 敏感服务如何授权,防止恶意调用
- 链路的安全防护 -- 消费者和服务提供者之间的长连接,需要增加安全防护,例如基于Token的安全认证机制
通信框架
- 关键技术点分析
- 长连接还是短连接
- 绝大多数的分布式服务框架都推荐使用长连接
- 相对比与短连接,长连接更节省资源。
- 如果每发送一条消息就要创建链路、发起握手认证、关闭链路释放资源,会损耗大量的系统资源。长连接只在首次创建时或者链路断连重连才创建链路,链路创建成功之后服务提供者和消费者会通过业务消息和心跳维系链路,实现多消息复用同一个链路节省资源
- 远程通信是常态,调用时延是关键指标
- 服务化之后,本地API调用变成了远程服务调用,大量本地方法烟花成了跨进程通信,网络时间成为关键指标之一。相对于一次简单的服务调用,链路的重建通常耗时更多,这就会导致链路层的时延消耗远远大于服务调用本身的损耗,这对于大型的业务系统而言是无法接受的
- 常见的I/O模型
- 阻塞I/O模型(BIO)
- 最广泛的模型是阻塞I/O模型,默认情况下,所有套接口都是阻塞的。进程调用recvfrom系统调用,整个过程是阻塞的,知道数据复制到进程缓冲区时才返回(系统调用被中断也会返回)
- 非阻塞I/O模型(NIO)
- 当我们把一个套接口设置为非阻塞时,就是在告诉内核,当请求的I/O操作无法完成时,不要将进程睡眠,而是返回一个错误。当数据没有准备好时,内核立即返回EWOULDBLOCK错误,第四次调用系统调用·时,数据已经存在,这时将数据复制到进程缓冲区。这其中有一个操作时轮询(polling))
- I/O复用模型
- 此模型用到select和poll函数,这两个函数也会使进程阻塞,select先阻塞,有活动套接字才返回,但是和阻塞I/O不同的是,这两个函数可以同时阻塞多个I/O操作,而且可以同时对多个读操作,多个写操作的I/O函数进行检测,知道有数据可读或可写。select被调用后,进程会被阻塞,内核监视所有select负责的socket,当有任何一个socket的数据准备好了,select就会返回套接字可读,我们就可以调用recvfrom处理数据
- 正是因为阻塞I/O只能阻塞一个I/O操作,而I/O复用模型能够阻塞多个I/O操作,所以才叫做多路复用
- 异步I/O模型(AIO)
- 进程发起read操作之后,立即就可以开始去做其他的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先他会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户闪存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉他read操作完成了
- 这个模型工作机制是:告诉内核启动某个操作,并让内核在整个操作(包括第二阶段,将数据从内核拷贝到进程缓冲区中)完成后通知我们。这种模型和前一种模型的区别在于:信号驱动I/O是由内核通知我们何时可以启动一个I/O操作,而异步I/O模型是由内核通知我们I/O操作何时完成
- 可靠性设计
- 链路有效性检测
- 通过心跳来确认双方c、s存活,保证链路可用,心跳检测机制分为3个层面
- TCP层面,即TCP的keep-alive,作用于整个TCP协议栈
- 协议层的心跳检测,主要存在于长连接协议中,例如smpp
- 应用层的心跳,业务双方的定时发送心跳消息
- 断连重连机制
- 不管因为网络挂了还是服务端宕机,还是心跳超时,导致链路不可用关闭,这个时候就需要链路重连,需要注意的是短连后,不要立即重连,留时间给系统释放资源,可以scheduler处理
- 消息缓存重发
- 底层消息不会立即发送(也会导致半包粘包),断链后,导致消息丢失,看有无业务需求,有业务需求支持断链后消息重发
- 资源释放
- 断链后一定要保证资源销毁和释放,也包括线程池,内存的释放等
- 性能设计
- 性能差的三原因
- 对于底层通信框架来说
- 网络传输方式问题
- 序列化性能差
- 线程模型,主要是服务端选择什么样的线程模型来处理消息
- 通信性能三原则
- 传输:BIO/NIO/AIO
- 选择自定义协议栈,便于优化
- 服务端线程模型,单线程处理还是线程池,线程池是一个,还是分优先级
序列化与反序列化
- 关键概念
- 我们将序列化称为编码(encode),它将对象序列化为字节数组,用于网络传输、数据持久化或者其他用途
- 反序列化/解码(decode)把从网络、磁盘等读取的字节数组还原成院士对象(通常是原始对象的副本),以方便后续的业务逻辑操作
- 进行远程跨进程服务调用时,需要使用特定的序列化技术,对需要进行网络传输的对象进行编码或者解码,以便完成远程调用
- 序列化与通信协议的关系
- 序列化与通信协议是解耦的,同一种通讯协议可能由多种序列化方式承载,同一种序列化方式也可以用在不同的协议里
- 在设计分布式服务框架时,序列化与反序列化是一个独立的接口和插件,他可以被多种协议重用,替换和扩展,以实现服务框架序列化方式的多样化,满足不同业务领域的用户需求
- 是否需要支持多种序列化方式
- 整体而言,序列化可以分为文本类和二进制类两种,不同的业务场景需求也不同,分布式服务框架面向的领域是多样化的,因此他的序列化/反序列化需要具备以下特征
- 默认支持多种常用的序列化/反序列化方式,文本类型的如XML/JSON等,二进制的如PB(protocol buffers)/thrift等
- 序列化框架可扩展,用户可以非常灵活】方便地扩展其他序列化方式
- 功能设计
- 序列化框架本身功能的丰富,支持的数据类型
- 多语言的支持
- 兼容性,设计的三个层面
- 服务接口的前后兼容
- 协议的兼容
- 支持的数据类型的兼容
- 性能,目的是最少的资源,最快的速度,最大的压缩主要的三个指标
- 序列化后码流大小
- 序列化的速度
- 序列化的资源占用
- 几种流行的序列化协议
- XML
- 定义
- XML(Extensible Markup Language)是一种常用的序列化和反序列化协议
- 优点
- 人机可读性好
- 可指定元素或特定的名称
- 缺点
- 序列化数据只包含数据本身以及类的结构,不包括类型标识和程序集信息
- 类必须有一个将由XmlSeriAlizer序列化的默认构造函数
- 只能序列化公共属性和字段
- 不能序列化方法
- 文件庞大,文件格式复杂,传输占带宽
- 使用场景
- 当做配制文件存储数据
- 实时数据转换
- JSON
- 定义
- JSON(JavaScript Object Notation,JS对象标记)是一种轻量级的数据交换格式。它基于ECMAScript(w3c制定的js规范)的一个子集,JSON采用与编程语言无关的文本格式,但是也使用了类C语言(包括C,C++,C#,Java,JavaScript,Perl,Python等)的习惯,简介和清晰的结构层次使得JSON成为理想的数据交换语言
- 优点
- 前后兼容性高
- 数据格式比较简单,易于读写
- 序列化后数据较小,可扩展性好,兼容性好
- 与XML相比,其协议比较简单,解析速度快
- 缺点
- 数据的描述比XML差
- 不适合性能要求为ms级别的情况
- 额外空间开销比较大
- 适用场景
- 跨防火墙访问
- 可调式性要求高的情况
- 基于Web browser的Ajax请求
- 传输数据量相对小,实时性要求相对低(秒级别)的服务
- Fastison
- 定义
- Fastjson是一个Java语言编写的高性能功能完善的JSON库。它采用一种"假定有序快速匹配"的算法,把JSON Parse的性能提升到极致
- 优点
- 接口简单易用
- 目前Java语言中最快的JSON库
- 缺点
- 过于注重快,而偏离了"标准"及功能性
- 代码质量不高,文档不全
- 适用场景
- 协议交互
- Web输出
- Android客户端
- Thrift
- 定义
- Thrift并不仅仅是序列化协议,而是一个RPC框架。它可以让你选择客户端与服务端之间传输通信协议的类别,即文本(text)和二进制(binary)传输协议,为节约带宽,提供传输效率,一般情况下使用二进制类型的传输协议
- 优点
- 序列化后的体积小,速度快
- 支持多种语言和丰富的数据类型
- 对于数据字段的增删具有较强的兼容性
- 支持二进制压缩编码
- 缺点
- 使用者较少
- 跨防火墙访问时,不安全
- 不具有可读性,调试代码时相对困难
- 不能与其他传输层协议共同使用(例如HTTP)
- 无法支持向持久层直接读取数据,即不适合做数据持久化序列化协议
- 适用场景
- 分布式系统的RPC解决方案
- Avro
- 定义
- Avro属于Apache Hadoop的一个子项目Avro提供两种序列化格式
- JSON格式或者Binary格式
- Binary格式在空间开销和解析性能方面可以和Protobuf媲美,Avro的产生解决了JSON的冗余和没有IDL的问题
- 优点
- 支持丰富的数据类型
- 简单的动态语言结合能力
- 具有自我描述属性
- 提高了数据解析速度
- 快速可压缩的二进制数据形式
- 可以实现远程过程调用RPC
- 支持跨编程语言实现
- 缺点
- 对于习惯静态类型语言的用户不直观
- 适用场景
- 在Hadoop中做Hive、Pig和MapReduce的持久化数据格式
- PB(protocol buffers)
- 定义
- protocol buffers由谷歌开源而来,它将数据结构以.proto文件进行描述,通过代码生成工具可以生成对应数据结构的POJO对象和Protobuf相关的方法和属性
- 优点
- 序列化后码流小,性能高
- 结构化数据存储格式
- 通过标识字段的顺序,可以实现协议的前向兼容
- 结构化的文档共容易管理和维护
- 缺点
- 需要依赖于工具生成代码
- 支持的语言相对较少,官方只支持Java、C++、Python
- 适用场景
- 对性能要求高的RPC调用
- 具有良好的跨防火墙的访问属性
- 适合应用层对象的持久化
- 其他
- Protostuff基于protobuf协议,但不需要配制proto文件,直接导包即可使用
- Jboss marshaling 可以直接序列化Java类,无序实现java.io.Serializable接口
- Message pack一个高效的二进制序列化格式
- Hessian 采用二进制协议的轻量级remoting onhttp工具
- kryo基于protobuf协议,只支持Java语言,需要注册(Registration),然后序列化(Output),反序列化(Input)
协议栈
- OSI与TCP/IP
- OSI
- OSI参考模型
- 应用层
- 表示层
- 会话层
- 传输层
- 网络层
- 数据链路层
- 物理层
- TCP/IP
- TCP/IP五层模型
- 应用层
- 传输层
- 网络层
- 数据链路层
- 物理层
- 基于TCP/IP的http请求
- 发送请求
- 传输层 -- TCP头|HTTP请求报文,表示当前的协议头,使用TCP协议传输
- 网络层 -- IP头|TCP头|HTTP请求报文,增加IP头,IP地址示是一个网卡在网络中的通讯地址
- 数据链路层 -- MAC头|IP头|TCP头|HTTP请求报文,增加MAC头,表示数据报要 送的网卡地址
- 物理层 -- 转化为比特流进行传输
- 接收请求
- 传输层 -- TCP头|HTTP请求报文,TCP头中会携带端口,将报文交给指定的端口进程去处理
- 网络层 -- IP头|TCP头|HTTP请求报文,拿到IP头,判断IP地址是不是自己的,如果不是就转发,继续交给上一层处理
- 数据链路层 -- MAC头|IP头|TCP头|HTTP请求报文,拿到数据后,从数据中摘除第二层的头,检查MAC地址和当前网卡的MAC地址是否匹配
- 物理层 -- 当数据通过网卡的时候判断是否满足条件
- TCP如何保证可靠传输
- 三次握手
- 第一次握手,客户端发送报文到服务端,等到服务端确认
- 第二次握手,服务端接收到请求报文,如果同意客户端进行连接,向客户端发送回执确认报文
- 第三次握手,客户端收到服务端发回的回执确认报文之后,向服务器给出确认报文,客户端和服务端进入连接状态
- 四次挥手
- 四次挥手,别名连接终止协议
- 第一次挥手,TCP客户端发送一个FIN,用来关闭客户端到服务端的数据传送
- 第二次挥手,服务端收到这个FIN,它发回一个ACK,确认序号为收到的序号+1
- 第三次挥手,服务端关闭客户端的连接,发送一个FIN给客户端
- 第四次挥手,客户端发回ACK报文确认,并将确认序号设置为收到序号+1
- 为什么握手三次挥手四次
- 三次握手是因为当Server端收到Client端的SYN连接请求报文后,可以直接发送 SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET(因为可能还有消息没处理完),所以只能先回复一个ACK报文,告诉Client端,已经收到报文。只有等到我Serve 端所有的报文都发送完了,才能发送FIN报文,因此不能一起发送,故需要四步握手。
- 关键技术点分析
- 大部分服务框架都支持多协议,但是多协议却不是必须的
- 是否必须支持多协议
- 、
- 答案是否定的,尽管从功能上来说,分布式服务框架默认支持的协议种类越来越多,功能越强大。但是,分布式服务架构与ESB最大的差异就是他不负责异构系统的对接,这意味着所有使用分布式服务框架服务化的业务都需要遵守服务化框架的规范。
- 公有协议还是私有协议
- 大部分分布式服务框架/RPC框架内部都采用私有的协议进行内部通信,但是,SOA理论告诉我们应该使用标准的Web Service提供服务
- SOA服务化的目标就是重用IT系统已有资产实现异构系统之间的灵活互通。重用已有的异构系统,实现不同系统之间的调用,最佳的做法就是使用标准的公有协议
- 分布式服务框架主要是为了解决内部服务化之后业务接口跨进程通信问题,吞吐量、时延等性能指标是关键。在性能方面,私有协议往往会根据业务的具体需求进行针对性的优化,因此性能更优异
- 以Web Service公有协议为例
- SOAP消息使用XML进行序列化,相比于PB等二进制序列化框架,性能低一大截
- SOAP通常用HTTP1.0/1.1不支持双向全双工通信,而且一般使用短连接通信,性能比较差
- 如果没有特殊需求,分布式服务框架默认使用性能更高、扩展性更好的私有协议
- 功能设计
- 功能描述
- 公有协议栈往往采用集成开源的方案,集成起来比较简单
- 私有协议承载了业务内部各模块之间的消息交互和服务调用
- 定义了私有协议的信息模式和消息定义
- 支持服务提供者和消费者之间采用点对点长连接通信
- 基于Java NIO通信框架,提供高性能的异步通信能力
- 提供可扩展的编解码框架,支持多种序列化格式
- 握手和安全认证机制
- 链路的高可用性
- 通讯模型
- 服务消费者和服务提供者之间的通信
- 服务消费者发送握手请求
- 服务提供者返回握手应答
- 服务消费者发送业务消息
- 服务提供者发送心跳消息
- 服务消费者返回心跳消息
- 服务提供者发送业务消息
- 关闭连接
- 可靠性设计
- 客户端连接超时
- 在传统的同步阻塞编程模式下,客户端socket发起网络连接,往往需要制定连接超时时间
- 在同步阻塞I/O模式中,连接操作时同步阻塞的,如果不设置超时时间,客户端I/O线程可能会被长时间阻塞,这会导致系统可用I/O线程数减少
- 业务层需要:大多数系统都会对业务流程执行时间限制
- 客户端重连机制
- 客户端通过监听器监听链路状态,如果链路中断,等待INTERVALUE时间后,由客户端发起重连操作,如果重连失败,间隔INTERVALUE后再次发起重连,一直到重连成功
- 为了保证服务端能够有充分的时间释放句柄资源,在首次断连时客户端需要等待INTERVALUE时间后再次发起重连,而不是失败后就立即重连
- 为了保证句柄资源能够及时释放,无论什么场景下的重连失败,客户端都必须保证自身的资源被及时释放,包括但不限于SocketChannel、Socket等
- 重连失败后,需要打印异常堆栈信息,方便后续的问题定位。为了防止服务端正常下线,客户端通常会设置重连次数限制,防止客户端无限重连,没有任何作为的损耗资源
- 客户端重复握手保护
- 当客户端握手成功后,在链路外处于正常状态下,不允许客户端重复握手,以防止客户端在异常状态下反复重连导致句柄资源耗尽
- 服务端接收到客户端的请求消息之后,首先对IP地址进行合法性校验,如果校验成功,在缓存地址中查看客户端是否已经登录,如果已经登录,则拒绝重复登录,返回错误代码-1,同时关闭TCP链路,并在服务端的日志中打印握手失败的原因
- 客户端接收到握手失败的应答消息之后,关闭客户端的TCP连接,等待INTERVALUE时间之后,再次发起TCP连接,直到认证成功
- 为了防止由服务服务端到客户端对链路状态理解不一致导致客户端握手失败的问题,当服务端连续N此心跳超时之后需要主动关闭链路,清空该客户端的地址缓存信息,以保证后续该客户端可以重连成功,防止被重复登录保护机制拒绝掉
- 消息缓存重发
- 无论客户端还是服务端,当发生链路中断之后,在链路恢复之前,缓存在消息队列中待发送的消息不能丢失,等链路恢复之后,重新发送这些消息,保证链路中断期间消息不能丢失
- 考虑到内存溢出的风险,消息队列会设置上限,当达到上限之后,会拒绝继续向该队列添加新的消息
- 心跳机制
- 在凌晨等业务低谷期时段,如果出现网络闪断、连接被夯住等网络问题时,由于没有业务消息,应用进程很难发现。到了业务高峰期的时候会出现大量网络通信失败,严重的会导致一段时间进程内无法处理业务消息。
- 为了解决这个问题,在网络空闲时会采用主动式心跳机制来检测链路的互通性,一旦发现网络故障,立即关闭链路,主动重连
- 安全性设计
- 为了保证集群环境的安全,内部长连接采用基于IP地址的安全认证机制,服务端对握手请求消息的IP地址进行合法性校验 -- 如果在白名单之内则校验通过,否则失败
- 如果需要将服务开放给第三方非信任域的消费者,需要采用更加严格的安全认证机制,比如基于密钥和AES加密的用户名+密码认证机制,也可以采用SSL/TSL安全传输
- 对于第三方开发的分布式服务框架的服务调用存在三种场景
- 在企业内网,开放给内部其他模块调用的服务,通常不需要进行安全认证和SSL/TSL传输
- 在企业内网,被外部其他模块调用的服务,往往需要利用IP黑名单,我手登录等方式进行安全认证,认证通过后双方使用普通的Socket进行通信,如果失败,拒绝客户端访问
- 开放给企业外部第三方应用访问的服务,往往需要监听公网IP(通常是防火墙的IP地址),由于对第三方服务调用者的监管存在困难,所以这些第三方应用实际是非授信的。为了有效应对安全风险,对于敏感的服务往往需要通过SSL/TSL进行安全传输