Улучшение Hi-Lo Id генератора - PullRequest
1 голос
/ 10 августа 2010

У меня есть генератор hi-lo Id, который я использую в многопоточной среде. Генератор может вызываться до 100 тыс. Раз в секунду на поток

У меня достаточно хорошая (и безопасная) реализация, которая прекрасно работает. IdAllocator - это объект, который выбирает следующий «пакет» идентификаторов. Вы можете предположить, что это потокобезопасно. Я также установил довольно большой размер партии (1 миллион)

private final IdAllocator idAllocator;
private final String idKey;
private final int batchSize;

private final Object lock = new Object();

private long currentId = 0;
private long nextFetchId = -1;

IdGeneratorImpl( IdAllocator idAllocator, String idKey, int batchSize )
{
    this.idAllocator = idAllocator;
    this.idKey = idKey;
    this.batchSize = batchSize;
}

public long getNextId()
{
    synchronized ( lock )
    {
        if ( currentId < nextFetchId )
        {
            long localCurrent = currentId;
            currentId++;
            return localCurrent;
        }

        currentId = idAllocator.allocateBatch( idKey, batchSize );
        nextFetchId = currentId + batchSize;

        return getNextId();
    }
}

В настоящее время я в основном, но не всегда, использую это без всякой поддержки. Однако в будущем он будет вызываться несколькими потоками.

Я рассмотрел создание одного экземпляра этого для потока, что, вероятно, будет лучшим подходом. Однако, как интеллектуальный / обучающий опыт, я задавался вопросом, могу ли я как-то улучшить эту реализацию, в частности, чтобы уменьшить потенциальный конфликт в getNextId (), когда несколько потоков вызывают его часто?

Ответы [ 2 ]

0 голосов
/ 28 сентября 2016

Бьюсь об заклад, вы слишком оптимистичны. Размер пакета в один миллион означает, что вы попадаете в БД при каждой миллионной вставке. Если бы вы использовали тысячу вместо этого, вы бы не увидели разницы в производительности и потратили бы гораздо меньше идентификаторов. Конечно, с long это не имеет большого значения.

Выполнение вышеуказанного в каждом потоке означает, что вы тратите до миллиона идентификаторов на каждый поток. Ничего страшного с long, я знаю.

Это также увеличит стоимость запуска без необходимости и, возможно, оставит много локального мусора. Опять же, ничего страшного, но в чем преимущество? Короткий код, подобный приведенному выше, занимает несколько наносекунд, а доступ к БД занимает на много порядков больше. Таким образом, никакие разногласия не могли бы действительно замедлить вас.

Однако правильный способ оптимизировать это, вероятно, через AtomicLong. Это имеет намного меньшую нагрузку, чем синхронизация. Используйте getAndIncrement и введите синхронизированный блок только тогда, когда вы переполните свой nextFetchId. В этом блоке сначала перепроверьте, выполнил ли другой поток работу, которую вы собираетесь выполнить, и если нет, получите следующий идентификатор из базы данных.

Намного проще использовать спящий режим HiLoOptimizer или его лучший вариант.

0 голосов
/ 10 августа 2010

Если вы посмотрите на Hibernate TableHiLoGenerator, он использует простой synchronized в методе генерации, что означает, что несколько потоков будут ожидать, чтобы только один из них выполнял метод одновременно , Вы сделали то же самое с вашим lock (который немного избыточен - метод synchronized делает то же самое). Итак, я бы пришел к выводу, что ваши реализации в порядке.

...