秒杀系统设计全攻略:从高并发瓶颈到分布式解决方案

大家好!今天咱们聊一个电商开发中的大难题 - 秒杀系统的设计与实现。如果你曾经参与过"双 11"、"618"这样的大促活动开发,一定知道秒杀功能有多么棘手。一个设计不当的秒杀系统,分分钟能让你的服务器崩溃,还可能导致超卖、订单错乱等一系列问题。

秒杀系统的核心挑战

秒杀活动的特点就是"人多、商品少、时间短",这就导致了几个典型挑战:

  1. 瞬时高并发 - 短时间内可能有上万甚至上百万用户同时抢购
  2. 库存超卖 - 没有正确的并发控制,100 件商品可能卖出 150 件
  3. 黄牛与恶意请求 - 大量机器人和脚本攻击
  4. 用户体验 - 系统需在高并发下保持响应速度

我们来用图表直观展示秒杀系统的请求特点:

graph LR A[秒杀开始] --> B[请求量] B --> C[峰值期] C --> D[平缓期] C --> E[系统压力] E --> F[数据库压力] E --> G[应用服务压力] E --> H[网络带宽压力]

整体架构设计

一个完整的秒杀系统架构通常包含以下几个部分:

这个架构的核心思想是:将瞬时高并发请求拦截在上游,只让有效的请求进入到核心业务逻辑处理流程,同时通过异步处理减轻数据库压力。

前端限流:第一道防线

秒杀系统的防护要从前端开始。实现思路:

  1. 动态 URL - 秒杀接口地址在活动开始前不对外公布,并添加签名验证防伪造
  2. 页面静态化 - 将秒杀页面做成静态资源,通过 CDN 分发,设置合理缓存策略
  3. 倒计时同步 - 服务端与客户端时间同步,避免提前请求
  4. 多维度验证码 - 结合行为轨迹分析的滑动拼图/点选验证,防 OCR 破解

前端性能极致优化

javascript 复制代码
// 资源预加载配置
document.addEventListener('DOMContentLoaded', function() {
    // 预加载秒杀页面资源
    var preloadLink = document.createElement('link');
    preloadLink.rel = 'preload';
    preloadLink.as = 'image';
    preloadLink.href = '/images/seckill_banner.webp'; // WebP格式图片
    document.head.appendChild(preloadLink);

    // 预连接CDN和API域名
    var domains = ['cdn.example.com', 'api.example.com'];
    domains.forEach(function(domain) {
        var link = document.createElement('link');
        link.rel = 'preconnect';
        link.href = 'https://' + domain;
        document.head.appendChild(link);
    });

    // 延迟加载非关键资源
    lazyLoadNonCriticalResources();
});

// 延迟加载非关键资源
function lazyLoadNonCriticalResources() {
    // 使用Intersection Observer API检测元素是否进入视口
    var observer = new IntersectionObserver(function(entries) {
        entries.forEach(function(entry) {
            if (entry.isIntersecting) {
                var lazyElement = entry.target;
                if (lazyElement.dataset.src) {
                    lazyElement.src = lazyElement.dataset.src;
                    lazyElement.removeAttribute('data-src');
                }
                observer.unobserve(lazyElement);
            }
        });
    });

    // 对所有懒加载元素进行观察
    document.querySelectorAll('.lazy-load').forEach(function(element) {
        observer.observe(element);
    });
}

// 注册Service Worker缓存策略
if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/sw.js')
        .then(function(registration) {
            console.log('Service Worker registered with scope:', registration.scope);
        })
        .catch(function(error) {
            console.error('Service Worker registration failed:', error);
        });
}

Service Worker 实现(sw.js):

javascript 复制代码
// 缓存版本号,更新缓存时更改此版本号
const CACHE_VERSION = 'v1.0.2';
const CACHE_NAME = `seckill-cache-${CACHE_VERSION}`;

// 需要缓存的资源列表
const CACHE_URLS = [
    '/',
    '/index.html',
    '/css/main.min.css',
    '/js/main.min.js',
    '/js/vendor.min.js',
    '/images/seckill_banner.webp',
    '/images/icons/favicon.ico'
];

// Service Worker安装时缓存静态资源
self.addEventListener('install', event => {
    event.waitUntil(
        caches.open(CACHE_NAME)
            .then(cache => {
                return cache.addAll(CACHE_URLS);
            })
            .then(() => self.skipWaiting())
    );
});

// 清理旧版本缓存
self.addEventListener('activate', event => {
    event.waitUntil(
        caches.keys().then(cacheNames => {
            return Promise.all(
                cacheNames
                    .filter(cacheName => cacheName.startsWith('seckill-cache-') && cacheName !== CACHE_NAME)
                    .map(cacheName => caches.delete(cacheName))
            );
        }).then(() => self.clients.claim())
    );
});

// 网络请求拦截策略:缓存优先,网络回退
self.addEventListener('fetch', event => {
    // 只处理GET请求
    if (event.request.method !== 'GET') return;

    // API请求不缓存
    if (event.request.url.includes('/api/')) {
        return;
    }

    event.respondWith(
        caches.match(event.request)
            .then(cachedResponse => {
                // 命中缓存则返回缓存
                if (cachedResponse) {
                    return cachedResponse;
                }

                // 未命中缓存则发起网络请求
                return fetch(event.request)
                    .then(response => {
                        // 检查状态码
                        if (!response || response.status !== 200 || response.type !== 'basic') {
                            return response;
                        }

                        // 复制响应,因为响应流只能被读取一次
                        var responseToCache = response.clone();

                        // 缓存新的响应
                        caches.open(CACHE_NAME)
                            .then(cache => {
                                cache.put(event.request, responseToCache);
                            });

                        return response;
                    });
            })
    );
});

Nginx HTTP/2 与压缩配置:

nginx 复制代码
server {
    listen 443 ssl http2;
    server_name seckill.example.com;

    ssl_certificate     /path/to/certificate.crt;
    ssl_certificate_key /path/to/private.key;

    # HTTP/2设置
    http2_push_preload on;
    http2_max_concurrent_streams 128;

    # Brotli压缩(比gzip更高效)
    brotli on;
    brotli_comp_level 6;
    brotli_types text/plain text/css application/javascript application/json image/svg+xml;

    # 缓存控制
    location ~* \.(css|js|jpg|jpeg|png|webp|svg|woff2)$ {
        expires 7d;
        add_header Cache-Control "public, max-age=604800, immutable";
    }

    # API请求不缓存
    location /api/ {
        proxy_pass http://backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        add_header Cache-Control "no-store, no-cache, must-revalidate";
    }
}

前端性能监控:

javascript 复制代码
// 性能指标收集
function collectPerformanceMetrics() {
    // 等待所有资源加载完成
    window.addEventListener('load', function() {
        setTimeout(function() {
            const perfData = window.performance.timing;
            const pageLoadTime = perfData.loadEventEnd - perfData.navigationStart;
            const domReadyTime = perfData.domContentLoadedEventEnd - perfData.navigationStart;
            const firstPaintTime = performance.getEntriesByType('paint')
                .find(entry => entry.name === 'first-paint')?.startTime || 0;

            // 收集首次内容绘制时间
            const firstContentfulPaintTime = performance.getEntriesByType('paint')
                .find(entry => entry.name === 'first-contentful-paint')?.startTime || 0;

            // 发送性能数据到分析服务器
            fetch('/api/performance', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    pageLoadTime,
                    domReadyTime,
                    firstPaintTime,
                    firstContentfulPaintTime,
                    userAgent: navigator.userAgent,
                    timestamp: new Date().toISOString()
                })
            });
        }, 0);
    });
}

collectPerformanceMetrics();

接口限流与防刷

后端接口层面,我们需要对请求进行限流和过滤:

机器学习增强的风控系统

