电商秒杀商品功能实现——rabbitMQ+redis

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 &gt; 0 and
        now() &gt; start_time and
        now() &lt; end_time
    </update>
</mapper>

猜你喜欢

转载自blog.csdn.net/weixin_42596778/article/details/106722485