
“叮——叮——”
手机告警电话像催命一样响,我赶紧打开手机看下告警原因,订单库 CPU 98%,钉钉群@全员已经 99+,我擦...
估计很多兄弟都会碰到这样的场景,半夜接到告警电话,真是要命...

高并发业务场景,不可预料的东西太多了,一步小心系统可能就会出问题。
我是“卡卡罗特”, 摸爬滚打八年,我发现解决高并发问题,其实就那么几招,今天且看我一招一招的告诉你。我公司现在的支付系统就靠它续命——日均 100 W 单,月 3000 W,年 3 个亿,照样稳得一批。
能学多少,看你造化。
我负责的业务系统是支付系统,其中“支付下单”的流程如下:
支付流程描述:
1
下单请求:业务系统向支付系统发起支付下单请求。
2
订单处理:支付系统接收到请求后:
•
将订单数据持久化存储到数据库
•
发送消息队列(MQ)通知
3
支付请求:支付系统异步消费MQ消息,向支付宝发起支付请求。
4
结果回调:支付宝通过回调接口将支付结果返回给支付系统。
5
状态查询:支付系统同时通过定时任务主动向支付宝查询订单状态。
6
状态返回:支付宝返回订单的当前状态信息。
7
结果通知:支付系统最后通过异步方式将最终的支付结果通知给业务系统。
为了应对高并发,系统需要做哪些措施了呢?🤔
系统不是钢铁侠,总有扛不住的时候。限流就是门口那个保安:一次只放 10 个人,第 11 个?对不起,先回去刷会儿微博。

限流的代码思路有很多种,以后Guava的限流类,公司里面一般使用**Redsi限流**。下面只是举个栗子。
@Service
public class OrderService {
// 创建一个每秒处理10个请求的限流器
private final RateLimiter rateLimiter = RateLimiter.create(10.0);
public String createOrder(OrderRequest request) {
// 尝试获取令牌,拿不到就提示“系统繁忙”
if (!rateLimiter.tryAcquire()) {
throw new RuntimeException("系统繁忙,请稍后再试");
}
// 拿到令牌,继续处理下单
return doCreateOrder(request);
}
}
在分布式环境下,多个服务实例可能同时处理同一笔请求。分布式锁能确保同一时间只有一个请求能创建订单,防止重复下单。
Redis 分布式锁,简单粗暴:
String lockKey = "order_lock:" + userId + ":" + productId;
if (Boolean.TRUE.equals(redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", Duration.ofSeconds(10)))) {
try {
return doCreateOrder(request);
} finally {
redisTemplate.delete(lockKey); // 办完事记得放人
}
} else {
throw new RuntimeException("别急,订单已经在处理了~");
}
就像银行取号,同一个号只能叫一次,办完了才叫下一个,杜绝插队的。
光有锁还不够,网络可能超时,客户端可能重复发请求。幂等性就是保证同一个请求不管来多少次,效果和只执行一次一样。这是防止重复数据的最后一道防线。
网络抽风、客户端重试,订单号要是重复了,数据库直接甩你一脸 DuplicateKeyException。
给订单号加唯一索引,让 MySQL 当最后一道门神:
ALTER TABLE `order` ADD UNIQUE INDEX `uk_order_no` (`order_no`);
代码里顺手接住异常,别让用户看到 500:
try {
orderMapper.insert(new Order(orderNo, ...));
} catch (DuplicateKeyException e) {
log.warn("订单已存在,直接返回旧单");
return queryOrderByNo(orderNo);
}
支付订单落库成功后,直接扔到 MQ 里,再通过 MQ 异步去调支付宝渠道扣款。这样一来,即使流量瞬间暴涨,MQ 也能帮你缓冲一下,避免系统被冲垮。

数据落库后,我们定时一批一批的捞取数据,去执行世纪的业务逻辑,也起到了**异步 + 缓冲**的作用。
比如,调支付宝下单接口后,支付成功/失败,支付宝都需要通过我们提供的API回调我接口。但是网络是不可靠的,支付宝的回调可能因为抖动没收到。
我们不能干等,得有个备胎方案——用一个定时 JOB 去主动查询那些**“未知”**状态的订单。

支付状态流程机制:
1、创建订单时,状态是“待支付”。
2、调支付宝后,如果没收到回调,订单可能一直“待支付”。
3、定时任务每隔几分钟扫一次状态是“待支付”且超时的订单。
4、对这些订单,主动调支付宝查询接口,根据结果更新状态。
@Scheduled(cron = "0 */2 * * * ?")
public void checkPendingOrders() {
List<Order> pendingOrders = orderMapper.selectPendingOrders();
pendingOrders.forEach(order -> {
String lockKey = "order_query_lock:" + order.getOrderNo();
// 获取分布式锁,防止并发处理
if (Boolean.TRUE.equals(redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", Duration.ofSeconds(30)))) {
try {
// 双重检查订单状态
Order freshOrder = orderMapper.selectById(order.getId());
if (!"PENDING".equals(freshOrder.getStatus())) return;
// 查询支付宝并更新状态
String actualStatus = alipayClient.queryOrderStatus(order.getOrderNo());
if ("SUCCESS".equals(actualStatus)) {
handleSuccess(order, "SUCCESS");
} else if ("CLOSED".equals(actualStatus)) {
handleFail(order, "FAILED");
}
} finally {
redisTemplate.delete(lockKey);
}
}
});
}
定时任务和回调的处理逻辑是一样的,注意加上锁,防止并发执行。
当单表数据量爆炸(比如一天 10w,一年 3600w),查询和插入都会变得巨慢。分库分表就是把一张大表拆成多个小表,分散到不同数据库里。

具体要分库,还是分表,还是又分库又分表,得看具体业务!
怎么分?(具体的分片策略有很多),比如:
•
**按时间分:**每个月一张新表,比如 order_202405。
•
**按用户 ID 取模分:**比如拆成 4 个表,user_id % 4,结果 0、1、2、3 分别存入 order_0 到 order_3。
企业中,一般使用ShardingJDBC 这类框架实现,非常还用,对代码没有侵入性!
高并发写业务虽然重点是写,但也离不开读。
比如下单时的支付渠道配置,这种数据不怎么变,完全可以丢进缓存。
甚至可以加载到内存里,再加个 JOB 定时刷新缓存,连 Redis 都不用每次都读——毕竟读 Redis 也有网络开销。
高并发这口锅,说大也大,说小也小。核心就是:

限流保命、锁防重、幂等兜底、MQ 缓峰、Job 补偿、分片扩容、缓存加速。
基本都是这几招,具体怎么用,就看具体业务逻辑了🤔
对应高并发,你还能想到哪些策略呢?欢迎评论区交流~