
大型线下活动票务系统在面临高并发抢购时如何保证系统稳定、公平公正同时处理好各种异常场景是技术团队必须面对的挑战。本文将以一次实际的大型展会票务销售事件为背景深入分析从技术选型、架构设计到具体代码实现的全流程为开发类似高并发系统的工程师提供可落地的解决方案。1. 理解高并发票务系统的核心挑战1.1 业务场景分析大型展会活动票务销售通常面临几个典型特征瞬时高并发、库存有限、用户操作频繁、交易实时性要求高。以BWBilibili World这类大型展会为例数万张门票可能在几分钟内售罄同时有数十万用户在线抢票。技术层面需要解决的核心问题包括库存超卖问题如何保证不会卖出超过实际库存数量的票系统稳定性如何应对瞬间流量峰值而不宕机公平性问题如何防止黄牛利用技术手段抢票用户体验如何减少用户等待时间提供清晰的反馈1.2 技术架构选型考量在技术架构选择上需要平衡性能、成本和开发复杂度。常见的方案包括Redis集群分布式锁适合中等并发场景实现相对简单消息队列削峰填谷适合流量波动大的场景但会增加系统复杂度数据库乐观锁实现简单但在极高并发下性能较差令牌桶限流控制入口流量保护下游系统在实际项目中往往采用多种技术组合的方案。下面我们重点分析基于Redis的分布式锁方案。2. 环境准备与核心技术栈2.1 开发环境要求为了完整复现票务系统需要准备以下环境服务器环境配置Linux服务器CentOS 7.6或Ubuntu 18.04JDK 1.8或11Redis 6.0集群模式MySQL 8.0或PostgreSQL 12Nginx 1.18用于负载均衡关键依赖版本dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId version2.7.0/version /dependency dependency groupIdorg.redisson/groupId artifactIdredisson-spring-boot-starter/artifactId version3.17.7/version /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId version2.7.0/version /dependency /dependencies2.2 Redis集群配置票务系统的核心是Redis集群的合理配置。以下是生产环境推荐的配置# application-redis.yml spring: redis: cluster: nodes: - 192.168.1.101:6379 - 192.168.1.102:6379 - 192.168.1.103:6379 max-redirects: 3 lettuce: pool: max-active: 1000 max-wait: -1ms max-idle: 10 min-idle: 5 timeout: 2000ms3. 核心业务逻辑实现3.1 数据库表设计合理的数据库设计是系统稳定的基础。票务相关核心表结构如下CREATE TABLE ticket_event ( id BIGINT PRIMARY KEY AUTO_INCREMENT, event_name VARCHAR(100) NOT NULL COMMENT 活动名称, total_tickets INT NOT NULL COMMENT 总票数, sold_tickets INT DEFAULT 0 COMMENT 已售票数, start_time DATETIME NOT NULL COMMENT 开售时间, end_time DATETIME NOT NULL COMMENT 结束时间, status TINYINT DEFAULT 1 COMMENT 状态1-待开始 2-进行中 3-已结束, version INT DEFAULT 0 COMMENT 乐观锁版本号, created_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE ticket_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) UNIQUE NOT NULL COMMENT 订单号, user_id BIGINT NOT NULL COMMENT 用户ID, event_id BIGINT NOT NULL COMMENT 活动ID, ticket_count INT NOT NULL COMMENT 购票数量, amount DECIMAL(10,2) NOT NULL COMMENT 订单金额, status TINYINT DEFAULT 1 COMMENT 状态1-待支付 2-已支付 3-已取消, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, pay_time DATETIME COMMENT 支付时间, INDEX idx_user_id (user_id), INDEX idx_event_id (event_id) );3.2 分布式锁实现抢票逻辑使用Redisson实现分布式锁确保在高并发下库存扣减的原子性Service Slf4j public class TicketService { Autowired private RedissonClient redissonClient; Autowired private TicketEventMapper ticketEventMapper; Autowired private TicketOrderMapper ticketOrderMapper; public ApiResult purchaseTicket(Long eventId, Long userId, Integer count) { // 参数校验 if (eventId null || userId null || count null || count 0) { return ApiResult.error(参数错误); } // 获取分布式锁锁粒度细化到具体活动 String lockKey ticket_purchase_lock: eventId; RLock lock redissonClient.getLock(lockKey); try { // 尝试加锁最多等待3秒锁持有时间30秒 boolean locked lock.tryLock(3, 30, TimeUnit.SECONDS); if (!locked) { return ApiResult.error(系统繁忙请稍后重试); } // 查询活动信息 TicketEvent event ticketEventMapper.selectById(eventId); if (event null) { return ApiResult.error(活动不存在); } // 检查活动状态 if (event.getStatus() ! 2) { return ApiResult.error(活动未开始或已结束); } // 检查库存 int availableTickets event.getTotalTickets() - event.getSoldTickets(); if (availableTickets count) { return ApiResult.error(库存不足); } // 生成订单 TicketOrder order createOrder(event, userId, count); // 扣减库存使用乐观锁防止超卖 int updateCount ticketEventMapper.updateSoldTickets( eventId, count, event.getVersion()); if (updateCount 0) { // 乐观锁冲突说明其他请求已经修改了库存 return ApiResult.error(库存不足请重新尝试); } // 订单创建成功 return ApiResult.success(抢票成功, order.getOrderNo()); } catch (InterruptedException e) { Thread.currentThread().interrupt(); log.error(抢票过程被中断, e); return ApiResult.error(系统异常); } catch (Exception e) { log.error(抢票异常, e); return ApiResult.error(系统繁忙); } finally { // 释放锁 if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } private TicketOrder createOrder(TicketEvent event, Long userId, Integer count) { TicketOrder order new TicketOrder(); order.setOrderNo(generateOrderNo()); order.setUserId(userId); order.setEventId(event.getId()); order.setTicketCount(count); order.setAmount(calculateAmount(event, count)); order.setStatus(1); // 待支付 ticketOrderMapper.insert(order); return order; } private String generateOrderNo() { return T System.currentTimeMillis() ThreadLocalRandom.current().nextInt(1000, 9999); } }3.3 库存预热与缓存策略在抢票开始前将库存信息预热到Redis中减少数据库压力Component public class TicketCacheService { private static final String TICKET_STOCK_KEY ticket:stock:; private static final String TICKET_EVENT_KEY ticket:event:; Autowired private RedisTemplateString, Object redisTemplate; /** * 活动开始前预热库存到Redis */ public void preheatStock(Long eventId) { TicketEvent event ticketEventMapper.selectById(eventId); if (event ! null) { String stockKey TICKET_STOCK_KEY eventId; String eventKey TICKET_EVENT_KEY eventId; // 设置库存 redisTemplate.opsForValue().set(stockKey, event.getTotalTickets() - event.getSoldTickets()); // 设置活动信息过期时间设置为活动结束后1小时 long expireTime calculateExpireTime(event.getEndTime()); redisTemplate.opsForValue().set(eventKey, event, expireTime, TimeUnit.SECONDS); } } /** * 从缓存中获取库存 */ public Integer getStockFromCache(Long eventId) { String key TICKET_STOCK_KEY eventId; Object value redisTemplate.opsForValue().get(key); return value ! null ? Integer.parseInt(value.toString()) : null; } /** * 扣减缓存中的库存 */ public boolean decreaseStock(Long eventId, Integer count) { String key TICKET_STOCK_KEY eventId; Long result redisTemplate.opsForValue().decrement(key, count); return result ! null result 0; } }4. 高并发优化与限流策略4.1 网关层限流配置使用网关层限流保护后端服务以下是基于Spring Cloud Gateway的配置spring: cloud: gateway: routes: - id: ticket-service uri: lb://ticket-service predicates: - Path/api/ticket/** filters: - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 100 # 每秒允许的请求数 redis-rate-limiter.burstCapacity: 200 # 瞬时最大请求数 key-resolver: #{userKeyResolver} - name: StripPrefix1对应的KeyResolver配置Bean KeyResolver userKeyResolver() { return exchange - { String userId exchange.getRequest().getQueryParams().getFirst(userId); if (StringUtils.isEmpty(userId)) { // 如果没有userId使用IP限流 return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()); } return Mono.just(userId); }; }4.2 服务层限流与降级在服务层使用Resilience4j实现更细粒度的限流和熔断Service public class TicketOrderService { private final RateLimiter rateLimiter; private final CircuitBreaker circuitBreaker; public TicketOrderService() { // 每秒钟最多处理50个请求 RateLimiterConfig rateLimiterConfig RateLimiterConfig.custom() .limitRefreshPeriod(Duration.ofSeconds(1)) .limitForPeriod(50) .timeoutDuration(Duration.ofMillis(500)) .build(); this.rateLimiter RateLimiter.of(ticketRateLimiter, rateLimiterConfig); // 熔断器配置失败率超过50%时熔断 CircuitBreakerConfig circuitBreakerConfig CircuitBreakerConfig.custom() .failureRateThreshold(50) .waitDurationInOpenState(Duration.ofSeconds(30)) .ringBufferSizeInClosedState(100) .ringBufferSizeInHalfOpenState(10) .build(); this.circuitBreaker CircuitBreaker.of(ticketCircuitBreaker, circuitBreakerConfig); } RateLimiter(name ticketService) CircuitBreaker(name ticketService, fallbackMethod purchaseFallback) public ApiResult purchaseWithRateLimit(Long eventId, Long userId, Integer count) { return purchaseTicket(eventId, userId, count); } // 降级方法 private ApiResult purchaseFallback(Long eventId, Long userId, Integer count, Exception e) { log.warn(票务服务降级eventId: {}, userId: {}, eventId, userId, e); return ApiResult.error(当前排队人数过多请稍后重试); } }5. 异常处理与事务一致性5.1 分布式事务处理在分布式环境下需要保证订单创建和库存扣减的事务一致性Service public class TicketTransactionService { Transactional(rollbackFor Exception.class) public ApiResult purchaseWithTransaction(Long eventId, Long userId, Integer count) { try { // 1. 检查库存 TicketEvent event checkStock(eventId, count); // 2. 生成订单 TicketOrder order createOrder(event, userId, count); // 3. 扣减库存 decreaseStock(eventId, count); // 4. 发送订单创建消息 sendOrderCreatedMessage(order); return ApiResult.success(下单成功, order.getOrderNo()); } catch (BusinessException e) { // 业务异常直接抛出触发回滚 throw e; } catch (Exception e) { log.error(下单过程异常, e); throw new RuntimeException(系统异常订单创建失败); } } /** * 使用消息队列保证最终一致性 */ Async public void sendOrderCreatedMessage(TicketOrder order) { try { OrderMessage message new OrderMessage(); message.setOrderNo(order.getOrderNo()); message.setUserId(order.getUserId()); message.setEventId(order.getEventId()); message.setCreateTime(new Date()); // 发送延迟消息15分钟后检查支付状态 rocketMQTemplate.asyncSend(ORDER_CREATED_TOPIC, message, new SendCallback() { Override public void onSuccess(SendResult sendResult) { log.info(订单创建消息发送成功: {}, order.getOrderNo()); } Override public void onException(Throwable throwable) { log.error(订单创建消息发送失败: {}, order.getOrderNo(), throwable); // 消息发送失败记录日志并人工处理 } }, 3000, 3); // 延迟3秒重试3次 } catch (Exception e) { log.error(发送订单消息异常, e); } } }5.2 常见异常处理方案在实际运行中需要针对不同异常类型制定处理策略异常类型现象描述处理方案预防措施库存超卖实际售出票数超过库存人工核对补偿用户使用分布式锁乐观锁重复下单同一用户短时间内重复下单检查用户最近订单前端防重复提交后端校验网络超时请求响应时间过长自动重试机制优化SQL增加缓存系统宕机服务不可用快速故障转移集群部署健康检查6. 监控与日志排查6.1 关键指标监控建立完善的监控体系及时发现系统问题Component public class TicketMetrics { private final MeterRegistry meterRegistry; private final Counter purchaseSuccessCounter; private final Counter purchaseFailCounter; private final Timer purchaseTimer; public TicketMetrics(MeterRegistry meterRegistry) { this.meterRegistry meterRegistry; this.purchaseSuccessCounter Counter.builder(ticket.purchase.success) .description(成功购票次数) .register(meterRegistry); this.purchaseFailCounter Counter.builder(ticket.purchase.fail) .description(购票失败次数) .register(meterRegistry); this.purchaseTimer Timer.builder(ticket.purchase.duration) .description(购票处理时间) .register(meterRegistry); } public void recordPurchaseSuccess(long duration) { purchaseSuccessCounter.increment(); purchaseTimer.record(duration, TimeUnit.MILLISECONDS); } public void recordPurchaseFail(String reason) { purchaseFailCounter.increment(); Tags tags Tags.of(reason, reason); Counter.builder(ticket.purchase.fail.reason) .tags(tags) .register(meterRegistry) .increment(); } }6.2 日志排查策略制定清晰的日志规范便于问题排查Slf4j Aspect Component public class TicketLogAspect { Around(execution(* com.example.ticket.service.*.*(..))) public Object logServiceMethod(ProceedingJoinPoint joinPoint) throws Throwable { String methodName joinPoint.getSignature().getName(); Object[] args joinPoint.getArgs(); long startTime System.currentTimeMillis(); String traceId MDC.get(traceId); if (traceId null) { traceId generateTraceId(); MDC.put(traceId, traceId); } try { log.info(【Ticket】方法开始: {}, 参数: {}, traceId: {}, methodName, Arrays.toString(args), traceId); Object result joinPoint.proceed(); long endTime System.currentTimeMillis(); log.info(【Ticket】方法结束: {}, 耗时: {}ms, traceId: {}, methodName, (endTime - startTime), traceId); return result; } catch (Exception e) { log.error(【Ticket】方法异常: {}, 错误: {}, traceId: {}, methodName, e.getMessage(), traceId, e); throw e; } finally { MDC.clear(); } } private String generateTraceId() { return TICKET_ System.currentTimeMillis() _ ThreadLocalRandom.current().nextInt(1000, 9999); } }7. 生产环境部署建议7.1 架构部署方案对于大型票务系统推荐采用多机房部署方案前端负载均衡层Nginx LVS (主备) 应用服务层Spring Boot微服务集群 (多机房部署) 缓存层Redis集群 (主从架构) 数据库层MySQL主从复制 分库分表 消息队列RocketMQ集群 监控告警Prometheus Grafana 告警中心7.2 压测与容量规划在上线前必须进行充分的压力测试压测关键指标单机QPS2000响应时间P99 500ms错误率 0.1%系统资源CPU 70%内存 80%容量规划公式所需服务器数量 预期峰值QPS / 单机可承受QPS × 安全系数(1.5-2.0) Redis内存需求 活动数量 × 每个活动缓存大小 × 副本数 × 安全系数 数据库连接数 应用实例数 × 每个实例最大连接数7.3 应急预案制定完善的应急预案包括流量激增预案自动扩容触发条件与流程系统故障预案故障转移与数据恢复方案数据不一致预案对账与补偿机制安全攻击预案DDoS防护与业务风控策略大型票务系统的技术实现需要综合考虑并发控制、系统稳定性、数据一致性和用户体验等多个维度。在实际项目中建议先从小规模场景开始验证逐步优化和完善各项技术方案最终形成适合自身业务特点的高可用架构。