Question: single operations require multiple calls to the database, query product information, user information, modify inventory data, resulting in performance bottlenecks.
Optimization direction: to read data read from the cache, to modify the inventory data in the modification data to modify the cache database with the asynchronous message queue. You can use asynchronous transactional message rocketmq to ensure redis database and data synchronization, anomalies in the cache can be used to restore database data.
1. Verify transaction optimization
(1) verify the existence of commodities, instead cache
Prior is to obtain product data directly from the database according to itemId
Redis now changed to obtain from the cache, do not get in from the database
@Override public ItemModel getItemByIdInCache(Integer id) { ItemModel itemModel = (ItemModel)redisTemplate.opsForValue().get("item_validate_"+id); if(itemModel == null) { itemModel = this.getItemById(id); redisTemplate.opsForValue().set("item_validate_"+id, itemModel); } return itemModel; }
(2) checking whether there is user information, to the cache
Before the user data is acquired directly from the database according to the userId
Redis now changed to obtain from the cache, do not get in from the database
@Override public UserModel getUserByIdInCache(Integer id) { UserModel userModel = (UserModel)redisTemplate.opsForValue().get("user_"+id); if(userModel == null) { userModel = this.getUserById(id); redisTemplate.opsForValue().getAndSet("user_"+id, userModel); } return userModel; }
2. Modify the inventory data changed to modify cache
Stock modify table data because itemId for the stock table's primary key, so there will row locks to prevent concurrency, but will affect the efficiency
So when buying activity released into inventory redis, first redis modify cache data when the next one, and then modify the synchronization rocketmq inventory to ensure the reliability of
a. launches, inventory cache
// launch event, this should be the operation and maintenance operations
@RequestMapping (value = "/ publishpromo" , method = {RequestMethod.GET}) @ResponseBody public CommonReturnType publisPromo(Integer id) { promoService.publishPromo(id); return CommonReturnType.create(null); }
@Override
public void publishPromo (Integer promoId) {
// event information acquired by the event ID
the Promodo the Promodo = promoDOMapper.selectByPrimaryKey (promoId);
IF (. PromoDO.getItemId the Promodo == null || () intValue () == 0) {
return;
}
itemModel itemModel = itemService.getItemById (promoDO.getItemId ());
// redis synchronized to the inventory
redisTemplate.opsForValue () getAndSet ( "promo_item_stock _ " + promoDO.getItemId (), itemModel.getStock ()).;
}
b. The Save single stock
@Override @Transactional public boolean decreaseStock(Integer itemId, Integer amount) throws BusinessException { /*int affectedRow = itemStockDOMapper.decreaseStock(itemId,amount); if(affectedRow > 0){ // update inventory success return true; }else{ // update inventory fails return false; }*/ Long result = redisTemplate.opsForValue().increment("promo_item_stock_"+itemId, amount.intValue()*-1); // send message queue modification inventory database
boolean sendResult = producer.asyncReduceStock (itemId, amount ); // remaining data if(result > 0 && sendResult){ // update inventory success return true; }else { // update inventory fails redisTemplate.opsForValue().increment("promo_item_stock_"+itemId, amount.intValue()); return false; } }
The producer transmits the message queue
@Component public class MQProducer { Log log = LogFactory.getLog(getClass()); @Value("${mq.nameserver.addr}") private String nameServer; @Value("${mq.topicname}") private String topicName; DefaultMQProducer producer; @PostConstruct public void init() throws MQClientException { producer = new DefaultMQProducer("producer"); producer.setNamesrvAddr(nameServer); producer.start(); } // synchronization deductions stock news public boolean asyncReduceStock(Integer itemId, Integer amount) { Map<String,Object> bodyMap = new HashMap<String,Object>(); bodyMap.put("itemId", itemId); bodyMap.put("amount", amount); Message msg = new Message(topicName,"increase", JSON.toJSON(bodyMap).toString().getBytes(Charset.forName("UTF-8"))); try { log.error("begin to send msg"); producer.send(msg); log.error("finish send msg"); } catch (MQClientException e) { e.printStackTrace (); return false; } catch (RemotingException e) { // TODO Auto-generated catch block e.printStackTrace (); return false; } catch (MQBrokerException e) { // TODO Auto-generated catch block e.printStackTrace (); return false; } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace (); return false; } return true; } }
Consumers reduced inventory database message queue
@Component public class MQConsumer { @Value("${mq.nameserver.addr}") private String nameServer; @Value("${mq.topicname}") private String topicName; DefaultMQPushConsumer consumer; @Autowired private ItemStockDOMapper itemStockDOMapper; private Log log = LogFactory.getLog(getClass()); @PostConstruct public void init() throws MQClientException { consumer = new DefaultMQPushConsumer("stock_consumer_group"); consumer.setNamesrvAddr(nameServer); consumer.subscribe(topicName, "*"); consumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) { log.error("begin to cosume msg"); Message msg = msgs.get(0); String jsonStr = new String(msg.getBody()); Map<String,Object> map = JSON.parseObject(jsonStr, Map.class); Integer itemId= (Integer)map.get("itemId"); Integer amount= (Integer)map.get("amount"); log.error("itemId:"+itemId+",amount:"+amount); int affectedRow = itemStockDOMapper.decreaseStock(itemId,amount); if(affectedRow > 0){ // update inventory success return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }else{ // update inventory fails return ConsumeConcurrentlyStatus.RECONSUME_LATER; } } }); consumer.start(); } }