Java秒杀业务实现方案详解
秒杀业务java
我来为您详细介绍Java秒杀业务的实现方案。秒杀业务的核心在于解决高并发、超卖、性能瓶颈等问题。
一、秒杀系统架构设计
1.1 整体架构
text
用户请求 → 负载均衡 → 网关层 → 服务层 → 缓存层 → 数据库层
↓
队列处理 ← 异步处理 ← 消息队列
二、核心代码实现
2.1 数据库设计
sql
CREATE TABLE `seckill_goods` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `goods_name` varchar(255) NOT NULL, `stock_count` int(11) NOT NULL, `start_time` datetime NOT NULL, `end_time` datetime NOT NULL, `version` int(11) DEFAULT '0', PRIMARY KEY (`id`) ); CREATE TABLE `seckill_order` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) NOT NULL, `goods_id` bigint(20) NOT NULL, `order_id` varchar(32) NOT NULL, `create_time` datetime DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_user_goods` (`user_id`,`goods_id`) );
2.2 实体类
java
@Data
public class SeckillGoods {
private Long id;
private String goodsName;
private Integer stockCount;
private Date startTime;
private Date endTime;
private Integer version;
}
@Data
public class SeckillOrder {
private Long id;
private Long userId;
private Long goodsId;
private String orderId;
private Date createTime;
}
2.3 核心Service实现
java
@Service
@Slf4j
public class SeckillService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private SeckillGoodsMapper seckillGoodsMapper;
@Autowired
private SeckillOrderMapper seckillOrderMapper;
@Autowired
private RedissonClient redissonClient;
/**
* 秒杀入口 - 使用Redis预减库存
*/
public SeckillResult seckill(Long goodsId, Long userId) {
// 1. 校验秒杀状态
if (!checkSeckillStatus(goodsId)) {
return SeckillResult.error("秒杀未开始或已结束");
}
// 2. 校验重复购买 - Redis set
String key = "seckill_user:" + goodsId;
Boolean isMember = redisTemplate.opsForSet().isMember(key, userId.toString());
if (Boolean.TRUE.equals(isMember)) {
return SeckillResult.error("请勿重复购买");
}
// 3. 预减库存 - Redis原子操作
String stockKey = "seckill_stock:" + goodsId;
Long stock = redisTemplate.opsForValue().decrement(stockKey);
if (stock == null || stock < 0) {
// 库存不足,恢复库存
redisTemplate.opsForValue().increment(stockKey);
return SeckillResult.error("商品已售罄");
}
// 4. 创建订单 - 异步处理
SeckillMessage message = new SeckillMessage(userId, goodsId);
sendSeckillMessage(message);
return SeckillResult.success("秒杀成功,请等待订单生成");
}
/**
* 异步创建订单
*/
@Async
public void handleSeckillOrder(SeckillMessage message) {
Long userId = message.getUserId();
Long goodsId = message.getGoodsId();
// 分布式锁,防止重复消费
RLock lock = redissonClient.getLock("seckill_order_lock:" + goodsId + ":" + userId);
try {
if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {
// 校验重复订单
SeckillOrder existOrder = seckillOrderMapper.selectByUserAndGoods(userId, goodsId);
if (existOrder != null) {
return;
}
// 数据库减库存 - 乐观锁
int result = seckillGoodsMapper.reduceStockWithVersion(goodsId);
if (result <= 0) {
// 减库存失败,恢复Redis库存
redisTemplate.opsForValue().increment("seckill_stock:" + goodsId);
return;
}
// 创建订单
SeckillOrder order = new SeckillOrder();
order.setUserId(userId);
order.setGoodsId(goodsId);
order.setOrderId(generateOrderId());
order.setCreateTime(new Date());
seckillOrderMapper.insert(order);
// 记录已购买用户
redisTemplate.opsForSet().add("seckill_user:" + goodsId, userId.toString());
}
} catch (InterruptedException e) {
log.error("获取锁失败", e);
Thread.currentThread().interrupt();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
private boolean checkSeckillStatus(Long goodsId) {
// 从Redis或数据库检查秒杀时间
// 实现略...
return true;
}
private void sendSeckillMessage(SeckillMessage message) {
// 发送到消息队列
// 实现略...
}
private String generateOrderId() {
return System.currentTimeMillis() + "" + (int)(Math.random() * 1000);
}
}
2.4 Mapper层 – 乐观锁实现
java
@Mapper
public interface SeckillGoodsMapper {
@Update("UPDATE seckill_goods SET stock_count = stock_count - 1, version = version + 1 " +
"WHERE id = #{goodsId} AND stock_count > 0 AND version = #{version}")
int reduceStockWithVersion(@Param("goodsId") Long goodsId, @Param("version") Integer version);
@Select("SELECT * FROM seckill_goods WHERE id = #{goodsId}")
SeckillGoods selectById(Long goodsId);
}
三、关键技术解决方案
3.1 库存超卖问题
解决方案:
Redis原子操作预减库存
数据库乐观锁
分布式锁
3.2 高性能实现
java
@Component
public class SeckillPreheat {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 秒杀开始前预热数据到Redis
*/
public void preheatSeckillData(Long goodsId) {
SeckillGoods goods = seckillGoodsMapper.selectById(goodsId);
if (goods != null) {
// 库存预热
redisTemplate.opsForValue().set(
"seckill_stock:" + goodsId,
goods.getStockCount()
);
// 商品信息预热
redisTemplate.opsForValue().set(
"seckill_goods:" + goodsId,
goods
);
}
}
}
3.3 限流防刷
java
@Component
public class RateLimiterService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 令牌桶限流
*/
public boolean tryAcquire(String key, int capacity, int rate) {
String redisKey = "rate_limit:" + key;
long now = System.currentTimeMillis();
RedisScript<Long> script = RedisScript.of(
"local key = KEYS[1]
" +
"local now = tonumber(ARGV[1])
" +
"local capacity = tonumber(ARGV[2])
" +
"local rate = tonumber(ARGV[3])
" +
"local lastTime = redis.call('hget', key, 'lastTime')
" +
"local tokens = redis.call('hget', key, 'tokens')
" +
"
" +
"if lastTime == false then
" +
" lastTime = now
" +
" tokens = capacity
" +
"else
" +
" local elapsed = now - lastTime
" +
" local newTokens = math.floor(elapsed / 1000 * rate)
" +
" tokens = math.min(capacity, tokens + newTokens)
" +
" lastTime = now
" +
"end
" +
"
" +
"if tokens >= 1 then
" +
" tokens = tokens - 1
" +
" redis.call('hset', key, 'lastTime', lastTime)
" +
" redis.call('hset', key, 'tokens', tokens)
" +
" redis.call('expire', key, math.ceil(capacity/rate) + 1)
" +
" return 1
" +
"else
" +
" return 0
" +
"end",
Long.class
);
Long result = redisTemplate.execute(script, Collections.singletonList(redisKey),
now, capacity, rate);
return result != null && result == 1;
}
}
四、完整配置
4.1 Redis配置
java
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
Jackson2JsonRedisSerializer<Object> serializer =
new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(
mapper.getPolymorphicTypeValidator(),
ObjectMapper.DefaultTyping.NON_FINAL
);
serializer.setObjectMapper(mapper);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
}
五、部署建议
前端优化:静态资源CDN、页面缓存、按钮防重复点击
网关层:Nginx负载均衡、限流配置
服务层:集群部署、服务降级
缓存层:Redis集群、持久化配置
数据库:主从复制、分库分表
这个方案能够支撑万级QPS的秒杀场景,通过多级防护确保系统的稳定性和数据的一致性。
页面缓存举例
页面缓存实现方案
一、前端页面缓存
1.1 HTML静态化 + CDN
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="Cache-Control" max-age="300">
<title>秒杀活动页</title>
<!-- CDN加速静态资源 -->
<link href="https://cdn.example.com/css/seckill.css?v=1.0.0" rel="stylesheet">
<script src="https://cdn.example.com/js/seckill.js?v=1.0.0"></script>
</head>
<body>
<div>
<div class="goods-info">
<h2 data-goods-id="1001">iPhone 15 Pro 秒杀</h2>
<p class="price">¥7999</p>
<p class="stock">剩余库存: <span>0</span></p>
<button class="btn-disabled">
1.2 服务端页面片段缓存
java
@Service
public class PageCacheService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final long PAGE_CACHE_EXPIRE = 30; // 30秒
/**
* 获取商品详情页缓存
*/
public String getGoodsDetailPage(Long goodsId) {
String cacheKey = "page:goods:detail:" + goodsId;
// 1. 尝试从缓存获取
String cachedPage = (String) redisTemplate.opsForValue().get(cacheKey);
if (cachedPage != null) {
return cachedPage;
}
// 2. 缓存未命中,生成页面
String html = generateGoodsDetailPage(goodsId);
// 3. 异步更新缓存
CompletableFuture.runAsync(() -> {
redisTemplate.opsForValue().set(cacheKey, html, PAGE_CACHE_EXPIRE, TimeUnit.SECONDS);
});
return html;
}
/**
* 生成商品详情页
*/
private String generateGoodsDetailPage(Long goodsId) {
SeckillGoods goods = seckillGoodsMapper.selectById(goodsId);
return "<!DOCTYPE html>
" +
"<html>
" +
"<head>
" +
" <title>" + goods.getGoodsName() + "</title>
" +
"</head>
" +
"<body>
" +
" <div class='goods-detail'>
" +
" <h1>" + goods.getGoodsName() + "</h1>
" +
" <p class='price'>价格: ¥" + calculatePrice(goods) + "</p>
" +
" <p class='stock'>库存: " + goods.getStockCount() + "</p>
" +
" <div class='countdown' id='countdown'></div>
" +
" </div>
" +
"</body>
" +
"</html>";
}
}
二、Spring Boot页面缓存实现
2.1 Controller层缓存
java
@Controller
public class SeckillPageController {
@Autowired
private PageCacheService pageCacheService;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 商品详情页 - 带缓存
*/
@GetMapping("/seckill/goods/{goodsId}")
public String goodsDetail(@PathVariable Long goodsId, Model model) {
// 检查页面缓存
String cacheKey = "page:goods:detail:" + goodsId;
String cachedHtml = (String) redisTemplate.opsForValue().get(cacheKey);
if (cachedHtml != null) {
// 直接返回缓存内容
return cachedHtml;
}
// 获取商品信息
SeckillGoods goods = getGoodsWithCache(goodsId);
model.addAttribute("goods", goods);
// 秒杀状态
SeckillStatus status = getSeckillStatus(goodsId);
model.addAttribute("status", status);
// 异步更新页面缓存
cacheGoodsPage(goodsId);
return "seckill/goods_detail";
}
/**
* 获取商品信息(带缓存)
*/
private SeckillGoods getGoodsWithCache(Long goodsId) {
String cacheKey = "goods:info:" + goodsId;
SeckillGoods goods = (SeckillGoods) redisTemplate.opsForValue().get(cacheKey);
if (goods == null) {
goods = seckillGoodsMapper.selectById(goodsId);
// 缓存5分钟
redisTemplate.opsForValue().set(cacheKey, goods, 5, TimeUnit.MINUTES);
}
return goods;
}
}
2.2 使用Spring Cache注解
java
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(30)) // 30秒过期
.disableCachingNullValues()
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}
}
@Service
public class SeckillGoodsService {
/**
* 使用Spring Cache缓存商品信息
*/
@Cacheable(value = "goods", key = "#goodsId")
public SeckillGoods getGoodsById(Long goodsId) {
return seckillGoodsMapper.selectById(goodsId);
}
/**
* 缓存秒杀活动列表
*/
@Cacheable(value = "seckillList", key = "'active_list'")
public List<SeckillGoods> getActiveSeckillList() {
Date now = new Date();
return seckillGoodsMapper.selectActiveList(now);
}
/**
* 更新时清除缓存
*/
@CacheEvict(value = "goods", key = "#goodsId")
public void updateGoods(SeckillGoods goods) {
seckillGoodsMapper.updateById(goods);
}
}
三、Nginx层面缓存配置
3.1 Nginx缓存配置
nginx
http {
# 缓存路径和配置
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=seckill_cache:10m
max_size=10g inactive=60m use_temp_path=off;
upstream seckill_backend {
server 127.0.0.1:8080 weight=1;
server 127.0.0.1:8081 weight=1;
}
server {
listen 80;
server_name seckill.example.com;
# 静态资源缓存
location ~* .(html|css|js|png|jpg|jpeg|gif|ico)$ {
expires 1h;
add_header Cache-Control "public, immutable";
root /var/www/seckill/static;
}
# 商品详情页缓存
location ~ ^/seckill/goods/d+$ {
proxy_pass http://seckill_backend;
# 启用缓存
proxy_cache seckill_cache;
proxy_cache_key "$scheme$request_method$host$request_uri";
proxy_cache_valid 200 10s; # 200响应缓存10秒
proxy_cache_valid 404 1m; # 404响应缓存1分钟
# 缓存相关头
add_header X-Proxy-Cache $upstream_cache_status;
add_header Cache-Control "public, max-age=10";
# 多个请求同时访问时,只让一个请求回源
proxy_cache_lock on;
proxy_cache_lock_timeout 5s;
# 缓存跳过条件(如带特定参数)
proxy_cache_bypass $arg_nocache;
}
# 动态接口不缓存
location /api/ {
proxy_pass http://seckill_backend;
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
}
}
3.2 Nginx限流配置
nginx
http {
limit_req_zone $binary_remote_addr zone=seckill_limit:10m rate=10r/s;
server {
location /seckill/goods/ {
# 限流配置
limit_req zone=seckill_limit burst=20 nodelay;
proxy_pass http://seckill_backend;
proxy_cache seckill_cache;
}
}
}
四、前端缓存策略
4.1 Service Worker缓存
javascript
// sw.js - Service Worker
const CACHE_NAME = 'seckill-v1';
const STATIC_URLS = [
'/static/css/seckill.css',
'/static/js/seckill.js',
'/static/images/loading.gif'
];
// 安装阶段缓存静态资源
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(STATIC_URLS))
);
});
// 拦截请求
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
// 返回缓存或网络请求
return response || fetch(event.request);
})
);
});
4.2 本地存储缓存
javascript
class PageCache {
constructor() {
this.cacheKey = 'seckill_page_cache';
this.expireTime = 5 * 60 * 1000; // 5分钟
}
// 缓存页面数据
cachePageData(goodsId, data) {
const cache = {
data: data,
timestamp: Date.now(),
expire: this.expireTime
};
const key = `${this.cacheKey}_${goodsId}`;
localStorage.setItem(key, JSON.stringify(cache));
}
// 获取缓存数据
getCachedPageData(goodsId) {
const key = `${this.cacheKey}_${goodsId}`;
const cached = localStorage.getItem(key);
if (!cached) return null;
const cache = JSON.parse(cached);
const isExpired = Date.now() - cache.timestamp > cache.expire;
if (isExpired) {
localStorage.removeItem(key);
return null;
}
return cache.data;
}
// 清除缓存
clearCache(goodsId) {
const key = `${this.cacheKey}_${goodsId}`;
localStorage.removeItem(key);
}
}
// 使用示例
const pageCache = new PageCache();
async function loadGoodsDetail(goodsId) {
// 先尝试从本地缓存获取
let goodsData = pageCache.getCachedPageData(goodsId);
if (!goodsData) {
// 缓存未命中,从服务器获取
goodsData = await fetchGoodsDetail(goodsId);
// 更新缓存
pageCache.cachePageData(goodsId, goodsData);
}
renderGoodsDetail(goodsData);
}
五、缓存更新策略
5.1 缓存失效和更新
java
@Service
public class CacheUpdateService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private ApplicationEventPublisher eventPublisher;
/**
* 商品信息变更时清除相关缓存
*/
@EventListener
public void handleGoodsUpdate(GoodsUpdateEvent event) {
Long goodsId = event.getGoodsId();
// 清除Redis缓存
String[] cacheKeys = {
"page:goods:detail:" + goodsId,
"goods:info:" + goodsId,
"seckill:status:" + goodsId
};
redisTemplate.delete(Arrays.asList(cacheKeys));
// 发布缓存更新事件
eventPublisher.publishEvent(new CacheRefreshEvent(goodsId));
}
/**
* 定时刷新缓存
*/
@Scheduled(fixedRate = 30000) // 30秒刷新一次
public void refreshHotGoodsCache() {
List<Long> hotGoodsIds = getHotGoodsIds();
for (Long goodsId : hotGoodsIds) {
// 异步刷新缓存
CompletableFuture.runAsync(() -> {
refreshGoodsCache(goodsId);
});
}
}
}
这样的页面缓存方案可以显著提升系统性能:
静态资源:CDN + 长期缓存
动态页面:Nginx缓存 + Redis缓存
API数据:Redis缓存 + 本地存储
缓存更新:事件驱动 + 定时刷新
网关层:Nginx负载均衡、限流配置
Nginx网关层配置详解
一、Nginx负载均衡配置
1.1 基础负载均衡配置
nginx
# nginx.conf
http {
upstream seckill_backend {
# 负载均衡策略
# 1. 轮询 (默认)
server 192.168.1.101:8080 weight=1;
server 192.168.1.102:8080 weight=1;
server 192.168.1.103:8080 weight=1;
# 2. 最少连接数
# least_conn;
# 3. IP哈希
# ip_hash;
# 4. 响应时间优先
# fair;
# 健康检查
check interval=3000 rise=2 fall=3 timeout=1000 type=http;
check_http_send "HEAD /health HTTP/1.0
";
check_http_expect_alive http_2xx http_3xx;
}
upstream seckill_api {
# API服务集群
server 192.168.1.111:8081 weight=2; # 权重更高
server 192.168.1.112:8081 weight=1;
server 192.168.1.113:8081 weight=1;
# 失败重试配置
proxy_next_upstream error timeout http_500 http_502 http_503;
proxy_next_upstream_tries 3;
proxy_next_upstream_timeout 10s;
}
server {
listen 80;
server_name seckill.example.com;
# 静态资源服务
location /static/ {
alias /var/www/seckill/static/;
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
# 防盗链
valid_referers none blocked seckill.example.com *.example.com;
if ($invalid_referer) {
return 403;
}
}
# 秒杀页面路由
location /seckill/ {
proxy_pass http://seckill_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 超时配置
proxy_connect_timeout 3s;
proxy_read_timeout 10s;
proxy_send_timeout 10s;
# 缓冲区优化
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
# 启用缓存
proxy_cache seckill_cache;
proxy_cache_valid 200 10s;
}
# API接口路由
location /api/ {
proxy_pass http://seckill_api;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# API特殊超时配置
proxy_connect_timeout 2s;
proxy_read_timeout 5s;
proxy_send_timeout 5s;
# 不缓存API响应
proxy_no_cache 1;
proxy_cache_bypass 1;
}
# 健康检查端点
location /health {
access_log off;
return 200 "healthy
";
add_header Content-Type text/plain;
}
}
}
1.2 高级负载均衡策略
nginx
http {
# 一致性哈希负载均衡 - 提高缓存命中率
upstream seckill_consistent {
hash $request_uri consistent;
server 192.168.1.101:8080;
server 192.168.1.102:8080;
server 192.168.1.103:8080;
server 192.168.1.104:8080;
}
# 基于URI的负载均衡
upstream seckill_goods {
server 192.168.1.101:8080;
}
upstream seckill_order {
server 192.168.1.102:8080;
}
upstream seckill_user {
server 192.168.1.103:8080;
}
server {
# 根据URI路径路由到不同后端
location ~ ^/api/goods/ {
proxy_pass http://seckill_goods;
}
location ~ ^/api/order/ {
proxy_pass http://seckill_order;
}
location ~ ^/api/user/ {
proxy_pass http://seckill_user;
}
}
}
二、Nginx限流配置
2.1 基础限流配置
nginx
http {
# 限流区域配置
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=seckill_limit:10m rate=5r/s;
limit_req_zone $server_name zone=global_limit:10m rate=1000r/s;
# 连接数限制
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
limit_conn_zone $server_name zone=server_conn:10m;
server {
listen 80;
server_name seckill.example.com;
# 全局连接数限制
limit_conn server_conn 10000;
# 静态资源 - 宽松限流
location /static/ {
limit_req zone=api_limit burst=50 nodelay;
limit_conn conn_limit 100;
# 静态资源处理...
}
# 商品查询API - 中等限流
location /api/goods/ {
limit_req zone=api_limit burst=20 nodelay;
limit_conn conn_limit 50;
limit_rate 500k; # 带宽限制
proxy_pass http://seckill_api;
error_page 429 =200 /api/too_many_requests.json;
}
# 秒杀接口 - 严格限流
location ~ ^/api/seckill/ {
limit_req zone=seckill_limit burst=10 nodelay;
limit_conn conn_limit 10;
# 限流后返回特定JSON
error_page 429 =200 /api/seckill_busy.json;
proxy_pass http://seckill_api;
}
# 限流响应页面
location /api/too_many_requests.json {
add_header Content-Type application/json;
return 200 '{"code":429,"message":"请求过于频繁,请稍后重试"}';
}
location /api/seckill_busy.json {
add_header Content-Type application/json;
return 200 '{"code":429,"message":"秒杀过于火爆,请稍后重试"}';
}
}
}
2.2 高级限流策略
nginx
http {
# 基于地理位置的限流
geo $limit_geo {
default 1;
192.168.0.0/24 0; # 内网不限流
10.0.0.0/8 0; # 办公网络不限流
}
map $limit_geo $limit_key {
0 ""; # 不限流
1 $binary_remote_addr; # 限流
}
limit_req_zone $limit_key zone=geo_limit:10m rate=5r/s;
# 基于用户代理的限流
map $http_user_agent $is_bot {
default 0;
"~*bot" 1;
"~*crawler" 1;
"~*spider" 1;
}
limit_req_zone $is_bot zone=bot_limit:1m rate=1r/m;
server {
# 地理位置限流
location /api/ {
limit_req zone=geo_limit burst=10 nodelay;
# 机器人严格限流
if ($is_bot) {
limit_req zone=bot_limit burst=1 nodelay;
}
proxy_pass http://seckill_api;
}
# 白名单配置
location /api/internal/ {
allow 192.168.0.0/24;
allow 10.0.0.0/8;
deny all;
# 内部接口不限流
proxy_pass http://seckill_api;
}
}
}
三、安全防护配置
3.1 DDoS防护
nginx
http {
# 请求频率限制
limit_req_zone $binary_remote_addr zone=flood:10m rate=100r/s;
# 连接数限制
limit_conn_zone $binary_remote_addr zone=addr:10m;
server {
# 全局防护
limit_req zone=flood burst=200 nodelay;
limit_conn addr 100;
# 大文件上传限制
client_max_body_size 10m;
client_body_timeout 10s;
# 隐藏Nginx版本信息
server_tokens off;
# 安全头设置
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
# 限制请求方法
if ($request_method !~ ^(GET|POST|HEAD)$) {
return 405;
}
# 阻止常见攻击
location ~* .(php|asp|aspx|jsp)$ {
return 404;
}
# 阻止敏感文件访问
location ~ /.(ht|git|svn) {
return 404;
}
}
}
3.2 WAF功能配置
nginx
http {
# 自定义WAF规则
map $http_user_agent $bad_agent {
default 0;
"~*nmap" 1;
"~*sqlmap" 1;
"~*nikto" 1;
"~*metasploit" 1;
}
map $args $sql_injection {
default 0;
"~*union.*select" 1;
"~*drop.*table" 1;
"~*insert.*into" 1;
"~*sleep(.*)" 1;
}
server {
# 阻止恶意User-Agent
if ($bad_agent) {
return 403;
}
# SQL注入检测
if ($sql_injection) {
return 403;
}
# XSS攻击检测
if ($request_uri ~* "<script>") {
return 403;
}
# 路径遍历检测
if ($request_uri ~* "../") {
return 403;
}
}
}
四、性能优化配置
4.1 缓存优化
nginx
http {
# 缓存路径配置
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=seckill_cache:10m
max_size=10g inactive=60m use_temp_path=off;
proxy_cache_path /var/cache/nginx/api levels=1:2 keys_zone=api_cache:5m
max_size=1g inactive=5m use_temp_path=off;
# 缓存key配置
proxy_cache_key "$scheme$request_method$host$request_uri$is_args$args";
server {
# 页面缓存
location ~ ^/seckill/goods/d+$ {
proxy_cache seckill_cache;
proxy_cache_valid 200 10s;
proxy_cache_valid 404 1m;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503;
proxy_cache_background_update on;
proxy_cache_lock on;
proxy_cache_lock_timeout 5s;
add_header X-Cache-Status $upstream_cache_status;
proxy_pass http://seckill_backend;
}
# API缓存 - 只缓存GET请求
location /api/goods/ {
proxy_cache api_cache;
proxy_cache_methods GET;
proxy_cache_valid 200 5s;
proxy_cache_valid 404 30s;
# 缓存条件
proxy_cache_bypass $http_cache_control;
proxy_no_cache $http_pragma $http_authorization;
proxy_pass http://seckill_api;
}
}
}
4.2 连接优化
nginx
events {
# 事件模型
use epoll;
# 连接数
worker_connections 65535;
# 多连接接受
multi_accept on;
}
http {
# 连接优化
keepalive_timeout 65;
keepalive_requests 1000;
# 缓冲区优化
client_body_buffer_size 16k;
client_header_buffer_size 4k;
large_client_header_buffers 4 16k;
# 文件传输优化
sendfile on;
tcp_nopush on;
tcp_nodelay on;
# Gzip压缩
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
server {
listen 80 reuseport; # 端口重用
# ... 其他配置
}
}
五、监控和日志配置
5.1 访问日志和监控
nginx
http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'rt=$request_time uct="$upstream_connect_time" '
'uht="$upstream_header_time" urt="$upstream_response_time" '
'cache_status=$upstream_cache_status';
log_format seckill_log '$remote_addr - $request_time - $status - $request_uri';
# 状态监控
server {
listen 8080;
server_name 127.0.0.1;
location /nginx_status {
stub_status on;
access_log off;
allow 127.0.0.1;
deny all;
}
location /server_status {
rtmp_stat all;
rtmp_stat_stylesheet stat.xsl;
allow 127.0.0.1;
deny all;
}
}
server {
access_log /var/log/nginx/seckill_access.log seckill_log;
error_log /var/log/nginx/seckill_error.log warn;
# 秒杀特定日志
location ~ ^/api/seckill/ {
access_log /var/log/nginx/seckill_api.log main;
# ... 其他配置
}
}
}
5.2 动态限流配置
nginx
http {
# Lua脚本支持动态限流
lua_shared_dict dynamic_limit 10m;
init_by_lua_block {
-- 初始化限流配置
local limit_config = {
["/api/seckill/start"] = {rate = 100, burst = 50},
["/api/seckill/end"] = {rate = 50, burst = 20}
}
ngx.shared.dynamic_limit:set("config", cjson.encode(limit_config))
}
server {
location /api/seckill/ {
access_by_lua_file /etc/nginx/lua/dynamic_limit.lua;
proxy_pass http://seckill_api;
}
}
}
这样的Nginx网关配置可以提供:
高性能负载均衡:多种策略支持
精细化限流:基于IP、路径、地理位置等
安全防护:DDoS防护、WAF功能
性能优化:缓存、连接优化
监控能力:详细日志和状态监控
集群部署
秒杀系统集群部署方案
一、整体集群架构
1.1 集群拓扑结构
text
用户请求 → CDN → 负载均衡层 (Nginx/LVS) → 网关层 → 业务集群 → 缓存集群 → 数据库集群
↓ ↓ ↓ ↓ ↓
Nginx集群 Spring Cloud 应用集群 Redis集群 MySQL集群
↓ ↓ ↓ ↓ ↓
Keepalived Gateway集群 多节点部署 哨兵模式 主从复制
二、应用层集群部署
2.1 Spring Boot应用集群配置
2.1.1 应用配置文件
application-cluster.yml
yaml
server:
port: 8080
tomcat:
max-threads: 1000
min-spare-threads: 50
max-connections: 10000
spring:
application:
name: seckill-service
# 集群配置
cloud:
inetutils:
preferred-networks: 192.168.1
# 服务发现 - Nacos配置
nacos:
discovery:
server-addr: 192.168.1.100:8848
cluster-name: CLUSTER_A
namespace: seckill-prod
# Redis集群配置
redis:
cluster:
nodes:
- 192.168.1.101:6379
- 192.168.1.102:6379
- 192.168.1.103:6379
- 192.168.1.104:6379
- 192.168.1.105:6379
- 192.168.1.106:6379
max-redirects: 3
lettuce:
pool:
max-active: 1000
max-wait: 1000ms
max-idle: 50
min-idle: 10
# 数据库集群配置
datasource:
dynamic:
primary: master
strict: false
datasource:
master:
url: jdbc:mysql:loadbalance://192.168.1.110:3306,192.168.1.111:3306/seckill?loadBalanceAutoCommitStatementThreshold=5&useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: seckill_user
password: ${DB_PASSWORD}
driver-class-name: com.mysql.cj.jdbc.Driver
slave:
url: jdbc:mysql:loadbalance://192.168.1.112:3306,192.168.1.113:3306/seckill?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: seckill_user
password: ${DB_PASSWORD}
driver-class-name: com.mysql.cj.jdbc.Driver
# MyBatis配置
mybatis-plus:
configuration:
cache-enabled: true
lazy-loading-enabled: true
map-underscore-to-camel-case: true
global-config:
db-config:
id-type: AUTO
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
# 集群部署配置
cluster:
node-id: ${HOSTNAME:local-1}
# 分布式锁配置
distributed-lock:
enabled: true
type: redis
# 会话配置
session:
store-type: redis
timeout: 1800
2.1.2 服务注册发现配置
Nacos配置
java
@Configuration
@EnableDiscoveryClient
public class NacosConfig {
@Bean
@LoadBalanced
public RestTemplate loadBalancedRestTemplate() {
return new RestTemplate();
}
}
// 服务间调用 - Feign客户端
@FeignClient(name = "seckill-service", path = "/api")
public interface SeckillServiceClient {
@GetMapping("/goods/{goodsId}")
ResponseEntity<GoodsInfo> getGoodsInfo(@PathVariable("goodsId") Long goodsId);
@PostMapping("/seckill/{goodsId}")
ResponseEntity<SeckillResult> executeSeckill(@PathVariable("goodsId") Long goodsId,
@RequestBody SeckillRequest request);
}
2.2 Docker容器化部署
2.2.1 Dockerfile
dockerfile
# 多阶段构建
FROM openjdk:8-jdk-alpine as builder
WORKDIR /app
COPY . .
RUN ./mvnw clean package -DskipTests
# 运行阶段
FROM openjdk:8-jre-alpine
RUN apk add --no-cache tzdata &&
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime &&
echo "Asia/Shanghai" > /etc/timezone
# 创建应用用户
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# 安装监控组件
RUN apk add --no-cache curl
USER appuser
WORKDIR /app
# 复制jar包
COPY --from=builder /app/target/seckill-service-*.jar app.jar
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3
CMD curl -f http://localhost:8080/actuator/health || exit 1
# JVM参数
ENV JAVA_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/app/logs/gc.log"
EXPOSE 8080
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app/app.jar --spring.profiles.active=cluster"]
2.2.2 Docker Compose集群配置
docker-compose-cluster.yml
yaml
version: '3.8'
services:
# Nacos服务注册中心
nacos:
image: nacos/nacos-server:2.0.3
container_name: nacos-server
environment:
- MODE=cluster
- PREFER_HOST_MODE=hostname
- NACOS_SERVERS=nacos1:8848 nacos2:8848 nacos3:8848
- SPRING_DATASOURCE_PLATFORM=mysql
- MYSQL_SERVICE_HOST=mysql-master
- MYSQL_SERVICE_DB_NAME=nacos
- MYSQL_SERVICE_USER=nacos
- MYSQL_SERVICE_PASSWORD=${NACOS_DB_PASSWORD}
ports:
- "8848:8848"
networks:
- seckill-cluster
deploy:
replicas: 1
# 秒杀应用集群
seckill-app:
image: seckill-service:1.0.0
deploy:
replicas: 6
resources:
limits:
memory: 2G
cpus: '1.0'
reservations:
memory: 1G
cpus: '0.5'
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
update_config:
parallelism: 2
delay: 10s
order: start-first
environment:
- SPRING_PROFILES_ACTIVE=cluster
- DB_PASSWORD=${DB_PASSWORD}
- REDIS_PASSWORD=${REDIS_PASSWORD}
- NACOS_SERVER_ADDR=nacos:8848
configs:
- source: app-config
target: /app/config/application.yml
networks:
- seckill-cluster
labels:
- "traefik.enable=true"
- "traefik.http.routers.seckill.rule=Host(`seckill.example.com`)"
- "traefik.http.services.seckill.loadbalancer.sticky.cookie=true"
# Redis集群
redis-node-1:
image: redis:6.2-alpine
command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD} --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000
ports:
- "6379"
networks:
- seckill-cluster
deploy:
replicas: 1
# MySQL集群
mysql-master:
image: mysql:8.0
environment:
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
- MYSQL_DATABASE=seckill
- MYSQL_USER=seckill_user
- MYSQL_PASSWORD=${DB_PASSWORD}
ports:
- "3306"
networks:
- seckill-cluster
volumes:
- mysql-master-data:/var/lib/mysql
- ./config/my.cnf:/etc/mysql/conf.d/my.cnf
configs:
app-config:
file: ./config/application-cluster.yml
networks:
seckill-cluster:
driver: overlay
attachable: true
volumes:
mysql-master-data:
redis-data:
三、缓存集群部署
3.1 Redis集群配置
3.1.1 Redis集群部署脚本
redis-cluster-setup.sh
bash
#!/bin/bash
# Redis集群节点配置
REDIS_NODES=("192.168.1.101:6379" "192.168.1.102:6379" "192.168.1.103:6379"
"192.168.1.104:6379" "192.168.1.105:6379" "192.168.1.106:6379")
# 创建集群
redis-cli --cluster create ${REDIS_NODES[@]} --cluster-replicas 1 -a ${REDIS_PASSWORD}
# 检查集群状态
redis-cli -h 192.168.1.101 -p 6379 -a ${REDIS_PASSWORD} cluster nodes
3.1.2 Redis集群配置
redis.conf
conf
# 集群配置
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 15000
cluster-require-full-coverage no
# 安全配置
requirepass ${REDIS_PASSWORD}
masterauth ${REDIS_PASSWORD}
# 性能配置
maxmemory 2gb
maxmemory-policy allkeys-lru
save 900 1
save 300 10
save 60 10000
# 网络配置
bind 0.0.0.0
protected-mode no
port 6379
# 持久化配置
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
3.2 Redis集群Java配置
java
@Configuration
public class RedisClusterConfig {
@Value("${spring.redis.cluster.nodes}")
private String clusterNodes;
@Value("${spring.redis.password}")
private String password;
@Bean
public RedisConnectionFactory redisConnectionFactory() {
RedisClusterConfiguration config = new RedisClusterConfiguration();
// 解析集群节点
String[] nodes = clusterNodes.split(",");
for (String node : nodes) {
String[] hostPort = node.split(":");
config.addClusterNode(new RedisNode(hostPort[0], Integer.parseInt(hostPort[1])));
}
config.setPassword(RedisPassword.of(password));
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
.commandTimeout(Duration.ofSeconds(2))
.shutdownTimeout(Duration.ZERO)
.build();
return new LettuceConnectionFactory(config, clientConfig);
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory());
// 序列化配置
Jackson2JsonRedisSerializer<Object> serializer =
new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(
mapper.getPolymorphicTypeValidator(),
ObjectMapper.DefaultTyping.NON_FINAL
);
serializer.setObjectMapper(mapper);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
}
四、数据库集群部署
4.1 MySQL主从复制配置
4.1.1 主库配置 (my.cnf)
ini
[mysqld]
# 服务器ID,主库为1
server-id=1
# 二进制日志配置
log-bin=mysql-bin
binlog-format=ROW
expire_logs_days=7
sync_binlog=1
binlog_cache_size=1M
# 需要同步的数据库
binlog-do-db=seckill
# 从库配置
relay-log=mysql-relay-bin
log_slave_updates=1
read_only=0
# InnoDB配置
innodb_flush_log_at_trx_commit=1
innodb_buffer_pool_size=2G
4.1.2 从库配置
ini
[mysqld]
# 服务器ID,从库递增
server-id=2
# 二进制日志配置
log-bin=mysql-bin
binlog-format=ROW
relay-log=mysql-relay-bin
log_slave_updates=1
read_only=1
# 需要同步的数据库
replicate-do-db=seckill
4.2 数据库读写分离配置
java
@Configuration
@EnableTransactionManagement
public class DataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public DataSource dynamicDataSource() {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("master", masterDataSource());
targetDataSources.put("slave", slaveDataSource());
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setTargetDataSources(targetDataSources);
dynamicDataSource.setDefaultTargetDataSource(masterDataSource());
return dynamicDataSource;
}
@Bean
public AbstractRoutingDataSource routingDataSource() {
return new AbstractRoutingDataSource() {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
};
}
}
// 数据源上下文持有器
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
public static void setDataSourceType(String dataSourceType) {
CONTEXT_HOLDER.set(dataSourceType);
}
public static String getDataSourceType() {
return CONTEXT_HOLDER.get();
}
public static void clearDataSourceType() {
CONTEXT_HOLDER.remove();
}
}
// AOP切面实现读写分离
@Aspect
@Component
public class DataSourceAspect {
@Around("@annotation(org.springframework.transaction.annotation.Transactional)")
public Object around(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
Transactional transactional = method.getAnnotation(Transactional.class);
if (transactional.readOnly()) {
// 读操作使用从库
DynamicDataSourceContextHolder.setDataSourceType("slave");
} else {
// 写操作使用主库
DynamicDataSourceContextHolder.setDataSourceType("master");
}
try {
return point.proceed();
} finally {
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
}
五、负载均衡和网关集群
5.1 Nginx负载均衡集群
nginx-cluster.conf
nginx
# 上游服务配置
upstream seckill_backend {
# 一致性哈希,提高缓存命中率
hash $request_uri consistent;
# 应用服务器节点
server 192.168.1.201:8080 weight=3 max_fails=3 fail_timeout=30s;
server 192.168.1.202:8080 weight=3 max_fails=3 fail_timeout=30s;
server 192.168.1.203:8080 weight=2 max_fails=3 fail_timeout=30s;
server 192.168.1.204:8080 weight=2 max_fails=3 fail_timeout=30s;
server 192.168.1.205:8080 weight=2 max_fails=3 fail_timeout=30s;
server 192.168.1.206:8080 weight=2 max_fails=3 fail_timeout=30s;
# 健康检查
check interval=5000 rise=2 fall=3 timeout=3000 type=http;
check_http_send "HEAD /actuator/health HTTP/1.0
";
check_http_expect_alive http_2xx http_3xx;
}
# 缓存服务负载均衡
upstream redis_cluster {
server 192.168.1.101:6379;
server 192.168.1.102:6379;
server 192.168.1.103:6379;
server 192.168.1.104:6379;
server 192.168.1.105:6379;
server 192.168.1.106:6379;
}
server {
listen 80;
server_name seckill.example.com;
# 连接数限制
limit_conn perip 100;
limit_conn perserver 10000;
# 请求频率限制
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
location / {
limit_req zone=api burst=20 nodelay;
proxy_pass http://seckill_backend;
# 代理配置
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 超时配置
proxy_connect_timeout 3s;
proxy_read_timeout 10s;
proxy_send_timeout 10s;
# 启用缓存
proxy_cache seckill_cache;
proxy_cache_valid 200 10s;
}
}
5.2 Spring Cloud Gateway集群
yaml
spring:
cloud:
gateway:
discovery:
locator:
enabled: true
routes:
- id: seckill-service
uri: lb://seckill-service
predicates:
- Path=/api/**
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
redis-rate-limiter.requestedTokens: 1
- name: CircuitBreaker
args:
name: seckillService
fallbackUri: forward:/fallback
- StripPrefix=1
六、监控和运维
6.1 集群监控配置
Prometheus配置
yaml
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'seckill-app'
metrics_path: '/actuator/prometheus'
static_configs:
- targets:
- '192.168.1.201:8080'
- '192.168.1.202:8080'
- '192.168.1.203:8080'
- '192.168.1.204:8080'
- '192.168.1.205:8080'
- '192.168.1.206:8080'
labels:
cluster: 'seckill-prod'
- job_name: 'redis-cluster'
static_configs:
- targets:
- '192.168.1.101:9121'
- '192.168.1.102:9121'
- '192.168.1.103:9121'
- job_name: 'mysql-cluster'
static_configs:
- targets:
- '192.168.1.110:9104'
- '192.168.1.111:9104'
6.2 应用监控端点
java
@Configuration
@EnablePrometheusMetrics
@EnableScheduling
public class MonitorConfig {
@Bean
public MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
return registry -> registry.config().commonTags(
"application", "seckill-service",
"cluster", "CLUSTER_A",
"instance", System.getenv("HOSTNAME")
);
}
}
// 自定义业务指标
@Component
public class SeckillMetrics {
private final Counter seckillRequestCounter;
private final Counter seckillSuccessCounter;
private final Gauge stockGauge;
public SeckillMetrics(MeterRegistry registry) {
seckillRequestCounter = Counter.builder("seckill.requests.total")
.description("Total seckill requests")
.tag("type", "request")
.register(registry);
seckillSuccessCounter = Counter.builder("seckill.success.total")
.description("Total successful seckills")
.tag("type", "success")
.register(registry);
}
public void recordSeckillRequest() {
seckillRequestCounter.increment();
}
public void recordSeckillSuccess() {
seckillSuccessCounter.increment();
}
}
这样的集群部署方案可以提供:
高可用性:多节点冗余,故障自动转移
弹性扩展:根据负载动态调整节点数量
负载均衡:智能路由,避免单点过载
数据一致性:集群数据同步机制
监控运维:全面的监控和告警体系
分库分表
秒杀系统分库分表方案
一、分库分表架构设计
1.1 整体分片策略
text
应用层 → 分库分表中间件 → 物理数据库集群
↓
订单表: user_id分片
商品表: goods_id分片
用户表: user_id分片
二、ShardingSphere分库分表实现
2.1 Maven依赖配置
xml
<dependencies>
<!-- ShardingSphere -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>5.3.2</version>
</dependency>
<!-- 数据源 -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
<!-- 数据库连接 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
</dependencies>
2.2 分库分表配置
application-sharding.yml
yaml
spring:
shardingsphere:
# 数据源配置
datasource:
names: ds0, ds1, ds2, ds3
# 数据库0 - 主库
ds0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://192.168.1.110:3306/seckill_0?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: seckill_user
password: ${DB_PASSWORD}
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
maximum-pool-size: 20
minimum-idle: 5
# 数据库1
ds1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://192.168.1.111:3306/seckill_1?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: seckill_user
password: ${DB_PASSWORD}
# 数据库2
ds2:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://192.168.1.112:3306/seckill_2?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: seckill_user
password: ${DB_PASSWORD}
# 数据库3
ds3:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://192.168.1.113:3306/seckill_3?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: seckill_user
password: ${DB_PASSWORD}
# 分片规则
rules:
sharding:
# 分片算法
sharding-algorithms:
# 订单表分库分表算法
order-database-inline:
type: INLINE
props:
algorithm-expression: ds$->{user_id % 4}
order-table-inline:
type: INLINE
props:
algorithm-expression: seckill_order_$->{user_id % 16}
# 商品表分表算法
goods-table-inline:
type: INLINE
props:
algorithm-expression: seckill_goods_$->{goods_id % 8}
# 时间分片算法 - 按月分表
order-time-inline:
type: INTERVAL
props:
datetime-pattern: "yyyy-MM-dd HH:mm:ss"
datetime-lower: "2024-01-01 00:00:00"
datetime-upper: "2024-12-31 23:59:59"
sharding-suffix-pattern: "yyyyMM"
datetime-interval-amount: 1
datetime-interval-unit: "MONTHS"
# 表规则
tables:
# 订单表分片规则 - 4库×16表=64张表
seckill_order:
actual-data-nodes: ds$->{0..3}.seckill_order_$->{0..15}
database-strategy:
standard:
sharding-column: user_id
sharding-algorithm-name: order-database-inline
table-strategy:
standard:
sharding-column: user_id
sharding-algorithm-name: order-table-inline
key-generate-strategy:
column: id
key-generator-name: snowflake
# 商品表分片规则 - 单库8表
seckill_goods:
actual-data-nodes: ds0.seckill_goods_$->{0..7}
table-strategy:
standard:
sharding-column: goods_id
sharding-algorithm-name: goods-table-inline
key-generate-strategy:
column: id
key-generator-name: snowflake
# 用户表分片规则 - 4库分片
user_info:
actual-data-nodes: ds$->{0..3}.user_info
database-strategy:
standard:
sharding-column: user_id
sharding-algorithm-name: order-database-inline
key-generate-strategy:
column: user_id
key-generator-name: snowflake
# 绑定表规则 - 优化关联查询
binding-tables:
- seckill_order, order_detail
# 广播表 - 小表全库同步
broadcast-tables:
- region_info, system_config
# 分布式序列算法
key-generators:
snowflake:
type: SNOWFLAKE
props:
worker-id: 123
# 属性配置
props:
sql-show: true
sql-simple: true
check-table-metadata-enabled: false
# MyBatis配置
mybatis-plus:
mapper-locations: classpath*:mapper/**/*.xml
configuration:
map-underscore-to-camel-case: true
cache-enabled: true
global-config:
db-config:
id-type: INPUT
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
三、数据库表结构设计
3.1 订单表分片设计
sql
-- 分库分表SQL生成脚本
-- 在每个数据库执行以下建表语句
-- 订单表 - 16张分表
CREATE TABLE `seckill_order_0` (
`id` bigint(20) NOT NULL COMMENT '订单ID',
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`goods_id` bigint(20) NOT NULL COMMENT '商品ID',
`order_no` varchar(32) NOT NULL COMMENT '订单号',
`goods_name` varchar(255) NOT NULL COMMENT '商品名称',
`quantity` int(11) NOT NULL DEFAULT '1' COMMENT '购买数量',
`unit_price` decimal(10,2) NOT NULL COMMENT '单价',
`total_amount` decimal(10,2) NOT NULL COMMENT '总金额',
`order_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '订单状态:0-待支付,1-已支付,2-已取消',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '删除标记',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_order_no` (`order_no`),
KEY `idx_user_id` (`user_id`),
KEY `idx_goods_id` (`goods_id`),
KEY `idx_create_time` (`create_time`),
KEY `idx_user_goods` (`user_id`,`goods_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='秒杀订单表';
-- 为每个分表创建相同的结构
-- seckill_order_1 到 seckill_order_15
3.2 商品表分片设计
sql
-- 商品表 - 8张分表
CREATE TABLE `seckill_goods_0` (
`id` bigint(20) NOT NULL COMMENT '商品ID',
`goods_name` varchar(255) NOT NULL COMMENT '商品名称',
`goods_title` varchar(255) NOT NULL COMMENT '商品标题',
`goods_img` varchar(500) DEFAULT NULL COMMENT '商品图片',
`goods_detail` text COMMENT '商品详情',
`goods_price` decimal(10,2) NOT NULL COMMENT '商品价格',
`seckill_price` decimal(10,2) NOT NULL COMMENT '秒杀价格',
`stock_count` int(11) NOT NULL COMMENT '库存数量',
`start_time` datetime NOT NULL COMMENT '开始时间',
`end_time` datetime NOT NULL COMMENT '结束时间',
`version` int(11) NOT NULL DEFAULT '0' COMMENT '版本号',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deleted` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_start_time` (`start_time`),
KEY `idx_end_time` (`end_time`),
KEY `idx_time_range` (`start_time`,`end_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='秒杀商品表';
-- 为每个分表创建相同的结构
-- seckill_goods_1 到 seckill_goods_7
3.3 用户表设计
sql
-- 用户表 - 每个库都有相同的表结构
CREATE TABLE `user_info` (
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`username` varchar(50) NOT NULL COMMENT '用户名',
`phone` varchar(20) NOT NULL COMMENT '手机号',
`email` varchar(100) DEFAULT NULL COMMENT '邮箱',
`level` tinyint(4) NOT NULL DEFAULT '1' COMMENT '用户等级',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deleted` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`user_id`),
UNIQUE KEY `uk_username` (`username`),
UNIQUE KEY `uk_phone` (`phone`),
KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户信息表';
四、Java代码实现
4.1 实体类设计
java
@Data
@TableName("seckill_order")
public class SeckillOrder {
@TableId(type = IdType.INPUT)
private Long id;
private Long userId;
private Long goodsId;
private String orderNo;
private String goodsName;
private Integer quantity;
private BigDecimal unitPrice;
private BigDecimal totalAmount;
private Integer orderStatus;
private Date createTime;
private Date updateTime;
private Integer deleted;
}
@Data
@TableName("seckill_goods")
public class SeckillGoods {
@TableId(type = IdType.INPUT)
private Long id;
private String goodsName;
private String goodsTitle;
private String goodsImg;
private String goodsDetail;
private BigDecimal goodsPrice;
private BigDecimal seckillPrice;
private Integer stockCount;
private Date startTime;
private Date endTime;
private Integer version;
private Date createTime;
private Date updateTime;
private Integer deleted;
}
4.2 Mapper层实现
java
@Mapper
public interface SeckillOrderMapper extends BaseMapper<SeckillOrder> {
/**
* 根据用户ID和商品ID查询订单
* 注意:这里需要指定分片键
*/
@Select("SELECT * FROM seckill_order WHERE user_id = #{userId} AND goods_id = #{goodsId} AND deleted = 0")
SeckillOrder selectByUserAndGoods(@Param("userId") Long userId, @Param("goodsId") Long goodsId);
/**
* 根据用户ID分页查询订单
*/
@Select("SELECT * FROM seckill_order WHERE user_id = #{userId} AND deleted = 0 ORDER BY create_time DESC")
List<SeckillOrder> selectByUserId(@Param("userId") Long userId);
/**
* 根据订单号查询
*/
@Select("SELECT * FROM seckill_order WHERE order_no = #{orderNo} AND deleted = 0")
SeckillOrder selectByOrderNo(@Param("orderNo") String orderNo);
}
@Mapper
public interface SeckillGoodsMapper extends BaseMapper<SeckillGoods> {
/**
* 扣减库存 - 带乐观锁
*/
@Update("UPDATE seckill_goods SET stock_count = stock_count - 1, version = version + 1 " +
"WHERE id = #{goodsId} AND stock_count > 0 AND version = #{version}")
int reduceStockWithVersion(@Param("goodsId") Long goodsId, @Param("version") Integer version);
/**
* 查询秒杀商品列表
*/
@Select("<script>" +
"SELECT * FROM seckill_goods WHERE deleted = 0 " +
"<if test='startTime != null'> AND start_time <= #{startTime} </if>" +
"<if test='endTime != null'> AND end_time >= #{endTime} </if>" +
"ORDER BY start_time DESC" +
"</script>")
List<SeckillGoods> selectSeckillGoodsList(@Param("startTime") Date startTime,
@Param("endTime") Date endTime);
}
4.3 Service层实现
java
@Service
@Slf4j
public class SeckillShardingService {
@Autowired
private SeckillOrderMapper seckillOrderMapper;
@Autowired
private SeckillGoodsMapper seckillGoodsMapper;
@Autowired
private DistributedIdGenerator idGenerator;
/**
* 创建秒杀订单 - 分库分表
*/
@Transactional(rollbackFor = Exception.class)
public SeckillResult createSeckillOrder(Long userId, Long goodsId) {
// 1. 查询商品信息
SeckillGoods goods = seckillGoodsMapper.selectById(goodsId);
if (goods == null) {
return SeckillResult.error("商品不存在");
}
// 2. 检查库存
if (goods.getStockCount() <= 0) {
return SeckillResult.error("商品已售罄");
}
// 3. 检查重复购买
SeckillOrder existOrder = seckillOrderMapper.selectByUserAndGoods(userId, goodsId);
if (existOrder != null) {
return SeckillResult.error("请勿重复购买");
}
// 4. 扣减库存
int updateResult = seckillGoodsMapper.reduceStockWithVersion(goodsId, goods.getVersion());
if (updateResult <= 0) {
return SeckillResult.error("库存不足");
}
// 5. 创建订单
SeckillOrder order = new SeckillOrder();
order.setId(idGenerator.nextId());
order.setUserId(userId);
order.setGoodsId(goodsId);
order.setOrderNo(generateOrderNo());
order.setGoodsName(goods.getGoodsName());
order.setQuantity(1);
order.setUnitPrice(goods.getSeckillPrice());
order.setTotalAmount(goods.getSeckillPrice());
order.setOrderStatus(0);
int insertResult = seckillOrderMapper.insert(order);
if (insertResult <= 0) {
throw new RuntimeException("创建订单失败");
}
return SeckillResult.success("秒杀成功", order.getOrderNo());
}
/**
* 查询用户订单列表 - 支持分页
*/
public PageResult<SeckillOrder> getUserOrders(Long userId, Integer page, Integer size) {
Page<SeckillOrder> pageHelper = new Page<>(page, size);
// 这里会自动路由到正确的分片
LambdaQueryWrapper<SeckillOrder> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SeckillOrder::getUserId, userId)
.eq(SeckillOrder::getDeleted, 0)
.orderByDesc(SeckillOrder::getCreateTime);
IPage<SeckillOrder> orderPage = seckillOrderMapper.selectPage(pageHelper, queryWrapper);
return new PageResult<>(
orderPage.getRecords(),
orderPage.getTotal(),
page,
size
);
}
/**
* 批量查询订单 - 使用Hint强制路由
*/
public List<SeckillOrder> batchGetOrders(List<Long> orderIds) {
// 对于跨分片的查询,需要分别查询每个分片
List<SeckillOrder> result = new ArrayList<>();
for (Long orderId : orderIds) {
// 这里需要根据orderId计算出分片信息
// 实际项目中可以使用ShardingSphere的HintManager
SeckillOrder order = seckillOrderMapper.selectById(orderId);
if (order != null) {
result.add(order);
}
}
return result;
}
private String generateOrderNo() {
return "SO" + System.currentTimeMillis() + RandomUtil.randomNumbers(6);
}
}
4.4 分布式ID生成器
java
@Component
public class DistributedIdGenerator {
/**
* 雪花算法ID生成
*/
public long nextId() {
// 使用ShardingSphere的雪花算法
// 或者自定义实现
return IdWorker.getId();
}
/**
* 根据用户ID计算分片信息
*/
public ShardInfo calculateShardInfo(Long userId) {
int databaseShard = (int) (userId % 4);
int tableShard = (int) (userId % 16);
return new ShardInfo(databaseShard, tableShard);
}
@Data
@AllArgsConstructor
public static class ShardInfo {
private int databaseShard;
private int tableShard;
}
}
五、数据迁移方案
5.1 历史数据迁移脚本
java
@Component
@Slf4j
public class DataMigrationService {
@Autowired
private DataSource dataSource;
@Autowired
private DistributedIdGenerator idGenerator;
/**
* 订单数据迁移
*/
@Async
public void migrateOrderData() {
log.info("开始迁移订单数据...");
// 1. 从原单表查询数据
String sourceSql = "SELECT * FROM seckill_order_old WHERE deleted = 0";
try (Connection sourceConn = getSourceConnection();
PreparedStatement pstmt = sourceConn.prepareStatement(sourceSql);
ResultSet rs = pstmt.executeQuery()) {
int batchCount = 0;
int totalCount = 0;
// 2. 批量插入到分片表
while (rs.next()) {
SeckillOrder order = convertToOrder(rs);
// 根据用户ID路由到正确的分片
insertToShardingTable(order);
batchCount++;
totalCount++;
// 批量提交
if (batchCount >= 1000) {
log.info("已迁移 {} 条订单数据", totalCount);
batchCount = 0;
}
}
log.info("订单数据迁移完成,总计: {} 条", totalCount);
} catch (Exception e) {
log.error("订单数据迁移失败", e);
throw new RuntimeException("数据迁移失败", e);
}
}
private SeckillOrder convertToOrder(ResultSet rs) throws SQLException {
SeckillOrder order = new SeckillOrder();
order.setId(rs.getLong("id"));
order.setUserId(rs.getLong("user_id"));
order.setGoodsId(rs.getLong("goods_id"));
order.setOrderNo(rs.getString("order_no"));
// ... 其他字段赋值
return order;
}
private void insertToShardingTable(SeckillOrder order) {
// 使用ShardingSphere JDBC插入,会自动路由到正确的分片
seckillOrderMapper.insert(order);
}
}
5.2 数据校验工具
java
@Component
public class DataValidator {
/**
* 校验分片数据一致性
*/
public boolean validateDataConsistency() {
// 校验总数据量
long sourceCount = getSourceDataCount();
long shardingCount = getShardingDataCount();
if (sourceCount != shardingCount) {
log.error("数据量不一致: 源表={}, 分片表={}", sourceCount, shardingCount);
return false;
}
// 抽样校验数据准确性
return validateSampleData();
}
private boolean validateSampleData() {
// 随机抽样1000条数据校验
List<Long> sampleIds = getSampleOrderIds();
for (Long orderId : sampleIds) {
SeckillOrder sourceOrder = getSourceOrder(orderId);
SeckillOrder shardingOrder = seckillOrderMapper.selectById(orderId);
if (!compareOrders(sourceOrder, shardingOrder)) {
log.error("数据不一致, orderId: {}", orderId);
return false;
}
}
return true;
}
}
六、监控和运维
6.1 分片监控配置
yaml
spring:
shardingsphere:
# 监控配置
metrics:
enabled: true
name: prometheus
# 日志配置
log:
slow-sql-millis: 1000
6.2 自定义分片监控
java
@Component
public class ShardingMonitor {
private final MeterRegistry meterRegistry;
private final Counter slowSqlCounter;
private final Gauge tableSizeGauge;
public ShardingMonitor(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.slowSqlCounter = Counter.builder("sharding.sql.slow")
.description("Slow SQL queries in sharding tables")
.register(meterRegistry);
// 监控分表数据量
this.tableSizeGauge = Gauge.builder("sharding.table.size")
.description("Sharding table data size")
.tag("table", "seckill_order")
.register(meterRegistry);
}
/**
* 记录慢查询
*/
public void recordSlowQuery(String sql, long executionTime) {
slowSqlCounter.increment();
log.warn("慢SQL查询: {}, 执行时间: {}ms", sql, executionTime);
}
/**
* 获取分表数据量统计
*/
public Map<String, Long> getTableStatistics() {
Map<String, Long> statistics = new HashMap<>();
for (int i = 0; i < 16; i++) {
String tableName = "seckill_order_" + i;
long count = getTableCount(tableName);
statistics.put(tableName, count);
}
return statistics;
}
}
这样的分库分表方案能够:
水平扩展:支持海量数据存储
高性能:分散读写压力
高可用:多库冗余,故障隔离
易维护:标准化的分片策略和迁移方案


