当前位置:网站首页>Brief analysis of order transaction
Brief analysis of order transaction
2022-07-28 06:44:00 【yfyh2021】
1. Order transaction process analysis
As a part of money involved, order trading is bound to be cautious , Let's roughly analyze the process .
- Check ( Block unnecessary requests back at the first time )
- Check whether the local cache is sold out
- Verify whether you have permission to purchase
- Judge redis Is there enough stock
- Check whether you are in line
- .... ...
- Get product information
- Verify whether the seckill or activity time has expired
- Get member information
- Get member address
- Reduce stock in advance
- Generate order item information
- Stock handling
The following mainly analyzes the most important 1,6 and 8 term .
2. Check
- The most important thing in the inspection is to check us redis Whether the inventory in is sufficient .
private CommonResult confirmCheck(Long productId, Long memberId, String token){
Integer stock = redisOpsUtil.get(RedisKeyPrefixConst.MIAOSHA_STOCK_CACHE_PREFIX+productId,Integer.class);
if(stock == null||stock<0){
return CommonResult.failed(" The goods are sold out , Please buy other goods !");
}
String async = redisOpsUtil.get(RedisKeyPrefixConst.MIAOSHA_ASYNC_WAITING_PREFIX + memberId + ":" + productId);
if (async != null && async.equals("1")) {
Map<String, Object> result = new HashMap<>();
result.put("orderStatus", "1");// How to place an order 0-> Place orders synchronously ,1-> Asynchronous order queuing ,-1-> Seckill failure ,>1-> Seckill success ( Return order No )
return CommonResult.failed(result, " Asynchronous order queuing ");
}
return CommonResult.success(null);
}Here we find that we need to synchronize the inventory of goods to redis in , Otherwise we will be out of stock forever when we check .
public void afterPropertiesSet() throws Exception {
FlashPromotionParam promotion = flashPromotionProductDao.getFlashPromotion(null);
if (null==promotion){
return;
}
Date now = new Date();
Date endDate = promotion.getEndDate();// End time
final Long expired = endDate.getTime()-now.getTime();// The rest of the time
promotion.getRelation().stream().forEach((item)->{
redisOpsUtil().setIfAbsent(
RedisKeyPrefixConst.MIAOSHA_STOCK_CACHE_PREFIX + item.getProductId()
, item.getFlashPromotionCount()
, expired
, TimeUnit.MILLISECONDS);
});
}Put inventory into redis And set the timeout , The end of the activity is invalid .
- Local cache
We are used to setting local cache on the back end to reduce redis The pressure of the , Similarly, how can we set the local cache during inventory detection ?
Of course, there is no need to redis It's so troublesome to store inventory , We only need a boolean flag bit , If there is inventory, it means true, If there is no inventory, it is false, If false There is no need to check redis 了 , Can greatly reduce our redis The load of . But there's a problem , How to synchronize the data of each node in the cluster environment ?
Using message queues for synchronization is a bit overqualified , Here are two recommended methods , One is to use zk Of watcher Mechanism synchronization , The other is to use redis Of channel Mechanism , Here we use the latter to realize .
First, let's update our code above , Add the content of local cache verification
private CommonResult confirmCheck1(Long productId, Long memberId, String token){
Boolean localCache = this.cache.getCache(RedisKeyPrefixConst.MIAOSHA_STOCK_CACHE_PREFIX + productId);
if(localCache!=null&&localCache){
return CommonResult.failed(" The goods are sold out , Please buy other goods !");
}
Integer stock = redisOpsUtil.get(RedisKeyPrefixConst.MIAOSHA_STOCK_CACHE_PREFIX+productId,Integer.class);
if(stock == null||stock<0){
cache.setLocalCache(RedisKeyPrefixConst.MIAOSHA_STOCK_CACHE_PREFIX+productId,true);
redisOpsUtil.publish("cleanNoStockCache", productId);
return CommonResult.failed(" The goods are sold out , Please buy other goods !");
}
String async = redisOpsUtil.get(RedisKeyPrefixConst.MIAOSHA_ASYNC_WAITING_PREFIX + memberId + ":" + productId);
if (async != null && async.equals("1")) {
Map<String, Object> result = new HashMap<>();
result.put("orderStatus", "1");// How to place an order 0-> Place orders synchronously ,1-> Asynchronous order queuing ,-1-> Seckill failure ,>1-> Seckill success ( Return order No )
return CommonResult.failed(result, " Asynchronous order queuing ");
}
return CommonResult.success(null);
}Then we configure our monitor , To configure channel
@Slf4j
public class RedisChannelListener implements MessageListener {
@Autowired
private LocalCache localCache;
@Override
public void onMessage(Message message, @Nullable byte[] pattern) {
log.info("sub message :) channel[cleanNoStockCache] !");
String productId = new String(message.getBody(), StandardCharsets.UTF_8);
localCache.remove(RedisKeyPrefixConst.MIAOSHA_STOCK_CACHE_PREFIX + productId);
}
} @Bean
ChannelTopic channelTopic(){
return new ChannelTopic("cleanNoStockCache");
}3. Reduce stock in advance
By acquiring redis To determine whether the inventory is less than 0, If it is less than 0 Then return to false And will redis The value in , The code is as follows .
private boolean preDecrRedisStock(Long productId, Long promotionId){
Long stock = redisOpsUtil.decr(RedisKeyPrefixConst.MIAOSHA_STOCK_CACHE_PREFIX + productId);
if(stock<0){
if(redisOpsUtil.hasKey(RedisKeyPrefixConst.MIAOSHA_STOCK_CACHE_PREFIX+productId)){
redisOpsUtil.incr(RedisKeyPrefixConst.MIAOSHA_STOCK_CACHE_PREFIX+productId);
}
return false;
}
return true;
}however , This is not the final version of our inventory reduction , Let's skip , Let's talk about asynchronous ordering later .
4. Order asynchronously
After the order status is encapsulated , It's time to commit asynchronously , On the first code
try {
boolean sendStatus = orderMessageSender.sendCreateOrderMsg(orderMessage);
if (sendStatus) {
redisOpsUtil.set(RedisKeyPrefixConst.MIAOSHA_ASYNC_WAITING_PREFIX + memberId + ":" + productId
, Integer.toString(1), 60, TimeUnit.SECONDS);
/*
* How to place an order 0-> Place orders synchronously ,1-> Asynchronous order queuing ,-1-> Seckill failure , Return order number successfully
*/
result.put("orderStatus", 1);
} else {
/*
* Restore pre reduced inventory
*/
incrRedisStock(productId);
/*
* Clear the local guavacache Sold out marks
*/
cache.remove(RedisKeyPrefixConst.MIAOSHA_STOCK_CACHE_PREFIX + productId);
// Determine whether the inventory is 0 Or is empty , If it is notification service group , Clear the locally sold out tag cache
if (shouldPublishCleanMsg(productId)) {
redisOpsUtil.publish("cleanNoStockCache", productId);
}
result.put("orderStatus", -1);
return CommonResult.failed(result, " Order failure ");
}
} catch (Exception e) {
log.error(" Message delivery failed :error msg:{}", e.getMessage(), e.getCause());
/*
* Restore pre reduced inventory
*/
incrRedisStock(productId);
/*
* Clear the local guavacache Sold out marks
*/
cache.remove(RedisKeyPrefixConst.MIAOSHA_STOCK_CACHE_PREFIX + productId);
// Inform the service group , Clear the locally sold out tag cache
if (shouldPublishCleanMsg(productId)) {
redisOpsUtil.publish("cleanNoStockCache", productId);
}
result.put("orderStatus", -1);
return CommonResult.failed(result, " Order failure ");
}We analyze the code step by step .
- First , adopt mq To send orders , Then there must be a certain reaction time . This time we go to redis Set the flag bit in , Let the front end query circularly redis state . that 1 Where did this state change ?
It should be received mq After creating an order after the message, you can change the status of the order queue , At this time, we will return the order number , At this time, we can judge whether our order is successfully generated by the status . Take a look at the following listening code .
@Override
public void onMessage(OrderMessage orderMessage) {
log.info("listen the rocketmq message");
Long memberId = orderMessage.getOrder().getMemberId();
Long productId = orderMessage.getOrderItem().getProductId();
try {
Long orderId = secKillOrderService.asyncCreateOrder(orderMessage.getOrder(),orderMessage.getOrderItem(),orderMessage.getFlashPromotionRelationId());
// Change queued tag status , The representative has placed an order successfully ,ID Set to snowflake after , use ID As status mark
redisOpsUtil.set(RedisKeyPrefixConst.MIAOSHA_ASYNC_WAITING_PREFIX + memberId
+ ":" + productId,orderId.toString(),60L, TimeUnit.SECONDS);
} catch (Exception e) {
log.error(e.getMessage(),e.getCause());
/*
* Order failure
*/
redisOpsUtil.set(RedisKeyPrefixConst.MIAOSHA_ASYNC_WAITING_PREFIX + memberId
+ ":" + productId,Integer.toString(-1),60L, TimeUnit.SECONDS);
// Restore pre reduced inventory
secKillOrderService.incrRedisStock(productId);
// Clear the local guava-cache Sold out marks
cache.remove(RedisKeyPrefixConst.MIAOSHA_STOCK_CACHE_PREFIX + productId);
// Inform the service group , Clear the locally sold out tag cache
if(secKillOrderService.shouldPublishCleanMsg(productId)) {
redisOpsUtil.publish("cleanNoStockCache", productId);
}
}
}You can see the method of generating orders asyncCreateOrder after , We have changed redis The state of .
- Let's take a look at the core method of generating orders asyncCreateOrder
public Long asyncCreateOrder(OmsOrder order, OmsOrderItem orderItem, Long flashPromotionRelationId) {
// Reduce inventory
Integer result = miaoShaStockDao.descStock(flashPromotionRelationId, 1);
if (result <= 0) {
throw new RuntimeException(" Didn't get it !");
}
// Insert order record
orderMapper.insertSelective(order);
//OrderItem relation
orderItem.setOrderId(order.getId());
orderItem.setOrderSn(order.getOrderSn());
// Insert orderItem
orderItemMapper.insertSelective(orderItem);
try {
boolean sendStatus = orderMessageSender.sendTimeOutOrderMessage(order.getId() + ":" + flashPromotionRelationId + ":" + orderItem.getProductId());
if (!sendStatus) {
throw new RuntimeException(" Order timeout cancellation message sending failed !");
}
} catch (Exception e) {
throw new RuntimeException(" Order timeout cancellation message sending failed !");
}
return order.getId();
}We can see that in the method of creating orders, the order generation operation is really carried out in the database . And send a delay message , It simply realizes the cancellation of unpaid orders .
Send a message
public boolean sendTimeOutOrderMessage(String cancelId){
Message message = MessageBuilder.withPayload(cancelId)
.setHeader(RocketMQHeaders.KEYS, cancelId)
.build();
SendResult result = rocketMQTemplate.syncSend(scheduleTopic+":"+TAG,message,5000,15);
return SendStatus.SEND_OK == result.getSendStatus();
}receive messages
@Override
public void onMessage(String cancelId) {
if(StringUtils.isEmpty(cancelId)){
return;
}
Long orderId = Long.parseLong(cancelId.split(":")[0]);
Long promotionId = Long.parseLong(cancelId.split(":")[1]);
Long productId = Long.parseLong(cancelId.split(":")[2]);
try {
// Cancelled order , Release DB stock
omsPortalOrderService.cancelOrder(orderId,promotionId);
// Cancelled order - Restore cache inventory
secKillOrderService.incrRedisStock(productId);
} catch (Exception e) {
log.error(" Order cancellation exception : Restore inventory failed ,please check:{}",e.getMessage(),e.getCause());
throw new RuntimeException();// Throw the exception out ,rocketmq Will be redelivered
}
}5. Inventory synchronization
In our process redis When inventory is predicted . When the inventory is insufficient, we should consider the problem of inventory synchronization , That is to say, we should guarantee redis and mysql Double write consistent , In case data changes lead to less buying .
Here we consider two solutions .
- Final consistency
Here our pre reduced inventory code needs to be modified
private boolean preDecrRedisStock1(Long productId, Long promotionId){
Long stock = redisOpsUtil.decr(RedisKeyPrefixConst.MIAOSHA_STOCK_CACHE_PREFIX + productId);
if(stock<0){
if(redisOpsUtil.hasKey(RedisKeyPrefixConst.MIAOSHA_STOCK_CACHE_PREFIX+productId)){
redisOpsUtil.incr(RedisKeyPrefixConst.MIAOSHA_STOCK_CACHE_PREFIX+productId);
}
if(!redisOpsUtil.hasKey(RedisKeyPrefixConst.STOCK_REFRESHED_MESSAGE_PREFIX+promotionId)){
if(orderMessageSender.sendStockSyncMessage(productId,promotionId)){
redisOpsUtil.set(RedisKeyPrefixConst.STOCK_REFRESHED_MESSAGE_PREFIX+promotionId,0);
}
}
return false;
}
return true;
}If redis There is no cache for this activity key, You need to send a 1min Delay message of , After sending successfully, we will redis In this activity key.
Send message code
public boolean sendStockSyncMessage(Long productId,Long promotionId){
Message message = MessageBuilder.withPayload(productId+":"+promotionId).build();
SendResult result = rocketMQTemplate.syncSend("stock-sync",message,5000,5);
return SendStatus.SEND_OK == result.getSendStatus();
}Receive message code
@Override
public void onMessage(String message) {
String[] param = message.split(":");
if(param.length <= 1){
log.error(" Inventory synchronization , Consumption message parameters are incomplete !");
return;
}
Long productID = Long.parseLong(param[0]);
Long promotionId = Long.parseLong(param[1]);
if(redisOpsUtil.hasKey(RedisKeyPrefixConst.STOCK_REFRESHED_MESSAGE_PREFIX + promotionId)){
log.info("start sync mysql stock to redis");
//todo Synchronize the inventory into the cache
Integer stock = stockManageFeignApi.selectStock(productID,promotionId).getData();
if(stock > 0){
// Reset inventory
redisOpsUtil.set(RedisKeyPrefixConst.MIAOSHA_STOCK_CACHE_PREFIX + productID,stock);
// Delete synchronization mark
redisOpsUtil.delete(RedisKeyPrefixConst.STOCK_REFRESHED_MESSAGE_PREFIX + promotionId);
}
}
}We see that if the data queried from the database is greater than 0, Then we need to reset our redis stock , At the same time, delete our synchronization marks . We may send more than one synchronization message under the high parallel transmission , But it can be tolerated .
- Consistency from time to time
Another solution is consistency at all times , Use Alibaba's open source components canal,canal The general principle of is to disguise yourself as a mysql Slave Library , Through analysis mysql Of binlog Log to synchronize data .
边栏推荐
猜你喜欢

