1.思路:
当秒杀开始的时候,会有大量的高并发,解决高并发是我们的第一个目标,其次就是高并发的时候,会有超卖的现象,解决超卖是我们的第二个目标。
- 解决高并发: 我们使用rabbitMQ消息队列,让用户点击秒杀后,就进去队列中,这样可以缓解服务器的压力,削弱高并发的峰值。
- 解决超卖: redis是单进程单线程模式,我们可以使用redis的这一特性,来解决超卖的问题。
2.实现过程:
1.引入pom中的依赖,application写配置信息。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
</dependency>
#rabbitmq
spring.rabbitmq.host=10.9.151.60
#redis
spring.redis.host=10.9.151.60
spring.redis.port=9000
3.radis中获取到需要秒杀商品的数量,后面消费者层会使用
2.写rabbitMQ的模板
启动类中写模板,并且用@bean声明,这样在系统启动的时候,就会生成bean对象让spring管理起来。
@SpringBootApplication
@EnableEurekaClient
@MapperScan("cn.tedu.seckill.mapper")
public class StarterSeckill {
public static void main(String[] args) {
SpringApplication.run(StarterSeckill.class,args);
}
//模板声明
@Bean//声明一个队列
public Queue queue01(){
return new Queue("seckillQ",false,false,false,null);
}
@Bean//声明交换机
public DirectExchange ex01(){
return new DirectExchange("seckillEX");
}
@Bean//绑定关系
public Binding bind01(){
return BindingBuilder.bind(queue01()).to(ex01()).with("seckill");
}
}
3.controller层
首先需用方法1要把所有的秒杀商品获取到,方法2是用户点击商品详细信息的时候调用的,方法3作为生产者使用了RabbitTemplate把用的手机号和秒杀商品的id拼接起来,放入消息队列中,并且调用convertAndSend方法,只有当消费者拿到了消息队列中的数据,消息队列才会进行下一步。
@RestController
public class SeckillController {
@Autowired(required = false)
private SeckillMapper seckillMapper;
//查询所有秒杀商品
@RequestMapping("/seckill/manage/list")
public List<Seckill> list(){
return seckillMapper.selectSeckills();
}
//根据选择商品返回详情,返回单件商品
@RequestMapping("/seckill/manage/detail")
public Seckill detail(Long seckillId){
return seckillMapper.selectOneById(seckillId);
}
//注入模板对象
@Autowired
private RabbitTemplate rabbitTemplate;
@RequestMapping("/seckill/manage/{seckillId}")
public SysResult startSeckill(@PathVariable Long seckillId){
//模拟每次不同用户访问商品
//如果只允许一个用户秒杀一次 可以使用redis
//随机生成一个电话号
String userPhone="1876678"+new Random().nextInt(9999);
String msg=userPhone+"/"+seckillId;
rabbitTemplate.convertAndSend
("seckillEX",
"seckill",msg);
//等待声明代码配置完毕再验证功能
return SysResult.ok();
}
}
4.消费者:
消费者监听着消息队列,当消息队列中有消息的时候,就把消息队列中的信息获取出来,拆出用户号码和商品信息,因为redis是单进程单线程,所以这里就不会产生超卖的问题,在对数据库里的库存进行减少时,我们先去redis中操作,如果在redis中商品数量已经为-1,则说明商品卖完了,不允许在卖,这样就避免了超卖。
@Component
public class SeckillConsumer {
@Autowired(required = false)
private SeckillMapper seckillMapper;
//编写消费逻辑的方法
@Autowired
private StringRedisTemplate redisTemplate;
//监听的队列名字
@RabbitListener(queues="seckillQ")
public void consume(String msg){
//msg=电话号码/seckillId
/*
1 userPhone seckillId 解析
2 根据seckillId 执行更新库存
update seckill set number=number-1
where seckill_id=#{seckillId}
and number>0
and now()>start_time
and now()<end_time
返回值 成功1 失败0
3 0 打印谁,秒杀哪个商品失败了
4 1 收集信息,入库成功的数据success
*///msg=18768728281/1
Long userPhone=Long.parseLong(msg.split("/")[0]);
Long seckillId=Long.parseLong(msg.split("/")[1]);
//increment("num_hw", -1),把redis中key为num_hw存储的值+(-1),再存入redis中
Long decr = redisTemplate.opsForValue().increment("num_hw", -1);
if(decr<0){
//if进入,说明商品已经被其他的消费端秒杀完了
System.out.println("商品已经秒杀完了");
return;
}
int result= seckillMapper.decrNumberById(seckillId);
if(result==0){
//条件不满足 秒杀失败的
System.out.println("用户:"+userPhone+";秒杀失败");
return;
}
//成功减库存,用户秒杀也就成功
//insert into success
Success suc=new Success();
suc.setSeckillId(seckillId);
suc.setUserPhone(userPhone);
suc.setCreateTime(new Date());
suc.setState(0);
seckillMapper.insertSuccess(suc);
}
}
5.mapper层:
public interface SeckillMapper {
List<Seckill> selectSeckills();
Seckill selectOneById(Long seckillId);
int decrNumberById(Long seckillId);
void insertSuccess(Success suc);
}
6.mappers映射文件:
<mapper namespace="cn.tedu.seckill.mapper.SeckillMapper">
<select id="selectSeckills" resultType="Seckill">
select * from seckill
</select>
<select id="selectOneById" resultType="Seckill">
select * from seckill where seckill_id=#{seckillId}
</select>
<insert id="insertSuccess">
insert into success (seckill_id,user_phone,state,create_time)
values (#{seckillId},#{userPhone},#{state},#{createTime})
</insert>
<!--减库存-->
<update id="decrNumberById">
update seckill set number=number-1
where seckill_id=#{seckillId} and
number > 0 and
now() > start_time and
now() < end_time
</update>
</mapper>