Генератор номеров для PSI WMS

Публикация № 309822

Программирование - Практика программирования

PSI WMS обмен уникальный код

4
Описан вариант решения с генерацией "почти последовательных" уникальных номеров для разных типов документов.

Текущее состояние.

Имеестся интерфейс обмена 1С с PSI WMS. Для обмена документами необходимо каждому документу генерировать уникальный номер, связка хранится в регистре сведений «Номера накладных» с измерением «Документ» тип: составной, состоящий из документов обмена и «НомерWMS» тип: число, содержит уникальный номер. Генератор номеров реализован в непереодическом регистре сведений «Счетчик номеров», с измерением «ИмяСчетчика» тип строка, и «Номер», тип целое число. Перед записью документа в регистр сведений «Номера накладных» пишется ссылка документа и уникальный порядоковый номер, получаемы из «Счетчик номеров», счетчик при этом плюсуется. Проблема возникает при получении нового номера. Перед чтением значения из «Счетчик номеров» регистр исключительно блокируется и находится в таком состоянии до конца транзакции, а именно до окончания записи документа. На месте блокировки возникает таймаут.

Решение.

Для реализации параллельного получения номера, был выбран следующий вариант: сделать 100 счетчиков и шаг счетчика не 1, а 100. Чтобы получить номер счетчика используестя генератор случайных чисел. Последовательный перебор исключен, так как небходимо где то хранить значение, которое при каждой генерации будет блокироваться, и возникнет опять узкое место. Данный метод расширяем если не хватает 100 то можно сделать N счетчиков с шагом N. При этом блокировать нужно не весь регистр, а только запись.

При реализации данного метода возникла  проблема с случайностью генератора на нагрузочном тесте из 10 фоновых заданий оставались только 3, остальные «падали» на таймауте. При разборе проблемы выяснилось что метод

ГСЧ = Новый ГенераторСлучайныхЧисел();
ГСЧ.СлучайноеЧисло(1, 100); 

не дает случайности в необходимом размере. В диапазоне целых чисел от 1 до 100 возникали до 8 подряд одинаковых номеров. Такая же проблема возникала при системном генераторе случайных чисел.

Rnd = COMОбъект("System.Random");

Сообщить(Rnd.Next());

Но вариант с использованием уникального идентификатора дал более приемлемые результаты, тоесть при тестах на параллельный запуск нескольких фоновых заданий (был проведен тест на 1000 фоновых заданий с генерацией в цикле 100 номеров) распределение номеров получалось равномерным.

Функция ПолучитьСлучайноеЧисло(Мин,Макс)
   Для Сч = 1 По 100 Цикл

     Уник = Новый УникальныйИдентификатор;

   КонецЦикла;
   Уник = СтрЗаменить(Уник,"-","");
   Уник = СтрЗаменить(Уник,"a","");
   Уник = СтрЗаменить(Уник,"b","");
   Уник = СтрЗаменить(Уник,"c","");
   Уник = СтрЗаменить(Уник,"d","");
   Уник = СтрЗаменить(Уник,"e","");
   Уник = СтрЗаменить(Уник,"f","");
   Уник = СтрЗаменить(Уник,Символы.НПП,"");
   Если СтрДлина(Уник) < СтрДлина(Строка(Макс)) Или СтрДлина(Уник) < 5 Тогда
     Уник = ПолучитьСлучайноеЧисло(Мин, Макс);
   КонецЕсли;
   Знаменатель = 10;
   Для н = 2 По (СтрДлина(СтрЗаменить(Уник,Символы.НПП,""))) Цикл
   Знаменатель = Знаменатель * 10;
   КонецЦикла;
   Случ = Число(Уник) / Знаменатель;
   ЧислоИзИнтервала = Мин(Макс(Окр(Мин + (Макс-Мин)*Случ),Мин),Макс);
   Возврат ЧислоИзИнтервала;
КонецФункции

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

Функция получения номера:

