一、什么是风险驱动设计

1. 定义

风险驱动设计:架构设计时,优先识别系统风险点,以化解、缓解风险作为架构决策第一驱动力,而不是上来就堆砌功能、套流行框架。

核心理念:

架构的首要目标不是实现功能,而是控制风险。 功能决定系统 “能不能做出来”;风险决定系统 “能不能稳定、安全、可维护地长期活下去”。

传统做法(功能驱动): 需求→功能列表→选技术→开发,风险往往后置,上线才暴露出性能、安全、扩展问题,改造成本极高。

RDD 做法: 需求 → 识别风险 → 评估风险等级 → 架构方案优先解决高风险 → 迭代验证风险是否被解决 → 再实现业务功能

2.RDD 中风险是什么?

风险 = 发生概率 × 影响损失 软件常见 5 大类风险

风险类型 说明 例子
性能风险 系统扛不住并发、响应慢 秒杀活动,1w QPS 数据库直接卡死
可用性风险 系统容易宕机、单点故障 单台应用服务器挂掉整个服务不可用
安全风险 数据泄露、权限越权、注入攻击 用户密码明文存储、接口没有鉴权
可变更 / 可维护风险 后续改需求代价巨大 模块严重耦合,改一个功能十几个地方要动
业务合规风险 不符合政策、行业规范 金融系统没有日志审计、支付缺少风控

风险分等级:高、中、低,架构资源优先投入高风险项。

二、RDD 完整标准实施步骤

一共 5 步循环迭代

步骤 1:识别风险 Identify

通读业务需求、非功能需求 (NFR),列出所有潜在风险清单

不光看功能,重点看非功能需求:并发量、数据量、SLA、安全、未来 2 年业务扩张。

举个电商订单系统例子 业务需求:用户可以下单、支付、查订单 识别风险清单: R1:大促峰值下单并发高,数据库压力过大(性能风险‑高) R2:订单服务单点故障,下单直接失败(可用性风险‑高) R3:订单支付接口被恶意重复调用,重复扣款(业务 + 安全风险‑高) R4:后期业务扩张,订单需要对接会员、物流、售后,模块耦合(可扩展‑中) R5:订单日志丢失,出现问题无法排查(可运维‑低)

步骤 2:风险评估 Assess

两个维度打分 1)发生可能性 (高 / 中 / 低) 2)一旦发生造成的损失 (高 / 中 / 低)

得到风险优先级矩阵,优先处理可能性高 + 损失大 上面案例评估

  • R1 大促数据库压力:可能性高、损失高 →最高优先级
  • R2 单点故障:可能性中、损失高 →高优先级
  • R3 重复支付:可能性中、损失高 →高优先级
  • R4 模块耦合:可能性高,损失中等 →中等优先级
  • R5 日志丢失:可能性低,损失低 →低优先级,可以后期再处理

步骤 3:架构方案 Mitigate 缓解风险

针对每一条高风险,给出对应的架构对策

一条风险可以有多种缓解策略:规避、降低、转移、接受

  1. 规避:直接从架构上消除风险源头,比如不用单体,拆分微服务
  2. 降低:减少风险发生概率 / 降低故障影响,例如加缓存、限流、重试
  3. 转移:风险交给第三方组件,比如用 MQ 削峰、云厂商负载均衡
  4. 接受:低风险,暂时不做特殊架构,后续迭代再优化

针对电商订单风险给出方案 R1:大促数据库压力大 缓解方案:读写分离 + Redis 热点订单缓存 + MQ 异步削峰 + 分库分表 R2:单点故障 缓解方案:服务集群部署、Nginx 负载均衡、服务健康检查、熔断降级 R3:重复支付 缓解方案:生成全局唯一订单幂等号,数据库唯一索引、分布式锁、幂等校验 R4:模块耦合 缓解方案:DDD 领域拆分,订单独立微服务,通过消息事件和物流会员解耦 R5:日志丢失 缓解方案:异步日志组件,暂时先用现成框架,后面接入 ELK(低风险先接受)

重点:架构决策必须能对应解决某个具体风险,不能为了微服务而微服务。如果单体架构已经可以解决全部高风险,那就不要强行微服务。

步骤 4:架构原型 / 验证风险 Validate

不能纸上谈兵,做最小原型验证风险方案是否真的生效 比如风险 R1 数据库压力 写压测脚本,模拟 1 万并发下单,测试加 MQ + 缓存之后,数据库 QPS 是否降到安全阈值。 如果压测仍然超时,说明当前架构方案不足以化解风险,需要重新调整方案,例如增加分库。

步骤 5:迭代,循环执行

风险不是一次性全部解决。随着需求变化、用户规模增长,不断产生新风险。RDD 是一个循环过程。 上线之后流量上来了,又出现新风险:例如 Redis 缓存雪崩 →再次走识别‑评估‑设计‑验证流程