java 复制代码
@Service
public class MLRiskControlService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private UserBehaviorRepository behaviorRepository;

    @Autowired
    private CacheManager cacheManager;

    // 加载训练好的随机森林模型
    private RandomForestClassifier riskModel;

    @PostConstruct
    public void init() {
        try {
            // 从文件加载序列化的模型
            riskModel = (RandomForestClassifier) new ObjectInputStream(
                new FileInputStream("/models/risk_forest_model.ser")).readObject();
            log.info("风控机器学习模型加载成功");
        } catch (Exception e) {
            log.error("风控模型加载失败,将使用规则引擎降级", e);
            // 降级为规则引擎
        }

        // 黑名单本地缓存
        blacklistCache = cacheManager.getCache("blacklist", String.class, Boolean.class);
    }

    // 风控评估,结合机器学习和规则引擎
    public RiskResult assessRisk(String userId, String ip, String deviceId, Map<String, Object> behaviorData) {
        RiskResult result = new RiskResult();

        // 1. 先检查黑名单缓存
        Boolean isBlacklisted = blacklistCache.get(userId);
        if (isBlacklisted != null && isBlacklisted) {
            result.setRiskLevel(RiskLevel.HIGH);
            result.setReasonCode("BLACKLISTED");
            return result;
        }

        // 2. 再检查Redis黑名单
        String blacklistKey = "risk:blacklist";
        Boolean isMember = redisTemplate.opsForSet().isMember(blacklistKey, userId);
        if (isMember != null && isMember) {
            blacklistCache.put(userId, true);
            result.setRiskLevel(RiskLevel.HIGH);
            result.setReasonCode("BLACKLISTED");
            return result;
        }

        // 3. 机器学习风险评分
        double riskScore = 0.0;

        if (riskModel != null && behaviorData != null) {
            try {
                // 特征提取
                double[] features = extractFeatures(behaviorData);

                // 使用模型预测风险得分
                riskScore = riskModel.predict_proba(new double[][]{features})[0][1]; // 获取属于风险类的概率

                result.setMlScore(riskScore);
                log.debug("用户{}的ML风险评分: {}", userId, riskScore);
            } catch (Exception e) {
                log.error("机器学习风险评估失败,降级为规则引擎", e);
                // 出错时降级为规则引擎
            }
        }

        // 4. 规则引擎评分
        double ruleScore = evaluateByRules(userId, ip, deviceId);
        result.setRuleScore(ruleScore);

        // 5. 结合两种评分
        double finalScore = (riskModel != null)
            ? riskScore * 0.7 + ruleScore * 0.3  // ML权重更高
            : ruleScore;                          // 仅规则引擎

        result.setFinalScore(finalScore);

        // 6. 风险等级判定
        if (finalScore > 0.8) {
            result.setRiskLevel(RiskLevel.HIGH);
            result.setReasonCode("HIGH_RISK_SCORE");
            // 加入黑名单
            redisTemplate.opsForSet().add(blacklistKey, userId);
            blacklistCache.put(userId, true);
        } else if (finalScore > 0.5) {
            result.setRiskLevel(RiskLevel.MEDIUM);
            result.setReasonCode("MEDIUM_RISK_SCORE");
        } else {
            result.setRiskLevel(RiskLevel.LOW);
        }

        // 7. 记录用户行为到数据仓库(用于后续模型更新)
        saveBehaviorForTraining(userId, ip, deviceId, behaviorData, result);

        return result;
    }

    // 从用户行为数据中提取特征
    private double[] extractFeatures(Map<String, Object> behaviorData) {
        // 特征向量构建 - 示例特征:
        // 1. 点击间隔标准差
        // 2. 操作序列熵值
        // 3. 轨迹曲率
        // 4. 鼠标移动速度变化
        // 5. 页面停留时间比例
        // 6. 请求频率特征
        // ...更多特征

        double[] features = new double[10]; // 假设有10个特征

        // 点击行为特征
        List<Map<String, Object>> clicks = (List<Map<String, Object>>) behaviorData.get("clicks");
        if (clicks != null && clicks.size() > 1) {
            // 计算点击间隔时间
            List<Long> intervals = new ArrayList<>();
            for (int i = 1; i < clicks.size(); i++) {
                long t1 = (long) clicks.get(i-1).get("timestamp");
                long t2 = (long) clicks.get(i).get("timestamp");
                intervals.add(t2 - t1);
            }

            // 计算间隔标准差
            double avgInterval = intervals.stream().mapToLong(Long::valueOf).average().orElse(0);
            double varianceSum = intervals.stream()
                .mapToDouble(i -> Math.pow(i - avgInterval, 2))
                .sum();
            features[0] = Math.sqrt(varianceSum / intervals.size());
        }

        // 鼠标轨迹特征
        List<Map<String, Object>> mouseTrack = (List<Map<String, Object>>) behaviorData.get("mouseTrack");
        if (mouseTrack != null && mouseTrack.size() > 2) {
            // 计算轨迹曲率和速度特征
            List<Double> speeds = new ArrayList<>();
            List<Double> curvatures = new ArrayList<>();

            for (int i = 1; i < mouseTrack.size(); i++) {
                Map<String, Object> p1 = mouseTrack.get(i-1);
                Map<String, Object> p2 = mouseTrack.get(i);

                double x1 = Double.parseDouble(p1.get("x").toString());
                double y1 = Double.parseDouble(p1.get("y").toString());
                double t1 = Double.parseDouble(p1.get("t").toString());

                double x2 = Double.parseDouble(p2.get("x").toString());
                double y2 = Double.parseDouble(p2.get("y").toString());
                double t2 = Double.parseDouble(p2.get("t").toString());

                double distance = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
                double timeDiff = (t2 - t1) / 1000.0;

                if (timeDiff > 0) {
                    speeds.add(distance / timeDiff);
                }

                // 计算曲率特征(需要3个点)
                if (i > 1) {
                    Map<String, Object> p0 = mouseTrack.get(i-2);
                    double x0 = Double.parseDouble(p0.get("x").toString());
                    double y0 = Double.parseDouble(p0.get("y").toString());

                    // 计算轨迹曲率
                    double dx1 = x1 - x0;
                    double dy1 = y1 - y0;
                    double dx2 = x2 - x1;
                    double dy2 = y2 - y1;

                    double cross = dx1 * dy2 - dy1 * dx2;
                    double len1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);
                    double len2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);

                    if (len1 > 0 && len2 > 0) {
                        double curvature = Math.abs(cross) / (len1 * len2);
                        curvatures.add(curvature);
                    }
                }
            }

            // 计算速度标准差和曲率平均值
            if (!speeds.isEmpty()) {
                double avgSpeed = speeds.stream().mapToDouble(Double::valueOf).average().orElse(0);
                double speedVariance = speeds.stream()
                    .mapToDouble(s -> Math.pow(s - avgSpeed, 2))
                    .sum() / speeds.size();
                features[1] = Math.sqrt(speedVariance);
            }

            if (!curvatures.isEmpty()) {
                features[2] = curvatures.stream().mapToDouble(Double::valueOf).average().orElse(0);
            }
        }

        // 设备指纹信息熵
        String fingerprint = (String) behaviorData.get("deviceFingerprint");
        if (fingerprint != null) {
            features[3] = calculateEntropyForString(fingerprint) / 8.0; // 归一化
        }

        // 时间行为特征
        Long sessionDuration = (Long) behaviorData.get("sessionDuration");
        Long activeTime = (Long) behaviorData.get("activeTime");
        if (sessionDuration != null && activeTime != null && sessionDuration > 0) {
            features[4] = (double) activeTime / sessionDuration;
        }

        // 请求行为特征
        Integer requestCount = (Integer) behaviorData.get("requestCount");
        if (requestCount != null) {
            features[5] = Math.min(requestCount / 20.0, 1.0); // 归一化
        }

        // 填充剩余特征 - 实际系统中还有更多特征

        return features;
    }

    // 计算字符串信息熵
    private double calculateEntropyForString(String input) {
        if (input == null || input.isEmpty()) {
            return 0;
        }

        int[] charCounts = new int[256];
        for (char c : input.toCharArray()) {
            charCounts[c]++;
        }

        double entropy = 0;
        for (int count : charCounts) {
            if (count > 0) {
                double probability = (double) count / input.length();
                entropy -= probability * (Math.log(probability) / Math.log(2));
            }
        }

        return entropy;
    }

    // 传统规则引擎评估
    private double evaluateByRules(String userId, String ip, String deviceId) {
        double riskScore = 0.0;

        // 检查IP访问频率
        String ipKey = "risk:ip:" + ip;
        Long ipCount = redisTemplate.opsForValue().get(ipKey) != null ?
                        Long.parseLong(Objects.requireNonNull(redisTemplate.opsForValue().get(ipKey))) : 0;

        // 更新计数器
        redisTemplate.opsForValue().increment(ipKey, 1);
        redisTemplate.expire(ipKey, 10, TimeUnit.MINUTES);

        // 检查设备访问频率
        String deviceKey = "risk:device:" + deviceId;
        Long deviceCount = redisTemplate.opsForValue().get(deviceKey) != null ?
                            Long.parseLong(Objects.requireNonNull(redisTemplate.opsForValue().get(deviceKey))) : 0;

        redisTemplate.opsForValue().increment(deviceKey, 1);
        redisTemplate.expire(deviceKey, 30, TimeUnit.MINUTES);

        // 检查用户访问频率
        String userKey = "risk:user:" + userId;
        Long userCount = redisTemplate.opsForValue().get(userKey) != null ?
                          Long.parseLong(Objects.requireNonNull(redisTemplate.opsForValue().get(userKey))) : 0;

        redisTemplate.opsForValue().increment(userKey, 1);
        redisTemplate.expire(userKey, 5, TimeUnit.MINUTES);

        // 计算风险得分 (0-1)
        if (ipCount > 80) riskScore += 0.5;
        if (deviceCount > 40) riskScore += 0.3;
        if (userCount > 15) riskScore += 0.2;

        // 归一化
        return Math.min(riskScore, 1.0);
    }

    // 保存行为数据用于模型训练
    private void saveBehaviorForTraining(String userId, String ip, String deviceId,
                                        Map<String, Object> behaviorData, RiskResult result) {
        try {
            UserBehavior behavior = new UserBehavior();
            behavior.setUserId(userId);
            behavior.setIp(ip);
            behavior.setDeviceId(deviceId);
            behavior.setBehaviorData(objectMapper.writeValueAsString(behaviorData));
            behavior.setRiskScore(result.getFinalScore());
            behavior.setRiskLevel(result.getRiskLevel().name());
            behavior.setTimestamp(new Date());

            behaviorRepository.save(behavior);
        } catch (Exception e) {
            log.error("保存用户行为数据失败", e);
        }
    }

    // 定期重训练模型 (每天凌晨)
    @Scheduled(cron = "0 0 2 * * ?")
    public void retrainModel() {
        if (!isProductionEnvironment()) {
            try {
                log.info("开始重训练风控模型");

                // 从数据仓库加载训练数据
                List<UserBehavior> trainingData = behaviorRepository.findRecentTrainingData(10000);

                // 提取特征和标签
                List<double[]> features = new ArrayList<>();
                List<Integer> labels = new ArrayList<>();

                for (UserBehavior behavior : trainingData) {
                    Map<String, Object> behaviorData = objectMapper.readValue(
                        behavior.getBehaviorData(), new TypeReference<Map<String, Object>>() {});

                    double[] featureVector = extractFeatures(behaviorData);
                    features.add(featureVector);

                    // 风险得分>0.7视为高风险样本(标签为1),否则为0
                    labels.add(behavior.getRiskScore() > 0.7 ? 1 : 0);
                }

                // 转换为训练格式
                double[][] X = features.toArray(new double[0][]);
                int[] y = labels.stream().mapToInt(i -> i).toArray();

                // 训练新模型
                RandomForestClassifier newModel = new RandomForestClassifier();
                newModel.fit(X, y);

                // 评估模型性能
                double accuracy = evaluateModel(newModel, X, y);
                log.info("新模型准确率: {}", accuracy);

                // 性能达标时更新模型
                if (accuracy > 0.85) {
                    // 保存新模型
                    try (ObjectOutputStream oos = new ObjectOutputStream(
                            new FileOutputStream("/models/risk_forest_model.ser"))) {
                        oos.writeObject(newModel);
                    }

                    // 更新当前模型
                    riskModel = newModel;
                    log.info("风控模型更新成功");
                } else {
                    log.warn("新模型性能不达标,保持使用当前模型");
                }

            } catch (Exception e) {
                log.error("模型重训练失败", e);
            }
        }
    }

    // 评估模型性能
    private double evaluateModel(RandomForestClassifier model, double[][] X, int[] y) {
        int correct = 0;
        for (int i = 0; i < X.length; i++) {
            int predicted = model.predict(new double[][]{X[i]})[0];
            if (predicted == y[i]) {
                correct++;
            }
        }
        return (double) correct / X.length;
    }

    private boolean isProductionEnvironment() {
        return "production".equals(System.getenv("SPRING_PROFILES_ACTIVE"));
    }

    // 风险等级枚举
    public enum RiskLevel {
        LOW, MEDIUM, HIGH
    }

    // 风险评估结果
    @Data
    public static class RiskResult {
        private RiskLevel riskLevel = RiskLevel.LOW;
        private String reasonCode;
        private double mlScore;
        private double ruleScore;
        private double finalScore;
    }
}