Функция ПолучитьСледующийНомерСчетчика(ИмяСчетчика)
   НачатьТранзакцию();
   Попытка
     Если ИмяСчетчика="WMS" Тогда
        НомерНакладнойМакс = 7000000;
     Иначе    
        НомерНакладнойМакс = 0;
     КонецЕсли;
     ШагСчетчика = 1;
     Если ИмяСчетчика = "WMS" Тогда
        НомерСчетчика = ПолучитьСлучайноеЧисло(1, 100);
        ИмяСчетчика = ИмяСчетчика + НомерСчетчика;
        ШагСчетчика = 100;
        НомерНакладнойМакс = НомерНакладнойМакс + НомерСчетчика;
      КонецЕсли;
        Блокировка = Новый БлокировкаДанных;
        ЭлементБлокировки = Блокировка.Добавить();
        ЭлементБлокировки.Область = "РегистрСведений.СчетчикиНомеровНакладных";
        ЭлементБлокировки.УстановитьЗначение("ИмяСчетчика", ИмяСчетчика);
        ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
        Блокировка.Заблокировать();
        Запрос = Новый Запрос;
        Запрос.Текст = "ВЫБРАТЬ
       |  ЕСТЬNULL(МАКСИМУМ(СчетчикиНомеровНакладных.НомерНакладной), 0) КАК НомерНакладнойМакс
       |ИЗ
       |  РегистрСведений.СчетчикиНомеровНакладных КАК СчетчикиНомеровНакладных
       |ГДЕ
       |  СчетчикиНомеровНакладных.ИмяСчетчика = &ИмяСчетчика";
       Запрос.УстановитьПараметр("ИмяСчетчика", ИмяСчетчика);
       РезультатЗапроса = Запрос.Выполнить();
       Если НЕ РезультатЗапроса.Пустой() Тогда
          Выборка = РезультатЗапроса.Выбрать();
          Если Выборка.Следующий() Тогда
             Если Выборка.НомерНакладнойМакс >= НомерНакладнойМакс Тогда
                НомерНакладнойМакс = Выборка.НомерНакладнойМакс + ШагСчетчика;
             КонецЕсли;
          КонецЕсли;
       КонецЕсли;
       МенеджерЗаписи = РегистрыСведений.СчетчикиНомеровНакладных.СоздатьМенеджерЗаписи();
       МенеджерЗаписи.ИмяСчетчика = ИмяСчетчика;
       МенеджерЗаписи.НомерНакладной = НомерНакладнойМакс;
       МенеджерЗаписи.Записать();           
       ЗафиксироватьТранзакцию();
       Возврат НомерНакладнойМакс;
       Исключение
       ЗаписьЖурналаРегистрации("СчетчикиНомеровНакладных", УровеньЖурналаРегистрации.Ошибка,,,"Ошибка номера " + ИмяСчетчика + " " + ОписаниеОшибки());
       ТекстСообщения = НСтр("ru='Невозможно создать номер документа для выгрузки в WMS!'");
       ОбщегоНазначенияКлиентСервер.СообщитьПользователю(ТекстСообщения,,,,);
       ОтменитьТранзакцию();
       ВызватьИсключение;
       КонецПопытки;
КонецФункции

4

См. также

Специальные предложения

Комментарии
Избранное Подписка Сортировка: Древо
1. BorovikSV 1314 01.02.17 08:00 Сейчас в теме
(0)
В диапазоне целых чисел от 1 до 100 возникали до 8 подряд одинаковых номеров


А не пробовали сделать диапазон от 1 до 1 000 000, а затем получить остаток от деления от 100? по природе генераторов случайных чисел это должно вам помочь

СлучайноеЧисло = ГСЧ.СлучайноеЧисло(1, 1000000) % 100; 


