春运和小长假一到,12306这类票务系统的访问压力通常会远高于平时。对这类系统来说,难点往往不在平均负载,而在开售那几分钟里涌进来的瞬时请求。这篇文章先从流量特征说起,再讲高并发票务场景里常见的三类应对办法,最后用一个模拟案例,把思路落到阿里云的具体能力上。

春运高峰的流量长什么样
票务业务的流量有两个明显特点。第一是尖峰。开售前后几十秒内,请求量会猛增到远超平日的水平,过了这一阵又迅速回落。平均值看起来很平稳,系统真正要扛的却是那一小段时间。
第二是读多写少。大量用户反复刷新余票、切换车次,这些都是查询,真正走完下单的只占很小一部分。读和写的比例相差很远,却常常被放在同一条链路上处理。
后果也很直接。查询请求会占满数据库连接和计算资源,轮到下单的时候,用户看到的是页面转圈,既刷不到票,也下不了单。所以拆分读写路径、按类别限流,通常是设计的第一步。
查询和下单为什么要分开扛
余票查询能容忍一点延迟。数据慢几秒,用户一般感觉不到,而且很适合缓存。下单则不一样,库存只能扣一次,超卖或重复扣减都是事故。两类请求对资源的要求几乎相反,硬放在一套资源里,调一边总会影响另一边。
实际做法上,查询请求先读缓存,缓存没有或已过期再回源数据库。下单请求进入独立队列,按顺序处理。队列长度超过承载能力时,直接给用户返回“排队中”的提示,而不是让所有请求都堆在数据库前面。这个提示要写清楚,否则用户会反复刷新,反而把流量推得更高。
阿里云上的三类常用能力
在阿里云上搭这类系统,一般会围绕三类能力来组织:
- 弹性伸缩:用弹性伸缩组(ESS)按CPU使用率、请求数或自定义指标增减计算实例。开售时间是固定的,可以提前配好计划,不必全靠实时指标临时扩容。
- 缓存与消息队列:用Redis承接余票查询的热点数据,车次信息提前预热。下单请求经消息队列进入后端,把瞬时高峰拉平成持续处理。
- 负载均衡与限流:通过负载均衡(SLB)分发流量,在网关层提前拦截明显超出容量的请求,让无效流量进不了核心数据库。
这三者要配合着用。只扩容不限流,实例数上去了,数据库还是会先被压垮。只有缓存没有队列,下单高峰依然会直接压到数据库上。
模拟案例:某区域票务平台的一次扩容推演
下面这个案例是按上述思路做的模拟推演,用来说明步骤和取舍,并不对应某个真实项目的数据。假设一个区域性票务平台,平日日均请求约300万次,节假日开售时瞬时峰值可能达到平日的40倍左右。改造大致分五步:
- 把余票查询改成缓存优先的读路径,热点车次数据提前预热进Redis,并把缓存命中率作为核心指标盯着,目标定在95%以上。
- 下单请求接入消息队列,按车次分区。同一车次的库存扣减保持顺序,避免并发下出现超卖。
- 配置弹性伸缩规则。开售前约30分钟按计划预扩容,开售后再依据队列长度和CPU使用率动态调整。扩容需要时间,预扩容的意义就在这里。
- 在网关层设置分级限流。对明显异常的高频单用户请求直接拦截,不让它们挤占正常用户的名额。
- 开售前做压测,模拟峰值流量,重点观察数据库连接数、消息堆积量和接口响应时间,再据此调整各项阈值。
按这套方案调整后,高峰期间下单链路能保持稳定,代价是查询接口的部分响应会略有延迟。对用户来说,这通常可以接受。大多数人更在意能不能下单成功,几百毫秒的余票刷新差异,感受并不明显。
落地时容易踩的几个坑
- 压测要尽量贴近真实场景。只压单个接口,很难暴露缓存击穿和队列堆积这类问题,它们往往是多个环节同时受压时才出现。
- 弹性伸缩有冷启动时间。新实例拉起、加载缓存、接入负载均衡都要花时间,预案里要把这段时间算进去。
- 排队和限流的提示文案要清楚,告诉用户大概要等什么、接下来该怎么做,减少无效刷新。
- 账号、支付和身份信息相关的接口要单独设计鉴权和风控,不要和普通查询混在同一套规则里。
如果云上的基础资源已经具备,真正花精力的往往是业务规则和稳定性验证,比如库存怎么扣、排队怎么排、压测数据怎么看。基础设施能省下搭建的时间,但这些规则还得团队自己想清楚。