系统架构的演变:一个不断增大->解耦的过程
-
单体
-
垂直应用(按照业务线垂直划分,每个业务为一个单体)
- 存在冗余
-
分布式(抽出公共模块,业务直接调用服务 i.e. N对N)
- 开始具备独立于业务的服务雏形。服务模块的bug可导致全站瘫痪,且调用关系复杂,不同服务间可能存在冗余
-
SOA (Service Oriented Architecture)
- 服务注册,业务通过一个中心access服务
- 系统设计中心化,从上至下,重构困难
-
微服务(拆分更细,SOA思想进入到单个业务系统内部,实现真正组件化)
-
从下而上,业务独立设计,因而开发效率高、故障是隔离的、每个服务自己拥有数据库
- 拆分原则:每个服务尽可能小,并能独立完成一个业务的闭环支撑。需求变化时,需要变更的服务组件范围最好是一个服务
-
运维难度陡然提升。通常要配合一些全局的服务配置和治理(管理服务注册&发现、load balance、扩缩容、稳定性等),链路追踪和监控组件。每个服务要分别实现一些与这些基础设施相关的功能
-
微服务架构
基本概念
- service服务:一组具有相同逻辑的运行实例(instance)
- 所有实例运行同一份代码
- 一个实例可以对应一个或多个进程/VM/k8s pod...
- 服务有压力的时候,可以通过增加实例来分散流量
- cluster集群:通常指服务内部的逻辑划分,包含多个实例
即,service包含cluster包含instance
-
有状态:服务的实例是否存储可持久化数据
-
服务间通信:对于微服务架构,服务间通信involves网络传输
服务注册 & 发现
由于服务可以有多个实例,ip:port是变化的,无法以hardcode方式指定地址。
用DNS也不方便:本地DNS的缓存会导致延时、负载均衡问题、不支持对服务实例的探活、DNS无法配置端口(必须靠约定)。
因此,用一个统一的service registry存储service name到instance的映射,查询后通过RPC调用服务。服务注册自己前,应先进行health check。
流量的链路
服务治理模块的核心功能
服务发布(i.e. 更新)
难点:服务不可用、服务抖动、服务回滚
不同的策略:
-
蓝绿部署:分集群进行发布。一个集群升级时,流量全部走另一边
- 简单稳定,但需要两倍资源
- 适合流量低时采用
-
灰度发布(canary):先新增一个实例,如果没问题,下线一个老的实例,如此反复直至全部更新
- 回滚的开销及难度很大
流量控制
微服务架构提供基于地区、集群、实例、请求等维度的路由路径控制,如:
Load balance
这种组件常见的策略包括Round Robin, Random, Ring Hash, Least Request等。
稳定性治理
在企业级应用中尤其重要。
-
限流rate limit:
挂在下游(被调用方)。拒绝超出范围的queries traffic
-
熔断curcuit breaker:
挂在上游(调用方)。调用方发现下游abnormal时,直接拒绝自己的上游流量,偶尔再尝试连接下游
-
过载保护dynamic overload:
挂在下游。当前实例压力较大时,拒绝上游来的一部分或全部流量
-
降级degrade:
挂在下游。当前实例压力较大时,只接收重要的上游请求,拒绝不重要的
字节的服务治理实践 -- 关于重试
本地函数调用失败,通常没什么重试的必要(结果大多是deterministic的)。但RPC语境下,网络抖动、下游或本地的超时而导致的调用失败,都可能需要重试数次。
重试可以降低错误率,提升对暂时、偶发错误的容忍度,绕开下游故障实例,降低长尾延时(i.e. 偶尔耗时较长的请求。对这种请求,重试或可提前返回)。
重试的难点:
- 幂等性
- 重试风暴(随着调用深度增加,重试次数指数级上涨,i.e. 越是下游,要重试的次数就越多)
- 如何确定超时时间
策略:
- 设置一个比例阈值,重试占所有请求比例不能超过阈值
- 防止链路重试(针对重试风暴):只让最下游重试。下游重试后,告知上游我已重试,不要重复重试
- hedged requests:对高延时请求,向另一下游实例发送相同的请求,等待先到达的响应
Ref & 延伸阅读:
developer.aliyun.com/article/839… www.infoq.cn/article/asg… juejin.cn/post/696028…