微服务架构的优势与不足
-
单一庞大(Monolithic)应用不足
- 逐渐变大, 几年后变成巨大怪物, 各个业务模块犬牙交错,重复代码随处可见,补丁代码越打越多
- 开发团队痛苦, 敏捷开发和部署举步维艰, 任何单一开发都不可能搞懂, 团队士气走下坡
- 降低开发速度, 启动时间加长
- 复杂而巨大的单体式应用也不利于持续性开发, 任何一个改动都需要一次全量发布,哪怕是修改一句文案
- 单体式应用在不同模块发生资源冲突时,扩展将会非常困难 (比如数据都只用一种, 版本也只能一致)
- 可靠性降低, bug(如内存泄露)将相互影响
- 单体式应用使得采用新架构和语言非常困难 (比如一个组件, 在一个小应用升级替换非常容易, 但是在大应用上就很难, 要避免影响所有现有功能变得非常困难)
一开始你有一个很成功的关键业务应用,后来就变成了一个巨大的,无法理解的怪物。因为采用过时的,效率低的技术,使得雇佣有潜力的开发者很困难。应用无法扩展,可靠性很低,最终,敏捷性开发和部署变的无法完成
-
微服务在3d模型中的定位
- Y轴代表将应用分解为微服务 (不同服务拆开)
- X轴代表运行多个隐藏在负载均衡器之后的实例 (负载均衡+集群)
- Z轴将服务分区 (相同服务分区, 拆小)
-
不像传统多个服务共享一个数据库,微服务架构每个服务都有自己的数据库 (好难)
-
微服务的好处
- 单个服务很容易开发、理解和维护
- 每个服务都可以有专门开发团队来开发。开发者可以自由选择开发技术,提供API服务; 重写重构,技术迭代变得不是很困难
- 易于独立部署, AB测试,持续化部署
- 每个服务独立扩展
-
微服务架构的不足
- there are no silver bullets
- 微服务应用是分布式系统,由此会带来固有的复杂性
- 因为数据分区, 也因为CAP理论, 微服务不得不使用最终一致性方案, 因此对开发要求更高
- 单体应用容易测试, 微服务测试复杂度提高, 需要各个微服务的stubs
- 改动的影响将波及很多应用, 不过这是面向服务架构的特点
- 服务拆分后, 服务增多, 部署难道和工作量增加
API Gateway
微服务调用方案一: 客户端到多个微服务直接通信
不足:
- 每个微服务暴露的细粒度API数量的不匹配, 客户端(移动端或者PC浏览器)需要发起多个(甚至数百个)服务请求, 在公网上效率非常低, 客户端代码非常复杂
- 微服务的协议可能并不是web友好型。一个服务可能是用Thrift的RPC协议,而另一个服务可能是用AMQP消息协议。它们都不是浏览或防火墙友好的,并且最好是内部使用。应用应该在防火墙外采用类似HTTP或者WEBSocket协议
- 很难重构微服务: 随着时间推移, 服务可能合并或者拆分, 如果客户端直接与微服务交互,那么这种重构就很难实施.
微服务调用方案二: API Gateway
API Gateway 的职责:
-
封装内部系统架构, 提供API给各个客户端
API Gateway可以提供给客户端一个定制化的API, API Gateway通过调用多个服务来处理这一个请求并返回结果
涉及: 请求转发, 合成和协议转换(在web协议与内部使用的非Web友好型协议间进行转换)
-
授权, 监控, 负载均衡, 缓存, 请求分片, 管理, 静态响应处理
优点:
- 封装应用内部结构, 减少了客户端与服务器端的通信次数,也简化了客户端代码
缺点:
-
必须要高可用, 必须开发,部署和管理 (额外成本)
-
可能成为开发的一个瓶颈
-
开发者必须更新API Gateway来提供新服务提供点来支持新暴露的微服务 (类似第一条)
实现(需要考虑的事情):
-
性能和可扩展性
-
采用反应性编程模型: Gateway 调用后端服务尽量并行, 事件驱动, 比如JavaScript的Promise
利用传统的同步回调方法来实现API合并的代码会使得你进入回调函数的噩梦中
-
API Gateway 需要支持多种通信方式
微服务是分布式系统, 微服务之间的通信方式有两种:
- 异步: 基于消息, 如AMQP(Advanced Message Queuing Protocol)
- 同步: HTTP, Thrift等
-
服务发现:
服务数量众多, 服务的实例也会动态的改变, API Gateway需要采用系统的服务发现机制, 两种方案:
- 服务端发现
- 客户端发现
-
处理部分失败
分布式系统中服务超时或者不可用时, API Gateway不应该被阻断并处于无限期等待下游服务的状态。但是,如何处理这种失败依赖于特定的场景和具体服务
API Gateway来确保系统错误不影响到用户体验
方案: 服务降级, 返回信息降级, 利用缓存旧数据
进程间通信
IPC交互模式:
一对一
-
同步:
- 请求/响应: REST(HTTP), Thrift
-
异步:
- 通知: 使用消息系统, channel只有一个消费者, 以实现点对点的通知
- 请求/异步响应: TODO
一对多
-
异步:
- 发布/订阅: 用消息系统, channel有多个消费者
- 发布/异步响应 TODO 如何实现
微服务架构有两类IPC机制可选,异步消息机制和同步请求/响应机制
API 设计
- 小的api改动, 设计客户端和服务端时候应该遵循健壮性原理, 使得客户端能和新旧版本兼容
-
大规模改动, 不可能强制让所有客户端立即升级, 可以考虑把版本号嵌入URL, 每个服务可以处理多个版本的API, 或者不同服务实例处理不同版本
-
处理失败
分布式系统中部分失败是普遍存在的问题, 某些服务过载或者反应很慢也是要考虑的问题
需要考虑的应对措施:
- 网络超时
- 限制请求次数
- Circuit Breaker Pattern: 记录成功和失败请求的数量。如果失效率超过一个阈值,触发断路器使得后续的请求立刻失败。如果大量的请求失败,就可能是这个服务不可用,再发请求也无意义。在一个失效期后,客户端可以再试,如果成功,关闭此断路器
- 提供回滚: 请求失败后的回滚逻辑, 比如使用备份缓存数据
服务发现
-
Why:
服务实例网络位置动态分配, 服务伸缩, 失效, 升级造成服务实例动态变化
-
客户端发现模式
服务实例启动时注册到服务注册表中, 在服务终止时删除, 通过心跳来定期刷新
优点: 客户端可以进行负载均衡
缺点: 客户端需要为每种语言开发不同的服务发现逻辑
-
服务端发现模式
优点: 客户端无需关注发现的细节, 客户端只需要简单的向负载均衡器发送请求,实际上减少了编程语言框架需要完成的发现逻辑
缺点: 负载均衡器需要高可用
-
服务注册表
服务注册表由若干使用复制协议保持同步的服务器构成
参考资料
2016-12-20 补充
-
考虑把大型项目拆解成微服务吗? 收益/代价/DevOps
我非常确信分解一个大型项目到微服务的过程是值得的。然而,这是一个长期的投资,可能需要一段时间才能收到回报