在 Controller 层集成风控系统:

java 复制代码
@RestController
@RequestMapping("/seckill")
public class SeckillController {

    @Autowired
    private MLRiskControlService riskControlService;

    // 其他依赖项...

    @PostMapping("/{productId}")
    public Result doSeckill(
            @PathVariable String productId,
            @RequestParam String signature,
            @RequestParam String timestamp,
            @RequestParam String deviceId,
            @RequestBody(required = false) Map<String, Object> behaviorData,
            HttpServletRequest request) {

        long start = System.currentTimeMillis();
        seckillCounter.increment();

        try {
            // 限流控制
            if (!rateLimiter.tryAcquire()) {
                return Result.error("活动太火爆,请稍后再试");
            }

            // 获取用户信息
            String userId = getUserId(request);
            if (StringUtils.isEmpty(userId)) {
                return Result.error("请先登录");
            }

            // 风控评估
            String ip = RequestUtils.getClientIp(request);
            MLRiskControlService.RiskResult riskResult =
                riskControlService.assessRisk(userId, ip, deviceId, behaviorData);

            // 高风险请求拦截
            if (riskResult.getRiskLevel() == MLRiskControlService.RiskLevel.HIGH) {
                log.warn("高风险请求被拦截: userId={}, ip={}, score={}, reason={}",
                        userId, ip, riskResult.getFinalScore(), riskResult.getReasonCode());
                return Result.error("请求异常,请稍后再试");
            }

            // 中等风险增加验证难度
            if (riskResult.getRiskLevel() == MLRiskControlService.RiskLevel.MEDIUM) {
                if (!validateStrictCaptcha(request)) {
                    return Result.error("请完成安全验证");
                }
            }

            // 其他逻辑...签名验证、库存检查、执行秒杀等

            return Result.success(data);

        } catch (Exception e) {
            log.error("秒杀异常", e);
            return Result.error("系统繁忙,请稍后再试");
        }
    }

    // 严格验证码校验
    private boolean validateStrictCaptcha(HttpServletRequest request) {
        // 实现严格的验证码校验逻辑
        return true; // 简化实现
    }
}

数据库分库分表优化

高并发场景下,单表数据量过大会导致性能瓶颈,通过 ShardingSphere 实现分库分表:

java 复制代码
@Configuration
public class ShardingConfig {

    // 配置分片数据源
    @Bean
    public DataSource shardingDataSource() throws SQLException {
        // 配置真实数据源
        Map<String, DataSource> dataSourceMap = createDataSourceMap();

        // 配置分片规则
        ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();

        // 订单表分片规则:按userId哈希到16张表
        shardingRuleConfig.getTableRuleConfigs().add(getOrderTableRuleConfiguration());

        // 库存表分片规则:按productId哈希分片
        shardingRuleConfig.getTableRuleConfigs().add(getStockTableRuleConfiguration());

        // 默认分库策略
        shardingRuleConfig.setDefaultDatabaseShardingStrategyConfig(
            new InlineShardingStrategyConfiguration("user_id", "ds${user_id % 2}"));

        // 默认分表策略
        shardingRuleConfig.setDefaultTableShardingStrategyConfig(
            new NoneShardingStrategyConfiguration());

        // 配置Properties
        Properties props = new Properties();
        props.setProperty("sql.show", "false");

        // 创建数据源
        return ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfig, props);
    }

    // 创建数据源映射
    private Map<String, DataSource> createDataSourceMap() {
        Map<String, DataSource> dataSourceMap = new HashMap<>();

        // 主库集群
        dataSourceMap.put("ds0", createDataSource("jdbc:mysql://master1:3306/seckill"));
        dataSourceMap.put("ds1", createDataSource("jdbc:mysql://master2:3306/seckill"));

        return dataSourceMap;
    }

    // 订单表分片规则
    private TableRuleConfiguration getOrderTableRuleConfiguration() {
        TableRuleConfiguration result = new TableRuleConfiguration("t_order", "ds${0..1}.t_order_${0..15}");

        // 分库策略
        result.setDatabaseShardingStrategyConfig(
            new StandardShardingStrategyConfiguration("user_id", new UserIdDatabaseShardingAlgorithm()));

        // 分表策略 - 按userId取模分表
        result.setTableShardingStrategyConfig(
            new StandardShardingStrategyConfiguration("user_id", new UserIdTableShardingAlgorithm()));

        // 主键生成策略
        result.setKeyGeneratorConfig(new KeyGeneratorConfiguration("SNOWFLAKE", "order_id"));

        return result;
    }

    // 库存表分片规则
    private TableRuleConfiguration getStockTableRuleConfiguration() {
        TableRuleConfiguration result = new TableRuleConfiguration("t_stock", "ds${0..1}.t_stock_${0..7}");

        // 分库策略
        result.setDatabaseShardingStrategyConfig(
            new StandardShardingStrategyConfiguration("product_id", new ProductIdDatabaseShardingAlgorithm()));

        // 分表策略 - 按productId取模分表
        result.setTableShardingStrategyConfig(
            new StandardShardingStrategyConfiguration("product_id", new ProductIdTableShardingAlgorithm()));

        return result;
    }

    // 创建基础数据源
    private DataSource createDataSource(String jdbcUrl) {
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setJdbcUrl(jdbcUrl);
        dataSource.setUsername("root");
        dataSource.setPassword("password");
        dataSource.setMaximumPoolSize(50);
        dataSource.setMinimumIdle(10);
        dataSource.setIdleTimeout(30000);
        dataSource.setMaxLifetime(1800000);
        dataSource.setConnectionTimeout(5000);
        return dataSource;
    }
}

