一、风控锁的困境:为什么高并发下锁成了瓶颈?从CAS到悲观锁,我们经历了什么?

做风控系统这些年,我踩过最大的坑,就是锁。

你想想看,一个典型的交易风控场景——用户下单,系统要查他的历史行为、当前设备指纹、关联账户、黑名单……这些数据分散在不同的缓存和数据库里。为了保证数据一致性,我们不得不上锁。但一上锁,性能就崩了。

为什么会这样?

说白了,锁的本质是「串行化」。你把并行操作强行排成队,CPU 利用率自然上不去。我见过最夸张的一次,线上风控接口的 TP99 从 5ms 飙到了 500ms,查了半天,就是一把 Redis 分布式锁在作祟。

1.1 从 CAS 开始:乐观锁的「轻」与「痛」

最早我们尝试用 CAS(Compare And Swap)来解决并发问题。CAS 是 CPU 级别的原子操作,不加锁,靠硬件保证「比较-交换」的原子性。

// Java 中的 CAS 示例
AtomicLong counter = new AtomicLong(0);
counter.incrementAndGet(); // 底层就是 CAS

CAS 的好处很明显——轻量、无阻塞。但我在项目中遇到过它的三个致命问题:

  • ABA 问题:值从 A 变成 B 又变回 A,CAS 检测不到变化。虽然可以用版本号解决,但增加了复杂度。
  • 自旋开销:高并发下 CAS 失败率极高,线程不断重试,CPU 空转。我记得有一次压测,CAS 自旋占了 40% 的 CPU 时间片。
  • 只能保证单个变量:风控规则往往涉及多个数据源,CAS 搞不定跨资源的原子操作。

我的经验:CAS 适合「冲突概率极低」的场景,比如计数器、状态标记。但在风控这种高冲突场景下,CAS 的自旋会让你怀疑人生。

1.2 悲观锁:简单粗暴,但代价高昂

既然 CAS 搞不定,那就上悲观锁吧。数据库的 SELECT ... FOR UPDATE,或者 Java 的 synchronized,都是典型代表。

// 数据库悲观锁示例
BEGIN;
SELECT * FROM user_risk WHERE user_id = 123 FOR UPDATE;
-- 业务逻辑处理
UPDATE user_risk SET risk_score = 80 WHERE user_id = 123;
COMMIT;

悲观锁的思路很简单:我怀疑你会跟我抢,所以先锁住,谁也别动。嗯,这里要注意——这种「先锁再说」的策略,在低并发下没问题,但一旦流量上来,就是灾难。

我曾经接手过一个风控系统,每天凌晨的结算任务会把数据库锁死。查日志发现,一把 FOR UPDATE 锁住了整张表,所有请求都在排队等锁释放。那场面,就像早高峰的地铁——所有人都挤在门口,谁也进不去。

避坑指南:我曾经在线上用过 SELECT FOR UPDATE 做风控额度扣减,结果死锁频发。后来发现,不同线程获取锁的顺序不一致,导致循环等待。解决方案是强制按「用户ID哈希」排序加锁。

1.3 锁的瓶颈到底在哪?一张图看懂

为了让你更直观地理解,我画了一张流程图。它展示了从请求进入风控系统到最终返回,锁可能卡在哪些环节。

风控锁的瓶颈链路 用户请求 第一层:分布式锁(Redis Redlock) 网络IO + 锁竞争 → 高延迟 第二层:数据库悲观锁 行锁 → 间隙锁 → 表锁升级 第三层:JVM 内部锁(synchronized) 锁粗化 → 锁膨胀 → 线程阻塞 响应延迟飙升 瓶颈分析 • 锁竞争:排队等待 • 上下文切换:CPU浪费 • 死锁风险:循环等待 • 锁升级:性能雪崩 • 网络IO:远程调用 • 锁粒度:过粗或过细 • 重入开销:递归加锁

从这张图你能看到,一个请求从进入风控系统到返回,可能要经过三层锁。每一层都在消耗时间,而且层与层之间还会互相影响。我见过最极端的案例——分布式锁超时了,数据库锁还在持有,结果整个系统雪崩。

1.4 我们到底经历了什么?

回顾一下,从 CAS 到悲观锁,其实是一个「从乐观到悲观」的演变过程:

方案 核心思想 优点 缺点 风控场景适用性
CAS 乐观,无锁 轻量、无阻塞 ABA、自旋、单变量 低冲突场景(如计数器)
分布式锁 外部协调 跨进程、跨机器 网络IO、锁超时、脑裂 资源竞争场景(如额度扣减)
数据库悲观锁 先锁后操作 强一致性、简单 死锁、性能差、锁升级 低频、强一致性场景
JVM内部锁 线程互斥 轻量、无网络开销 单机、锁膨胀 本地资源竞争

我的建议:不要迷信任何一种锁方案。CAS 不是万能的,悲观锁也不是一无是处。关键是要根据你的业务场景——冲突概率、数据一致性要求、延迟容忍度——来选择最合适的锁策略。

嗯,说到这里,你可能会问:那到底该怎么优化?别急,后面的章节我会逐一拆解。但今天你得先记住一个核心观点——锁的瓶颈不在于锁本身,而在于你对并发的理解

我个人习惯把风控系统的锁优化分为三个层次:

  1. 减少锁的持有时间:能快进快出,就别磨蹭。
  2. 降低锁的粒度:能锁一行,就别锁一张表。
  3. 避免不必要的锁:能用无锁数据结构,就别加锁。

这三个层次,我会在后续章节里用真实案例一一展开。今天这一章,就当是开胃菜吧。


专注资料整理