三、RDD 和 其他架构方法对比

设计方法 核心驱动力 适合场景
RDD 风险驱动 优先解决高风险 需求不稳定、高并发、高可用、大型复杂系统
FDD 功能驱动 优先实现业务功能 简单内部小系统、低并发、业务简单
DDD 领域驱动 以业务领域模型驱动 业务逻辑极其复杂,领域边界模糊的系统
ATAM 架构权衡分析法 多方案风险评估 已经有多个候选架构方案,需要做取舍评估

示例:

业务功能需求

  1. 运营可以上架秒杀商品,设置库存、秒杀开始 / 结束时间
  2. 用户可以查看正在秒杀的商品
  3. 用户参与秒杀,成功后生成秒杀订单
  4. 用户支付订单,15 分钟未支付自动释放库存

硬性非功能约束

  1. 秒杀开启瞬间预估 8000 QPS 的瞬时峰值请求
  2. 业务数据库 MySQL,单表单库最多承受 800 写 QPS
  3. 绝对不能超卖:实际卖出数量不能大于商品库存,一旦超卖直接造成公司经济损失
  4. 同一个用户不能重复抢到同一件秒杀商品
  5. 秒杀的大流量不能拖垮普通商城(购物车、普通下单)功能

RDD 风险驱动完整实施全过程

RDD 核心思想:先把全部风险找出来,优先解决高风险,再开发业务功能

步骤 1:风险识别

通读业务 + 非功能需求,逐条挖掘潜在风险

编号 风险名称 风险详细描述 风险分类
R1 瞬时流量打垮数据库风险 秒杀瞬间 8000 并发写请求,MySQL 最多扛 800 写 QPS,数据库直接卡死,整个服务不可用 性能风险
R2 库存超卖风险 高并发场景下,多线程同时读取库存 > 0,同时扣减库存,最终实际销量大于商品库存,造成资金损失 业务资损风险
R3 用户重复抢购风险 同一用户短时间多次提交秒杀请求,多次抢到同一个商品 业务风险
R4 流量雪崩风险 秒杀超大流量占用全部服务器资源,商城普通下单、购物车等正常业务被拖垮 可用性风险
R5 恶意爬虫刷接口风险 脚本批量访问秒杀接口,大量无效请求消耗服务器资源 安全风险
R6 超时未支付库存不释放风险 用户秒杀成功 15 分钟没有付款,库存没有回收,商品库存被永久占用 业务逻辑风险
R7 故障排查风险 缺少完整操作日志,一旦出现超卖、报错无法定位问题根因 可运维风险

步骤 2:风险评估

两个维度打分:发生概率 + 损失严重程度,计算风险优先级

风险等级规则 高风险:概率高 + 损失高,必须在架构层面提前解决 中风险:解决成本可控,可以二期优化 低风险:暂时接受风险,后期迭代补充

风险编号 发生概率 损失严重度 最终风险等级
R1 很高 极高 🔴最高优先级
R2 很高 极高 🔴最高优先级
R3 中等 🟠高优先级
R4 中等 🟠高优先级
R5 很高 中等 🟡中等优先级
R6 中等 中等 🟡中等优先级
R7 🟢低优先级,暂时接受

架构资源优先投入 R1 R2 R3 R4,先搞定最高风险,再做剩下的业务功能

步骤 3:风险缓解方案

风险应对 4 种策略

  • 降低:减少风险发生概率 / 降低故障损失
  • 转移:交给中间件、第三方组件承担风险
  • 规避:从架构层面直接消除风险源头
  • 接受:风险很小,暂时不做专门处理

R1:瞬时高并发压垮 MySQL

风险:8000QPS 直接打数据库,MySQL 扛不住 可选方案对比 方案 1:直接升级高配数据库(成本高,治标不治本) 方案 2:Redis 预加载库存 + MQ 异步削峰(选中方案) 缓解策略:转移 + 降低 架构对策:

  1. 秒杀开始前,提前把秒杀商品库存全部加载进 Redis 缓存
  2. 用户秒杀请求,第一步先走 Redis 做库存判断,不直接访问数据库
  3. Redis 校验库存充足之后,发送消息到 RabbitMQ 消息队列,立刻给前端返回「排队中」
  4. 后台消费者线程慢慢消费 MQ 消息,再操作数据库扣减库存

把瞬时 8000 的峰值流量削平,数据库实际写入压力被降到安全范围

 代码

@RestController
@RequestMapping("/seckill")
public class SeckillController {

    @Resource
    private StringRedisTemplate redisTemplate;
    @Resource
    private RabbitTemplate rabbitTemplate;

