1. 抢票系统核心挑战:

  • 瞬时高并发:百万级别用户同时请求
  • 数据强一致性:扣除库存保证准确,防止超卖
  • 系统稳定性:防止雪崩
  • 涉及多方系统:查询系统、订单系统、支付系统、通知系统等
  • 公平性:防止黄牛恶意刷票,保证用户公平抢票

2. 架构设计

  • 客户端请求API网关
  • 网关做限流(RateLimter)、防刷、路由(Apigee/Nginx)操作
  • 订单服务:订单服务收到请求,先检查用户状态、余额等
  • 订单服务调用库存服务,尝试扣减库存(库存服务是核心,必须高性能、高可用)
  • 库存服务:扣减库存成功——》创建订单——》返回“抢票成功,请支付”
  • 用户服务:抢票成功后发送消息到MQ:通知用户/超时支付任务
  • 支付服务:用户支付——》支付服务回调——》订单服务更新状态——》通知库存服务确认库存扣减

 image

 3. 核心模块设计

3.1 库存模块(核心模块)

 库存模块必须满足:

  • 高并发读写
  • 强一致性
  • 防超卖
  • 高性能

方案流程:Redis+MySQL+分布式锁

  1.  库存预热

    系统启动时或者定时任务将库存从DB加载到redis。redis中存储总库存、已售库存、版本号(乐观锁用)

   2. 扣减库存与库存回滚流程

    使用Redis原子操作(INCR/DECR或者LUA脚本);

    如下代码逻辑:首先初始化当前航班总库存到redis中,key是当前航班号,value是当前航班所有库存;

          然后进行库存扣除:通过redis lua脚本判断当前库存和要扣除数量对比,如果扣除数量<当前库存则允许扣除库存 redis.call('decrby', key, quantity);                

// 基于Redis的分布式库存管理
@Component
public class TicketInventoryService {
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    // 初始化库存;flightId:被抢机票系统对应航班号;totalSeat:总库存
    public void initInventory(String flightId, int totalSeats) {
        String key = "inventory:flight:" + flightId;
        redisTemplate.opsForValue().set(key, String.valueOf(totalSeats));
        
        // 设置库存预热缓存
        String stockKey = "stock:hot:" + flightId;
        redisTemplate.opsForHash().putAll(stockKey, 
            Map.of("total", String.valueOf(totalSeats),
                   "available", String.valueOf(totalSeats)));
    }
    
    // 扣减库存(使用Lua脚本保证原子性);flightId:航班号;quantity:扣减库存数量
    public boolean deductInventory(String flightId, int quantity) {
        String luaScript = """
            local key = KEYS[1]
            local quantity = tonumber(ARGV[1])
            local current = tonumber(redis.call('get', key) or 0) //current当前总库存
            
            if current >= quantity then
                redis.call('decrby', key, quantity)
                return 1
            else
                return 0
            end
            """;
        
        RedisScript<Long> script = RedisScript.of(luaScript, Long.class);
        Long result = redisTemplate.execute(script, 
            Collections.singletonList("inventory:flight:" + flightId), 
            String.valueOf(quantity));
        
        return result == 1;
    }
    
    // 库存回滚--订单取消或者支付超时后恢复库存
    public void rollbackInventory(String flightId, int quantity) {
        redisTemplate.opsForValue().increment(
            "inventory:flight:" + flightId, quantity);
    }
}

 

  3. 最终一致性

  redis扣减库存成功后,异步写入MySQL中,如果写入失败,则通过重试+补偿机制保证最终一致性

   4. 分布式锁

  为了防止并发问题,使用redis lock来保证

  5. 库存回滚

  订单超时未支付——》释放缓存

  通过MQ延迟消息实现

3.2 订单抢票模块

方案流程:Redis分布式锁+雪花算法+异步MQ

基于航班号为key的redis分布式锁——》调用库存模块库存检验判断当前库存——》基于雪花算法生成订单号——》调用库存模块进行库存扣除——》使用MQ异步处理订单

@Service
public class TicketGrabService {
    
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private TicketInventoryService inventoryService;
    @Autowired
    private TicketOrderService orderService;
    @Autowired
    private KafkaTemplate kafkaTemplate;
    
