基础业务与项目进度
12306 铁路购票服务是与大家生活和出行相关的关键系统,包括会员、购票、订单、支付和网关等服务。
目前实现了已经实现了前端界面
、网关服务模块
、会员服务模块
、车票查询
以及部分车票购买
与 订单管理
等功能。
项目架构
实现思路
网关服务
在用户登录时执行 身份认证 和 黑白名单检查。
1. 认证用户身份
- 用户通过 用户名/邮箱/手机号 + 密码 登录。
- 服务器调用 认证服务 进行 校验(JWT Token)。
- 如果登录成功,则返回 Token 并设置到 Cookie 或前端存储。
2. 进行黑白名单检测
-
黑名单:
-
存储方式:Redis、MySQL。
-
判断逻辑:
- IP 黑名单:检测请求 IP 是否在黑名单中,如发现则直接拒绝请求(如 403)。
- 用户黑名单:检测
userId
或username
是否在黑名单中,阻止登录或访问关键接口。
-
-
白名单:
- 对于某些 受限接口(如管理员后台、API 访问),只有 白名单用户 才能使用。
具体实现
(1)网关拦截器 在 API 网关层添加一个拦截器:
- 校验 Token:检查
Authorization
头,是否是有效 Token。 - 黑名单检查:在 Redis/数据库中查找该用户是否在黑名单。
- 白名单检查:如果该接口需要白名单,检查用户是否符合要求。
const checkBlacklist = async (userId, ip) => {
const isBlacklisted = await redis.sismember("blacklist:users", userId);
const isIPBlacklisted = await redis.sismember("blacklist:ips", ip);
if (isBlacklisted || isIPBlacklisted) {
throw new Error("您的账号/IP 被禁止访问");
}
};
const loginHandler = async (req, res) => {
const { username, password } = req.body;
// 验证用户身份
const user = await authenticateUser(username, password);
if (!user) return res.status(401).json({ message: "用户名或密码错误" });
// 黑名单检测
await checkBlacklist(user.id, req.ip);
// 生成 Token 并返回
const token = generateJWT(user);
res.json({ token, user });
};
(2)前端登录逻辑增强
实现了 fetchLogin 方法。
fetchLogin({
...formState
}).then((res) => {
if (res.success) {
Cookies.set('token', res.data?.accessToken);
router.push('/ticketSearch');
} else if (res.code === 403) {
message.error("您的账号已被封禁,请联系管理员");
} else {
message.error(res.message);
}
});
车票查询功能
12306 车票搜索用的是 Redis。
12306的搜索条件
搜索条件如下:
-
单程或者往返
-
出发地
-
目的地
-
出发日或者返程日
-
普通或者学生
-
车次类型
-
出发车站
-
到达车站
-
车次席别
-
发车时间
-
显示积分兑换车次
-
显示全部可预订车次
为什么使用 Redis 缓存存储列车数据
- 实时性: Redis 以内存为基础,具有极低的读取延迟,可以快速响应实时查询请求,这对于需要即时更新的列车数据非常重要。单程或往返、出发日期等条件可以通过快速的 Redis 查询来满足。
- 缓存: Redis 是一个出色的缓存数据库,可以用于缓存热门的列车路线和查询结果,从而减轻后端数据库的负载。对于需要被查询的路线,可以将其结果缓存在 Redis 中,以提高响应速度。
- 简单性: Redis 的数据模型相对简单,适合存储简单的键值对或一些常规数据结构。这使得 Redis 适合存储一些搜索条件,如出发地、目的地、车次类型等,以便快速筛选结果。
- 部署成本: Redis 是一款轻量级的数据库,易于部署和维护。它的内存占用相对较低,可以在相对较小的硬件配置上运行,从而减少了部署成本。
- 只需后端查询一次: 在实际操作中,页面上的搜索条件大多是前端筛选,而只有在点击查询按钮时才会发起后端请求。因此,Redis 可以用于快速存储和检索列车数据。
车票购票
初步完成车票购票
以及订单管理功能
, 目前出现部分错误。
- 购买车票
目前还没实现 订单提交
部分。
- 车票付款
订单管理
- 订单管理
乘车人信息
- 乘车人
并发场景
用户请求高峰
场景:
- 12306 可能在短时间内涌入大量用户同时抢购车票。
- 特别是春运、五一、国庆等节假日放票时,瞬间并发请求可达数百万 QPS(查询次数/秒)。
- 用户在搜索余票、提交订单、在线支付等环节,都会产生高并发请求。
技术挑战:
- 流量削峰:通过如虚拟队列防止瞬间请求压垮服务器 ->
消息队列MQ
- 高效缓存:热点数据(如车次信息、余票查询)-> 使用
Redis
等缓存技术减少数据库压力。 - CDN 分流:静态资源(如网页、图片等)-> 通过
CDN
缓存减少服务器压力。
订单提交与支付
场景:
- 订单提交时,需要多个操作:锁定座位、扣除余票、生成订单、引导支付。
- 高并发下,可能会出现超卖、订单丢失等问题。
技术挑战:
- 分布式锁:确保同一张票不会被多个用户同时购买 -> 使用
Redis 分布式锁
- 幂等性控制:防止用户因网络原因重复提交订单。
- 消息队列:下单请求进入消息队列,后台异步处理并生成订单,避免数据库写入过载。 ->
MQ
秒杀抢票
场景:
- 放票瞬间,大量用户并发请求导致服务器负载飙升。
- 恶意黄牛可能会利用脚本自动抢票,加剧竞争压力。
技术挑战:
-
验证码 & 人机验证:防止黄牛脚本自动抢票,提高抢票公平性。->
图形/点选/滑动/行为验证码
-
限流策略:限制单个 IP 或单个用户的请求频率,防止恶意刷票。->
Nginx限流
orRedis计数器
-
排队系统:用户进入排队队列,按顺序放行购买请求,避免系统崩溃。->
MQ
处理异步购票请求
并发量测试
使用 Jmeter
对系统并发能力进行测试
测试结果如下 :
线程数 | 有线程池 |
---|---|
2000 | 1ms内 |
4000 | 1s内 |
6000 | 2s内 |
8000 | 3s ~ 6s |
10000 | 3s ~ 7s |