学习极客时间-左耳听风专栏的个人学习记录。

管理设计

索引:

  • 分布式锁
  • 配置中心
  • 边车模式
  • 服务网格
  • 网关模式
  • 部署升级策略

分布式锁

目的:分布式架构下对共享资源的更新加锁,防止冲突。
实现:一般可用 Redis、Zookeeper、数据库等实现。
特点

  1. 安全性(排他性):任一时刻只能一方拿到锁。
  2. 避免死锁:最终一定能获得锁,持锁方崩溃时支持自动释放锁。
  3. 容错性:锁服务集群中只要多数节点正常,就可加锁/释放锁。

Redis的锁服务

  1. 加锁:设置一个全局唯一的value作为锁的标识 SET key value NX PX 30000ms 
    • NX参数:仅当value不存在时,才能赋值成功。
    • PX参数:指定锁的自动释放时间(即锁过期时间)。
  2. 释放锁: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)(乐观锁机制)

既然用到数据库来控制版本号,何不把版本号保留到数据库中呢?

即:更新前比对此时的版本号和开始时查到的版本号是否一致,一致才可以更新。

延伸思考

既然讲到这了,请问我们还需要最开始说的分布式锁么?

  1. 实现上来说,确实可以用CAS之类的方法替换分布式锁
CAS 分布式锁
类型 客观控制 悲观控制
性能 低冲突时:高;高冲突:低 低冲突时:中等;高冲突时:相对稳定
适用 单个记录原子更新 任何需互斥的操作
  1. 除了常用的写入涉及锁,其他操作可能也涉及加锁(如同步操作)。这种无法用数据库锁实现。

分布式锁设计重点

  • 避免死锁
  • 高可用
  • 可重入
  • 要提供非阻塞方式的锁服务(非阻塞:如果锁不可用(被其他线程持有),它不会停止或等待!它会立即得知失败(通常通过一个返回值,比如 false),然后线程可以自由地去做其他事情

配置中心

配置包括软件的关键信息,如连接池大小、数据库用户和密码、队列长度等,但用以前写入的配置文件方式容易出错,管理不便,故引入集中式的配置管理系统进行管理。

配置中心的设计

  1. 区分软件的配置
    • 静态配置:启动时的一些配置,运行时基本不会修改,如网络端口、Docker启动参数。
    • 动态配置:在运行时可能会被修改,如降级开关、日志级别。
      • 运行环境:开发、测试、预发、生产,配置大同小异。
      • 作用区分:分为依赖配置(如外部依赖的MySQL资源)和不依赖配置(完全内部使用的配置)。
    • 按层次分:可分为 IaaS、PaaS、SaaS 层。基础层是操作系统配置,中间层有中间件的配置,上层是应用层的配置。
  2. 配置中心的管理
    • 一般来说,操作系统、平台层的配置由运维角色或专门角色维护(最好是有限选项的维护)。应用层的配置命名最好规范化。
    • 外部服务依赖的配置,建议不要放配置中心,而要放在服务发现系统,因为这部分配置很动态(频繁上下线)
    • 配置要有版本管理,最好与软件的版本做关联。
  3. 配置中心的架构
    • 配置录入 → 配置中心→ (变更通知)配置变更控制器→配置变更
    • 主要角色:配置中心、配置变更控制器。
    • 交互方式:通过 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 三者对比

  1. Sidecar:可改造已有服务,适配已有应用
  2. Service Mesh:统一管理集群内的 Sidecar,业务方只需把应用放入网格即可,不关心控制面逻辑。
  3. Gateway:统一负责服务集群(或组)的入口流量管理,粒度可粗可细。

网关设计重点

  1. 高性能:不能成为系统性能瓶颈。
  2. 高可用
    • 集群化:部署多个实例避免单点故障。
    • 服务化:支持在不间断服务下动态修改配置。
    • 持续化:优雅重启
  3. 易扩展:可支持扩展必要的业务逻辑
  4. 运维方面
    • 与后端业务松耦合,与协议紧耦合。
    • 应用监控、性能监控、调用链追踪等集成。
    • 内置弹力设计(如限流、熔断)保护后端服务。
    • 支持 DevOps,实现自动化运维和管理。
  5. 架构方面
    • 将后端聚合服务逻辑在网关核心代码化,不侵入业务服务。
    • 网关和后端服务网络要靠近(同一个内网),确保低延时,减少网络问题。
    • 网关自身也需支持容量扩展,配合负载均衡器。
    • 集成服务发现机制。
    • 可考虑 Bulkhead 隔离方式,不同网关服务不同后端或前端流量。
  6. 安全性
    • SSL 证书管理、数据加密传输。
    • 异常请求识别、拦截、告警。
    • 校验基本协议头信息,如用户名是否已登录、Session 信息等。

部署升级策略

停机部署 (Big Bang/Recreate)

  • 简单粗暴:先停机再部署再开机。
  • 缺点:会停止对外服务,需尽量避免。
  • 适用场景:新旧版本完全不兼容(如数据库表设计新老不兼容),只能停机部署。

蓝绿部署 (Blue/Green, Staged)

  • 需要两套相同的环境(生产环境 Prod 和预发环境 Stage)。
  • 流程:
    1. 先在预发环境 Stage 上部署新版本。
    2. 测试正常后,将流量全部切换到 Stage 环境(此时 Stage 变为生产环境)。
  • 注意:一般数据库还是连接同一套,因此需要避免新旧版本不兼容的数据库操作。

滚动部署 (Rolling Update/Ramped)

  • 流程:缓慢依次更新现有服务实例。当新版本实例部署完成并健康后,加入服务池;同时将旧版本实例从服务池中移除(下线)。
  • 缺点:会有新旧版本实例并存的中间状态,可能带来兼容性问题。

灰度部署/金丝雀发布 (Canary)

  • 指逐渐将用户流量从老版本切换到新版本。
  • 适用场景:需要对新版本进行充分测试后再逐步切换的情况。例如,先将 1% 的流量切到新版本,观察稳定性和指标,再逐步增加比例。

A/B 测试 (A/B Testing)

  • 指同时上线两个(或多个)版本。
  • 目的:比较不同版本的功能表现(如可用性、用户体验、转化率等)。
  • 实现:通过浏览器 cookie、用户 ID、地理位置、设备类型等规则将用户分配到不同版本。