The articles
- “Graduation Design from Scratch” -ELS Express Logistics Scheduling System in Taiwan (I) Building project environment – Digging Gold (Juejin. Cn)
- “Graduation Design from Scratch” -ELS Zhongtai Express Logistics Scheduling system (II) Basic order function – Juejin (JUejin.cn)
- “Graduation Design from Scratch” -ELS Express Logistics Scheduling system in Taiwan (iii) Basic logistics address function – Juejin (CN)
Leaning back
In the last article, we completed the data preparation portion of the logistics address and used Caffeine as a local cache to respond faster. In this paper, we will implement logistics details. Logistics details are a highlight of the project.
Logistics service
In ELS logistics scheduling system, we are only responsible for logistics order scheduling, and also need access from many other third parties. For example, express delivery system, trading system, e-commerce system and so on. Therefore, we need to encapsulate a service template for the third party implementation, not only at the interface level encapsulation, but also abstract a set of service template template. In the form of a commodity.
For example, in our SMS service, we define templates in advance and then let users fill in their own customized parts in our templates.
The service relationship in our project is as follows:
We first abstract out the service template, and then the third-party express company encapsulates it into its own template according to the interface. We input the template into the database in advance, which can be added into the cache later, because the template data generally does not change much.
Dig a hole here, and you can use two design patterns: strategic pattern and template method. Can design better scalability, does not need a lot of if-else statements, and the third party express company if modified, does not need to change the original code, in line with the open and closed principle.
Input data
Data such as logistics company and freight template can be entered into the database by ourselves in advance at the early stage, without setting it through the back-end. There’s a lot of work left over. If your business grows, you can set up an interface for adding shipping templates and service templates.
Logistics details
Logistics details are mainly communicated with third-party express delivery companies. It contains all kinds of information about objects. When our order is placed, we need to generate logistics details and then tell the third party express company. We don’t need to know the specific logistics operation, just his state. Details are as follows:
Generate logistics details
There are many ways to generate logistics details. Here are a few that the author can think of, you can add later.
Logistics order direct call
This is the simplest. After the logistics order is created, we call the logistics details from the restTemplate and tell it to generate the logistics details.
@PostMapping("/order")
public ResultVO creatOrder(@RequestBody LogisticsOrder order) {
ResultVO result = new ResultVO();
result.setCode(200);
result.setMsg("true");
logger.info("Start creating order");
transactionTemplate.execute(new TransactionCallback<Object>() {
@Override
public Object doInTransaction(TransactionStatus transactionStatus) {
try {
return service.save(order);
} catch (Exception e) {
logger.error(Failed to create order with id:{}, order.getId(), e);
transactionStatus.setRollbackOnly();
return false; }}});// Call directly
restTemplate.postForObject("http://logistics-services/logistics-service/test", order,LogisticsOrder.class);
return result;
}
Copy the code
The most basic business requirements can be met, but there is still a lot of room for optimization. It is important to note that a programmatic transaction, transactionTemplate, is used. If a declarative transaction is directly used, then the transaction will be under the pressure of network IO, which may cause a large transaction, and then lead to a series of problems such as master-slave replication delay. Specific want to understand the programming business can see my previous a specific introduction to the graduation design from zero -ELS express logistics scheduling system (two) basic order function – nuggets (juejin. Cn).
The message queue
Go network IO is too not elegant, order object is not a small object, there are many fields, the network consumption is relatively large. Is there a better way?
We can network IO too slow, we go message queue ah!
After the order is created, through the subscription/publication message model, the order is published, and then the logistics details are consumed, and the logistics details are generated when the information is received. This is certainly faster than direct subscription, and QPS, throughput can improve a grade, logistics orders will not bear a lot of pressure. It should be noted that the message we sent was the ID of the order, so we can query the specific order in the logistics order later.
Some people may ask: isn’t it still necessary to go back to the order query order?
Difference from direct call
Finch does have to go back, but it’s not the same as calling it directly. The direct call is still in the order generation method, and there are transactions in it. If there is too much business logic in a method, it will affect the RT of that method. We want the ordering process to be as highly available as possible, so decouple the business. And what we did was we split the microservices, so the message queue is basically transferring the pressure of the order to the logistics details.
conclusion
The simple understanding is: the order can be created, but I do not guarantee that the logistics details will be created as soon as the order is created. The logistics detail gets the message and can execute the query slowly. But orders must be persisted to prevent loss. And then you don’t have to deal with it.
Just like in the e-commerce system, there are three stages of shopping: stock deduction, order and transaction
If you order — trade — withhold inventory. Such a process, then your system concurrency is very low.
But if you’re withholding inventory — order — trade. Not in the same league as the former. If you are interested, you can open an article about it, which will not be detailed, but also involves a series of technologies such as delay queue.
Specific implementation, I will not post code, because I did not use this way, I used a more ruthless!
Delay queue
Why was message queuing not adopted in the end? In fact, I have written the message queue code, after debugging, there is a very serious bug, is the inconsistency of data.
Disadvantages of message queues
@PostMapping("/order")
public ResultVO creatOrder(@RequestBody LogisticsOrder order) {
ResultVO result = new ResultVO();
result.setCode(200);
result.setMsg("true");
logger.info("Start creating order");
transactionTemplate.execute(new TransactionCallback<Object>() {
@Override
public Object doInTransaction(TransactionStatus transactionStatus) {
try {
return service.save(order);
} catch (Exception e) {
logger.error(Failed to create order with id:{}, order.getId(), e);
transactionStatus.setRollbackOnly();
return false; }}});// Send a message
redisTemplate.convertAndSend(ELSConstants.REDIS_ORDER_SERVICE_QUEUE, order.getId());
return result;
}
Copy the code
After creating the order, we send a message to the logistics details in Redis. Then the logistics details took the ID to find, and found that they could not find the object.
The reason is: with time delay, the database has not created a good object, there began to check.
I certainly can’t use a timed task to keep polling to see if the object is created, and in Redis, messages can only be consumed once and then gone. That won’t work. It’s time to rely on the delay queue of our final hero Redis.
The specific implementation
Logistics Order:
@PostMapping("/order")
public ResultVO creatOrder(@RequestBody LogisticsOrder order) {
ResultVO result = new ResultVO();
result.setCode(200);
result.setMsg("true");
logger.info("Start creating order");
transactionTemplate.execute(new TransactionCallback<Object>() {
@Override
public Object doInTransaction(TransactionStatus transactionStatus) {
try {
return service.save(order);
} catch (Exception e) {
logger.error(Failed to create order with id:{}, order.getId(), e);
transactionStatus.setRollbackOnly();
return false; }}});// Store the delay queue
redisTemplate.opsForZSet().add(ELSConstants.REDIS_DELAY_QUEUE_ORDER, String.valueOf(order.getId()), System.currentTimeMillis());
// The logistics details can be obtained through Redis first, if not, then HTTP call
redisTemplate.opsForValue().set(String.valueOf(order.getId()), order, 12000 + new Random().nextInt(6000), TimeUnit.MILLISECONDS);
return result;
}
Copy the code
Logistics details:
The logistics details side needs a monitor to poll the Redis queue to see if there is data.
@Component
@Slf4j
public class RedisDelayQueueRunner implements CommandLineRunner {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private LogisticsDetailServiceImpl service;
@Override
public void run(String... args) throws Exception {
new Thread(() -> {
while (true) {
Set set = redisTemplate.opsForZSet().rangeByScore(ELSConstants.REDIS_DELAY_QUEUE_ORDER, 0, System.currentTimeMillis());
if (set.isEmpty()) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
continue;
}
log.info("Redis delay queue has data.");
Iterator<String> iterator = set.iterator();
while(iterator.hasNext()){
// Fetch data
String data= iterator.next();
// Todo handles data
handleData(data);
// Delete key from zset to avoid duplication
redisTemplate.opsForZSet().remove(ELSConstants.REDIS_DELAY_QUEUE_ORDER,data);
log.info("Finished executing a task id {} and successfully deleted",data);
}
}
}).start();
log.info("Redis delay queue started successfully");
}
public void handleData(String id){
LogisticsOrder order = JSON.parseObject(JSON.toJSONString(redisTemplate.opsForValue().get(id)), LogisticsOrder.class);
if(order==null) {// There is no data in redis
//todo: invokes the logistics order
}
LogisticsDetail detail = new LogisticsDetail();
/ / copy
BeanUtils.copyProperties(order,detail);
//todo: can be changed to transactiondetail.setOuterOrderType(order.getOrderType()); service.save(detail); }}Copy the code
The effect
At the end
In this paper, the generation of logistics details through delayed queues has been completed, and the most basic business process of the whole system: order generation — logistics details have also been completed. What follows is a refinement of the basic functionality.
This section to be completed
- Use design pattern to reconstruct logistics service, about template part
- Throw in the logistics address section as well
- Consider whether to write a Courier terminal and transaction terminal, simulate the operation of the Courier and transaction information.