异步消息扣减 MySQL 中的库存
RocketMQ 安装
- 下载二进制的包,解压;
- runserver.sh 和 runbroker.sh 两个文件虚拟机参数改小;
- 启动 Name人Server:
nohup sh bin/mqnamesrv &,启动 Broker:nohup sh bin/mqbroker -n localhost:9876 &;- 验证启动是否成功:
export NAMESRV_ADDR=localhost:9876,sh bin/tools.sh org.apache.rocketmq.example.quickstart.Producer,sh bin/tools.sh org.apache.rocketmq.example.quickstart.Consumer;创建主题
./mqadmin updateTopic -n localhost:9876 -t stock -c DefaultCluster;
- application.properties
mq.nameserver.addr=127.0.0.1:9876
mq.topicname=stock
- 依赖
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.3.0</version>
</dependency>
- MQ 生产者封装
package com.lixinlei.miaosha.mq;
import com.alibaba.fastjson.JSON;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.exception.RemotingException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
@Component
public class MqProducer {
private DefaultMQProducer producer;
@Value("${mq.nameserver.addr}")
private String nameAddr;
@Value("${mq.topicname}")
private String topicName;
/**
* 在 Bean 初始化完成之后调用
*/
@PostConstruct
public void init() throws MQClientException {
producer = new DefaultMQProducer("producer_group");
producer.setNamesrvAddr(nameAddr);
producer.start();
}
/**
* 让 MySQL 同步 Redis 中库存扣减的消息
* @param itemId
* @param amount
* @return
*/
public boolean asyncReduceStock(Integer itemId, Integer amount) {
Map<String, Object> bodyMap = new HashMap<>();
bodyMap.put("itemId", itemId);
bodyMap.put("amount", amount);
Message message = new Message(
topicName,
"increase",
JSON.toJSON(bodyMap).toString().getBytes(Charset.forName("UTF-8")));
try {
producer.send(message);
} catch (MQClientException e) {
e.printStackTrace();
return false;
} catch (RemotingException e) {
e.printStackTrace();
return false;
} catch (MQBrokerException e) {
e.printStackTrace();
return false;
} catch (InterruptedException e) {
e.printStackTrace();
return false;
}
return true;
}
}
- MQ 消费者封装
package com.lixinlei.miaosha.mq;
import com.alibaba.fastjson.JSON;
import com.lixinlei.miaosha.dao.ItemStockDOMapper;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageExt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Map;
@Component
public class MqConsumer {
private DefaultMQPushConsumer consumer;
@Value("${mq.nameserver.addr}")
private String nameAddr;
@Value("${mq.topicname}")
private String topicName;
@Autowired
private ItemStockDOMapper itemStockDOMapper;
@PostConstruct
public void init() throws MQClientException {
consumer = new DefaultMQPushConsumer("stock_consumer_group");
consumer.setNamesrvAddr(nameAddr);
consumer.subscribe(topicName, "*");
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
ConsumeConcurrentlyContext consumeConcurrentlyContext) {
Message msg = msgs.get(0);
String jsonString = new String(msg.getBody());
Map<String, Object> map = JSON.parseObject(jsonString, Map.class);
Integer itemId = (Integer) map.get("itemId");
Integer amount = (Integer) map.get("amount");
itemStockDOMapper.decreaseStock(itemId, amount);
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
}
}
- 扣减库存操作
@Override
@Transactional
public boolean decreaseStock(Integer itemId, Integer amount) {
// int affectedRows = itemStockDOMapper.decreaseStock(itemId, amount);
long result = redisTemplate.opsForValue().increment("promo_item_stock_" + itemId, amount * -1);
if (result >= 0) {
boolean mqResult = mqProducer.asyncReduceStock(itemId, amount);
if (!mqResult) {
redisTemplate.opsForValue().increment("promo_item_stock_" + itemId, amount);
return false;
}
return true;
} else {
redisTemplate.opsForValue().increment("promo_item_stock_" + itemId, amount);
return false;
}
}
异步消息扣减 MySQL 中的库存的问题
- 异步消息发送失败;
- 扣减操作执行失败;
- 下单失败无法正确回补库存,比如用户取消支付;
本文介绍如何使用RocketMQ实现异步库存扣减,包括RocketMQ的安装配置、生产者与消费者封装,以及如何在交易过程中确保库存的准确扣减。
1039

被折叠的 条评论
为什么被折叠?