    // 分布式锁实现抢票
    public GrabResult grabTicket(GrabRequest request) {
        String lockKey = "ticket:lock:" + request.getFlightNo(); //航班号作为key
        RLock lock = redissonClient.getLock(lockKey);
        
        try {
            // 尝试获取锁,等待3秒,锁有效期10秒
            if (lock.tryLock(3, 10, TimeUnit.SECONDS)) {
                // 1. 检查库存
                if (!inventoryService.checkInventory(request)) {
                    return GrabResult.fail("票已售罄");
                }
                
                // 2. 雪花算法生成订单号;雪花算法是生成分布式唯一ID的核心方案,能高效生成有序、唯一的Long型ID
                String orderNo = generateOrderNo();
                
                // 3. 扣减库存
                if (inventoryService.reduceInventory(request.getFlightNo(), 1)) {
                    // 4. 异步处理订单
            Order order=buildOrder(request,orderNo); //创建订单
                    kafkaTemplate.send(order); //订单异步落库;之后通过kafka消费者接收到消息后进行异步落库处理
                    return GrabResult.success(orderNo);
                }
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
        
        return GrabResult.fail("抢票失败");
    }

// 生成订单号(雪花算法)
    private String generateOrderNo() {
        Snowflake snowflake = IdUtil.getSnowflake(1, 1);
        return "TKT" + DateUtil.format(new Date(), "yyyyMMdd") 
               + snowflake.nextIdStr().substring(0, 10);
    }
}
View Code

 

 

总结抢票系统关键技术解决方案

1.高并发解决方案:

1.1 缓存预热--抢票前预热数据到redis

1.2令牌桶限流--(基于Guava/Redis RateLimiter限流策略

@Component
public class RateLimitService {
    
    private final RateLimiter rateLimiter = RateLimiter.create(1000); // 每秒1000个请求
    
    public boolean tryAcquire() {
        return rateLimiter.tryAcquire();
    }
    
    // 分布式限流(基于Redis)
    public boolean distributedRateLimit(String key, int limit, int timeout) {
        String luaScript = 
            "local current = redis.call('incr', KEYS[1]) " +
            "if current == 1 then " +
            "    redis.call('expire', KEYS[1], ARGV[1]) " +
            "end " +
            "return current <= tonumber(ARGV[2])";
        
        return (Boolean) redisTemplate.execute(
            new DefaultRedisScript<>(luaScript, Boolean.class),
            Collections.singletonList(key),
            timeout, limit
        );
    }
}
View Code

 

2 防超卖解决方案

2.1 Redis Lua脚本原子操作

public class AtomicInventoryService {
    
    private static final String REDUCE_INVENTORY_SCRIPT = 
        "local key = KEYS[1] " +
        "local quantity = tonumber(ARGV[1]) " +
        "local available = tonumber(redis.call('get', key) or '0') " +
        
        "if available >= quantity then " +
        "    redis.call('decrby', key, quantity) " +
        "    return 1 " +
        "else " +
        "    return 0 " +
        "end";
    
    public boolean atomicReduce(String key, int quantity) {
        Long result = (Long) redisTemplate.execute(
            new DefaultRedisScript<>(REDUCE_INVENTORY_SCRIPT, Long.class),
            Collections.singletonList(key),
            String.valueOf(quantity)
        );
        return result == 1;
    }
}
View Code

 

2.2 数据库乐观锁

@Repository
public class TicketOrderRepository {
    
    @Transactional
    public boolean createOrderWithOptimisticLock(Order order) {
        // 版本号控制
        String sql = "UPDATE flight_seats SET " +
                    "available = available - 1, " +
                    "version = version + 1 " +
                    "WHERE flight_no = ? " +
                    "AND available > 0 " +
                    "AND version = ?";
        
        int rows = jdbcTemplate.update(
            sql, 
            order.getFlightNo(),
            order.getVersion()
        );
        
        return rows > 0;
    }
}
View Code

 

3 可靠性保证 -- 重试 降级

@Component
public class ReliabilityService {
    
    // 1. 重试机制
    @Retryable(value = Exception.class, 
               maxAttempts = 3,
               backoff = @Backoff(delay = 1000, multiplier = 2))
    public OrderResult createOrderWithRetry(OrderRequest request) {
        return ticketService.createOrder(request);
    }
    
    // 2. 降级策略
    @HystrixCommand(fallbackMethod = "fallbackGrabTicket",
                   commandProperties = {
                       @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000"),
                       @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20")
                   })
    public GrabResult grabTicketWithFallback(GrabRequest request) {
        return grabService.grabTicket(request);
    }
    
    public GrabResult fallbackGrabTicket(GrabRequest request) {
        // 返回友好提示或加入异步队列
        return GrabResult.fail("系统繁忙,请稍后重试");
    }
}
View Code

 

4 防刷策略 -- 验证码 分析用户抢票频率

@Component
public class AntiBrushService {
    
    // IP限流
    public boolean checkIpLimit(String ip) {
        String key = "limit:ip:" + ip + ":" + LocalDate.now();
        return rateLimitService.distributedRateLimit(key, 100, 86400);
    }
    
    // 用户行为分析
    public boolean isMaliciousUser(String userId) {
        String patternKey = "user:pattern:" + userId;
        // 分析用户抢票频率、成功率等特征
        // 使用机器学习模型判断
        return false;
    }
    
    // 验证码验证
    public boolean verifyCaptcha(String sessionId, String captcha) {
        String key = "captcha:" + sessionId;
        String storedCaptcha = (String) redisTemplate.opsForValue().get(key);
        return captcha != null && captcha.equalsIgnoreCase(storedCaptcha);
    }
}
View Code

 

5 监控与日志  grafana(数据可视化分析平台)和prometheus(数据采集存储时序数据库)

 

posted on 2026-01-27 12:35  colorfulworld  阅读(0)  评论(0)    收藏  举报