大家好!今天咱们聊一个电商开发中的大难题 - 秒杀系统的设计与实现。如果你曾经参与过"双 11"、"618"这样的大促活动开发,一定知道秒杀功能有多么棘手。一个设计不当的秒杀系统,分分钟能让你的服务器崩溃,还可能导致超卖、订单错乱等一系列问题。
秒杀系统的核心挑战
秒杀活动的特点就是"人多、商品少、时间短",这就导致了几个典型挑战:
- 瞬时高并发 - 短时间内可能有上万甚至上百万用户同时抢购
- 库存超卖 - 没有正确的并发控制,100 件商品可能卖出 150 件
- 黄牛与恶意请求 - 大量机器人和脚本攻击
- 用户体验 - 系统需在高并发下保持响应速度
我们来用图表直观展示秒杀系统的请求特点:
graph LR
A[秒杀开始] --> B[请求量]
B --> C[峰值期]
C --> D[平缓期]
C --> E[系统压力]
E --> F[数据库压力]
E --> G[应用服务压力]
E --> H[网络带宽压力]
整体架构设计
一个完整的秒杀系统架构通常包含以下几个部分:

这个架构的核心思想是:将瞬时高并发请求拦截在上游,只让有效的请求进入到核心业务逻辑处理流程,同时通过异步处理减轻数据库压力。
前端限流:第一道防线
秒杀系统的防护要从前端开始。实现思路:
- 动态 URL - 秒杀接口地址在活动开始前不对外公布,并添加签名验证防伪造
- 页面静态化 - 将秒杀页面做成静态资源,通过 CDN 分发,设置合理缓存策略
- 倒计时同步 - 服务端与客户端时间同步,避免提前请求
- 多维度验证码 - 结合行为轨迹分析的滑动拼图/点选验证,防 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 集成 + 基于指标触发 |
测试层 | 容错演练 | 验证系统韧性 | 混沌工程 + 自动化测试 |