这是我参与「第三届青训营 -后端场」笔记创作活动的第4篇笔记
系统架构演变历史
单体架构
all in one process
优势:性能最高、冗余小
劣势:debug困难、模块项目影响、模块分工、开发流程
垂直应用架构
按照业务线垂直划分
优势:业务单独开发维护
劣势:不同业务存在冗余、仍然存在单体架构的问题
分布式架构
抽出业务无关的公共模块
优势:业务无关的独立服务,进行公共业务的复用
劣势:业务系统到服务层是一个n对n的调用,用户服务可以影响全栈的业务线、调用关系复杂、不同服务冗余
SOA架构
面向服务:有了服务注册中心
优势:服务注册
劣势:整个系统设计是中心化的、需要从上至下设计、重构困难
微服务架构
彻底的服务化
优势:开发效率、业务独立设计、自下而上、故障隔离(服务粒度拆分很细,造成的影响最低)
劣势:治理、运维难度、观测挑战、安全性、分布式系统
核心要素:
- 服务治理
- 可观测性: 链路追踪
- 安全: 服务调用必须要授权、身份验证
微服务
基本概念
服务:一组具有相同逻辑的运行实体
实例:一个服务中,每个运行实体即为实例
集群:通常指服务内部的逻辑划分,包含多个实例
服务包含集群,集群包含实例
实例与进程的关系:没有比如安关系,一个实例可以对应一个或多个进程
实例承载形式:进程、VM、k8s pod
有状态/无状态服务:服务的实例是否存储了可持久化的数据
服务间通信:
对于单体服务,不同模块通信只是简单的函数调用。
对于微服务,服务间通信意味着网络传输。
服务注册及发现
如何指定调用一个目标服务的地址(ip : port )?
方案一:硬编码
// 硬编码
client := grpc.NewClient("10.23.345.67:8080")
- 地址动态变化,不可能写死
- 一个服务有多个实例,不可能写死
\
方案二:域名 + DNS
- 本地DNS存在缓存,导致延时
- 负载均衡问题,一个域名指定三个ip,但往往只会返回第一个ip
- 不支持服务实例的探活检查
- 域名无法配置端口,仍然要硬编码:8080
\
方案三:新增一个同意的服务注册中心,用于存储「服务名」到「服务实例」的映射
可以自己实现负载均衡算法
服务实例上线及下线过程
直接下线?有流量过来,直接线上产品故障
下线:首先把服务注册中心中对应实例的映射删除,然后下线实例
上线:先把服务的实例提起来,再注册到服务注册中心
流量特征
统一网关入口
内网通信多数采用RPC
网状调用链路
核心服务治理功能
服务发布
服务发布,即指让一个服务「升级」运行新的代码的过程
难点:
- 服务不可用
- 服务抖动
- 服务回滚:不管问题是什么,先回滚,将损失降到最小
方案一:
蓝绿部署:分集群,控制流量
简单,稳定,但需要「两倍资源」
方案二:
灰度发布(金丝雀发布):
服务回滚对基础设施的要求特别高,k8s这方面做的比较好
流量治理
基于地区、集群、实例、请求等维度,对端到端流量的路由路径选择
负载均衡
负责分配请求在每个下游实例上的分布
稳定性治理(企业实践关键)
线上服务一定会出问题,这与程序的正确性无关。
- 网络攻击
- 流量突增
- 机房断电
- 光纤被挖
- 机器故障
- 网络故障
- 机房空调故障
客观的因素没办法避免,需要稳定性治理:
- 限流
- 熔断
- 过载保护
- 降级
\
企业服务治理实践
本地函数调用没有特殊重试的意义。
远程函数调用有重试的必要。
重试可以避免偶发的错误,提高SLA(Service-Level Agreement)
重试的意义:
- 降低错误率
- 降低长尾延时
- 容忍暂时性错误
- 避开下游故障实例
\
重试这么好,为什么默认不用呢?
难点:
- 幂等性:发生一次和发生多次,效果一样
- 重试风暴:长链路下重试次数几何增加(限制重试比例)(防止链路重试,用特殊的status“请求失败,但别重试”)
- 超时设置:超时时间通用设置不了(长尾99时间设置)