第4章:令牌化服务架构设计

这一章是骨架。说白了,就是你要把令牌化系统拆成几层,每层该放什么,彼此怎么协作。我刚开始做这类系统时,喜欢把所有逻辑塞到一个服务里——结果呢?改个存储就得重新部署整个模块,线上出了故障都分不清是API超时还是令牌生成器卡住了。嗯,后来才明白,分层不仅是代码整洁问题,更是命脉。

4.1 三层架构总览

我把令牌化服务拆成三个层:API层、业务层、存储层。各层职责清晰,互不越界。你想想看,每一层只关心自己那点事,出了毛病也容易定位。

  • API层:处理外部请求,做认证、限流、参数校验。不参与任何业务逻辑。
  • 业务层:核心逻辑所在——生成令牌、映射关系管理、脱敏/还原。也是状态最复杂的地方。
  • 存储层:负责持久化。令牌映射表、密钥、审计日志,统统丢给它。

核心要点:层与层之间只通过接口通信,不允许业务层直接操作数据库API或API层调用存储层的内部方法。我在项目中吃过这个亏——直接调了存储层一个内部函数,结果存储层改表结构,业务层跟着全崩。从那以后,我强制团队用gRPC或REST明确定义接口边界。

令牌化服务三层架构 API层 RESTful端点 · 身份认证 · 限流 · 参数校验 · 请求日志 /v1/tokenize · /v1/detokenize · /v1/alias 业务层 令牌生成器 · 映射管理器 · 脱敏引擎 · 密钥轮换器 VaultTokenGenerator · RangeMapper · FormatPreservingEncrypt 缓存模块 · 审计模块 · 事件通知 存储层 映射表 · 密钥仓库 · 审计日志 · 配置中心 MySQL Cluster · Redis Sentinel · HashiCorp Vault gRPC / REST DAO / SDK 注意:层间依赖只允许自上而下,禁止反向依赖。业务层不直接访问数据库。

4.2 模块划分——我建议这样拆

每个层内部再拆成独立的模块。为什么?因为模块可以单独部署、单独扩缩容。举个例子,如果令牌生成压力大,我只扩容生成器模块,不用动整个业务层。看看这张表,你就能快速对齐每个模块的职责:

模块名所在层主要职责我个人偏好的实现
GatewayAPI层统一入口,认证限流Kong / Envoy + OAuth2 Proxy
TokenGenerator业务层生成随机令牌或保格式令牌基于UUIDv4或FF1算法
MappingManager业务层维护明文↔令牌的双向映射内存中LRU缓存 + 数据库持久化
KeyRotation业务层定期轮换加密密钥CronJob + Vault Transit Engine
AuditLogger业务层记录所有令牌操作行为异步写入Kafka + Elasticsearch
StorageProxy存储层提供统一的DAO接口MyBatis-Plus + ShardingSphere

一个小技巧:模块间的接口定义要尽量早定。我习惯先写protobuf文件,再实现业务逻辑。这样团队并行开发时不会打架,而且后期修改接口的成本也低。

4.3 高可用设计——避免单点故障

令牌化服务不能挂。你想想,一旦停了,所有依赖它做脱敏的业务都得阻塞。我自己经历过一次线上事故:令牌生成器模块的单节点宕机,导致调用方全部超时,五分钟内警报打爆手机。从那以后,我对高可用的要求变成了:任何单点故障都不能导致服务不可用。

怎么做?分三层来看:

API层高可用

  • 多实例部署,至少2个节点。前端加负载均衡(NLB或HAProxy)。
  • 健康检查接口(/health)必须包含对业务层的连通性检测。
  • 限流策略要独立,防止某个恶意租户打爆整个API层。

业务层高可用

  • 所有业务模块无状态化。Session信息不存本地,丢进Redis。
  • 令牌生成器采用多活模式:每个节点都可以独立生成全局唯一令牌。我们使用Snowflake派生的方案,每个节点分配唯一workId。
  • 映射管理器必须支持主备切换。一旦主节点出问题,备节点自动接管。

我曾经踩过的坑:映射管理器的缓存忘记做故障转移。主节点挂了之后,备节点没有预热缓存,请求大量穿透到数据库,直接把DB打挂了。正确做法:备节点定时从Redis同步热点映射,或者用一致性哈希让每个映射只落在固定节点上。

存储层高可用

  • 数据库采用主从复制 + 自动故障转移。我一般用MySQL Group Replication或Galera Cluster。
  • Redis用Sentinel或Redis Cluster。注意:令牌映射的缓存不能丢,必须开启AOF持久化。
  • 密钥存储使用HashiCorp Vault,Vault本身需要3-5个节点组成高可用集群。

4.4 整体架构设计文档示例

纸上谈兵完了,给你一个简化版的架构设计文档模板(YAML风格)。实际项目中我会用这样的描述来对齐团队理解:

# 令牌化服务架构设计(简版)
service: tokenization
version: v1

layers:
  - name: api-layer
    components:
      - gateway: [rate_limiter, auth_checker, request_validator]
      - endpoints: [tokenize, detokenize, alias_management]
    ha:  [multiple_instances, health_check, lb]
  
  - name: business-layer
    components:
      - _generator: [random_strategy, fpe_strategy, vault_strategy]
      - mapping_manager: [cache(redis), db_persistence, dual_write]
      - key_rotation: [cron_trigger, vault_transit, no_downtime]
    ha: [stateless, active_active, circuit_breaker]
  
  - name: storage-layer
    components:
      - mapping_db: [mysql_cluster, read_write_separation, auto_failover]
      - cache: [redis_sentinel, aof_enabled, key_eviction_policy=noeviction]
      - vault: [3_node_cluster, seal_unseal, auto_unseal]
    ha: [replication, backup, disaster_recovery]

这段配置不是能直接跑的配置,但能帮你快速给新人讲解整个服务是怎么搭起来的。我每次做方案评审时,都会先把这种层级图+模块表+HA策略丢出来。大家一看就懂,少了很多无效沟通。

4.5 高可用测试你做了吗?

架构设计完了,不测试等于白做。我个人建议至少覆盖下面几个场景:

  1. 单节点故障测试:拔掉一个API节点,看服务是否仍可用。
  2. 缓存穿透测试:停掉Redis集群中的一个节点,观察请求是否被正确路由到主节点。
  3. 存储故障测试:杀掉主数据库,看应用是否能自动切换到从库。
  4. 密钥轮换不对等测试:在业务高峰期触发密钥轮换,看是否有令牌生成失败。

强调一点:高可用不是写死代码就能得到的,它贯穿在架构设计、编码实现、运维监控的每一个环节。我见过太多团队把HA写在PPT上,结果生产环境一崩再崩。所以,测试一定要做实,别走形式。