    @PostMapping("/do")
    public String seckill(Long goodsId,Long userId){
        String redisKey = "seckill:stock:"+goodsId;
        //1 先查Redis库存,不访问数据库
        String stockStr = redisTemplate.opsForValue().get(redisKey);
        if(stockStr == null || Integer.parseInt(stockStr) <= 0){
            return "商品已经抢空";
        }
        //2 Redis预扣库存
        Long remain = redisTemplate.opsForValue().decrement(redisKey);
        if(remain < 0){
            //扣减后小于0,回滚库存
            redisTemplate.opsForValue().increment(redisKey);
            return "商品已经抢空";
        }
        //3 请求发送到MQ,异步处理数据库操作
        SeckillMessage msg = new SeckillMessage();
        msg.setGoodsId(goodsId);
        msg.setUserId(userId);
        rabbitTemplate.convertAndSend("seckill.direct","seckill.order",msg);
        return "排队成功,请稍后查看订单";
    }
}

这段代码作用:把绝大多数流量拦截在 Redis 层,数据库不再直面瞬时大流量

R2:库存超卖风险

风险:多线程同时读取库存,同时扣减,造成超卖 Redis 的 decrement 是原子操作,可以防止 Redis 层面超卖。 但是 MQ 异步落库的时候,仍然存在数据库超卖风险。 缓解策略:降低风险 对策:数据库库存字段上加乐观锁 扣减库存时带上版本号,只有库存 >=1 并且版本号匹配,才允许扣减 数据库库存表增加 version 版本字段 Java MQ 消费者代码(解决 R2 超卖风险)

@Component
public class SeckillConsumer {

    @Resource
    private StockMapper stockMapper;
    @Resource
    private OrderMapper orderMapper;

    @RabbitListener(queues = "seckill.queue")
    public void consume(SeckillMessage message){
        Long goodsId = message.getGoodsId();
        Long userId = message.getUserId();

        //乐观锁扣库存
        int rows = stockMapper.deductStockOptimistic(goodsId);
        //rows等于0:扣减失败,已经没有库存
        if(rows <= 0){
            //库存不足,回滚Redis库存
            redisTemplate.opsForValue().increment("seckill:stock:"+goodsId);
            return;
        }
        //扣库存成功,生成秒杀订单
        createSeckillOrder(goodsId,userId);
    }
}

Mapper 层 SQL 乐观锁

UPDATE stock
SET num = num - 1 , version = version +1
WHERE goods_id=#{goodsId} AND num > 0 AND version = #{version}

即使 Redis 出现多扣,数据库乐观锁可以兜底,彻底杜绝超卖

R3:同一个用户重复抢购同一件商品

风险:用户疯狂点击,多次秒杀成功,抢到多份商品 缓解策略:规避风险 方案:Redis 保存已经成功抢到商品的用户 Id,秒杀前先校验用户是否已经抢购过 改造 Controller,增加用户限购校验

//用户限购key
String userBuyKey = "seckill:user:buy:"+goodsId;
//判断该用户是否已经抢到过
Boolean isMember = redisTemplate.opsForSet().isMember(userBuyKey,userId.toString());
if(Boolean.TRUE.equals(isMember)){
    return "您已经抢购过该商品,不可重复购买";
}
//抢购成功之后把用户id存入集合
redisTemplate.opsForSet().add(userBuyKey,userId.toString());

在 Redis 层直接拦截重复用户,不需要访问数据库

R4:秒杀流量拖垮普通商城业务

缓解策略:风险隔离 架构对策

  1. 秒杀服务单独部署独立服务器集群,不和普通商城服务共用实例
  2. Nginx 网关层配置限流:秒杀接口单独限流,和普通接口流量分开
  3. 秒杀服务单独使用独立线程池,线程资源互不抢占

R5:爬虫恶意刷接口

缓解策略:降低风险 方案:网关层增加验证码、频率限流,同一个 IP 一秒钟最多访问 5 次秒杀接口 可以放在二期迭代开发,优先保证 R1‑R4

R6:15 分钟未支付库存释放

方案:Redis 设置订单过期时间 + 定时任务,超时没有支付,库存回补到 Redis 可以放在二期实现

R7:日志缺失

暂时接受风险,优先保障秒杀核心稳定性,后续接入日志框架

步骤 4:风险验证

架构方案做完之后,必须验证风险是否真正被解决,每个风险对应验证方式

风险编号 验证方案 验收标准
R1 压测工具 JMeter,模拟 8000 并发请求 MySQL 数据库写 QPS 稳定小于 700,服务不宕机
R2 100 库存,并发 2000 请求 最终生成订单数量≤100,零超卖
R3 同一个账号并发提交 10 次秒杀请求 只能下单成功一次
R4 秒杀压测同时,调用普通购物接口 普通接口响应速度不受明显影响

Logo

电商企业物流数字化转型必备!快递鸟 API 接口,72 小时快速完成物流系统集成。全流程实战1V1指导,营造开放的API技术生态圈。

更多推荐