资讯动态

12306高并发架构解析:Redis与消息队列实战应用

发布时间:2026/9/6 1:25:19 来源:尧图企业网站定制
每到节假日12306抢票系统都会成为技术圈热议的话题。很多人好奇为什么一个看似简单的购票系统需要如此复杂的技术架构为什么普通电商系统能轻松应对的并发量在12306这里却成了技术难题实际上12306面临的挑战远超普通电商系统。它不仅要处理数百万用户同时抢票的并发压力还要解决票务库存这个特殊商品的复杂业务逻辑。一张车票从北京到上海中间可能经过10个站点每个区间段的座位库存都需要精确管理。这种复杂的业务场景让传统的数据库锁机制和简单的缓存方案都显得力不从心。本文将深入剖析12306抢票系统的技术架构重点解析它如何通过Redis、消息队列等技术手段来应对百万级并发。更重要的是我们会通过实际代码示例展示这些技术在实际项目中的具体应用让你不仅理解原理更能掌握实现方法。1. 12306面临的独特技术挑战1.1 为什么普通电商架构无法直接套用很多人以为12306就是一个大型电商系统但实际上两者在业务逻辑上存在本质差异。普通电商商品库存管理相对简单一个商品对应一个库存数量用户下单减库存取消订单加库存。但火车票库存管理要复杂得多。以一张从北京到广州的高铁票为例它可能被拆分为多个区间段销售北京-石家庄石家庄-郑州郑州-武汉武汉-广州每个座位在不同区间段都可以独立销售这就产生了复杂的库存计算问题。当多个用户同时购买不同区间的车票时系统需要确保同一个座位不会被重复售出。1.2 高并发下的数据一致性难题在春运等高峰期12306需要应对每秒数十万次的查询请求和数万次的购票请求。这种并发量下传统的数据序列化方案会立即崩溃。即使使用数据库行锁也会因为锁竞争导致系统性能急剧下降。更复杂的是购票业务需要保证ACID特性原子性扣减库存和创建订单必须同时成功或失败一致性不能超售也不能少售隔离性多个用户抢同一张票时要有合理的处理机制持久性成功购票后数据不能丢失2. 核心技术架构解析2.1 分层架构设计12306采用典型的分层架构将系统拆分为多个独立的服务层用户界面层 → 网关层 → 业务服务层 → 数据访问层 → 存储层每层都有特定的职责和优化策略用户界面层负责页面渲染和静态资源缓存大量使用CDN加速网关层进行流量控制、安全过滤和请求分发业务服务层处理核心业务逻辑采用微服务架构数据访问层封装数据操作提供缓存和数据库访问存储层使用多种存储方案应对不同场景2.2 读写分离与数据分片为了应对高并发查询12306采用了读写分离架构。所有的查询请求都被路由到只读副本只有写操作才会访问主数据库。这种设计大幅降低了主数据库的压力。数据分片是另一个关键技术。根据车次、日期等维度将数据分布到不同的数据库实例上避免单点瓶颈。例如不同日期的车票数据存储在不同的数据库分片中。3. Redis在抢票系统中的核心作用3.1 为什么选择RedisRedis之所以成为12306的首选缓存方案主要基于以下几个优势极高的性能内存操作读写速度达到微秒级丰富的数据结构支持String、Hash、List、Set等适合不同的业务场景原子操作原生支持原子性操作避免并发问题持久化能力支持RDB和AOF两种持久化方式保证数据安全3.2 车票库存缓存设计12306使用Redis的Hash结构来存储车票库存信息。每个车次对应一个Hash字段表示座位类型和区间段值表示剩余票数。# Redis存储结构示例 HSET train:D1234 2024-02-10:business:北京-上海 100 HSET train:D1234 2024-02-10:first:北京-上海 200 HSET train:D1234 2024-02-10:second:北京-上海 500对应的Java操作代码// 初始化车票库存 public void initTrainTicketStock(String trainNo, String date, MapString, Integer seatStock) { String redisKey train: trainNo : date; try (Jedis jedis jedisPool.getResource()) { // 使用pipeline批量操作提高性能 Pipeline pipeline jedis.pipelined(); for (Map.EntryString, Integer entry : seatStock.entrySet()) { pipeline.hset(redisKey, entry.getKey(), entry.getValue().toString()); } // 设置过期时间避免内存泄漏 pipeline.expire(redisKey, 24 * 3600); pipeline.sync(); } } // 查询剩余票数 public int getRemainingTickets(String trainNo, String date, String seatType) { String redisKey train: trainNo : date; try (Jedis jedis jedisPool.getResource()) { String remaining jedis.hget(redisKey, seatType); return remaining ! null ? Integer.parseInt(remaining) : 0; } }3.3 分布式锁实现座位预留在用户选座过程中需要临时锁定座位防止其他用户同时选择。12306使用Redis分布式锁来实现这一功能。// 基于Redis的分布式锁实现 public class SeatLockManager { private static final String LOCK_PREFIX seat_lock:; private static final int LOCK_EXPIRE 30; // 锁过期时间30秒 // 尝试锁定座位 public boolean tryLockSeat(String trainNo, String date, String seatId) { String lockKey LOCK_PREFIX trainNo : date : seatId; try (Jedis jedis jedisPool.getResource()) { // 使用SETNX实现原子性加锁 String result jedis.set(lockKey, locked, NX, EX, LOCK_EXPIRE); return OK.equals(result); } } // 释放座位锁 public void unlockSeat(String trainNo, String date, String seatId) { String lockKey LOCK_PREFIX trainNo : date : seatId; try (Jedis jedis jedisPool.getResource()) { jedis.del(lockKey); } } // 续期锁 public boolean renewLock(String trainNo, String date, String seatId) { String lockKey LOCK_PREFIX trainNo : date : seatId; try (Jedis jedis jedisPool.getResource()) { // 检查锁是否还存在如果存在则续期 if (jedis.exists(lockKey)) { jedis.expire(lockKey, LOCK_EXPIRE); return true; } return false; } } }4. 消息队列的异步处理机制4.1 为什么需要消息队列在抢票高峰期如果所有的购票请求都同步处理系统很容易因为瞬时压力而崩溃。消息队列通过异步处理的方式将峰值流量削峰填谷保证系统的稳定性。12306主要使用消息队列处理以下场景订单创建和库存扣减的异步化支付结果的异步通知短信和邮件通知的发送数据统计和日志收集4.2 RabbitMQ在12306中的应用以下是一个简化的购票消息处理示例// 购票消息生产者 Component public class TicketOrderProducer { Autowired private RabbitTemplate rabbitTemplate; // 发送购票消息 public void sendOrderMessage(TicketOrder order) { try { Message message MessageBuilder .withBody(JsonUtils.toJson(order).getBytes()) .setContentType(MessageProperties.CONTENT_TYPE_JSON) .setHeader(order_type, ticket_purchase) .build(); // 发送到订单队列设置消息持久化 rabbitTemplate.convertAndSend(order.exchange, order.ticket, message, new MessagePostProcessor() { Override public Message postProcessMessage(Message message) throws AmqpException { message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT); return message; } }); } catch (Exception e) { // 记录日志并触发补偿机制 log.error(发送购票消息失败: {}, order.getOrderId(), e); compensateOrder(order); } } } // 购票消息消费者 Component public class TicketOrderConsumer { RabbitListener(queues order.ticket.queue) public void processOrderMessage(Message message) { try { TicketOrder order JsonUtils.fromJson(new String(message.getBody()), TicketOrder.class); // 处理订单业务逻辑 boolean success orderService.processOrder(order); if (success) { // 确认消息消费成功 channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); } else { // 处理失败进入重试或死信队列 channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true); } } catch (Exception e) { log.error(处理购票消息异常, e); // 根据异常类型决定重试或进入死信队列 handleException(message, e); } } }4.3 消息队列的可靠性保证为了保证消息不丢失12306采用了以下措施消息持久化将消息和队列都设置为持久化模式生产者确认确保消息成功发送到Broker消费者确认手动ACK确保消息被成功处理死信队列处理失败的消息进入死信队列进行人工干预5. 数据库优化策略5.1 MySQL分库分表设计虽然Redis承担了大部分的读压力但MySQL仍然是最终的数据存储。12306采用分库分表策略来分散写入压力。-- 按日期分表示例 CREATE TABLE ticket_order_20240210 ( id BIGINT PRIMARY KEY, order_no VARCHAR(32) NOT NULL, train_no VARCHAR(10) NOT NULL, passenger_id BIGINT NOT NULL, seat_info JSON, order_status TINYINT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_order_no(order_no), INDEX idx_train_date(train_no, create_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 按车次分库不同的车次路由到不同的数据库实例 -- 通过中间件实现分库路由5.2 数据库连接池优化高并发场景下数据库连接成为稀缺资源。12306对连接池进行了深度优化// HikariCP连接池配置示例 Configuration public class DataSourceConfig { Bean public HikariDataSource dataSource() { HikariConfig config new HikariConfig(); config.setJdbcUrl(jdbc:mysql://localhost:3306/ticket_db); config.setUsername(username); config.setPassword(password); config.setDriverClassName(com.mysql.cj.jdbc.Driver); // 连接池优化配置 config.setMaximumPoolSize(50); // 最大连接数 config.setMinimumIdle(10); // 最小空闲连接 config.setConnectionTimeout(30000); // 连接超时时间 config.setIdleTimeout(600000); // 空闲连接超时时间 config.setMaxLifetime(1800000); // 连接最大生命周期 config.setLeakDetectionThreshold(60000); // 泄漏检测阈值 // 优化SQL执行 config.addDataSourceProperty(cachePrepStmts, true); config.addDataSourceProperty(prepStmtCacheSize, 250); config.addDataSourceProperty(prepStmtCacheSqlLimit, 2048); return new HikariDataSource(config); } }6. 限流与熔断机制6.1 网关层限流在系统入口处进行限流防止过多请求涌入系统// 基于令牌桶的限流实现 Component public class RateLimiter { private final RateLimiter limiter RateLimiter.create(1000); // 每秒1000个令牌 public boolean tryAcquire() { return limiter.tryAcquire(); } public boolean tryAcquire(int permits) { return limiter.tryAcquire(permits); } } // 在网关过滤器中使用 Component public class RateLimitFilter implements GlobalFilter { Autowired private RateLimiter rateLimiter; Override public MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) { if (!rateLimiter.tryAcquire()) { // 返回429 Too Many Requests exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS); return exchange.getResponse().setComplete(); } return chain.filter(exchange); } }6.2 服务熔断与降级当某个服务出现故障时熔断器可以防止故障扩散// 使用Resilience4j实现熔断 CircuitBreaker(name ticketService, fallbackMethod fallbackMethod) Service public class TicketService { public TicketInfo getTicketInfo(String trainNo, String date) { // 调用车票查询服务 return remoteTicketService.query(trainNo, date); } // 降级方法 public TicketInfo fallbackMethod(String trainNo, String date, Exception e) { // 返回缓存数据或默认值 return getCachedTicketInfo(trainNo, date); } } // 熔断器配置 Configuration public class CircuitBreakerConfig { Bean public CircuitBreakerConfigCustomizer circuitBreakerConfig() { return CircuitBreakerConfigCustomizer .of(ticketService, builder - builder .slidingWindowSize(100) // 滑动窗口大小 .failureRateThreshold(50) // 失败率阈值 .waitDurationInOpenState(Duration.ofSeconds(60)) // 熔断等待时间 .permittedNumberOfCallsInHalfOpenState(10) // 半开状态允许的调用次数 ); } }7. 实战构建简易抢票系统7.1 环境准备让我们搭建一个简化版的抢票系统来验证上述技术方案# 安装Redis docker run -d --name redis -p 6379:6379 redis:6.2-alpine # 安装RabbitMQ docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3.9-management # 创建Spring Boot项目 spring init --dependenciesweb,data-redis,amqp my-ticket-system7.2 核心业务实现// 车票服务核心实现 Service public class TicketServiceImpl implements TicketService { Autowired private RedisTemplateString, Object redisTemplate; Autowired private TicketOrderProducer orderProducer; Override public boolean purchaseTicket(PurchaseRequest request) { String lockKey lock:ticket: request.getTrainNo() : request.getDate(); String stockKey stock:ticket: request.getTrainNo() : request.getDate(); // 使用Redis分布式锁 String lockValue UUID.randomUUID().toString(); try { // 尝试加锁 Boolean locked redisTemplate.opsForValue() .setIfAbsent(lockKey, lockValue, Duration.ofSeconds(30)); if (!Boolean.TRUE.equals(locked)) { throw new BusinessException(系统繁忙请稍后重试); } // 检查库存 Integer stock (Integer) redisTemplate.opsForValue().get(stockKey); if (stock null || stock 0) { throw new BusinessException(票已售罄); } // 扣减库存 Long remaining redisTemplate.opsForValue().decrement(stockKey); if (remaining 0) { // 库存不足回滚 redisTemplate.opsForValue().increment(stockKey); throw new BusinessException(票已售罄); } // 创建订单 TicketOrder order createOrder(request); orderProducer.sendOrderMessage(order); return true; } finally { // 释放锁 if (lockValue.equals(redisTemplate.opsForValue().get(lockKey))) { redisTemplate.delete(lockKey); } } } }7.3 压力测试验证使用JMeter进行并发测试!-- JMeter测试计划示例 -- ?xml version1.0 encodingUTF-8? jmeterTestPlan version1.2 properties5.0 jmeter5.4.1 hashTree TestPlan guiclassTestPlanGui testclassTestPlan testnameTicket System Stress Test boolProp nameTestPlan.functional_modefalse/boolProp stringProp nameTestPlan.comments12306抢票系统压力测试/stringProp /TestPlan hashTree ThreadGroup guiclassThreadGroupGui testclassThreadGroup testname并发用户组 intProp nameThreadGroup.num_threads1000/intProp intProp nameThreadGroup.ramp_time60/intProp longProp nameThreadGroup.start_time0/longProp longProp nameThreadGroup.end_time0/longProp boolProp nameThreadGroup.schedulerfalse/boolProp /ThreadGroup hashTree HTTPSamplerProxy guiclassHttpTestSampleGui testclassHTTPSamplerProxy testname购票请求 stringProp nameHTTPSampler.domainlocalhost/stringProp stringProp nameHTTPSampler.port8080/stringProp stringProp nameHTTPSampler.protocolhttp/stringProp stringProp nameHTTPSampler.path/api/ticket/purchase/stringProp stringProp nameHTTPSampler.methodPOST/stringProp /HTTPSamplerProxy /hashTree /hashTree /hashTree /jmeterTestPlan8. 常见问题与解决方案8.1 超售问题问题现象同一张票被多个用户成功购买根本原因库存检查和高库存扣减不是原子操作解决方案使用Redis的原子操作或数据库悲观锁// 使用Redis Lua脚本保证原子性 private static final String PURCHASE_SCRIPT local stock tonumber(redis.call(get, KEYS[1])) if stock and stock 0 then redis.call(decr, KEYS[1]) return 1 else return 0 end; public boolean atomicPurchase(String stockKey) { DefaultRedisScriptLong script new DefaultRedisScript(); script.setScriptText(PURCHASE_SCRIPT); script.setResultType(Long.class); Long result redisTemplate.execute(script, Collections.singletonList(stockKey)); return result ! null result 0; }8.2 缓存穿透问题问题现象大量请求查询不存在的车次信息直接访问数据库解决方案布隆过滤器缓存空值// 使用布隆过滤器防止缓存穿透 Component public class BloomFilterService { Autowired private RedisTemplateString, Object redisTemplate; private static final String BLOOM_FILTER_KEY bloom:train; // 初始化布隆过滤器 public void initBloomFilter(ListString trainNos) { for (String trainNo : trainNos) { addToBloomFilter(trainNo); } } // 添加元素 public void addToBloomFilter(String trainNo) { long[] offsets getHashOffsets(trainNo); for (long offset : offsets) { redisTemplate.opsForValue().setBit(BLOOM_FILTER_KEY, offset, true); } } // 检查元素是否存在 public boolean mightContain(String trainNo) { long[] offsets getHashOffsets(trainNo); for (long offset : offsets) { if (!Boolean.TRUE.equals(redisTemplate.opsForValue().getBit(BLOOM_FILTER_KEY, offset))) { return false; } } return true; } private long[] getHashOffsets(String value) { // 使用多个哈希函数计算偏移量 // 简化实现实际项目应使用更复杂的哈希算法 return new long[]{ Math.abs(value.hashCode()) % 1000000, Math.abs((value salt1).hashCode()) % 1000000, Math.abs((value salt2).hashCode()) % 1000000 }; } }8.3 消息重复消费问题现象同一条购票消息被处理多次导致重复扣款解决方案幂等性处理消息去重// 幂等性处理 Service public class IdempotentService { Autowired private RedisTemplateString, Object redisTemplate; private static final String IDEMPOTENT_PREFIX idempotent:; private static final Duration EXPIRE_TIME Duration.ofHours(24); // 检查并标记请求ID public boolean checkAndMark(String requestId) { String key IDEMPOTENT_PREFIX requestId; Boolean success redisTemplate.opsForValue() .setIfAbsent(key, processed, EXPIRE_TIME); return Boolean.TRUE.equals(success); } } // 在消息消费者中使用 Component public class OrderMessageConsumer { Autowired private IdempotentService idempotentService; RabbitListener(queues order.queue) public void processOrder(OrderMessage message) { // 检查幂等性 if (!idempotentService.checkAndMark(message.getMessageId())) { log.warn(重复消息直接确认: {}, message.getMessageId()); channel.basicAck(tag, false); return; } // 处理业务逻辑 orderService.processOrder(message); } }9. 生产环境最佳实践9.1 监控与告警建立完善的监控体系是保证系统稳定性的关键# Prometheus监控配置示例 scrape_configs: - job_name: ticket-service static_configs: - targets: [localhost:8080] metrics_path: /actuator/prometheus - job_name: redis static_configs: - targets: [localhost:6379] - job_name: rabbitmq static_configs: - targets: [localhost:15672] # 关键监控指标 - 系统QPS、响应时间、错误率 - Redis内存使用率、连接数、命中率 - 数据库连接池使用情况、慢查询 - 消息队列堆积情况、消费速率9.2 容量规划与弹性伸缩根据业务峰值进行容量规划日常容量按平均流量的2倍配置资源节假日容量按历史峰值的1.5倍配置资源弹性伸缩基于CPU使用率、请求队列长度等指标自动扩容# Kubernetes HPA配置 apiVersion: autoscaling/v2beta2 kind: HorizontalPodAutoscaler metadata: name: ticket-service-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: ticket-service minReplicas: 2 maxReplicas: 20 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 709.3 灾难恢复与数据备份建立多活架构和数据备份机制# Redis数据备份脚本 #!/bin/bash # 每天凌晨执行Redis RDB备份 redis-cli BGSAVE sleep 60 cp /var/lib/redis/dump.rdb /backup/redis/redis-$(date %Y%m%d).rdb # 保留最近7天的备份 find /backup/redis -name redis-*.rdb -mtime 7 -delete12306抢票系统的高并发架构设计体现了现代分布式系统的核心技术思想。通过Redis实现高速缓存和分布式锁通过消息队列实现异步处理和流量削峰通过数据库分片和读写分离解决存储瓶颈这些技术组合在一起构成了一个能够应对百万级并发的稳定系统。在实际项目中应用这些技术时需要根据具体业务场景进行调整和优化。关键是要理解每种技术解决的问题和适用场景避免盲目套用。建议从简单的单机版开始逐步引入分布式组件通过压力测试验证系统性能最终构建出适合自己业务的高并发架构。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价