// 用户ID分库算法
public class UserIdDatabaseShardingAlgorithm implements PreciseShardingAlgorithm<String> {
    @Override
    public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<String> shardingValue) {
        for (String targetName : availableTargetNames) {
            if (targetName.endsWith(String.valueOf(Math.abs(shardingValue.getValue().hashCode()) % 2))) {
                return targetName;
            }
        }
        throw new UnsupportedOperationException();
    }
}

// 用户ID分表算法
public class UserIdTableShardingAlgorithm implements PreciseShardingAlgorithm<String> {
    @Override
    public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<String> shardingValue) {
        int tableIndex = Math.abs(shardingValue.getValue().hashCode()) % 16;
        for (String targetName : availableTargetNames) {
            if (targetName.endsWith("_" + tableIndex)) {
                return targetName;
            }
        }
        throw new UnsupportedOperationException();
    }
}

// 商品ID分库算法
public class ProductIdDatabaseShardingAlgorithm implements PreciseShardingAlgorithm<String> {
    @Override
    public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<String> shardingValue) {
        for (String targetName : availableTargetNames) {
            if (targetName.endsWith(String.valueOf(Math.abs(shardingValue.getValue().hashCode()) % 2))) {
                return targetName;
            }
        }
        throw new UnsupportedOperationException();
    }
}

// 商品ID分表算法
public class ProductIdTableShardingAlgorithm implements PreciseShardingAlgorithm<String> {
    @Override
    public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<String> shardingValue) {
        int tableIndex = Math.abs(shardingValue.getValue().hashCode()) % 8;
        for (String targetName : availableTargetNames) {
            if (targetName.endsWith("_" + tableIndex)) {
                return targetName;
            }
        }
        throw new UnsupportedOperationException();
    }
}

数据库表结构设计:

