应用架构演进
这里的架构演进应该是从服务化的角度来说, 应该说随着业务发展, 应用规模扩大, 系统的一些公共服务就会抽取出来, 独立开发, 部署, 维护, 用来解决并发, 扩展, 维护的问题.
传统垂直架构
有的地方也叫单体应用, 以 mvc 模式开发:
所有应用代码统一打包, 代码所有接口本地 API 调用, 很少存在远程服务调用;
单机或主备, 应用做集群部署;
DB 主从等.
这种并没有什么不好, 发展初期大多是这样, 体量没那么大, 也不需要考虑高并发大流量可扩展性什么的, 简单粗暴, 解决业务需求就好, 活下去才能活的更好.
但是必须明白这种简单架构存在的一些问题:
1. 业务不断发展, 功能逐渐增多, 应用的开发维护成本变高, 部署效率降低, 随便改个代码, 编译一次十几分钟就浪费了. 悲剧, 我们有个系统才开发 3 年, 就碰到这种情况, 一次打包编译部署, 13 分钟结束.
2. 不同的人负责不同的部分, 一些通用代码, 公共代码就各写各的, 不能复用, 如果只是 util 还好, 但是一些外部服务的都有重复, 那就 happy 了(不过这种情况的出现, 不一定是架构问题, 更多可能是管理);
3. 不断地上新需求, 不断地改代码, 有时测试不到位, 指定哪里埋了 bug, 上生产后系统就 down 了, 牵一发而动全身;
4. 可维护性, 可靠性, 扩展性变差.
既然有这些问题, 就要解决啊, 业务就会提要求, 你要解决啊, 要不然影响我业务发展, 影响我 ipo 上市啊, 苦逼的码农开始干活了.
不提应用的拆分主从那些手段, 但从拆分后应用交互看, 原来的本地 API 交互变成的远程 API 的调用, 这里就出现了 rpc, 当然也有走 esb,webservice. 其实拆分后挺麻烦的, 光一个分布式事务就能折腾死人.
RPC 架构
Remote Procedure Call, 远程方法调用, 屏蔽底层实现细节, 像调用本地方法一样调用远程服务.
上个作者的图:
这个图对于大多数 rpc 框架通用, 实现的几个技术点:
1. 服务提供者发布服务: 服务接口定义, 数据结构, 服务提供者信息等;
2. 客户端远程调用: 通常是使用 jdk 的代码代理拦截;
3. 底层通信: 现在应该更多是使用 netty 吧, 当然也有走支持 http 的;
4. 序列化: 关注序列化反序列性能, xml,JSON,hessiaon,pb,protostuff,kryo 等;
作者给了个 socket 实现简单 demo, 来实现远程调用, 说明上面几个技术点.
常用的 rpc 框架
1. Thrift;
2. Hadoop 的 Avro-RPC;
- Hessian;
- gRPC;
单论 rpc 的话, 没太多可说的, 可是如果加上服务治理, 那复杂度就几何倍数增长了. 服务治理里面东西太多了, 动态注册, 动态发现, 服务管控, 调用链分析等等问题这些问题, 单凭 rpc 框架解决不了, 所以现在常用的说的服务化框架, 通常指的是 rpc + 服务治理 2 个点.
SOA 服务化架构
感觉 soa 架构应该是在 rpc 之前出现, 用来解决异构系统的交互, 通常的实现是通过 ESB,WSDL 来处理. 其粒度通常来说是比较粗的. 也存在服务治理方面的问题.
微服务
MSA 也是一种服务化架构风格, 正流行 ing, 服务划分
1. 原子服务, 粒度细;
2. 独立部署, 主要是容器;
分享篇文章: 云栖肥侠的文章 微服务 (Microservice) 那点事 .
MSA 与 SOA 的对比:
服务拆分粒度: soa 首要解决的是异构系统的服务化, 微服务专注服务的拆分, 原子服务;
服务依赖: soa 主要处理已有系统, 重用已有的资产, 存在大量服务间依赖, 微服务强调服务自治, 原子性, 避免依赖耦合的产生;
服务规模: soa 服务粒度大, 大多数将多个服务合并打包, 因此服务实例数有限, 微服务强调自治, 服务独立部署, 导致规模膨胀, 对服务治理有挑战;
架构差异: 微服务通常是去中心化的, soa 通常是基于 ESB 的;
服务治理: 微服务的动态治理, 实时管控, 而 soa 通常是静态配置治理;
交付: 微服务的小团队作战.
感觉在有了 docker 后, 微服务这个概念突然火了起来, 总结就是微服务 + 容器 + DevOps.
分布式服务框架入门
背景
应用从集中式走向分布式
随着业务的发展导致功能的增多, 传统的架构模式开发, 测试, 部署整个流程变长, 效率变低, 后台服务的压力变大, 只能通过硬件扩容来暂时缓解压力, 但解决不了根本性问题:
应用规模变大, 开发维护成本变高, 部署效率降低;
代码复用: 原来是本地 API 调用, 导致一些公用功能可能是按需开发, 不统一, 随意等问题;
交付面临困难: 主要是业务变得复杂, 新增修改测试变得困难, 拉长整个流程.
通用法宝: 拆分, 大系统拆小系统, 独立扩展和伸缩.
纵向: 分业务模块;
横向: 提炼核心功能, 公共业务;
需要服务治理
大拆小, 核心服务提炼后, 服务的数量变多, 而且需要一些运行态的管控, 这时候就需要服务治理:
服务生命周期管理;
服务容量规划;
运行期治理;
服务安全.
服务框架介绍
Dubbo
阿里开源的 Dubbo 应该是业界分布式服务框架最出名的了吧, 看过公司的 rpc 框架, Dubbo 的扩展性比我们的好的多了, 我们的框架每次升级, 改动都很多, 改天要看下 Dubbo 的源码了解了解扩展性.
HSF
淘宝的体量决定了他对极致性能的追求, HSF 跨机房特性挺牛.
Coral Service
这个没听说过, 孤陋寡闻了.
框架设计
架构原理
万变不离其中, 这张图可以概括 rpc 的一些通用原理:
细化了下:
rpc 层: 底层的通讯框架, 通讯协议, 序列化和反序列化;
服务发布订阅;
服务治理;
功能
性能
可靠性
分布式的, 面试会问, 用池子的话讲就是, 知识点啊.
服务治理
通讯框架
技术点
长连接: 主要是链路的创建过程到最后的关闭, 耗时耗资源; 每次调用都要创建的话, 调用时延的问题, 很可能链路创建的耗时比代码真正执行时长还多;
BIO 还是 NIO: 主要是线程模型的选择, 推荐篇文章 IO - 同步, 异步, 阻塞, 非阻塞 (亡羊补牢篇);
自研还是使用开源 NIO 框架: 一般来说还是使用开源吧, 技术成熟, 社区支持, 现在 netty 和 mina 使用较多了吧.
在功能设计方面, 作者基于 netty 给了 demo 服务端和客户端的代码, 个人理解:
1. 通用性 API;
2. 扩展性, 封装底层, 提供上层接口, 隔离协议和底层通讯;
可靠性设计
谈分布式系统必谈可靠性.
链路有效性
通过心跳来确认双方 c,s 存活, 保证链路可用, 心跳检测机制分为 3 个层面:
1. tcp 层面, 即 tcp 的 keep-alive, 作用于整个 tcp 协议栈;
2. 协议层的心跳检测, 主要存在于长连接协议中, 例如 smpp 协议;
3. 应用层的心跳, 业务双方的定时发送心跳消息;
第 2 个没听说过, 常用的是 1,3. 一般使用 netty 的话用的是 netty 的读写空闲来实现心跳.
断连
不管因为网络挂了还是服务端宕机, 还是心跳超时什么的, 导致链路不可用关闭, 这时候就需要链路重连, 需要注意的一点就是短连后, 不要立即重连, 留时间给系统释放资源, 可以 scheduler 处理.
消息缓存重发
底层消息不会立即发送(也会导致半包粘包), 断链后, 导致消息丢失, 看有无业务需求, 有就支持断链后消息重发.
资源释放
主要是断链后, 一定要保证资源销毁和释放, 当然也包括一些线程池, 内存等的释放.
性能设计
性能差的三宗罪
对于底层通讯框架来说, 主要是下面几个:
1. 通讯模型的选择, 主要是阻塞非阻塞那些东西;
2. 序列化反序列化(后面有章单讲序列化);
3. 线程模型, 主要是服务端选择什么样的线程模型来处理消息.
通信性能三原则
既然有上面的 3 个问题, 那就针对这些做优化了:
传输: BIONIOAIO 的选择;
选择自定义协议栈, 便于优化;
服务端线程模型, 单线程处理还是线程池, 线程池是一个, 还是分优先级, Reactor 还是其他什么的.
高性能之道这节作者讲了 netty 的优势.
序列化与反序列化
也就是通常所说的编码, 解码. 通常的通讯框架会提供编解码的接口, 也会内置一些常用的序列化反序列化工具支持.
与通讯框架和协议的关系, 感觉可以理解为: 通讯框架是通道, 其上跑的码流数据是利用各种序列化编码后的各种协议.
功能设计
各种序列化框架需要考虑的主要有:
序列化框架本身的功能的丰富, 支持的数据类型;
多语言的支持;
兼容性, 往大了说:
服务接口的前后兼容;
协议的兼容;
支持的数据类型的兼容.
性能, 目的是最少的资源, 最快的速度, 最大的压缩:
序列化后码流大小;
序列化的速度;
序列化的资源占用.
在扩展性这节, 作者讲了 netty 的对序列化的一些内置支持, 但实际开发中, 一般不太会使用这些东西, 都会提供序列化反序列接口, 自行扩展定义, 所以扩展性特重要.
常用的序列化, xml,JSON,hessian,kryo,pb,ps, 看需求需要支持那种, 具体可以搜索各序列化的性能和压缩后大小.
协议栈
这一章最主要的是讲了自定义协议栈的设计, 已经交互的过程, 其他讲的可靠性设计什么的跟之前通讯框架一章有重复.
通信模型
服务提供者和消费者之间采用单链路, 长连接通信, 链路创建流程:
1. 客户端发送握手请求, 携带节点 ID 等认证信息;
2. 服务端校验: 节点 ID 有效性, 重复登录, ip 地址黑白名单等, 通过后, 返回握手应答信息;
3. 链路建立后, 客户端发送业务消息;
4. 客户端服务端心跳维持链路;
5. 服务端退出时, 关闭连接, 客户端感知连接关闭, 关闭客户端连接.
协议消息定义
通过 attachment 兼容了扩展性. 作者还讲了将消息头的通用序列化和消息体的自定义序列化, 看需求吧, 我们公司的框架没做这部分支持, 做了简化, 将消息头和消息体统一封装, 然后再加一个序列化方式组成一条消息发送.
安全性设计
内部的, 不一定需要认证, 也有基于系统, 域名, ip 的黑白名单, 安全认证的;
外部开发平台的话, 基于秘钥认证;
服务路由
服务路由指的是服务提供者集群部署, 消费端如何从服务列表中选择合适的服务提供者提供服务进行调用.
透明化路由
基于 zk 的服务注册中心的发布订阅;
消费者本地缓存服务提供者列表, 注册中心宕机后, 不影响已有的使用, 只是影响新服务的注册和老服务的下线.
负载均衡
随机
轮循
服务调用时延
一致性 Hash
有个一致性 hash 算法, 挺有意思的, Redis 的客户端 shard 用的
黏滞连接
这个应该不太常用, 服务提供者多数无状态, 一旦有状态, 不利于扩展
这些都是点对点的连接, 负载均衡大多会在客户端执行, 有种场景会取决于服务端负载, 就是服务端服务配置的是域名.
本地路由优先策略
injvm:jvm 也提供了消费端的服务, 可以改成优先本 jvm, 对于消费端来说, 不需关注提供者;
innative:injvm 比较少, 多得是可能是这种, 一个物理机部署多个虚拟机, 或者一个容器部署多个服务提供者, 消费者不需远程调用, 本机, 本地或本机房优先.
路由规则
除了上面提供的各种路由负载均衡, 还容许自定义路由规则:
- 条件路由: 主要是通过条件表达式来实现;
- 脚本路由: 通过脚本解析实现.
其实应该还有一种客户端通过代码自定义路由选择. 这些主要是为了扩展性.
路由策略定制
自定义路由场景:
1. 灰度;
2. 引流;
路由策略:
1. 框架提供接口扩展;
2. 配置平台提供路由脚本配置;
配置化路由
本地配置: 包括服务提供者和消费者, 全局配置 3 种;
注册中心: 路由策略统一注册到服务注册中心, 集中化管理;
动态下发: 配置后动态下发各服务消费端.
集群容错
指的是服务调用失败后, 根据容错策略进行自动容错处理.
集群容错场景
通信链路故障:
通信过程中, 对方宕机导致链路中断;
解码失败等原因 REST 掉链接;
消费者 read-write socketchannel 发生 IOException 导致链路中断;
网络闪断故障;
交换机异常导致链路中断;
长时间 Full GC 导致;
服务端超时:
服务端没有及时从网络读取客户端请求消息, 导致消息阻塞;
服务端业务处理超时;
服务端长时间 Full GC;
服务端调用失败:
服务端解码失败;
服务端流控;
服务端队列积压;
访问权限校验失败;
违反 SLA 策略;
其他系统异常;
业务执行异常不属于服务端异常.
容错策略
这图不错, 关系很清晰.
失败自动切换(Failover):
调用失败后切换链路调用;
服务提供者的防重;
重试次数和超时时间的设置.
失败通知(FailBack): 失败后直接返回, 由消费端自行处理;
失败缓存(Failcache): 主要是失败后, 缓存重试重发, 注意:
缓存时间, 缓存数量;
缓存淘汰算法;
定时重试的周期 T, 重试次数;
快速失败(Failfast): 失败不处理, 记录日志分析, 可用于大促期间, 对非核心业务的容错.
容错策略扩展
容错接口的开放;
屏蔽底层细节, 用户自定义;
支持扩展.
其实还有一点, 感觉也挺重要, 就是支持容错后本地 mcok. 调用失败后的链路切换和快速失败肯定要支持, 缓存重发可以不用.
本篇篇幅过长, 相信能坚持看到最后的都是对知识求知如渴的人, 将来必定不凡. 顺便点点关注点点赞呗.
来源: https://juejin.im/post/5baf4b556fb9a05cf23007cc