Java+Redis高可用电商系统源码:秒杀与优惠券模块详解
Java+Redis高可用电商系统:秒杀与优惠券模块架构设计与实战
本文将深入探讨基于Java+Redis的高可用电商系统中秒杀与优惠券模块的核心架构设计,通过完整的代码实例展示如何应对高并发场景。
1. 引言:高并发电商系统的挑战
在当今互联网时代,电商平台的秒杀活动和优惠券促销是吸引用户、提升转化的关键手段。这些场景往往伴随着极高的并发访问,如何保证系统的高可用性、数据一致性和性能成为技术设计的核心挑战。
传统架构的痛点:
- 数据库成为性能瓶颈,无法承受瞬时高并发
- 超卖问题导致库存不一致
- 优惠券被重复使用或超发
- 系统单点故障导致服务不可用
本文将基于Spring Boot + Redis技术栈,详细解析高可用电商系统中秒杀与优惠券模块的设计与实现。
2. 技术栈选型与整体架构
2.1 核心技术组件
```xml
org.springframework.boot
spring-boot-starter-data-redis
org.redisson
redisson-spring-boot-starter
3.27.0
org.apache.commons
commons-pool2
```
2.2 系统架构设计
```java
@Component
public class SeckillSystemConfig {
// Redis集群配置
@Bean
@ConfigurationProperties(prefix = "spring.redis")
public RedisClusterConfiguration redisClusterConfiguration() {
return new RedisClusterConfiguration();
}
// Lettuce连接工厂
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
.useSsl().and()
.commandTimeout(Duration.ofSeconds(2))
.shutdownTimeout(Duration.ZERO)
.build();
return new LettuceConnectionFactory(redisClusterConfiguration(), clientConfig);
}
// RedisTemplate配置
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory());
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
}
```
3. 秒杀模块核心设计与实现
3.1 秒杀业务流程设计
```java
@Service
@Slf4j
public class SeckillServiceImpl implements SeckillService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private RedissonClient redissonClient;
@Autowired
private OrderService orderService;
// 秒杀令牌生成
@Override
public String generateSeckillToken(Long userId, Long seckillId) {
String key = "seckill:token:" + seckillId + ":" + userId;
String token = UUID.randomUUID().toString().replace("-", "");
// 令牌有效期为10秒
redisTemplate.opsForValue().set(key, token, Duration.ofSeconds(10));
return token;
}
// 秒杀核心逻辑
@Override
public SeckillResponse executeSeckill(Long userId, Long seckillId, String token) {
// 1. 验证令牌
if (!validateToken(userId, seckillId, token)) {
return SeckillResponse.fail("非法请求或令牌已过期");
}
// 2. 校验用户重复购买
if (checkUserPurchased(userId, seckillId)) {
return SeckillResponse.fail("您已参与过本次秒杀");
}
// 3. 获取分布式锁
RLock lock = redissonClient.getLock("seckill:lock:" + seckillId);
try {
// 尝试加锁,最多等待100ms,锁持有时间5秒
if (lock.tryLock(100, 5000, TimeUnit.MILLISECONDS)) {
try {
// 4. 校验库存
Integer stock = getSeckillStock(seckillId);
if (stock == null || stock <= 0) {
return SeckillResponse.fail("商品已售罄");
}
// 5. 扣减库存
if (decrementStock(seckillId)) {
// 6. 创建订单
Order order = createSeckillOrder(userId, seckillId);
// 7. 记录用户购买记录
recordUserPurchase(userId, seckillId);
return SeckillResponse.success(order);
} else {
return SeckillResponse.fail("扣减库存失败");
}
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
} else {
return SeckillResponse.fail("系统繁忙,请重试");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return SeckillResponse.fail("系统异常");
}
}
// Lua脚本实现原子性库存扣减
private static final String DECR_STOCK_SCRIPT =
"if redis.call('exists', KEYS[1]) == 1 then " +
" local stock = tonumber(redis.call('get', KEYS[1])) " +
" if stock > 0 then " +
" redis.call('decr', KEYS[1]) " +
" return 1 " +
" end " +
" return 0 " +
"end " +
"return -1";
private boolean decrementStock(Long seckillId) {
String stockKey = "seckill:stock:" + seckillId;
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
script.setScriptText(DECR_STOCK_SCRIPT);
script.setResultType(Long.class);
Long result = redisTemplate.execute(script, Collections.singletonList(stockKey));
return result != null && result == 1;
}
}
```
3.2 库存预热与缓存策略
```java
@Service
public class SeckillPreheatService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private SeckillItemMapper seckillItemMapper;
// 秒杀开始前预热数据到Redis
@Async
public void preheatSeckillData(Long seckillId) {
SeckillItem seckillItem = seckillItemMapper.selectById(seckillId);
if (seckillItem == null) {
log.error("秒杀商品不存在: {}", seckillId);
return;
}
// 设置商品库存
String stockKey = "seckill:stock:" + seckillId;
redisTemplate.opsForValue().set(stockKey, seckillItem.getStock());
// 设置商品详情缓存
String itemKey = "seckill:item:" + seckillId;
redisTemplate.opsForValue().set(itemKey, seckillItem, Duration.ofHours(2));
// 设置秒杀开始结束时间
String timeKey = "seckill:time:" + seckillId;
Map<String, Object> timeMap = new HashMap<>();
timeMap.put("startTime", seckillItem.getStartTime().getTime());
timeMap.put("endTime", seckillItem.getEndTime().getTime());
redisTemplate.opsForHash().putAll(timeKey, timeMap);
log.info("秒杀商品数据预热完成: {}", seckillId);
}
// 获取秒杀商品信息
public SeckillItem getSeckillItem(Long seckillId) {
String key = "seckill:item:" + seckillId;
SeckillItem item = (SeckillItem) redisTemplate.opsForValue().get(key);
if (item == null) {
// 缓存未命中,从数据库加载
item = seckillItemMapper.selectById(seckillId);
if (item != null) {
redisTemplate.opsForValue().set(key, item, Duration.ofHours(1));
}
}
return item;
}
}
```
4. 优惠券模块设计与实现
4.1 优惠券发放与使用
```java
@Service
@Slf4j
public class CouponServiceImpl implements CouponService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private CouponMapper couponMapper;
// 发放优惠券
@Override
public boolean grantCoupon(Long userId, Long couponId) {
String lockKey = "coupon:grant:lock:" + couponId;
RLock lock = redissonClient.getLock(lockKey);
try {
if (lock.tryLock(50, 10, TimeUnit.SECONDS)) {
// 检查优惠券库存
String stockKey = "coupon:stock:" + couponId;
Integer stock = (Integer) redisTemplate.opsForValue().get(stockKey);
if (stock == null) {
// 从数据库加载库存
Coupon coupon = couponMapper.selectById(couponId);
if (coupon == null) return false;
stock = coupon.getStock();
redisTemplate.opsForValue().set(stockKey, stock);
}
if (stock <= 0) {
return false;
}
// 使用Lua脚本原子性扣减库存
String luaScript =
"if redis.call('get', KEYS[1]) >= '1' then " +
" redis.call('decr', KEYS[1]) " +
" return 1 " +
"else " +
" return 0 " +
"end";
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
script.setScriptText(luaScript);
script.setResultType(Long.class);
Long result = redisTemplate.execute(script, Collections.singletonList(stockKey));
if (result != null && result == 1) {
// 发放优惠券给用户
String userCouponKey = "user:coupon:" + userId;
String couponInfo = couponId + ":" + System.currentTimeMillis();
redisTemplate.opsForSet().add(userCouponKey, couponInfo);
redisTemplate.expire(userCouponKey, Duration.ofDays(30));
log.info("用户{}成功领取优惠券{}", userId, couponId);
return true;
}
}
return false;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
// 使用优惠券
@Override
public boolean useCoupon(Long userId, Long couponId, String orderId) {
String userCouponKey = "user:coupon:" + userId;
String usedKey = "coupon:used:" + couponId + ":" + userId;
// 检查优惠券是否已使用
if (redisTemplate.hasKey(usedKey)) {
return false;
}
// 检查用户是否拥有该优惠券
Boolean hasCoupon = redisTemplate.opsForSet().isMember(userCouponKey,
couponId + ":");
if (Boolean.TRUE.equals(hasCoupon)) {
// 标记优惠券为已使用
redisTemplate.opsForValue().set(usedKey, orderId, Duration.ofDays(30));
// 从用户优惠券集合中移除(或标记为已使用)
String pattern = couponId + ":";
Set<Object> userCoupons = redisTemplate.opsForSet().members(userCouponKey);
for (Object coupon : userCoupons) {
if (coupon.toString().startsWith(couponId + ":")) {
redisTemplate.opsForSet().remove(userCouponKey, coupon);
break;
}
}
log.info("用户{}使用优惠券{}于订单{}", userId, couponId, orderId);
return true;
}
return false;
}
}
```
4.2 优惠券核销与防刷策略
```java
@Service
public class CouponValidationService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 优惠券防刷限制
public boolean checkRateLimit(Long userId, Long couponId) {
String limitKey = "coupon:limit:" + couponId + ":" + userId;
String dailyLimitKey = "coupon:daily_limit:" + couponId + ":" +
LocalDate.now().toString();
// 个人总限制
Long userTotal = redisTemplate.opsForValue().increment(limitKey, 1);
if (userTotal != null && userTotal == 1) {
redisTemplate.expire(limitKey, Duration.ofDays(30)); // 30天过期
}
// 每日限制
Long dailyCount = redisTemplate.opsForValue().increment(dailyLimitKey, 1);
if (dailyCount != null && dailyCount == 1) {
redisTemplate.expire(dailyLimitKey, Duration.ofDays(1));
}
Coupon coupon = getCouponInfo(couponId);
if (coupon == null) return false;
// 检查限制
if (coupon.getUserLimit() > 0 && userTotal > coupon.getUserLimit()) {
return false;
}
if (coupon.getDailyLimit() > 0 && dailyCount > coupon.getDailyLimit()) {
return false;
}
return true;
}
// 批量核销优惠券
public BatchCouponResult batchVerifyCoupons(BatchCouponRequest request) {
String batchKey = "coupon:batch:" + UUID.randomUUID().toString();
BatchCouponResult result = new BatchCouponResult();
// 使用Redis事务保证原子性
redisTemplate.setEnableTransactionSupport(true);
redisTemplate.multi();
try {
for (CouponVerifyItem item : request.getItems()) {
String verifyKey = "coupon:verify:" + item.getCouponCode();
// 检查优惠券状态
if (!redisTemplate.hasKey(verifyKey)) {
// 设置优惠券为已核销
redisTemplate.opsForValue().set(verifyKey,
item.getOrderId(), Duration.ofHours(24));
result.addSuccess(item.getCouponCode());
} else {
result.addFailed(item.getCouponCode(), "优惠券已核销");
}
}
redisTemplate.exec();
return result;
} catch (Exception e) {
redisTemplate.discard();
throw new RuntimeException("批量核销失败", e);
} finally {
redisTemplate.setEnableTransactionSupport(false);
}
}
}
```
5. 高可用架构保障
5.1 Redis集群配置与故障转移
```yaml
application-redis.yml
spring:
redis:
cluster:
nodes:
- 192.168.1.101:6379
- 192.168.1.102:6379
- 192.168.1.103:6379
max-redirects: 3
lettuce:
pool:
max-active: 20
max-idle: 10
min-idle: 5
max-wait: 1000ms
cluster:
refresh:
adaptive: true
period: 2000ms
timeout: 1000ms
```
5.2 熔断降级策略
```java
@Component
public class RedisCircuitBreaker {
private final CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.failureRateThreshold(50)
.waitDurationInOpenState(Duration.ofSeconds(30))
.ringBufferSizeInHalfOpenState(10)
.ringBufferSizeInClosedState(100)
.build();
private final CircuitBreaker circuitBreaker = CircuitBreaker.of("redis", config);
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private DatabaseFallbackService fallbackService;
public <T> T executeWithCircuitBreaker(Supplier<T> redisOperation,
String operationName) {
return circuitBreaker.executeSupplier(() -> {
try {
return redisOperation.get();
} catch (RedisConnectionFailureException e) {
log.warn("Redis操作失败,触发熔断: {}", operationName);
throw e;
}
});
}
// 降级到数据库的方案
public boolean seckillWithFallback(Long userId, Long seckillId, String token) {
try {
return executeWithCircuitBreaker(() -> {
// Redis秒杀逻辑
return doSeckillInRedis(userId, seckillId, token);
}, "seckill_operation");
} catch (CallNotPermittedException e) {
// 熔断开启,降级到数据库方案
log.info("触发熔断,降级到数据库方案");
return fallbackService.seckillInDatabase(userId, seckillId);
}
}
}
```
5.3 监控与告警
```java
@Component
public class RedisMonitor {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 监控Redis关键指标
public void monitorRedisHealth() {
// 连接数监控
String info = redisTemplate.execute((RedisCallback<String>) connection ->
connection.info("clients").get("connected_clients"));
// 内存使用监控
String memoryInfo = redisTemplate.execute((RedisCallback<String>) connection ->
connection.info("memory").get("used_memory"));
// 慢查询监控
List<Object> slowLogs = redisTemplate.opsForList().range("slowlog", 0, -1);
// 发送到监控系统
sendToMonitorSystem(info, memoryInfo, slowLogs);
}
// 自定义监控指标
@Bean
public MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
return registry -> registry.config().commonTags(
"application", "ecommerce-seckill",
"module", "redis-cache"
);
}
}
```
6. 性能优化实战
6.1 Pipeline批量操作优化
```java
@Service
public class RedisPipelineService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 使用pipeline批量查询用户优惠券
public Map<String, Boolean> batchCheckUserCoupons(Long userId, List<Long> couponIds) {
return redisTemplate.executePipelined(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
String userCouponKey = "user:coupon:" + userId;
for (Long couponId : couponIds) {
String pattern = couponId + ":";
connection.sIsMember(userCouponKey.getBytes(), pattern.getBytes());
}
return null;
}
});
}
// 批量获取秒杀商品信息
public List<SeckillItem> batchGetSeckillItems(List<Long> seckillIds) {
List<Object> results = redisTemplate.executePipelined(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
for (Long seckillId : seckillIds) {
String key = "seckill:item:" + seckillId;
connection.get(key.getBytes());
}
return null;
}
});
return results.stream()
.map(item -> (SeckillItem) item)
.collect(Collectors.toList());
}
}
```
7. 总结与最佳实践
本文详细介绍了基于Java+Redis的高可用电商系统秒杀与优惠券模块的设计与实现。关键要点总结如下:
7.1 核心技术要点
- 原子操作保障:使用Lua脚本确保库存扣减的原子性
- 分布式锁应用:通过Redisson实现分布式环境下的互斥访问
- 缓存预热策略:提前加载热点数据到Redis,降低数据库压力
- 熔断降级机制:在Redis不可用时自动降级到数据库方案
7.2 性能优化实践
- Pipeline批量操作:减少网络往返时间,提升批量查询效率
- 连接池优化:合理配置连接池参数,避免连接瓶颈
- 集群监控:实时监控Redis集群状态,及时发现问题
7.3 高可用保障
- 多级缓存策略:本地缓存+分布式缓存组合使用
- 故障转移机制:主从切换、集群自动故障恢复
- 容量规划:根据业务峰值合理规划Redis集群容量
通过本文的架构设计和代码实现,电商系统可以支撑万级甚至十万级的并发秒杀场景,同时保证系统的高可用性和数据一致性。在实际项目中,还需要根据具体业务需求进行适当的调整和优化。
参考资料:
- Spring官方文档:Spring Data Redis
- Redis官方文档:Cluster Tutorial
- 阿里云开发者社区:Redis最佳实践
- GitHub开源项目:redisson、resilience4j
本文详细代码示例已上传至GitHub,欢迎Star和贡献:https://github.com/example/ecommerce-seckill
更多推荐


所有评论(0)