генерация 9-значных идентификаторов без последовательности базы данных

Я хотел бы создать 9-значные числовые идентификаторы, которые уникальны для разных машин. В настоящее время я использую для этого последовательность базы данных, но мне интересно, можно ли это сделать без нее. Последовательности будут использоваться для транзакций X12 EDI, поэтому они не обязательно должны быть уникальными навсегда. Может быть, даже только в течение 24 часов.

Моя единственная идея:

  1. Каждый сервер имеет двухзначный идентификатор сервера.
  2. Каждый сервер поддерживает файл, который, по сути, отслеживает локальную последовательность.
  3. id = + ‹7-значная последовательность, которая переносит>

Моя самая большая проблема с этим заключается в том, что делать, если жесткий диск выходит из строя. Я бы не знал, где он остановился.

Все остальные мои идеи, по сути, сводятся к воссозданию последовательности централизованной базы данных.

Есть предположения?


x12
person Dave    schedule 27.07.2009    source источник
comment
А что, если жесткий диск вашей централизованной базы данных выйдет из строя? Это проблема не только метода последовательности для каждого сервера.   -  person David Z    schedule 27.07.2009
comment
Зачем вам знать, где остановился счетчик?   -  person m_vitaly    schedule 27.07.2009


Ответы (6)


В случае сбоя HD вы можете просто установить новый и неиспользуемый 2-значный идентификатор сервера и быть уверенным, что номер уникален (по крайней мере, в течение 24 часов).

person m_vitaly    schedule 27.07.2009
comment
Это, наверное, моя лучшая ставка. Ответ пользователя pb будет работать, за исключением того, что мы можем генерировать слишком много в минуту, чтобы это работало. Поэтому мне нужно выяснить, как отслеживать, какие идентификаторы используются и когда они снова становятся доступными. - person Dave; 27.07.2009

Последующий

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

Где {XX} — номер машины, {dd} — день месяца, {HHmm} — текущее время (24 часа), а {N} — порядковый номер.

Сбой HD займет больше минуты, поэтому повторный запуск с 0 не проблема.

Вы также можете заменить {dd} на {ss} для секунд, в зависимости от требований. Период уникальности и количество запросов в минуту.

person Paul van Brenk    schedule 27.07.2009

Как насчет создания GUID (обеспечивает уникальность), а затем использования какой-либо хэш-функции для преобразования GUID в 9-значное число?
Просто пришло в голову...

person Traveling Tech Guy    schedule 27.07.2009
comment
Над хэш-функцией нужно хорошо подумать, не так ли? - person m_vitaly; 27.07.2009

Используйте вариант:

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

Просто мысль.

person grantwparks    schedule 27.07.2009

В моем недавнем проекте я также столкнулся с этим требованием, чтобы сгенерировать N-значный длинный порядковый номер без какой-либо базы данных.

На самом деле это хороший вопрос для интервью, потому что есть соображения по производительности и восстановлению программного обеспечения после сбоев. Дополнительная литература, если интересно.

Следующий код имеет следующие функции:

  1. Префикс каждой последовательности с префиксом.

  2. Кэш последовательности, такой как Oracle Sequence.

  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.

person oraclesoon    schedule 20.02.2017

Если вы используете первые 9 цифр какого-либо другого источника уникальных данных, например:

  1. случайное число
  2. Системное время
  3. Время безотказной работы

Подумав об этом в течение двух секунд, ни один из них не уникален сам по себе, но вы можете использовать их в качестве начальных значений для хеш-функций, как было предложено в другом ответе.

person Crippledsmurf    schedule 27.07.2009