Redis与RabbitMQ实现秒杀(redis消息队列和mq)
对redis进行配置
redis: host: 192.168.203.124 port: 6379 # redis数据库索引 database: 0 timeout: 10000ms lettuce: pool: max-active: 8 max-wait: 10000ms max-idle: 200 min-idle: 5复制代码
首先要先定义好自己的RedisTemplate
这里我们配置的是<String,Object>类型的<key,value>
@Configuration public class RedisConfig { @Bean(value = "myRedisTemplate") public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){ RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); //key 序列化 redisTemplate.setKeySerializer(new StringRedisSerializer()); //value 序列化 redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); redisTemplate.setConnectionFactory(redisConnectionFactory); return redisTemplate; } }复制代码
RabbitMQ的使用
这里我们选择的是使用Topic模式,首先我们要对RabbitMQ写自己的配置类
@Configuration public class RabbitMQTopicConfig { private static final String QUEUE = "seckillQueue"; private static final String EXCHANGE = "seckillExchange"; /** * 消息队列 * @return */ @Bean public Queue queue(){ return new Queue(QUEUE); } /** * 交换机 * @return */ @Bean public TopicExchange topicExchange(){ return new TopicExchange(EXCHANGE); } /** *topic模式下的绑定 * @return */ @Bean public Binding binding(){ return BindingBuilder.bind(queue()).to(topicExchange()).with("seckill.#"); } }复制代码
RabbitMQ使用的是生产者消费者的模式,与之对应的就是发送者和接收者,我们需要建立自己的发送者和接收者。 发送者如下:
public class MQSender { @Autowired private RabbitTemplate rabbitTemplate; public void sendKillMessage(String message){ log.info("发送消息:" + message); rabbitTemplate.convertAndSend("seckillExchange","seckill.message",message); } }复制代码
发送者仅仅是简单的通过消息队列发送消息,而接收者在收到消息以后便可以进行相应的业务操作,这便实现了异步的操作。
public class MQReciver { @Autowired private IGoodsService iGoodsService; @Autowired @Qualifier("myRedisTemplate") private RedisTemplate redisTemplate; @Autowired private OrderServiceImpl orderService; /** * 下订单操作 */ @RabbitListener(queues = "seckillQueue") public void recive(String message){ seckillMessage seckillMessage1 = new seckillMessage(); log.info("接收到的消息为: " + message); String s = JacksonUtil.toJson(message); seckillMessage seckillMessage = JacksonUtil.fromJson(message, seckillMessage1.getClass()); Long goodsId = seckillMessage.getGoodsId(); User user = seckillMessage.getUser(); GoodsVo goodsVo = iGoodsService.listGoods(goodsId); if(goodsVo.getStockCount() < 1){ return; } SeckillOrder seckillOrder = (SeckillOrder) redisTemplate.opsForValue().get("order:" + user.getId() + ":" + goodsId); if(seckillOrder != null){ return ; } orderService.sekill(user, goodsVo); } }复制代码
在秒杀系统中下订单的具体应用
秒杀成功的重要标志就是在数据库中生成订单,因此我们要保证订单数据的正确性。
[1 ] 在我们系统初始化的时候,我们应该把商品库存数量提前加载到redis当中
/** * 系统初始化,把商品库存数量加载到redis * @throws Exception */ @Override public void afterPropertiesSet() throws Exception { List<GoodsVo> goods = iGoodsService.getGoods(); if(CollectionUtils.isEmpty(goods)){ return; } goods.forEach(goodsVo -> { redisTemplate.opsForValue().set("seckillGoods" + goodsVo.getId(), goodsVo.getGoodsStock()); EmptyStockMap.put(goodsVo.getId(),false); } ); }复制代码
[2 ] 在秒杀开始时,大批量的用户即将访问我们提前加载到redis中的数据,为了缓解redis的压力,我们可以采用内存标记的方法,减少redis的压力。
//用于标记某商品的库存是否已经为0 //再对redis库存进行访问之前,可以先对该Map进行访问 private Map<Long,Boolean> EmptyStockMap = new HashMap<>();复制代码
[3] 用户对redis进行访问以后,我们先不用立刻对数据库生成相应的订单,而是应该对redis进行预减库存的操作
Long stock = valueOperations.decrement("seckillGoods" + goodsId); if(stock < 0){ //库存不足直接标记为true,就不用再次来访问了 EmptyStockMap.put(goodsId,true); valueOperations.increment("seckillGoods"+goodsId); return "not enough"; }复制代码
[4]在整个流程完毕以后,我们最后调用RabbitMQ对数据库进行相应的操作,这样就达到了异步的操作
seckillMessage seckillMessage = new seckillMessage(user, goodsId); mqSender.sendKillMessage(JacksonUtil.toJson(seckillMessage));
作者:SillyWg
链接:https://juejin.cn/post/7039290815369183268