sql 复制代码
-- 订单表(分表)
CREATE TABLE t_order_0 (
    order_id BIGINT NOT NULL,
    order_no VARCHAR(32) NOT NULL,
    user_id VARCHAR(32) NOT NULL,
    product_id VARCHAR(32) NOT NULL,
    product_name VARCHAR(128),
    product_price DECIMAL(10,2) NOT NULL,
    status TINYINT NOT NULL DEFAULT 0,
    create_time DATETIME NOT NULL,
    update_time DATETIME NOT NULL,
    PRIMARY KEY (order_id),
    UNIQUE KEY idx_order_no (order_no),
    KEY idx_user_id (user_id),
    KEY idx_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 库存表(分表)
CREATE TABLE t_stock_0 (
    id BIGINT NOT NULL AUTO_INCREMENT,
    product_id VARCHAR(32) NOT NULL,
    total_stock INT NOT NULL,
    available_stock INT NOT NULL,
    frozen_stock INT NOT NULL DEFAULT 0,
    version INT NOT NULL DEFAULT 0,
    create_time DATETIME NOT NULL,
    update_time DATETIME NOT NULL,
    PRIMARY KEY (id),
    UNIQUE KEY idx_product_id (product_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 用户行为分析表(用于机器学习风控)
CREATE TABLE t_user_behavior (
    id BIGINT NOT NULL AUTO_INCREMENT,
    user_id VARCHAR(32) NOT NULL,
    ip VARCHAR(64),
    device_id VARCHAR(128),
    behavior_data MEDIUMTEXT,
    risk_score DECIMAL(5,4),
    risk_level VARCHAR(20),
    timestamp DATETIME NOT NULL,
    PRIMARY KEY (id),
    KEY idx_user_id (user_id),
    KEY idx_timestamp (timestamp)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

高可用验证码实现

java 复制代码
@Service
public class CaptchaService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Value("${geetest.id}")
    private String geetestId;

    @Value("${geetest.key}")
    private String geetestKey;

    // 本地验证码生成器
    @Autowired
    private DefaultKaptcha kaptcha;

    // 验证码服务路由(本地/第三方)
    public CaptchaInitResult initCaptcha(String deviceId, String productId, boolean preferLocal) {
        // 限制单个设备的验证码请求频率
        String rateKey = "captcha:rate:" + deviceId;
        Long count = redisTemplate.opsForValue().increment(rateKey, 1);
        redisTemplate.expire(rateKey, 60, TimeUnit.SECONDS);

        if (count != null && count > 5) {
            return new CaptchaInitResult(false, null, null, false, "请求过于频繁,请稍后再试");
        }

        // 根据参数或故障监测决定使用哪种验证码
        if (preferLocal || isThirdPartyCaptchaUnavailable()) {
            return initLocalCaptcha(deviceId, productId);
        } else {
            try {
                return initGeeTestCaptcha(deviceId, productId);
            } catch (Exception e) {
                log.warn("第三方验证码初始化失败,降级为本地验证码", e);
                // 记录故障状态到缓存,后续请求自动降级
                redisTemplate.opsForValue().set("captcha:third-party:status", "down", 5, TimeUnit.MINUTES);
                return initLocalCaptcha(deviceId, productId);
            }
        }
    }

    // 检查第三方验证码服务是否可用
    private boolean isThirdPartyCaptchaUnavailable() {
        return Boolean.TRUE.equals(redisTemplate.hasKey("captcha:third-party:status"));
    }

    // 初始化本地验证码
    public CaptchaInitResult initLocalCaptcha(String deviceId, String productId) {
        try {
            // 生成验证码文本
            String captchaText = generateRandomCode(4);
            String captchaId = UUID.randomUUID().toString();

            // 生成验证码图片
            String captchaImage = generateCaptchaImage(captchaText);

            // 存储验证码,60秒有效期
            String cacheKey = "captcha:local:" + captchaId;
            redisTemplate.opsForValue().set(cacheKey, captchaText, 60, TimeUnit.SECONDS);

            CaptchaInitResult result = new CaptchaInitResult();
            result.setSuccess(true);
            result.setCaptchaType("local");
            result.setCaptchaId(captchaId);
            result.setCaptchaUrl(captchaImage);
            result.setProductId(productId);

            return result;
        } catch (Exception e) {
            log.error("生成本地验证码失败", e);
            return new CaptchaInitResult(false, null, null, false, "验证码生成失败");
        }
    }

    // 生成本地验证码图片
    private String generateCaptchaImage(String text) {
        BufferedImage image = kaptcha.createImage(text);
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        try {
            ImageIO.write(image, "jpg", outputStream);
            return "data:image/jpeg;base64," + Base64.getEncoder().encodeToString(outputStream.toByteArray());
        } catch (IOException e) {
            log.error("验证码图片生成失败", e);
            return "";
        }
    }

    // 极验验证初始化
    public CaptchaInitResult initGeeTestCaptcha(String deviceId, String productId) {
        // 限制单个设备的验证码请求频率
        String rateKey = "captcha:rate:" + deviceId;
        Long count = redisTemplate.opsForValue().increment(rateKey, 1);
        redisTemplate.expire(rateKey, 60, TimeUnit.SECONDS);

        if (count != null && count > 5) {
            return new CaptchaInitResult(false, null, null, false, "请求过于频繁,请稍后再试");
        }

        // 生成验证码会话ID
        String captchaSessionId = UUID.randomUUID().toString();

        // 调用极验API获取初始化参数
        GeetestLib gtLib = new GeetestLib(geetestId, geetestKey);
        String userId = deviceId.substring(0, 8); // 取设备ID前8位作为用户标识
        HashMap<String, String> param = new HashMap<>();
        param.put("user_id", userId);
        param.put("client_type", "web");
        param.put("ip_address", "127.0.0.1");

        int gtServerStatus = gtLib.preProcess(param);

        // 记录服务器状态,用于后续校验
        String statusKey = "captcha:gt_server_status:" + captchaSessionId;
        redisTemplate.opsForValue().set(statusKey, String.valueOf(gtServerStatus), 5, TimeUnit.MINUTES);

        // 保存param,用于后续校验
        String paramKey = "captcha:gt_param:" + captchaSessionId;
        redisTemplate.opsForValue().set(paramKey, JSON.toJSONString(param), 5, TimeUnit.MINUTES);

        CaptchaInitResult result = new CaptchaInitResult();
        result.setSuccess(true);
        result.setCaptchaType("third-party");
        result.setGt(geetestId);
        result.setChallenge(gtLib.getResponseStr());
        result.setNewCaptcha(gtServerStatus == 1);
        result.setProductId(productId);

        return result;
    }

    // 校验验证码(支持多种类型)
    public boolean verifyCaptcha(Map<String, String> validateResult, String mouseTrack) {
        String captchaType = validateResult.get("captchaType");

        if ("geetest".equals(captchaType)) {
            return verifyGeeTestCaptcha(
                validateResult.get("geetest_challenge"),
                validateResult.get("geetest_validate"),
                validateResult.get("geetest_seccode"),
                validateResult.get("captchaSessionId"),
                mouseTrack
            );
        } else if ("local".equals(captchaType)) {
            return verifyLocalCaptcha(
                validateResult.get("captchaId"),
                validateResult.get("captchaCode")
            );
        }

        return false;
    }

    // 校验本地验证码
    public boolean verifyLocalCaptcha(String captchaId, String inputCode) {
        if (StringUtils.isEmpty(captchaId) || StringUtils.isEmpty(inputCode)) {
            return false;
        }

        String cacheKey = "captcha:local:" + captchaId;
        String correctCode = redisTemplate.opsForValue().get(cacheKey);

        if (correctCode != null && correctCode.equalsIgnoreCase(inputCode)) {
            // 验证通过后立即删除,防止重复使用
            redisTemplate.delete(cacheKey);
            return true;
        }

        return false;
    }

    // 验证极验验证码结果
    public boolean verifyGeeTestCaptcha(String challenge, String validate, String seccode,
                                      String captchaSessionId, String mouseTrack) {
        String statusKey = "captcha:gt_server_status:" + captchaSessionId;
        String gtServerStatusStr = redisTemplate.opsForValue().get(statusKey);

        if (gtServerStatusStr == null) {
            return false;
        }

        int gtServerStatus = Integer.parseInt(gtServerStatusStr);

        String paramKey = "captcha:gt_param:" + captchaSessionId;
        String paramJson = redisTemplate.opsForValue().get(paramKey);

        if (paramJson == null) {
            return false;
        }

        HashMap<String, String> param = JSON.parseObject(paramJson,
                                          new TypeReference<HashMap<String, String>>(){});

        GeetestLib gtLib = new GeetestLib(geetestId, geetestKey);
        int result = gtLib.enhencedValidateRequest(challenge, validate, seccode, param);

        // 验证通过后还要分析鼠标轨迹,是否符合人类操作特征
        if (result == 1) {
            return validateMouseTrack(mouseTrack);
        }

        return false;
    }

    // 验证鼠标轨迹是否符合人类操作特征
    private boolean validateMouseTrack(String mouseTrackJson) {
        if (StringUtils.isEmpty(mouseTrackJson)) {
            return false;
        }

        try {
            List<Map<String, Object>> tracks = JSON.parseObject(mouseTrackJson,
                                                new TypeReference<List<Map<String, Object>>>(){});

            if (tracks.size() < 10) {
                return false; // 轨迹点太少,可能是脚本
            }

            // 计算轨迹速度变化
            double[] speeds = new double[tracks.size() - 1];
            for (int i = 0; i < tracks.size() - 1; i++) {
                Map<String, Object> current = tracks.get(i);
                Map<String, Object> next = tracks.get(i + 1);

                double x1 = Double.parseDouble(current.get("x").toString());
                double y1 = Double.parseDouble(current.get("y").toString());
                double t1 = Double.parseDouble(current.get("t").toString());

                double x2 = Double.parseDouble(next.get("x").toString());
                double y2 = Double.parseDouble(next.get("y").toString());
                double t2 = Double.parseDouble(next.get("t").toString());

                double distance = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
                double timeDiff = (t2 - t1) / 1000.0; // 转换为秒

                if (timeDiff > 0) {
                    speeds[i] = distance / timeDiff;
                }
            }

            // 计算速度变化的标准差
            double avgSpeed = Arrays.stream(speeds).average().orElse(0);
            double variance = Arrays.stream(speeds)
                                  .map(s -> Math.pow(s - avgSpeed, 2))
                                  .sum() / speeds.length;
            double stdDev = Math.sqrt(variance);

            // 人类操作的轨迹速度会有变化,标准差应该不会太小
            return stdDev > 5.0;

        } catch (Exception e) {
            return false;
        }
    }

    // 生成随机字符串
    private String generateRandomCode(int length) {
        String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        StringBuilder sb = new StringBuilder();
        Random random = new Random();

        for (int i = 0; i < length; i++) {
            sb.append(chars.charAt(random.nextInt(chars.length())));
        }

        return sb.toString();
    }
}

Redis 集群与容错设计

增强系统可用性的集群和容错设计:

java 复制代码
@Configuration
public class RedisClusterConfig {

    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();

        // Redis集群配置
        config.useClusterServers()
              .setScanInterval(2000) // 集群状态扫描间隔
              .addNodeAddress("redis://192.168.1.1:7001")
              .addNodeAddress("redis://192.168.1.2:7002")
              .addNodeAddress("redis://192.168.1.3:7003")
              .addNodeAddress("redis://192.168.1.4:7004")
              .addNodeAddress("redis://192.168.1.5:7005")
              .addNodeAddress("redis://192.168.1.6:7006")
              .setRetryAttempts(3)
              .setRetryInterval(1000)
              .setTimeout(3000)
              .setConnectTimeout(10000);

        return Redisson.create(config);
    }

    @Bean
    public CacheManager cacheManager() {
        // 本地二级缓存配置
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();

        cacheManager.setCaffeine(Caffeine.newBuilder()
            .initialCapacity(100)
            .maximumSize(1000)
            .expireAfterWrite(5, TimeUnit.MINUTES)
            .recordStats());

        return cacheManager;
    }

    // 配置HikariCP连接池,防止连接耗尽
    @Bean
    public HikariConfig hikariConfig() {
        HikariConfig config = new HikariConfig();
        config.setMaximumPoolSize(50); // 最大连接数
        config.setMinimumIdle(10); // 最小空闲连接
        config.setIdleTimeout(30000); // 空闲超时时间
        config.setMaxLifetime(1800000); // 连接最大生命周期(30分钟)
        config.setConnectionTimeout(5000); // 连接超时时间(5秒)
        config.setValidationTimeout(3000); // 验证超时时间
        config.setKeepaliveTime(60000); // 心跳保活时间

        return config;
    }
}

// Redis故障容错服务
@Service
public class RedisFallbackService {

    @Autowired
    private CacheManager cacheManager;

    @Autowired
    private ProductMapper productMapper;

    // 本地缓存,作为Redis故障时的兜底
    private Cache<String, String> stockCache;

    @PostConstruct
    public void init() {
        stockCache = cacheManager.getCache("stockCache", String.class, String.class);
    }

    // 缓存预热时同步更新本地缓存
    public void updateLocalCache(String productId, String stock) {
        stockCache.put(productId, stock);
    }

    // Redis故障时获取商品库存
    public String getProductStockWithFallback(String productId) {
        try {
            // 先尝试从Redis获取
            String stock = redisTemplate.opsForValue().get("seckill:stock:" + productId);

            if (stock != null) {
                // 成功获取,更新本地缓存
                stockCache.put(productId, stock);
                return stock;
            }
        } catch (Exception e) {
            log.error("Redis获取库存异常,启用本地缓存", e);
            // Redis异常,使用本地缓存
        }

        // 从本地缓存获取
        String localStock = stockCache.get(productId);
        if (localStock != null) {
            log.info("使用本地缓存获取商品{}库存: {}", productId, localStock);
            return localStock;
        }

        // 本地缓存也没有,降级到数据库
        log.warn("本地缓存无商品{}库存,降级到数据库", productId);
        Product product = productMapper.selectByPrimaryKey(productId);
        if (product != null) {
            String dbStock = String.valueOf(product.getStock());
            // 更新本地缓存
            stockCache.put(productId, dbStock);
            return dbStock;
        }

        return "0"; // 默认返回0
    }

    // 带降级的库存扣减
    public boolean decrementStockWithFallback(String productId) {
        try {
            // 先尝试Redis扣减
            Long result = (Long) redisTemplate.execute(
                new DefaultRedisScript<Long>(StockService.STOCK_DECREMENT_SCRIPT, Long.class),
                Collections.singletonList("seckill:stock:" + productId)
            );

            if (result != null && result == 1L) {
                // 更新本地缓存
                String currentStock = redisTemplate.opsForValue().get("seckill:stock:" + productId);
                if (currentStock != null) {
                    stockCache.put(productId, currentStock);
                }
                return true;
            }
            return false;
        } catch (Exception e) {
            log.error("Redis扣减库存异常,降级到数据库", e);

            // 降级策略:使用数据库乐观锁
            return decrementStockInDB(productId);
        }
    }

    // 数据库乐观锁扣减库存
    private boolean decrementStockInDB(String productId) {
        try {
            Product product = productMapper.selectByPrimaryKey(productId);
            if (product != null && product.getStock() > 0) {
                int affected = productMapper.decreaseStockByVersion(
                    productId, 1, product.getVersion());

                if (affected > 0) {
                    // 更新本地缓存
                    product = productMapper.selectByPrimaryKey(productId);
                    stockCache.put(productId, String.valueOf(product.getStock()));
                    return true;
                }
            }
            return false;
        } catch (Exception e) {
            log.error("数据库扣减库存异常", e);
            return false;
        }
    }
}

实时监控与自动扩容

完善的监控与预警对秒杀系统至关重要:

java 复制代码
@Configuration
public class MonitorConfig {

    @Bean
    public MeterRegistry prometheusRegistry() {
        return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
    }

    // 秒杀系统核心指标
    @Bean
    public Counter seckillCounter(MeterRegistry registry) {
        return Counter.builder("seckill.requests.count")
               .description("秒杀请求总数")
               .register(registry);
    }

    @Bean
    public Counter seckillSuccessCounter(MeterRegistry registry) {
        return Counter.builder("seckill.success.count")
               .description("秒杀成功数")
               .register(registry);
    }

    @Bean
    public Gauge stockGauge(MeterRegistry registry, List<String> hotProductIds) {
        // 监控多个热门商品库存
        for (String productId : hotProductIds) {
            Gauge.builder("seckill.stock", this, s -> getProductStock(productId))
                 .tag("productId", productId)
                 .description("商品库存")
                 .register(registry);
        }
        return null;
    }

    // Redis指标监控,集成实际Redis API
    @Bean
    public Gauge redisClusterStateGauge(MeterRegistry registry) {
        return Gauge.builder("redis.cluster.state", this, MonitorConfig::getRedisClusterState)
               .description("Redis集群状态: 0-故障 1-部分可用 2-健康")
               .register(registry);
    }

    @Bean
    public Gauge redisMemoryGauge(MeterRegistry registry) {
        return Gauge.builder("redis.memory.used", this, MonitorConfig::getRedisMemoryUsage)
               .description("Redis内存使用率")
               .register(registry);
    }

    @Autowired
    private RedissonClient redissonClient;

    // 获取Redis集群实际状态
    private static double getRedisClusterState() {
        try {
            NodesGroup nodesGroup = redissonClient.getNodesGroup();
            Collection<ClusterNode> nodes = nodesGroup.getNodes();

            int totalNodes = nodes.size();
            int availableNodes = 0;

            for (ClusterNode node : nodes) {
                if (node.ping()) {
                    availableNodes++;
                }
            }

            // 全部可用
            if (availableNodes == totalNodes) {
                return 2.0;
            }
            // 部分可用
            else if (availableNodes > 0) {
                return 1.0;
            }
            // 全部不可用
            else {
                return 0.0;
            }
        } catch (Exception e) {
            log.error("获取Redis集群状态失败", e);
            return 0.0;
        }
    }

    // 获取Redis内存使用率
    private static double getRedisMemoryUsage() {
        try {
            // 使用Redisson API获取内存信息
            NodesGroup nodesGroup = redissonClient.getNodesGroup();
            Collection<ClusterNode> nodes = nodesGroup.getNodes();

            double totalMemoryUsage = 0.0;
            int nodeCount = 0;

            for (ClusterNode node : nodes) {
                Map<String, String> info = node.info(InfoSection.MEMORY);
                if (info.containsKey("used_memory") && info.containsKey("total_system_memory")) {
                    long usedMemory = Long.parseLong(info.get("used_memory"));
                    long totalMemory = Long.parseLong(info.get("total_system_memory"));
                    totalMemoryUsage += (double) usedMemory / totalMemory;
                    nodeCount++;
                }
            }

            return nodeCount > 0 ? totalMemoryUsage / nodeCount : 0.0;
        } catch (Exception e) {
            log.error("获取Redis内存使用率失败", e);
            return 1.0; // 异常时返回100%作为警示
        }
    }

    // 消息队列监控,集成RabbitMQ API
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Bean
    public Gauge rabbitmqQueueSizeGauge(MeterRegistry registry) {
        return Gauge.builder("rabbitmq.queue.size", this, this::getQueueSize)
               .tag("queue", "seckill.queue")
               .description("RabbitMQ队列长度")
               .register(registry);
    }

    // 获取RabbitMQ队列实际长度
    private double getQueueSize() {
        try {
            // 通过RabbitMQ Admin API获取队列信息
            RabbitAdmin admin = new RabbitAdmin(rabbitTemplate.getConnectionFactory());
            Properties queueProperties = admin.getQueueProperties("seckill.queue");

            if (queueProperties != null && queueProperties.containsKey(RabbitAdmin.QUEUE_MESSAGE_COUNT)) {
                return (Integer) queueProperties.get(RabbitAdmin.QUEUE_MESSAGE_COUNT);
            }
            return 0;
        } catch (Exception e) {
            log.error("获取队列长度失败", e);
            return 0;
        }
    }

    // 获取商品库存
    @Autowired
    private StringRedisTemplate redisTemplate;

    private double getProductStock(String productId) {
        try {
            String stock = redisTemplate.opsForValue().get("seckill:stock:" + productId);
            return stock == null ? 0 : Double.parseDouble(stock);
        } catch (Exception e) {
            log.error("获取商品库存异常", e);
            return 0;
        }
    }

    // JVM监控
    @Bean
    public JvmThreadMetrics threadMetrics(MeterRegistry registry) {
        return new JvmThreadMetrics().bindTo(registry);
    }

    @Bean
    public JvmMemoryMetrics memoryMetrics(MeterRegistry registry) {
        return new JvmMemoryMetrics().bindTo(registry);
    }

    @Bean
    public JvmGcMetrics gcMetrics(MeterRegistry registry) {
        return new JvmGcMetrics().bindTo(registry);
    }
}

// K8s自动扩容集成服务
@Service
public class K8sAutoScalingService {

    @Autowired
    private KubernetesClient kubernetesClient;

    // 根据队列长度自动扩容消费者服务
    public void scaleConsumers(String deploymentName, int replicasToAdd) {
        try {
            // 获取当前消费者服务状态
            Deployment deployment = kubernetesClient.apps().deployments()
                    .inNamespace("seckill").withName(deploymentName).get();

            if (deployment != null) {
                int currentReplicas = deployment.getSpec().getReplicas();
                int maxReplicas = 20; // 最大副本数

                // 计算新的副本数(当前+增加,不超过最大)
                int newReplicas = Math.min(currentReplicas + replicasToAdd, maxReplicas);

                // 只有需要扩容时才执行
                if (newReplicas > currentReplicas) {
                    kubernetesClient.apps().deployments()
                            .inNamespace("seckill")
                            .withName(deploymentName)
                            .edit(d -> new DeploymentBuilder(d)
                                    .editSpec()
                                    .withReplicas(newReplicas)
                                    .endSpec()
                                    .build());

                    log.info("服务{}扩容成功: {} -> {}", deploymentName, currentReplicas, newReplicas);
                }
            }
        } catch (Exception e) {
            log.error("K8s自动扩容失败", e);
        }
    }

    // 缩容服务
    public void scaleDown(String deploymentName, int minReplicas) {
        try {
            // 获取当前部署状态
            Deployment deployment = kubernetesClient.apps().deployments()
                    .inNamespace("seckill").withName(deploymentName).get();

            if (deployment != null) {
                int currentReplicas = deployment.getSpec().getReplicas();

                // 如果当前副本数大于最小副本数,则缩容
                if (currentReplicas > minReplicas) {
                    kubernetesClient.apps().deployments()
                            .inNamespace("seckill")
                            .withName(deploymentName)
                            .scale(minReplicas);

                    log.info("服务{}缩容成功: {} -> {}", deploymentName, currentReplicas, minReplicas);
                }
            }
        } catch (Exception e) {
            log.error("K8s缩容失败", e);
        }
    }
}

// 混沌工程自动化测试服务
@Service
public class ChaosTesterService {

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private KubernetesClient kubernetesClient;

    @Scheduled(cron = "0 0 3 * * 7") // 每周日凌晨3点执行
    public void runChaosTest() {
        // 仅在非生产环境执行
        if (!isProductionEnvironment()) {
            log.info("开始执行混沌工程测试...");

            // 1. Redis节点故障测试
            testRedisNodeFailure();

            // 2. 消息队列堆积测试
            testMessageQueueBacklog();

            // 3. 数据库慢查询测试
            testDatabaseSlowQuery();

            log.info("混沌工程测试完成");
        }
    }

    private boolean isProductionEnvironment() {
        return "production".equals(System.getenv("SPRING_PROFILES_ACTIVE"));
    }

    // 模拟Redis节点故障
    private void testRedisNodeFailure() {
        try {
            log.info("开始Redis节点故障测试");

            // 随机选择一个Redis节点
            NodesGroup nodesGroup = redissonClient.getNodesGroup();
            List<ClusterNode> nodes = new ArrayList<>(nodesGroup.getNodes());

            if (!nodes.isEmpty()) {
                ClusterNode targetNode = nodes.get(new Random().nextInt(nodes.size()));
                String nodeAddress = targetNode.getAddr().toString();

                // 通过K8s暂时关闭这个Redis节点的Pod
                Pod redisPod = findRedisPodByAddress(nodeAddress);
                if (redisPod != null) {
                    // 记录测试前的状态
                    monitorBeforeTest("redis_node_failure");

                    // 删除Pod,触发K8s重建
                    kubernetesClient.pods().inNamespace("redis").withName(redisPod.getMetadata().getName()).delete();

                    // 等待观察系统降级效果
                    Thread.sleep(60000); // 等待1分钟

                    // 记录测试结果
                    monitorAfterTest("redis_node_failure");
                }
            }
        } catch (Exception e) {
            log.error("Redis节点故障测试异常", e);
        }
    }

    // 根据Redis地址找到对应的K8s Pod
    private Pod findRedisPodByAddress(String address) {
        // 实际实现根据环境而定
        return null;
    }

    // 记录测试前的监控数据
    private void monitorBeforeTest(String testName) {
        // 记录测试前的关键指标
    }

    // 记录测试后的监控数据
    private void monitorAfterTest(String testName) {
        // 记录测试后的关键指标,并评估容错效果
    }

    // 模拟消息队列堆积
    private void testMessageQueueBacklog() {
        // 实现略
    }

    // 模拟数据库慢查询
    private void testDatabaseSlowQuery() {
        // 实现略
    }
}

完整秒杀流程图

下面通过流程图来完整展示秒杀系统的处理流程:

完整秒杀业务代码

以下是一个结合了多种优化策略的核心代码:

java 复制代码
@RestController
@RequestMapping("/seckill")
public class SeckillController {

    private static final Logger log = LoggerFactory.getLogger(SeckillController.class);

    @Autowired
    private SeckillService seckillService;

    @Autowired
    private RateLimiter rateLimiter;

    @Autowired
    private MLRiskControlService riskControlService;

    @Autowired
    private CaptchaService captchaService;

    @Autowired
    private SnowflakeIdGenerator idGenerator;

    @Autowired
    private Counter seckillCounter;

    @Autowired
    private Counter seckillSuccessCounter;

    @Autowired
    private StockSyncService stockSyncService;

    @Autowired
    private RedisFallbackService redisFallbackService;

    // 验证码初始化接口 - 支持本地和三方双引擎
    @GetMapping("/captcha/init")
    public Result initCaptcha(@RequestParam String productId,
                             @RequestParam String deviceId,
                             @RequestParam(required = false, defaultValue = "false") boolean preferLocal) {
        try {
            CaptchaInitResult result = captchaService.initCaptcha(deviceId, productId, preferLocal);
            if (result.isSuccess()) {
                return Result.success(result);
            } else {
                return Result.error(result.getErrorMsg());
            }
        } catch (Exception e) {
            log.error("初始化验证码异常", e);
            return Result.error("系统繁忙,请稍后再试");
        }
    }

    // 获取秒杀URL接口
    @PostMapping("/url")
    public Result getSeckillUrl(
            @RequestParam String productId,
            @RequestParam String deviceId,
            @RequestParam Map<String, String> validateResult,
            HttpServletRequest request) {

        try {
            // 获取用户ID
            String userId = getUserId(request);
            if (StringUtils.isEmpty(userId)) {
                return Result.error("请先登录");
            }

            // 验证码校验 - 支持多种验证码类型
            String mouseTrack = validateResult.get("mouseTrack");
            if (!captchaService.verifyCaptcha(validateResult, mouseTrack)) {
                return Result.error("验证失败,请重新验证");
            }

            // 风控检查
            String ip = RequestUtils.getClientIp(request);
            if (!riskControlService.isAllowedRequest(userId, ip, deviceId)) {
                return Result.error("请求异常,请稍后再试");
            }

            // 生成签名
            long timestamp = System.currentTimeMillis();
            String seckillUrl = "/api/seckill/" + productId;
            String signature = DigestUtils.md5DigestAsHex((seckillUrl + timestamp + SECRET_KEY).getBytes());

            Map<String, Object> data = new HashMap<>();
            data.put("url", seckillUrl);
            data.put("signature", signature);
            data.put("timestamp", String.valueOf(timestamp));

            return Result.success(data);
        } catch (Exception e) {
            log.error("获取秒杀URL异常", e);
            return Result.error("系统繁忙,请稍后再试");
        }
    }

    // 执行秒杀接口
    @PostMapping("/{productId}")
    public Result doSeckill(
            @PathVariable String productId,
            @RequestParam String signature,
            @RequestParam String timestamp,
            @RequestParam String deviceId,
            @RequestBody(required = false) Map<String, Object> behaviorData,
            HttpServletRequest request) {

        long start = System.currentTimeMillis();
        // 记录请求
        seckillCounter.increment();

        try {
            // 1. 限流控制
            if (!rateLimiter.tryAcquire()) {
                log.info("秒杀请求被限流,productId: {}", productId);
                return Result.error("活动太火爆,请稍后再试");
            }

            // 2. 获取用户信息
            String userId = getUserId(request);
            if (StringUtils.isEmpty(userId)) {
                return Result.error("请先登录");
            }

            // 3. 风控评估
            String ip = RequestUtils.getClientIp(request);
            MLRiskControlService.RiskResult riskResult =
                riskControlService.assessRisk(userId, ip, deviceId, behaviorData);

            // 高风险请求拦截
            if (riskResult.getRiskLevel() == MLRiskControlService.RiskLevel.HIGH) {
                log.warn("高风险请求被拦截: userId={}, ip={}, score={}, reason={}",
                        userId, ip, riskResult.getFinalScore(), riskResult.getReasonCode());
                return Result.error("请求异常,请稍后再试");
            }

            // 中等风险增加验证难度
            if (riskResult.getRiskLevel() == MLRiskControlService.RiskLevel.MEDIUM) {
                if (!validateStrictCaptcha(request)) {
                    return Result.error("请完成安全验证");
                }
            }

            // 4. 验证签名
            if (!riskControlService.validateSignature(productId, signature, timestamp)) {
                log.warn("秒杀请求签名验证失败,userId: {}, productId: {}", userId, productId);
                return Result.error("请求已过期,请刷新页面");
            }

            // 5. 判断是否重复下单
            if (!seckillService.checkRepeatSubmit(userId, productId)) {
                log.info("用户重复下单,userId: {}, productId: {}", userId, productId);
                return Result.error("您已参与过此活动");
            }

            // 6. 内存标记,快速失败(库存不足直接拦截)
            if (stockSyncService.isProductMarkedSoldOut(productId)) {
                log.info("商品已售罄(内存标记),productId: {}", productId);
                return Result.error("商品已售罄");
            }

            // 7. 执行秒杀
            String orderNo = "SK" + idGenerator.nextIdStr(); // 使用雪花算法生成订单号
            boolean success = seckillService.secKill(userId, productId, orderNo);

            long end = System.currentTimeMillis();
            log.info("秒杀请求处理耗时: {}ms, userId: {}, productId: {}, success: {}",
                     (end - start), userId, productId, success);

            if (success) {
                // 记录成功
                seckillSuccessCounter.increment();
                return Result.success(Map.of("orderNo", orderNo));
            } else {
                return Result.error("秒杀失败,商品已抢光");
            }
        } catch (Exception e) {
            long end = System.currentTimeMillis();
            log.error("秒杀请求处理异常,耗时: {}ms, productId: {}", (end - start), productId, e);
            return Result.error("系统繁忙,请稍后再试");
        }
    }

    // 获取用户ID(实际项目中从会话或Token中获取)
    private String getUserId(HttpServletRequest request) {
        // 实际项目中应从JWT token或session中获取用户ID
        return request.getHeader("X-User-Id");
    }

    // 严格验证码校验
    private boolean validateStrictCaptcha(HttpServletRequest request) {
        // 实现严格的验证码校验逻辑
        return true; // 简化实现
    }
}

// 秒杀服务核心实现
@Service
public class SeckillServiceImpl implements SeckillService {

    private static final Logger log = LoggerFactory.getLogger(SeckillServiceImpl.class);

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private StockSyncService stockSyncService;

    @Autowired
    private StockService stockService;

    @Autowired
    private RedisFallbackService redisFallbackService;

    @Override
    public boolean checkRepeatSubmit(String userId, String productId) {
        String key = "seckill:submitted:" + userId + ":" + productId;
        try {
            Boolean isFirstSubmit = redisTemplate.opsForValue().setIfAbsent(key, "1", 60, TimeUnit.SECONDS);
            return isFirstSubmit != null && isFirstSubmit;
        } catch (Exception e) {
            log.error("检查重复提交异常", e);
            // 出现异常时保守处理,避免重复下单
            return false;
        }
    }

    @Override
    public boolean secKill(String userId, String productId, String orderNo) {
        // 使用Redisson分布式锁
        RLock lock = redissonClient.getLock("seckill:lock:" + productId);

        try {
            // 尝试获取锁,最多等待100ms,10s自动释放(Redisson会自动续期)
            if (lock.tryLock(100, 10, TimeUnit.SECONDS)) {
                try {
                    // 使用预定义Lua脚本执行库存扣减
                    Long result = stockService.decrementStock(productId);

                    if (result == 1L) {
                        // 发送消息创建订单
                        SeckillMessage message = new SeckillMessage();
                        message.setUserId(userId);
                        message.setProductId(productId);
                        message.setOrderNo(orderNo);
                        message.setCreateTime(new Date());

                        // 使用可靠消息投递
                        String messageId = orderNo;
                        rabbitTemplate.convertAndSend(
                            "seckill.exchange",
                            "seckill.create.order",
                            message,
                            msg -> {
                                // 设置消息属性
                                MessageProperties props = msg.getMessageProperties();
                                props.setMessageId(messageId);
                                props.setTimestamp(new Date());
                                props.setPriority(5); // 普通优先级
                                props.setDeliveryMode(MessageDeliveryMode.PERSISTENT); // 持久化
                                return msg;
                            },
                            new CorrelationData(messageId) // 用于发送确认
                        );

                        // 检查库存是否为0,触发同步和售罄标记
                        String stockKey = "seckill:stock:" + productId;
                        String currentStock = redisTemplate.opsForValue().get(stockKey);
                        if ("0".equals(currentStock)) {
                            stockSyncService.triggerStockSync(productId);
                            stockSyncService.markProductSoldOut(productId);
                        }

                        return true;
                    } else if (result == 0L) {
                        // 库存不足,标记售罄
                        stockSyncService.markProductSoldOut(productId);
                    }
                } finally {
                    // 释放锁
                    if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                        lock.unlock();
                    }
                }
            } else {
                log.warn("获取商品{}的分布式锁超时", productId);
            }
        } catch (RedisConnectionException e) {
            log.error("Redis连接异常,降级到本地缓存", e);
            // Redis故障,使用本地缓存和数据库
            return redisFallbackService.decrementStockWithFallback(productId);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("获取分布式锁被中断", e);
        } catch (Exception e) {
            log.error("秒杀异常", e);
        }

        return false;
    }
}

总结

下面用表格来总结秒杀系统设计的关键点:

层次 技术点 解决问题 核心实现
前端层 页面静态化 减轻服务器压力 CDN + HTTP/2 + Brotli 压缩
前端层 动态 URL 防止恶意请求 动态 URL + 签名验证
前端层 多引擎验证码 防机器人攻击 三方验证码 + 本地验证码兜底
前端层 Service Worker 离线加载 资源缓存 + 优先加载策略
接口层 限流 控制并发请求数量 令牌桶算法 + Redis 计数器
接口层 机器学习风控 精准识别异常行为 随机森林模型 + 行为特征分析
接口层 防重复提交 避免用户重复下单 Redis SETNX + 超时时间
服务层 内存标记 快速失败 带持久化的内存 Map + 定时清理
服务层 缓存预热 减轻数据库压力 定时预热 + 逻辑过期
服务层 分布式锁 防止并发问题 Redisson 带自动续期的锁
数据层 缓存击穿防护 防止热点失效 逻辑过期 + 异步刷新
数据层 库存同步 保持数据一致性 触发式同步 + 定时对账
数据层 分库分表 突破单表瓶颈 ShardingSphere + 哈希分片
架构层 消息队列 流量削峰 可靠投递 + 幂等消费
架构层 多级容错 提高系统可用性 主从集群 + 本地缓存降级
系统层 数据库连接池 防止慢查询影响 HikariCP 优化配置
监控层 自动扩容 应对突发流量 K8s 集成 + 基于指标触发
测试层 容错演练 验证系统韧性 混沌工程 + 自动化测试
相关推荐
Miraitowa_cheems1 小时前
[Java EE] Spring 配置 和 日志
java·spring·java-ee
SuperherRo2 小时前
Web开发-JavaEE应用&原生和FastJson反序列化&URLDNS链&JDBC链&Gadget手搓
java·java-ee·jdbc·fastjson·反序列化·urldns
Blossom.1183 小时前
KWDB创作者计划—深度解析:AIoT时代的分布式多模型数据库新标杆
数据库·分布式·架构·边缘计算·时序数据库·持续部署·ai集成
xxjiaz4 小时前
二分查找-LeetCode
java·数据结构·算法·leetcode
nofaluse4 小时前
JavaWeb开发——文件上传
java·spring boot
爱的叹息4 小时前
【java实现+4种变体完整例子】排序算法中【插入排序】的详细解析,包含基础实现、常见变体的完整代码示例,以及各变体的对比表格
java·算法·排序算法
爱的叹息5 小时前
【java实现+4种变体完整例子】排序算法中【快速排序】的详细解析,包含基础实现、常见变体的完整代码示例,以及各变体的对比表格
java·算法·排序算法
6v6-博客5 小时前
2024年网站开发语言选择指南:PHP/Java/Node.js/Python如何选型?
java·开发语言·php
Miraitowa_cheems5 小时前
[Java EE] Spring AOP 和 事务
java·java-ee·aop·spring 事务
光头小小强0075 小时前
致远OA——自定义开发rest接口
java·经验分享·spring·tomcat