RayMarching实现体积光渲染

valgrind工具

图形管线基础(一)

【C笔记】数据类型及存储

What are the open earphones? Four types of air conduction earphones with excellent sound quality are recommended

Leetcode 刷题日记 剑指 Offer II 053. 二叉搜索树中的中序后继

Treasure plan TPC system development DAPP construction

What's a gift for girls on Chinese Valentine's day? Selfie online and thoughtful gift recommendation
![[c语言]--一步一步实现扫雷小游戏](/img/ee/49ddfcd948ccd5c8c9dec3c48c6112.png)
[c语言]--一步一步实现扫雷小游戏

SSAO By Computer Shader(一)
随机推荐
【无标题】
C语言memcpy库函数与memmove的作用
SSAO by computer shader (III)
2021-11-10
OJ 1253 点菜问题
Pyppeteer is recognized to bypass detection
OJ 1505 fuse
Project compilation nosuch*** error problem
Getting started with hugging face
Redhawk Dynamic Analysis
如何模拟实现strcpy库函数
关于时间复杂度,你不知道的都在这里
Treasure plan TPC system development DAPP construction
【自我救赎的开始】
OJ 1018 counting game
[basic knowledge of binary tree]
水瓶效果制作
Battle plague Cup -- my account book
【详解如何一步步实现三子棋】
浮点型数据在内存中如何存储