7. IgorNastenko 13 01.02.17 10:23 Сейчас в теме
(1)Не помогала, даже более того, что любая функциональная зависимость, основанная на арифметических операциях, при одинаковых входных данных дает одинаковые выходные, поэтому понадобилось что то похитрее.
2. m-rv 706 01.02.17 08:22 Сейчас в теме
Молодец, давно тебе говорил: опубликуй эту идею
8. IgorNastenko 13 01.02.17 10:24 Сейчас в теме
(2)Да случайно у себя на неё наткнулся, подумал, пора)
3. BorovikSV 1314 01.02.17 08:35 Сейчас в теме
(0)
Практические испытания (10 000 полученных значений) показывают то, что штатный генератор распределяет значения куда лучше, чем ваша реализация основанная на UUID.
см. во вложении диаграмму.
Верхняя диаграмма использует:
ГСЧ.СлучайноеЧисло(0,99);

Нижняя диаграмма использует:
ПолучитьСлучайноеЧисло(0,99);

Обратите внимание на нижней диаграмме граничные значения (0 и 99). Тут ваш алгоритм вообще плохо себя ведет. Я уже не говорю о том что использование UUID получается на порядки медленней.
Прикрепленные файлы:
6. IgorNastenko 13 01.02.17 10:12 Сейчас в теме
(3) У меня дополнительные требования были к генератору, кроме нормального распределения, должно быть еще, что бы в один момент времени не было повторяющихся номеров, это выявится если ГСЧ.СлучайноеЧисло(0,99) запустить в несколько потоков.
9. BorovikSV 1314 01.02.17 11:31 Сейчас в теме
(6)
это выявится если ГСЧ.СлучайноеЧисло(0,99) запустить в несколько потоков.

так вы попробуйте при создании генератора инициализировать его случайным образом (идентификатор сеанса например)
ГСЧ = новый ГенераторСлучайныхЧисел(НомерСоединенияИнформационнойБазы());
4. TODD22 17 01.02.17 09:16 Сейчас в теме
В диапазоне целых чисел от 1 до 100 возникали до 8 подряд одинаковых номеров.

Может потому что в одну секунду их генерировал? На сколько помню генератор завязан на системное время.
5. v3rter 01.02.17 09:50 Сейчас в теме
Посмотрите тут https://habrahabr.ru/post/132217/ начиная с текста "Получение псевдослучайных чисел на основе полиномиального счетчика", возможно этот метод тоже подойдет.
10. slawa 25 01.02.17 12:01 Сейчас в теме
Зачем после возврата из рекурсивного вызова (Уник = ПолучитьСлучайноеЧисло(Мин, Макс);) опять приводится к интревалу?
Сразу его вернуть нельзя "Возврат ПолучитьСлучайноеЧисло(Мин, Макс);" ?
11. IgorNastenko 13 01.02.17 12:56 Сейчас в теме
(10)Чтобы избежать случая, где GUID будет состоять из недостаточного количества цифр (составляющие GUID будут больше 9), в данном случае это не произойдет, но если этот генератор нужен будет для использования с длинной числа > 16 то лучше сделать эту проверку.
17. slawa 25 02.02.17 09:15 Сейчас в теме
(11)
Зачем нужен рекурсивный вызов - понятно. (для перегенерации случ.числа при получении гуид с малым количеством цифр)

