At the end of last year, we had an online accident that looked like this:

There are two identical order numbers in the system, but the content of the order is not different. Moreover, the system has been throwing mistakes when querying according to the order number, and it cannot be called back normally. Moreover, the thing happened more than once, so this system upgrade must be solved.

My colleague had changed it several times before, but the effect was not good: there was always the problem of repeating the order number, so I took advantage of this problem to properly arrange the code written by my colleague. Here’s a quick look at the code:

/** * OD order number generation rule: Public static String getYYMMDDHHNumber(String merchId){StringBuffer orderNo = new StringBuffer(new SimpleDateFormat("yyMMddHHmmssSSS").format(new Date())); If (StringUtils. IsNotBlank (merchId)) {if (merchId. The 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(); } / * * / * * generate specified digits random 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); }Copy the code

As you can see, this code is actually not very good, the code part will not discuss, code to make the order number does not repeat the main factor point is random number and millisecond, but here the random number is only two digits

In high concurrency environment is extremely prone to repeat the question, milliseconds that choice is not very good at the same time, under the multicore multi-threading CPU, a certain period of time (minimum) this milliseconds can be fixed tested (test), so here I first with 100 concurrent test under the order number generation, focus on WeChat subscription number artisan notes, Reply to the schema for a list of architectural knowledge. 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 (0100). The parallel () forEach (I - > {orderNos. Add (getYYMMDDHHNumber (merchId)); }); List<String> filterOrderNos = orderNos.stream().distinct().collect(Collectors.toList()); System.out.println(" generate order: "+ ordernos.size ()); System.out.println(" + filterOrdernos.size ()); System.out.println(" duplicate order: "+(ordernos.size () -filterOrdernos.size ())); }Copy the code

Sure enough, the test results were as follows:

The number of generated orders is 100. The number of repeated orders is 87. The number of repeated orders is 13Copy the code

At that time I was shocked 🤯, 100 concurrent inside actually have 13 repeat!! , I hurriedly let with prior do not send version, this job I accepted!

I spent about 6+ minutes discussing the business scenario with my colleagues and decided to make the following changes:

  • Remove the input of merchant ID (according to my colleague, the input of merchant ID is also to prevent repeated orders, but it has not been used)
  • Keep only three milliseconds (reduce the length and make sure there is no duplication between application switches)
  • Use a thread-safe counter for numerical increments (three-digit minimum guaranteed concurrency 800 not repeated, I gave 4 bits in the code)
  • Replace date converted to Java8 date classes for formatting (thread safety and code brevity considerations)

My final code is:

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(); }Copy the code

Now we need to test the main function:

public static void main(String[] args) { List<String> orderNos = Collections.synchronizedList(new ArrayList<String>()); IntStream. Range (0800). The parallel () forEach (I - > {orderNos. Add (generateOrderNo ()); }); List<String> filterOrderNos = orderNos.stream().distinct().collect(Collectors.toList()); System.out.println(" generate order: "+ ordernos.size ()); System.out.println(" + filterOrdernos.size ()); System.out.println(" duplicate order: "+(ordernos.size () -filterOrdernos.size ())); } /** Test result: Generated order number: 8000 Filtered repeat order number: 8000 Repeated order number: 0 **/Copy the code

Great, one time success, can go straight online…

However, when I look back at the above code, although the problem of duplicate order numbers is solved to the greatest extent, there is still a potential problem for our system architecture: if the current application has multiple instances (clusters), is there no possibility of duplicate? Follow wechat subscription number maker notes and reply to architecture to get some list of architecture knowledge.

Since this problem requires an effective solution, I wondered: How can multiple instance application order numbers be distinguished? Here are some general directions of my thinking:

  • Use a UUID(initialize one the first time an order number is generated)
  • Use Redis to record a growth ID
  • Maintain a growth ID using database tables
  • IP address of the network where the application resides
  • Port number of the application
  • Use third-party algorithms (Snowflake algorithms, etc.)
  • Use process ids (to some extent possible)

Here I think, our application is running in docker, and the application port in each Docker container is the same, but the network IP does not exist the problem of duplication, as for the process also exists the possibility of duplication, for the way of UUID suffered losses before, in a word, redis or DB is also a better way. But less independent…

At the same time, another important factor is that all applications involved in order number generation are on the same host (Linux physical server), so I choose IP for the current system architecture.

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 {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 (); // 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 (" failed to obtain IP :"+ LLDB message ()); 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 (0800). The parallel () forEach (I - > {orderNos. Add (generateOrderNo ()); }); List<String> filterOrderNos = orderNos.stream().distinct().collect(Collectors.toList()); System.out.println(" ordernos.get (22) "); System.out.println(" generate order: "+ ordernos.size ()); System.out.println(" + filterOrdernos.size ()); System.out.println(" duplicate order: "+(ordernos.size () -filterOrdernos.size ())); Order example: 20082115575546011022 Generated order number: 8000 Filtered repeat order number: 8000 Repeated order number: 0 **/Copy the code

The last

Code description and some suggestions

  • The generateOrderNo() method does not need to be locked because the CAS self-rotating lock is used within AtomicInteger.
  • GetLocalIpSuffix () method does not need to add synchronization locks to non-null logic (two-way check locks, which is a secure singleton)
  • The way I implement is not the only way to solve the problem, the specific solution to the problem needs to be specific according to the current system architecture

Any testing is necessary, my colleague did not test himself after the first few attempts to solve this problem, not testing hurts development professionalism!

Author: funnyZpC cnblogs.com/funnyzpc/p/13541713.html