秒杀流程图
第一步:远程查询最近 3 天内秒杀的活动 以及 秒杀活动的关联的商品信息,具体demo展示
//1、扫描最近三天的商品需要参加秒杀的活动
R lates3DaySession = couponFeignService.getLates3DaySession();
if (lates3DaySession.getCode() == 0) {
//上架商品
List<SeckillSessionWithSkusVo> sessionData = lates3DaySession.getData("data", new TypeReference<List<SeckillSessionWithSkusVo>>() {
});
//缓存到Redis
//1、缓存活动信息
saveSessionInfos(sessionData);
//2、缓存活动的关联商品信息
saveSessionSkuInfo(sessionData);
}
第二步:在Redis中保存秒杀场次信息
private final String SESSION__CACHE_PREFIX = "seckill:sessions:";
private final String SECKILL_CHARE_PREFIX = "seckill:skus";
private final String SKU_STOCK_SEMAPHORE = "seckill:stock:"; //+商品随机码
/**
* 缓存秒杀活动信息
* @param sessions
*/
private void saveSessionInfos(List<SeckillSessionWithSkusVo> sessions) {
sessions.stream().forEach(session -> {
//获取当前活动的开始和结束时间的时间戳
long startTime = session.getStartTime().getTime();
long endTime = session.getEndTime().getTime();
//存入到Redis中的key
String key = SESSION__CACHE_PREFIX + startTime + "_" + endTime;
//判断Redis中是否有该信息,如果没有才进行添加
Boolean hasKey = redisTemplate.hasKey(key);
//缓存活动信息
if (!hasKey) {
//获取到活动中所有商品的skuId
List<String> skuIds = session.getRelationSkus().stream()
.map(item -> item.getPromotionSessionId() + "-" + item.getSkuId().toString()).collect(Collectors.toList());
redisTemplate.opsForList().leftPushAll(key,skuIds);
}
});
}
数据如图:
key为seckill:sessions:开始时间_结束时间
value 为 活动场次id_skuid
第三步、在Redis中保存秒杀活动关联的商品信息
/**
* 缓存活动的关联商品信息
*
* @param sessions
*/
private void saveSessionSkuInfo(List<SeckillSessionsWithSkus> sessions) {
sessions.stream().forEach(session -> {
// 准备Hash操作
BoundHashOperations<String, Object, Object> ops = redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
session.getRelationSkus().stream().forEach(seckillSkuVo -> {
// 生成随机码
String token = UUID.randomUUID().toString().replace("_", "");
// 1)、缓存商品
if (!ops.hasKey(seckillSkuVo.getPromotionSessionId().toString()+"_"+seckillSkuVo.getSkuId().toString())) {
SecKillSkuRedisTo redisTo = new SecKillSkuRedisTo();
// 1、Sku的基本数据
R skuInfo = productFeignService.getSkuInfo(seckillSkuVo.getSkuId());
if (skuInfo.getCode() == 0) {
SkuInfoVo info = skuInfo.getData("skuInfo", new TypeReference<SkuInfoVo>() {
});
redisTo.setSkuInfo(info);
}
// 2、Sku的秒杀信息
BeanUtils.copyProperties(seckillSkuVo, redisTo);
// 3、设置上当前商品的秒杀时间信息
redisTo.setStartTime(session.getStartTime().getTime());
redisTo.setEndTime(session.getEndTime().getTime());
// 4、商品的随机码
redisTo.setRandomCode(token);
String jsonString = JSON.toJSONString(redisTo);
ops.put(seckillSkuVo.getPromotionSessionId().toString()+"_"+seckillSkuVo.getSkuId().toString(), jsonString);
// 如果当前这个场次的商品的库存信息已经上架就不需要上架
// 5、引入分布式的信号量 限流
RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + token);
// 商品可以秒杀的数量作为信号量
semaphore.trySetPermits(seckillSkuVo.getSeckillCount().intValue());
}
});
});
}
redis保存 sku的基础信息: seckill:skus
reids具体 操作
BoundHashOperations<String, Object, Object> operations = redisTemplate.boundHashOps(SECKILL_CHARE_PREFIX);
operations.put(seckillSkuVo.getPromotionSessionId().toString() + "-" + seckillSkuVo.getSkuId().toString(),seckillValue);
redis信号量保存sku的库存key: seckill:stock:+随机token
Reid具体操作
RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + token);
// 商品可以秒杀的数量作为信号量
semaphore.trySetPermits(seckillSkuVo.getSeckillCount());
第四步 保证秒杀商品上架接口幂等性
使用redissonClient 的分布式锁,防止并发
package com.atguigu.gulimall.seckill.scheduled;
@Slf4j
@Service
public class SeckillSkuScheduled {
@Autowired
SeckillService seckillService;
@Autowired
RedissonClient redissonClient;
private final String upload_lock = "seckill:upload:lock";
// TODO 幂等性处理
@Scheduled(cron = "* * 3 * * ?")
public void uploadSeckillSkuLatest3Days() {
// 1、重复上架无需处理
log.info("上架秒杀商品的信息");
// 分布式锁。锁的业务执行完成,状态已经更新完成。释放锁以后,其他人获取到就会拿到最新的状态
RLock lock = redissonClient.getLock(upload_lock);
lock.lock(10, TimeUnit.SECONDS);
try {
seckillService.uploadSeckillSkuLatest3Days();
} finally {
lock.unlock();
}
}
}