Не понял зачем полученный из рекурсии результат опять приводится к интервалу. Ведь он и так должен вернуться в нужном диапазоне.
12. bulpi 150 01.02.17 14:33 Сейчас в теме
ЭЭЭЭ.... А на фига все это ? Я тоже общаюсь с wms, и мне хватает префиксов в номерах документов, чтобы получить уникальный номер Ж)
14. IgorNastenko 13 01.02.17 14:57 Сейчас в теме
(12) Согласен что нумератор надо использовать из документа. Такое вот наследство было, и хорошо что вообще не в константе не хранили, а переделывать нельзя было, во первых, ответственная часть, во-вторых, этот элемент использовался в интеграции с другими системами. Если что то не так пойдет то много процессов бы встало, а так если отделаться малой кровью и избавиться от проблемы блокировки, использовалось такое решение.
13. v3rter 01.02.17 14:52 Сейчас в теме
А глобальная статическая переменная не выход? Пожалуй, не выход.
15. v3rter 01.02.17 15:29 Сейчас в теме
Я бы попробовал хранить счетчик(и) во внешней базе MS SQL, используя хранимую функцию, возвращающую и автоинкрементирующую нужный счетчик в момент обращения.
16. IgorNastenko 13 01.02.17 16:46 Сейчас в теме
(15)Проверял разные готовые генераторы, в том числе и виндовые, они не дают нужную последовательность случайных чисел, и скорее всего скуль тоже использует те же генераторы, если оно так, то такая автоинкрементация будет давать в один момент времени один и тот же счетчик, да и с внешней базой тоже вопрос, получается такая мелкая, но очень важная база, про которую скорее всего все забудут, и в какой нибудь момент когда что нибудь накроется или переедет, могут быть проблемы.
18. v3rter 02.02.17 11:14 Сейчас в теме
Интересно, если использовать все цифры гуида распределение ухудшается или нет?

   УникЧисло16=0;
   Для Позиция = 1 По СтрДлина(Уник) Цикл
        УникЧисло16=УникЧисло*16+Найти("123456789abcdef",Сред(Уник, Позиция ,1));
   КонецЦикла;

22. IgorNastenko 13 05.02.17 21:13 Сейчас в теме
(18)Давно конечно эксперементировал, но все эти генераторы давали достаточно хорошее "нормальное" распределение, если не брать создание в 1 момент времени, да и с 16-ричными числами в 1с работать как то не удобно.
19. slawa 25 02.02.17 17:53 Сейчас в теме
Взято тут: http://www.forum.mista.ru/topic.php?id=196865
Не проверял
В конце:

//преобразуем его в случайное число из заданного интервала, округляем до целого 

//ЧислоИзИнтервала = Мин(Макс(Окр(Мин + (Макс-Мин)*Случ),Мин),Макс);
 
// Делаем правильно вот так, т.к. по предыдущей формуле крайние значения выпадают в 2 раза реже.
 
    ЧислоИзИнтервала = Цел(0.5 + (Макс - Мин + 1) * Случ) + Мин - 1;
    Если ЧислоИзИнтервала = Мин - 1 Тогда
         ЧислоИзИнтервала = Макс;
    КонецЕсли;

Показать
20. v3rter 03.02.17 12:01 Сейчас в теме
Чисто академический интерес - Новый УникальныйИдентификатор несёт в себе дату-время как в https://helpf.pro/faq/view/1099.html ?
Как по GUID определить время и дату создания ссылки?
Код 1C v 8.
 Функция ДатаСозданияСсылки(Ссылка)
    ГУИД = Ссылка.УникальныйИдентификатор();
    Строка16 = Сред(ГУИД, 16, 3) + Сред(ГУИД, 10, 4) + Сред(ГУИД, 1, 8);
    Разрядность = СтрДлина(Строка16);
    ЧислоСек = 0;
    Для Позиция = 1 По Разрядность Цикл
        ЧислоСек = ЧислоСек + Найти("123456789abcdef",Сред(Строка16,Позиция,1))*Pow(16,Разрядность - Позиция);
    КонецЦикла;
    ЧислоСек = ЧислоСек / 10000000;
    Возврат Дата(1582, 10, 15, 04, 00, 00) + ЧислоСек;
КонецФункции   
Показать
Отсюда же я взял преобразование из 16-ричной системы, только слегка упростил формулу.
21. v3rter 03.02.17 16:36 Сейчас в теме
(20) Проверил. Нет. Не несет.
23. IgorNastenko 13 05.02.17 21:15 Сейчас в теме
(21)Вроде он должен нести в себе дату время + счетчик. Может только в какой нибудь другой зависимости.
Оставьте свое сообщение