看到"西班牙夺冠狂欢震动地震仪"这条热搜,我第一反应是:这得多少人同时跳啊,都把地震仪整震动了。
然后我就想到了我们做系统的,高并发场景不也是这样吗?平时没啥事,一到大促或者热点事件,流量瞬间涌进来,系统能不能扛住?
今天就聊聊高并发场景下,代购集运系统的架构设计。这玩意儿我踩过的坑,说出来都是泪。
先说说我踩过的坑
前年黑五,我负责的一个代购系统差点崩了。
那天流量是平时的8倍,一开始还好好的,到了晚上8点,突然就不行了。接口响应时间从几十毫秒变成了几秒,然后就是各种超时。用户那边就是页面转圈圈,下单下不了,付款付不了。
老板在群里疯狂@我,我手心全是汗。最后没办法,只能限流,把一部分用户挡在外面。那时候真的是度日如年,感觉每一分钟都像一个小时那么长。
后来复盘,发现问题出在好几个地方:数据库连接池满了、Redis缓存击穿了、还有个定时任务刚好在高峰期跑,把CPU占满了。
从那以后,我对高并发就有了心理阴影,做系统的时候第一件事就是想:这玩意儿能不能扛住流量峰值?
高并发架构的核心思路
说穿了,高并发架构就三个字:分、缓、异。
分:就是拆分,服务拆分、数据库拆分、读写分离。
缓:就是缓存,能缓存的都缓存。
异:就是异步,能异步的都异步。
道理很简单,但真要做好,每一步都是坑。
```# 用Python写的简单限流示例
实际项目中建议用Redis的令牌桶算法
import time
from collections import defaultdict
class RateLimiter:
"""
简单的滑动窗口限流
踩坑:
1. 分布式环境下要用Redis,不能用本地内存
2. 限流粒度要想好:按IP?按用户?按接口?
3. 限流阈值要根据压测结果来定,不能拍脑袋
"""
def __init__(self, max_requests: int, window_seconds: int):
self.max_requests = max_requests
self.window_seconds = window_seconds
self.requests = defaultdict(list) # key -> 时间戳列表
def is_allowed(self, key: str) -> bool:
"""检查是否允许请求"""
now = time.time()
window_start = now - self.window_seconds
# 清理窗口外的请求记录
self.requests[key] = [t for t in self.requests[key] if t > window_start]
if len(self.requests[key]) < self.max_requests:
self.requests[key].append(now)
return True
else:
return False
用法
limiter = RateLimiter(max_requests=100, window_seconds=60) # 每分钟100次
if limiter.is_allowed("user_123"):
print("允许访问")
else:
print("限流了")
### 数据库层面的优化
数据库永远是高并发的瓶颈。
我做过的几个代购系统,无一例外都是数据库先扛不住。为什么?因为订单、包裹这些数据,读写都很频繁,而且数据量增长很快。
我一般会做这几件事:
**1. 读写分离**
主库写,从库读。大部分查询都走从库,主库只负责写。这样主库的压力就小很多。
**2. 分库分表**
订单表按用户ID哈希分表,我一般分8张或者16张。单表数据量控制在千万级以内,查询速度才有保证。
**3. 索引优化**
这个说过很多次了,但还是有人不重视。慢查询日志一定要开,定期分析,该加索引的加索引,该优化SQL的优化SQL。
```-- 分库分表后的查询示例
-- 踩坑:分表后跨表查询是个大问题,要尽量避免
-- 如果一定要查,最好用中间件或者自己写聚合逻辑
-- 按用户ID查询订单(可以直接定位到某张表)
SELECT * FROM order_003 WHERE user_id = 12345 ORDER BY created_at DESC LIMIT 20;
-- 按订单号查询(订单号里要包含分表信息)
-- 比如订单号格式:前缀 + 分表号 + 时间戳 + 随机数
SELECT * FROM order_005 WHERE order_no = 'ORD0520260721123456789';
缓存的正确用法
缓存这东西,用好了是神器,用不好是灾难。
我见过太多人,为了性能,什么都往缓存里塞,结果缓存雪崩、缓存击穿、缓存穿透,各种问题都来了。
我的原则是:
- 热点数据才缓存,冷数据没必要
- 缓存过期时间要错开,别同一时间大批量过期
- 缓存击穿要加互斥锁
- 缓存穿透要做空值缓存或者布隆过滤器
还有一点很重要:缓存和数据库的一致性问题。这个没有完美的解决方案,只能根据业务场景来权衡。是强一致性还是最终一致性?能接受多长时间的不一致?这些都要想清楚。
异步化改造
能异步的都异步,这是高并发系统的基本原则。
比如下单,用户点了提交,你非要等订单创建完、库存扣完、通知发完,才返回给用户,那肯定慢。
正确的做法是:用户提交订单,系统校验一下基本信息,没问题就返回"下单成功",然后后台异步去处理后续流程。
用户体验也好,系统压力也小。
```# 用Celery做异步任务的示例
实际项目中还要考虑任务重试、幂等性、死信队列等等
from celery import Celery
app = Celery('tasks', broker='redis://localhost:6379/0')
@app.task
def process_order(order_id):
"""
异步处理订单
踩坑:
1. 任务一定要保证幂等,重复执行不能出问题
2. 要设置重试次数和重试间隔
3. 失败了要有告警,不能悄无声息地就没了
"""
order = Order.objects.get(id=order_id)
# 1. 扣减库存
deduct_inventory(order)
# 2. 通知采购
notify_purchase(order)
# 3. 发送通知
send_notification(order.user_id, "您的订单已提交,我们会尽快采购")
# 4. 更新订单状态
order.status = "processing"
order.save()
下单接口里这么调用
def create_order(request):
# ... 校验参数 ...
# ... 创建订单(初始状态:待处理) ...
# 异步处理
process_order.delay(order.id)
return Response({"code": 0, "msg": "下单成功", "data": {"order_id": order.id}})
```
最后说两句
高并发这事儿,说难也难,说简单也简单。难的是各种边界情况和异常处理,简单的是核心思路就那么几条。
taocarts的代购系统在这方面做得就不错,我研究过他们的架构,分层很清晰,缓存策略也很合理。人家能扛住黑五、双11这种大促,不是没有原因的。
当然了,架构不是越复杂越好,适合自己业务的才是最好的。你一天就几百单,搞微服务搞分布式,那是吃饱了撑的。等业务量上来了,再逐步演进也不迟。
今天就聊到这儿。你们做过高并发系统吗?有没有什么难忘的踩坑经历?评论区聊聊。