当前位置:网站首页>An accident caused by repeated order number has made me miserable!

An accident caused by repeated order number has made me miserable!

2020-12-06 13:58:19 Zhang Zhixiang

We had an accident online , This is what the accident looks like :

As like as two peas, the order number is two. , The content of the order is not the same , And the system has been throwing the wrong one in accordance with the order number , There is no normal callback , And it happened more than once , So this system upgrade must be solved .

My colleagues have changed several times before , But the effect is not good , There is always a problem of duplicate order numbers , So I took advantage of this problem to sort out the code written by my colleagues .

Here's a quick look at the code :

/**
 * OD Order number generation 
 *  Order number generation rules :OD + yyMMddHHmmssSSS + 5 digit ( Merchant ID3 position + random number 2 position ) 22 position 
 */
public static String getYYMMDDHHNumber(String merchId){
      StringBuffer orderNo = new StringBuffer(new SimpleDateFormat("yyMMddHHmmssSSS").format(new Date()));
      if(StringUtils.isNotBlank(merchId)){
          if(merchId.length()>3){
              orderNo.append(merchId.substring(0,3));
          }else {
              orderNo.append(merchId);
          }
      }
      int orderLength = orderNo.toString().length();
      String randomNum = getRandomByLength(20-orderLength);
      orderNo.append(randomNum);
      return orderNo.toString();
}


  /**  Generates a random number of specified digits  **/
  public static String getRandomByLength(int size){
      if(size>8 || size<1){
          return "";
      }
      Random ne = new Random();
      StringBuffer endNumStr = new StringBuffer("1");
      StringBuffer staNumStr = new StringBuffer("9");
      for(int i=1;i<size;i++){
          endNumStr.append("0");
          staNumStr.append("0");
      }
      int randomNum = ne.nextInt(Integer.valueOf(staNumStr.toString()))+Integer.valueOf(endNumStr.toString());
      return String.valueOf(randomNum);
  }      

You can see , This code is not very good , The code part is not discussed for now , In the code, the main factors that make the order number not duplicate are random number and millisecond , But there are only two random numbers here , In high concurrency environment, it is very easy to have duplication problem .

At the same time, the choice of milliseconds is not very good , In multicore CPU Multithreading , For a certain period of time ( Minimal ) This millisecond can be said to be fixed ( Test verified ), So I'll start with 100 This order number is generated under concurrent tests .

The test code is as follows :

public static void main(String[] args) {
    final String merchId = "12334";
    List<String> orderNos = Collections.synchronizedList(new ArrayList<String>());
    IntStream.range(0,100).parallel().forEach(i->{
        orderNos.add(getYYMMDDHHNumber(merchId));
    });

    List<String> filterOrderNos = orderNos.stream().distinct().collect(Collectors.toList());

    System.out.println(" Number of orders generated :"+orderNos.size());
    System.out.println(" Number of orders after filtering :"+filterOrderNos.size());
    System.out.println(" Number of repeat orders :"+(orderNos.size()-filterOrderNos.size()));
}

Sure enough , The results of the test are as follows :

I was shocked 🤯 了 , There are 100 concurrent applications 13 A repetition of !!!

I quickly asked my colleagues not to publish the version , I took the job !

If you don't have a clear solution, you can't do it , I probably spent 6+ Minutes and colleagues to discuss business scenarios , It was decided to make the following changes :

  • Remove merchants ID The introduction of ( According to colleagues , Incoming merchants ID It's also to prevent duplicate orders , It turns out it doesn't work )

  • Only three bits are reserved in milliseconds ( Reduce the length and ensure that there is no repetition of application switching )

  • Use thread safe counters to increment numbers ( Three digit minimum guaranteed concurrency 800 No repetition , I gave it in the code 4 position )

  • Change date to java8 To format the date class ( Thread safety and code simplicity considerations )

After the above thinking, my final code is :

/**  Order number generation (NEW) **/
private static final AtomicInteger SEQ = new AtomicInteger(1000);
private static final DateTimeFormatter DF_FMT_PREFIX = DateTimeFormatter.ofPattern("yyMMddHHmmssSS");
private static ZoneId ZONE_ID = ZoneId.of("Asia/Shanghai");
public static String generateOrderNo(){
    LocalDateTime dataTime = LocalDateTime.now(ZONE_ID);
    if(SEQ.intValue()>9990){
        SEQ.getAndSet(1000);
    }
    return  dataTime.format(DF_FMT_PREFIX)+SEQ.getAndIncrement();
}

Of course, when the code is finished, it can't be so casually finished , Now we have to take a test main Look at the function :

