1.秒杀场景-电商平台促销活动
2.秒杀特点
- 高并发
- 抢购有时间限制
- 数量有限,先到先得
3.java实践
秒杀在高并发情况下,性能问题、 数据不一致的问题(只有10件库存, 被12个人买到问题)需要解决,我们借助redis实现秒杀, redis作为内存数据库,支持高并发,支持原子操作,适合分布式扩容特点
- 秒杀活动表模型MYSQL(商品详情表,商品参数表 自己构建)
status:活动状态,0 待开始,1 进行中 2 已结束
2.活动开始
每各10秒轮询待开始秒杀活动表,符合开始条件,则初始化一个redis队列,存放指定数量的活动商品到队列中(商品id相同),更改活动状态为开始种, 代码片段如下:
@Scheduled(cron = "0/10 * * * * ?")
public void startSecKill() {
List<SecKill> list = secKillDao.findWaitingSecKillList();
boolean lock=redisTemplate.opsForValue().setIfAbsent("seckill:lock",
"1", 5L, TimeUnit.SECONDS);
if (lock) {
for (SecKill sk : list) {
redisTemplate.delete(Constant.SEK_KILL_QUEUE + sk.getId());
for (int i = 0; i < sk.getCount(); i++) {
redisTemplate.opsForList().rightPush(
Constant.SEK_KILL_QUEUE+ sk.getId(), sk.getProductId());
}
sk.setStatus(1);
secKillDao.updateStatus(sk);
}
}
}
- 用户下单
用户在登录的状态下购买,并发用户从队列中取商品id,如果用户已经购买,将获取的商品id重新还回队列。否则消费掉,直到队列中数据为0,抢购完毕,代码片段如下:
//controller
@RequestMapping("/seckill")
@ResponseBody
public ResponseData order(Long id, HttpServletRequest request)
{
ResponseData result = new ResponseData();
try {
Integer userId = this.getUserId(request);
if (userId == null) {
result.setStatus(ResponseData.STATUS_ERROR);
result.setMessage("请先登录");
return result;
}
secKillService.handle(id, userId);
} catch (Exception e) {
result.setStatus(ResponseData.STATUS_ERROR);
result.put("message", e.getMessage());
}
return result;
}
private Integer getUserId(HttpServletRequest request) {
UserInfo userInfo = (UserInfo) request.getSession().getAttribute(
"login_user");
if (userInfo == null) {
return null;
}
return userInfo.getUserId();
}
//constant
package com.study.seckill;
public class Constant {
public static final String SEK_KILL_QUEUE="seckill:items:";
public static final String SEK_KILL_BUY="seckill:users:";
}
//service
package com.study.seckill.service;
import com.study.seckill.dao.SecKillDao;
import com.study.seckill.model.SecKill;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class SecKillService {
@Resource
private SecKillDao secKillDao;
@Resource
private RedisTemplate redisTemplate;
private void check(SecKill sk) throws Exception{
if (sk == null) {
throw new Exception("秒杀活动不存在");
}
if (sk.getStatus() == 0) {
throw new Exception("秒杀活动还未开始");
} else if (sk.getStatus() == 2) {
throw new Exception("秒杀活动已经结束");
}
}
/**
*
* <B>方法名称:</B><BR>
* <B>概要说明:</B><BR>
* @param id 活动id
* @param userid
* @param num
* @throws Exception
*/
public void handle(Long id, Integer userid) throws Exception {
SecKill sk = secKillDao.findById(id);
check(sk);
Integer productId = (Integer) redisTemplate.opsForList().leftPop(Constant.SEK_KILL_QUEUE + sk.getId());
if (productId != null) {
boolean isExisted = redisTemplate.opsForSet().isMember(Constant.SEK_KILL_BUY + sk.getId(), userid);
if (!isExisted) { redisTemplate.opsForSet().add(Constant.SEK_KILL_BUY + sk.getId(), userid);
}else{
redisTemplate.opsForList().rightPush("seckill:items:" + sk.getId(), sk.getProductId());
throw new Exception("您已经参加过此活动");
}
} else {
throw new Exception("该商品已被抢光!");
}
}
}