2

我想创建跨机器唯一的 9 位数字 ID。我目前正在为此使用数据库序列,但我想知道是否可以在没有数据库的情况下完成。这些序列将用于 X12 EDI 交易,因此它们不必永远唯一。甚至可能只在 24 小时内独一无二。

我唯一的想法:

  1. 每台服务器都有一个 2 位数的服务器标识符。
  2. 每个服务器都维护一个文件,该文件基本上跟踪本地序列。
  3. id = + <7 位数字序列包含>

我最大的问题是如果硬盘驱动器发生故障该怎么办。我不知道它从哪里停下来。

我所有的其他想法基本上都是重新创建一个集中的数据库序列。

有什么想法吗?

4

6 回答 6

3

以下

{XX}{dd}{HHmm}{N}

其中 {XX} 是机器编号 {dd} 是当前时间(24 小时)当月中的某天 {HHmm},{N} 是序列号。

hd 崩溃将花费一分钟以上的时间,因此再次从 0 开始不是问题。

您还可以将 {dd} 替换为 {ss} 几秒钟,具体取决于要求。唯一性周期与每分钟请求数。

于 2009-07-27T04:33:03.223 回答
2

如何生成 GUID(确保唯一性),然后使用某种散列函数将 GUID 转换为 9 位数字?
就在我的头顶...

于 2009-07-27T04:14:52.807 回答
2

如果 HD 失败,您可以设置新的和未使用的 2 位服务器标识符,并确保该号码是唯一的(至少 24 小时)

于 2009-07-27T04:19:55.607 回答
0

使用变体:

md5(uniqid(rand(), true));

只是一个想法。

于 2009-07-27T04:54:15.710 回答
-1

在我最近的项目中,我也遇到了这个要求,在没有任何数据库的情况下生成 N 位长序列号。

这实际上是一个很好的面试问题,因为有性能和软件崩溃恢复的考虑。有兴趣的进一步阅读。

以下代码具有以下功能:

  1. 为每个序列加上前缀。

  2. 序列缓存,如 Oracle 序列。

  3. 最重要的是,有恢复逻辑可以从软件崩溃中恢复序列。

附上完整的实现:

import java.util.concurrent.atomic.AtomicLong;
import org.apache.commons.lang.StringUtils;

/**
 * This is a customized Sequence Generator which simulates Oracle DB Sequence Generator. However the master sequence
 * is stored locally in the file as there is no access to Oracle database. The output format is "prefix" + number.
 * <p>
 * <u><b>Sample output:</u></b><br>
 * 1. FixLengthIDSequence(null,null,15,0,99,0) will generate 15, 16, ... 99, 00<br>
 * 2. FixLengthIDSequence(null,"K",1,1,99,0) will generate K01, K02, ... K99, K01<br>
 * 3. FixLengthIDSequence(null,"SG",100,2,9999,100) will generate SG0100, SG0101, ... SG8057, (in case server crashes, the new init value will start from last cache value+1) SG8101, ... SG9999, SG0002<br>
 */
public final class FixLengthIDSequence {

    private static String FNAME;
    private static String PREFIX;
    private static AtomicLong SEQ_ID;
    private static long MINVALUE;
    private static long MAXVALUE;
    private static long CACHEVALUE;

    // some internal working values.
    private int iMaxLength; // max numeric length excluding prefix, for left padding zeros.
    private long lNextSnapshot; // to keep track of when to update sequence value to file. 
    private static boolean bInit = false; // to enable ShutdownHook routine after program has properly initialized