public static void main(String[] args) {

    List<String> orderNos = Collections.synchronizedList(new ArrayList<String>());
    IntStream.range(0,8000).parallel().forEach(i->{
        orderNos.add(generateOrderNo());
    });

    List<String> filterOrderNos = orderNos.stream().distinct().collect(Collectors.toList());

    System.out.println(" Number of orders generated :"+orderNos.size());
    System.out.println(" Number of orders after filtering :"+filterOrderNos.size());
    System.out.println(" Number of repeat orders :"+(orderNos.size()-filterOrderNos.size()));
}

/**
   test result : 
   Number of orders generated :8000
   Number of orders after filtering :8000
   Number of repeat orders :0
**/

splendid , One time , It's ready to go online ...

However , I'll go back to the code above , Although the problem of duplication of concurrent orders is solved to the greatest extent , However, there is still a potential risk for our system architecture : If the current application has multiple instances ( colony ) Is there no possibility of repetition ?

In view of this problem, an effective solution is necessary , So then I thought : How to distinguish the application order number of multiple instances ?

The following is my general direction of thinking :

I think about it here , Our application is running in docker Inside , And each docker The application ports in the container are the same , But the Internet IP There will be no duplication , As for the process, there is also the possibility of duplication , about UUID The way we've lost before , Far away ,redis or DB It's a better way , But independence is poor ...

At the same time, there is another factor that is important , That is, all applications related to order number generation are in the same host (linux Physical servers ) On , So I chose the current system architecture IP The way .

Here is my code :

import org.apache.commons.lang3.RandomUtils;

import java.net.InetAddress;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class OrderGen2Test {

    /**  Order number generation  **/
    private static ZoneId ZONE_ID = ZoneId.of("Asia/Shanghai");
    private static final AtomicInteger SEQ = new AtomicInteger(1000);
    private static final DateTimeFormatter DF_FMT_PREFIX = DateTimeFormatter.ofPattern("yyMMddHHmmssSS");
    public static String generateOrderNo(){
        LocalDateTime dataTime = LocalDateTime.now(ZONE_ID);
        if(SEQ.intValue()>9990){
            SEQ.getAndSet(1000);
        }
        return  dataTime.format(DF_FMT_PREFIX)+ getLocalIpSuffix()+SEQ.getAndIncrement();
    }

    private volatile static String IP_SUFFIX = null;
    private static String getLocalIpSuffix (){
        if(null != IP_SUFFIX){
            return IP_SUFFIX;
        }
        try {
            synchronized (OrderGen2Test.class){
                if(null != IP_SUFFIX){
                    return IP_SUFFIX;
                }
                InetAddress addr = InetAddress.getLocalHost();
                //  172.17.0.4  172.17.0.199 ,
                String hostAddress = addr.getHostAddress();
                if (null != hostAddress && hostAddress.length() > 4) {
                    String ipSuffix = hostAddress.trim().split("\\.")[3];
                    if (ipSuffix.length() == 2) {
                        IP_SUFFIX = ipSuffix;
                        return IP_SUFFIX;
                    }
                    ipSuffix = "0" + ipSuffix;
                    IP_SUFFIX = ipSuffix.substring(ipSuffix.length() - 2);
                    return IP_SUFFIX;
                }
                IP_SUFFIX = RandomUtils.nextInt(10, 20) + "";
                return IP_SUFFIX;
            }
        }catch (Exception e){
            System.out.println(" obtain IP Failure :"+e.getMessage());
            IP_SUFFIX =  RandomUtils.nextInt(10,20)+"";
            return IP_SUFFIX;
        }
    }


    public static void main(String[] args) {
        List<String> orderNos = Collections.synchronizedList(new ArrayList<String>());
        IntStream.range(0,8000).parallel().forEach(i->{
            orderNos.add(generateOrderNo());
        });

        List<String> filterOrderNos = orderNos.stream().distinct().collect(Collectors.toList());

        System.out.println(" Sample order :"+ orderNos.get(22));
        System.out.println(" Number of orders generated :"+orderNos.size());
        System.out.println(" Number of orders after filtering :"+filterOrderNos.size());
        System.out.println(" Number of repeat orders :"+(orderNos.size()-filterOrderNos.size()));
    }
}

/**
   Sample order :20082115575546011022
   Number of orders generated :8000
   Number of orders after filtering :8000
   Number of repeat orders :0
**/

Last , Code description and some suggestions

  • generateOrderNo() There is no need to lock within a method , because AtomicInteger The CAS Spin lock ( Ensure visibility as well as atomicity , Please find out by yourself )

  • getLocalIpSuffix() There is no need to set the null With synchronous lock ( Bidirectional check lock , The whole is a safe singleton mode )

  • The way I realize it is not the only way to solve the problem , The specific solution to the problem depends on the current system architecture

  • Any test is necessary , My colleagues have not tested themselves after trying to solve this problem several times , Not testing undermines development professionalism !

版权声明
本文为[Zhang Zhixiang]所创,转载请带上原文链接,感谢
https://chowdera.com/2020/12/202012061355402056.html