学习极客时间-左耳听风专栏的个人学习记录。
管理设计
索引:
- 分布式锁
- 配置中心
- 边车模式
- 服务网格
- 网关模式
- 部署升级策略
分布式锁
目的:分布式架构下对共享资源的更新加锁,防止冲突。
实现:一般可用 Redis、Zookeeper、数据库等实现。
特点:
- 安全性(排他性):任一时刻只能一方拿到锁。
- 避免死锁:最终一定能获得锁,持锁方崩溃时支持自动释放锁。
- 容错性:锁服务集群中只要多数节点正常,就可加锁/释放锁。
Redis的锁服务
- 加锁:设置一个全局唯一的value作为锁的标识
SET key value NX PX 30000msNX参数:仅当value不存在时,才能赋值成功。PX参数:指定锁的自动释放时间(即锁过期时间)。
- 释放锁:client只能释放本client持有的某value的锁,不能释放其他client加的锁。
- 实现:
if redis.call("GET", KEYS[1]) == ARGV[1]then 才可以解锁- 其中,
KEYS[1]为锁的名称,ARGV[1]为此client持有的锁名称
- 其中,
- 实现:
关于超时后再次执行的问题
典型场景:
- Client A 持锁要修改某值
X=1。 - Client B 在排队等待修改
X。 - Client A 突然阻塞住了。
- Client A 的锁因超时而自动释放。
- Client B 持锁,修改了值
X=2。 - Client A 恢复后,重新持锁,修改
X=1。 - 结果:Client B 修改的
X=2被覆盖了,数据出现错误。
解决方案:Fence 栅栏技术(一种乐观锁机制)
- 引入版本号的概念:每次更新需携带一个分配到的版本号。
- 由数据库对版本号做检查(版本号必须单调递增),控制是否可执行。
CAS(Compare and Swap)(乐观锁机制)
既然用到数据库来控制版本号,何不把版本号保留到数据库中呢?
即:更新前比对此时的版本号和开始时查到的版本号是否一致,一致才可以更新。
延伸思考
既然讲到这了,请问我们还需要最开始说的分布式锁么?
- 实现上来说,确实可以用CAS之类的方法替换分布式锁
| CAS | 分布式锁 | |
|---|---|---|
| 类型 | 客观控制 | 悲观控制 |
| 性能 | 低冲突时:高;高冲突:低 | 低冲突时:中等;高冲突时:相对稳定 |
| 适用 | 单个记录原子更新 | 任何需互斥的操作 |
- 除了常用的写入涉及锁,其他操作可能也涉及加锁(如同步操作)。这种无法用数据库锁实现。
分布式锁设计重点
- 避免死锁
- 高可用
- 可重入
- 要提供非阻塞方式的锁服务(非阻塞:如果锁不可用(被其他线程持有),它不会停止或等待!它会立即得知失败(通常通过一个返回值,比如
false),然后线程可以自由地去做其他事情)
配置中心
配置包括软件的关键信息,如连接池大小、数据库用户和密码、队列长度等,但用以前写入的配置文件方式容易出错,管理不便,故引入集中式的配置管理系统进行管理。
配置中心的设计
- 区分软件的配置
- 静态配置:启动时的一些配置,运行时基本不会修改,如网络端口、Docker启动参数。
- 动态配置:在运行时可能会被修改,如降级开关、日志级别。
- 运行环境:开发、测试、预发、生产,配置大同小异。
- 作用区分:分为依赖配置(如外部依赖的MySQL资源)和不依赖配置(完全内部使用的配置)。
- 按层次分:可分为 IaaS、PaaS、SaaS 层。基础层是操作系统配置,中间层有中间件的配置,上层是应用层的配置。
- 配置中心的管理
- 一般来说,操作系统、平台层的配置由运维角色或专门角色维护(最好是有限选项的维护)。应用层的配置命名最好规范化。
- 外部服务依赖的配置,建议不要放配置中心,而要放在服务发现系统,因为这部分配置很动态(频繁上下线)。
- 配置要有版本管理,最好与软件的版本做关联。
- 配置中心的架构
- 配置录入 → 配置中心→ (变更通知)配置变更控制器→配置变更
- 主要角色:配置中心、配置变更控制器。
- 交互方式:通过 Pub/Sub 的通知服务方式,由配置变更控制器去主动 Pull 配置中心的数据(使用API)。
- 应用服务配置更新方式:
- 通过开发框架或SDK解决:缺乏依赖解耦。
- 通过运维服务调用应用方提供的脚本动作:易出bug。
- 结合这两种方案:为每个应用服务单独做一个Agent,对外以 Admin API 方式服务(对外暴露一个定义良好的 HTTP (RESTful/gRPC) API 接口**)。
配置中心设计重点
- 简化、统一、标准化配置管理。
- 配置版本最好和软件版本对应上,配置更新是一个事务,有问题可以一起回滚
- 配置变更的生效,需要应用服务的配合。如配置的
reload、服务的优雅重启、服务的 Admin API 等。
边车模式 Sidecar
目的:将控制逻辑和业务逻辑分离开,如将限流、日志记录、熔断、服务注册等控制面上的逻辑都放在边车实现(类似服务的Agent)。
边车模式设计
边车模式基本特点:边车和服务共生(一起启动,一起停止),进出(含进和出)服务的所有通信都会通过Agent完成。
一般实现这类控制逻辑,有以下两种方式: 1. 使用 SDK/Lib/Framework 包方式集成到业务服务中。 2. 使用边车 (Sidecar) 模式。 这两种方式各有优缺点: 3. 软件包方式:与应用代码集成,有利于资源利用;但对应用有侵入性,受编程语言和技术限制。当软件包升级时,应用也需要重新编译部署。 4. 边车模式:对应用无侵入性,也不受技术限制。同时可与应用分开部署。但是,增加应用服务的依赖性,增加了应用的延迟,增加了部署、管理、运维等的复杂性。
边车一般和应用部署在一个节点中(一个应用实例,一般对应有一个边车实例,最好和Docker方式一起使用)。边车可实现:
- 服务注册、健康检查(不健康的话,从服务发现系统中把服务实例去掉)。
- 服务路由。
- 接管进出流量,做日志监控、调用链追踪、流控熔断等
- 控制应用服务,如流控、下线等。
边车设计重点
- 与服务的通信方式最好通过网络远程调用的方式(因为都在
127.0.0.1上通信,故开销并不明显),不要用侵入性的通信方式(如共享内存、信号方式)。 - 服务协议方面,使用标准统一的方式,不使用语言相关的协议。
- 使用 Sidecar 带来的部署、管理、运维等复杂性增长,使用 Docker 可大大降低难度。
- 最好不要把业务相关逻辑放到边车里。
- 小心 Sidecar 中一些通用控制。如重试逻辑不一定适合所有通信(可能不是所有请求都满足幂等性)。
- 应用服务和 Sidecar 的上下文传递机制。如 Sidecar 告知应用限流发生等信息,两者配合得更好。
Sidecar 适用和不适用场景
- 适用场景:
- 老旧系统的改造和扩展(这类对老旧代码本身不好改造)。
- 系统内采用的语言是混合的。
- 应用服务由不同提供商提供。
- 注重应用和控制分离。
- 不适用场景:
- 架构并不复杂时,用 API Gateway/Nginx/HAProxy 等组件实现即可。
- 服务间协议非标准且无法转换。
- 非分布式架构。
服务网格 Service Mesh
服务网格是边车模式 (Sidecar Pattern) 的进化版,是边车模式的集群化表现。不同于边车需要和应用共同部署,服务网格核心组件可完全独立部署,可作为类似 PaaS 的基础设施平台。
服务网格类似网络七层模型中的传输层模型,介于底层通讯和业务应用层之间。
相关开源软件
- Istio
- Linkerd
- Conduit
- 等
服务网格的设计重点
- 需要注意:服务网格核心组件不可用时,对整个架构可能是致命的,所以需小心考虑,必须是高可靠的设计。
- 一种好的方式是,除了有本机的 Sidecar,还设置一个集中式的sidecar。这样当本机 Sidecar 有问题时,可改走集中式sidecar。
网关模式 Gateway
前面提的边车 (Sidecar) 或服务网格 (Service Mesh) 模式,使用的 Sidecar 是为每个服务实例都配置的。而还有一种模式是网关模式 (Gateway),可为一个服务集群(或一组类似的服务)配置一个 Gateway 即可(当然也可针对各服务实例配置单独的,粒度可粗可细)。
一般来说,Gateway 可作为进入所有服务的唯一入口节点。
网关常用功能
- 请求路由:由网关路由到具体的后端地址。
- 服务注册与发现:如基于 HTTP 的 Restful 请求。
- 弹力设计:实现限流、熔断、异步、重试、幂等、监控等。
- 安全:SSL 加密、证书管理、Session 验证、授权等。
- 负载均衡:在多个后端实例间分配流量。
- 灰度发布:对相同服务的不同版本进行请求分流。
- API 聚合:由网关调用多个后端服务,并将结果聚合后返回客户端。
- API 编排:定义更复杂的服务调用流程。
Gateway、Sidecar、Service Mesh 三者对比
- Sidecar:可改造已有服务,适配已有应用
- Service Mesh:统一管理集群内的 Sidecar,业务方只需把应用放入网格即可,不关心控制面逻辑。
- Gateway:统一负责服务集群(或组)的入口流量管理,粒度可粗可细。
网关设计重点
- 高性能:不能成为系统性能瓶颈。
- 高可用:
- 集群化:部署多个实例避免单点故障。
- 服务化:支持在不间断服务下动态修改配置。
- 持续化:优雅重启
- 易扩展:可支持扩展必要的业务逻辑
- 运维方面:
- 与后端业务松耦合,与协议紧耦合。
- 应用监控、性能监控、调用链追踪等集成。
- 内置弹力设计(如限流、熔断)保护后端服务。
- 支持 DevOps,实现自动化运维和管理。
- 架构方面:
- 将后端聚合服务逻辑在网关核心代码化,不侵入业务服务。
- 网关和后端服务网络要靠近(同一个内网),确保低延时,减少网络问题。
- 网关自身也需支持容量扩展,配合负载均衡器。
- 集成服务发现机制。
- 可考虑 Bulkhead 隔离方式,不同网关服务不同后端或前端流量。
- 安全性:
- SSL 证书管理、数据加密传输。
- 异常请求识别、拦截、告警。
- 校验基本协议头信息,如用户名是否已登录、Session 信息等。
部署升级策略
停机部署 (Big Bang/Recreate)
- 简单粗暴:先停机再部署再开机。
- 缺点:会停止对外服务,需尽量避免。
- 适用场景:新旧版本完全不兼容(如数据库表设计新老不兼容),只能停机部署。
蓝绿部署 (Blue/Green, Staged)
- 需要两套相同的环境(生产环境 Prod 和预发环境 Stage)。
- 流程:
- 先在预发环境 Stage 上部署新版本。
- 测试正常后,将流量全部切换到 Stage 环境(此时 Stage 变为生产环境)。
- 注意:一般数据库还是连接同一套,因此需要避免新旧版本不兼容的数据库操作。
滚动部署 (Rolling Update/Ramped)
- 流程:缓慢依次更新现有服务实例。当新版本实例部署完成并健康后,加入服务池;同时将旧版本实例从服务池中移除(下线)。
- 缺点:会有新旧版本实例并存的中间状态,可能带来兼容性问题。
灰度部署/金丝雀发布 (Canary)
- 指逐渐将用户流量从老版本切换到新版本。
- 适用场景:需要对新版本进行充分测试后再逐步切换的情况。例如,先将 1% 的流量切到新版本,观察稳定性和指标,再逐步增加比例。
A/B 测试 (A/B Testing)
- 指同时上线两个(或多个)版本。
- 目的:比较不同版本的功能表现(如可用性、用户体验、转化率等)。
- 实现:通过浏览器 cookie、用户 ID、地理位置、设备类型等规则将用户分配到不同版本。