【问题标题】:generating 9 digit ids without database sequence生成没有数据库序列的 9 位 id
【发布时间】:2010-11-14 06:09:42
【问题描述】:

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

我唯一的想法:

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

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

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

有什么想法吗?

【问题讨论】:

  • 那么,如果您的集中式数据库的硬盘出现故障怎么办?这不是每个服务器序列方法独有的问题。
  • 为什么你需要知道计数器在哪里停止?

标签: x12


【解决方案1】:

以下

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

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

高清崩溃需要一分钟以上,因此再次从 0 开始不是问题。

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

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • 这可能是我最好的选择。用户 pb 的回答会起作用,只是我们每分钟可能会产生太多而无法使用。所以我需要弄清楚如何跟踪正在使用的 id 以及它们何时再次可用。
    【解决方案3】:

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

    【讨论】:

    • 哈希函数你得好好想想,不是吗?
    【解决方案4】:

    使用变体:

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

    只是一个想法。

    【讨论】:

      【解决方案5】:

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

      这其实是一个很好的面试题,因为有性能和软件崩溃恢复方面的考虑。 Further Reading如果有兴趣。

      以下代码具有以下特点:

      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 继续。

      【讨论】:

        【解决方案6】:

        冷你使用其他一些独特数据来源的前 9 位数字,例如:

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

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

        【讨论】:

          猜你喜欢
          • 2011-06-02
          • 1970-01-01
          • 2010-11-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-10-30
          • 1970-01-01
          相关资源
          最近更新 更多