Текущее состояние.
Имеестся интерфейс обмена 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!'");
ОбщегоНазначенияКлиентСервер.СообщитьПользователю(ТекстСообщения,,,,);
ОтменитьТранзакцию();
ВызватьИсключение;
КонецПопытки;
КонецФункции