    static {
        // Inspiration from http://stackoverflow.com/questions/22416826/sequence-generator-in-java-for-unique-id#35697336.
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            if (bInit) { // Without this, saveToLocal may hit NullPointerException.
                saveToLocal(SEQ_ID.longValue());
            }
        }));
    }

    /**
     * This POJO style constructor should be initialized via Spring Singleton. Otherwise, rewrite this constructor into Singleton design pattern.
     * 
     * @param sFilename This is the absolute file path to store the sequence number. To reset the sequence, this file needs to be removed manually.
     * @param prefix The hard-coded identifier.
     * @param initvalue
     * @param minvalue
     * @param maxvalue
     * @param cache
     * @throws Exception
     */
    public FixLengthIDSequence(String sFilename, String prefix, long initvalue, long minvalue, long maxvalue, int cache) throws Exception {
        bInit = false;
        FNAME = (sFilename==null)?"C:\\Temp\\sequence.txt":sFilename;
        PREFIX = (prefix==null)?"":prefix;
        SEQ_ID = new AtomicLong(initvalue);
        MINVALUE = minvalue;
        MAXVALUE = maxvalue; iMaxLength = Long.toString(MAXVALUE).length();
        CACHEVALUE = (cache <= 0)?1:cache; lNextSnapshot = roundUpNumberByMultipleValue(initvalue, cache); // Internal cache is always 1, equals no cache.

        // If sequence file exists and valid, restore the saved sequence.
        java.io.File f = new java.io.File(FNAME);
        if (f.exists()) {
            String[] saSavedSequence = loadToString().split(",");
            if (saSavedSequence.length != 6) {
                throw new Exception("Local Sequence file is not valid");
            }

            PREFIX = saSavedSequence[0];
            //SEQ_ID = new AtomicLong(Long.parseLong(saSavedSequence[1])); // savedInitValue
            MINVALUE = Long.parseLong(saSavedSequence[2]);
            MAXVALUE = Long.parseLong(saSavedSequence[3]); iMaxLength = Long.toString(MAXVALUE).length();
            CACHEVALUE = Long.parseLong(saSavedSequence[4]);
            lNextSnapshot = Long.parseLong(saSavedSequence[5]);

            // For sequence number recovery
            // The rule to determine to continue using SEQ_ID or lNextSnapshot as subsequent sequence number:
            // If savedInitValue = savedSnapshot, it was saved by ShutdownHook -> use SEQ_ID.
            // Else if saveInitValue < savedSnapshot, it was saved by periodic Snapshot -> use lNextSnapshot+1.
            if (saSavedSequence[1].equals(saSavedSequence[5])) {
                long previousSEQ = Long.parseLong(saSavedSequence[1]);
                SEQ_ID = new AtomicLong(previousSEQ);
                lNextSnapshot = roundUpNumberByMultipleValue(previousSEQ,CACHEVALUE);
            } else {
                SEQ_ID = new AtomicLong(lNextSnapshot+1); // SEQ_ID starts fresh from lNextSnapshot+!.
                lNextSnapshot = roundUpNumberByMultipleValue(SEQ_ID.longValue(),CACHEVALUE);
            }
        }

        // Catch invalid values.
        if (minvalue < 0) {
            throw new Exception("MINVALUE cannot be less than 0");
        }
        if (maxvalue < 0) {
            throw new Exception("MAXVALUE cannot be less than 0");
        }
        if (minvalue >= maxvalue) {
            throw new Exception("MINVALUE cannot be greater than MAXVALUE");
        }
        if (cache >= maxvalue) {
            throw new Exception("CACHE value cannot be greater than MAXVALUE");
        }

        // Save the next Snapshot.
        saveToLocal(lNextSnapshot);
        bInit = true;
    }

    /**
     * Equivalent to Oracle Sequence nextval.
     * @return String because Next Value is usually left padded with zeros, e.g. "00001".
     */
    public String nextVal() {
        if (SEQ_ID.longValue() > MAXVALUE) {
            SEQ_ID.set(MINVALUE);
            lNextSnapshot = roundUpNumberByMultipleValue(MINVALUE,CACHEVALUE);
        }

        if (SEQ_ID.longValue() > lNextSnapshot) {
            lNextSnapshot = roundUpNumberByMultipleValue(lNextSnapshot,CACHEVALUE);
            saveToLocal(lNextSnapshot);
        }

        return PREFIX.concat(StringUtils.leftPad(Long.toString(SEQ_ID.getAndIncrement()),iMaxLength,"0"));
    }

    /**
     * Store sequence value into the local file. This routine is called either by Snapshot or ShutdownHook routines.<br>
     * If called by Snapshot, currentCount == Snapshot.<br>
     * If called by ShutdownHook, currentCount == current SEQ_ID.
     * @param currentCount - This value is inserted by either Snapshot or ShutdownHook routines.
     */
    private static void saveToLocal (long currentCount) {
        try (java.io.Writer w = new java.io.BufferedWriter(new java.io.OutputStreamWriter(new java.io.FileOutputStream(FNAME), "utf-8"))) {
            w.write(PREFIX + "," + SEQ_ID.longValue() + "," + MINVALUE + "," + MAXVALUE + "," + CACHEVALUE + "," + currentCount);
            w.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Load the sequence file content into String.
     * @return
     */
    private String loadToString() {
        try {
            return new String(java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(FNAME)));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }

    /**
     * Utility method to round up num to next multiple value. This method is used to calculate the next cache value.
     * <p>
     * (Reference: http://stackoverflow.com/questions/18407634/rounding-up-to-the-nearest-hundred)
     * <p>
     * <u><b>Sample output:</b></u>
     * <pre>
     * System.out.println(roundUpNumberByMultipleValue(9,10)); = 10
     * System.out.println(roundUpNumberByMultipleValue(10,10)); = 20
     * System.out.println(roundUpNumberByMultipleValue(19,10)); = 20
     * System.out.println(roundUpNumberByMultipleValue(100,10)); = 110
     * System.out.println(roundUpNumberByMultipleValue(109,10)); = 110
     * System.out.println(roundUpNumberByMultipleValue(110,10)); = 120
     * System.out.println(roundUpNumberByMultipleValue(119,10)); = 120
     * </pre>
     * 
     * @param num Value must be greater and equals to positive integer 1.
     * @param multiple Value must be greater and equals to positive integer 1.
     * @return
     */
    private long roundUpNumberByMultipleValue(long num, long multiple) {
        if (num<=0) num=1;
        if (multiple<=0) multiple=1;
        if (num % multiple != 0) {
            long division = (long) ((num / multiple) + 1);
            return division * multiple;
        } else {
            return num + multiple;
        }
    }

    /**
     * Main method for testing purpose.
     * @param args
     */
    public static void main(String[] args) throws Exception {
        //FixLengthIDSequence(Filename, prefix, initvalue, minvalue, maxvalue, cache)
        FixLengthIDSequence seq = new FixLengthIDSequence(null,"H",50,1,999,10);
        for (int i=0; i<12; i++) {
            System.out.println(seq.nextVal());
            Thread.sleep(1000);
            //if (i==8) { System.exit(0); }
        }
    }

}

要测试代码,让序列正常运行。您可以按 Ctrl+C 来模拟服务器崩溃。下一个序列号将从 NextSnapshot+1 继续。

于 2017-02-20T12:39:45.087 回答
-2

冷,您使用其他一些唯一数据源的前 9 位数字,例如:

  1. 一个随机数
  2. 系统时间
  3. 正常运行时间

经过两秒钟的思考,这些都不是唯一的,但您可以将它们用作哈希函数的种子值,如另一个答案中所建议的那样。

于 2009-07-27T04:15:54.927 回答