举一个简单的例子,一个商品限购10人
使用jmeter测试
发现是乱的。。。。当然了 我们可以通过 synchronized 来解决
的确是可以的,但是这样真的太慢了。不建议这么去弄。
=========================================================================》
可以通过redis来写
思路:首先通过List去存储商品ID来解决超卖问题
通过Set存储账户ID来解决重复购买问题
首先创建一个数据库表
CREATE TABLE `seckill` (
`id` int(11) NOT NULL,
`product_id` int(11) DEFAULT NULL,
`num` int(11) DEFAULT NULL,
`price` decimal(10,0) DEFAULT NULL,
`start_time` timestamp NULL DEFAULT NULL,
`end_time` timestamp NULL DEFAULT NULL,
`status` int(2) DEFAULT NULL COMMENT '0代表未开始,1代表已开始,3代表结束',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
dao
public interface SeckillDao {
List<Seckill> getSeckill();
Seckill getSeckillById(int id);
void updateSeckill(@Param("status") int status,@Param("id") int id);
}
xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<!-- 以表名称为单元声明命名空间 -->
<mapper namespace="com.xccc.seckill.seckilll.dao.SeckillDao">
<!-- 获取 -->
<select id="getSeckill" resultType="com.xccc.seckill.seckilll.model.Seckill">
SELECT id,product_id productId,num,price,`status` FROM seckill where `status` = 0 and (start_time <NOW() and end_time > NOW())
</select>
<!-- 获取 -->
<select id="getSeckillById" resultType="com.xccc.seckill.seckilll.model.Seckill" parameterType="int">
SELECT id,product_id productId,num,price,`status` FROM seckill where id = #{id}
</select>
<update id="updateSeckill">
update seckill set `status` = #{status} where id = #{id}
</update>
</mapper>
service
public interface RedisSeckillService {
public void startTask();
public void seckillProduct(int id,int user) throws Exception;
}
impl
@Service
public class RedisSeckillServiceImpl implements RedisSeckillService {
@Autowired
private SeckillDao seckillDao;
@Autowired
private RedisTemplate redisTemplate;
@Override
public void startTask() {
// 开启秒杀
List<Seckill> seckill = seckillDao.getSeckill();
ListOperations listOperations = redisTemplate.opsForList();
for(Seckill s : seckill){
// key值
String key = "seckill_product_"+s.getId();
// 向reids插入数据
for(int i=1;i<=s.getNum();i++){
listOperations.rightPush(key,s.getProductId());
}
// 更新秒杀状态
seckillDao.updateSeckill(1,s.getId());
}
}
@Override
public void seckillProduct(int id, int user) throws Exception{
// key值
String key = "seckill_product_"+id;
Seckill seckill = seckillDao.getSeckillById(id);
if(seckill==null){
throw new Exception("商品不存在");
}
ListOperations listOperations = redisTemplate.opsForList();
Object o = listOperations.leftPop(key);
if(o == null){
throw new Exception("商品已经秒杀完毕");
}
String ukey = "seckill_user_"+id;
SetOperations setOperations = redisTemplate.opsForSet();
if(setOperations.isMember(ukey,user)){
listOperations.rightPush(key,seckill.getProductId());
throw new Exception("不能重复购买商品");
}
System.out.println("用户{"+user+"}购买了商品");
setOperations.add(ukey,user);
}
}
controller
@RestController
@RequestMapping("/task")
public class StartTaskController {
@Autowired
private RedisSeckillServiceImpl redisSeckillService;
@GetMapping("/start")
public Map<String,Object> start(){
Map<String,Object> map = new HashMap<>();
redisSeckillService.startTask();
return map;
}
@GetMapping("/startKill")
public Map<String,Object> startSeckill(String p,String u){
Map<String,Object> map = new HashMap<>();
try {
redisSeckillService.seckillProduct(Integer.valueOf(p),Integer.valueOf(u));
} catch (Exception e) {
map.put("msg",e.getMessage());
}
return map;
}
}
步骤:
首先通过http://localhost:8080/task/start来实现秒杀开始(这个地方实际要写个定时任务,我这里演示就写成controller自己调用了)
通过jemeter进行测试,开启100个线程,每个账户调用2次
完美解决