Розробка багатопотокового сервера СУБД для WindowsNT, Інші СУБД, Бази даних, статті

А.М.Андреев, к.т.н., доц. МГТУ ім. Н. Е. Баумана,
Д.В.Березкін, к.т.н., директор НПЦ “ІНТЕЛТЕК ПЛЮС”,
Ю.А.Кантоністов, провідний розробник НПЦ “ІНТЕЛТЕК ПЛЮС”,
Р.С.Самарев, студент МГТУ ім. Н. Е. Баумана,
контактний тел. (095) – 177 -80 -28

Введення

Системи управління базами даних (СКБД) мають велике значення в нашому житті. До тих пір, поки їх використовували для виконання обмеженого кола завдань при невеликій кількості користувачів, враховуючи також обмежені можливості обчислювальних систем, проблема паралельної обробки запитів не виникала. Сучасна СУБД може використовуватися великою кількістю користувачів, що працюють одночасно. Прикладом такого використання СУБД може служити система “АСУ-Експрес”. Для того, щоб забезпечити одночасну роботу великої кількості користувачів в нормальному режимі (при мінімальних тимчасових затримках) необхідно застосувати або високопродуктивні однопроцесорні обчислювальні системи при послідовній обробці запитів, або менш продуктивні (за швидкістю виконання звичайних операцій) багатопроцесорні системи та паралельну обробку запитів, що є економічно більш вигідно, через простоти збільшення продуктивності за рахунок числа процесорів.

Сучасні операційні системи (ОС) такі як UNIX-системи, Windows NT забезпечують можливість роботи в багатозадачному режимі і використовувати для роботи багатопроцесорні системи.

СУБД ODB-Jupiter є об’єктної СУБД, що дозволяє вести роботу в многопользовательском режимі з декількома базами даних [1]. Забезпечується спільний доступ декількох користувачів до документів однієї бази даних, а також спільний доступ декількох користувачів до документів різних баз даних.

Кожна база даних ODB-Jupiter фізично складається з двох файлів: файлу даних, в якому власне і зберігаються всі дані, що містяться в БД, а також індексного файлу, який забезпечує швидкий пошук необхідних даних.
Вибір операційної системи

Протягом останніх років спостерігається стійке зростання популярності операційної системи Windows NT, особливо це справедливо для Росії. Такий вибір обумовлений як історичними причинами (популярність ОС подібних MS-DOS, поява графічного інтерфейсу Windows тобто Win16), а також простотою реалізації програм різного призначення (є достатньо великий набір засобів розробки та комплектів документації). У даній статті буде розглядатися ОС Windows NT версії 4.0 [2]. ОС Windows 95/98, а також WIN32S для Windows 3.11 не розглядаються, оскільки не можуть використовувати ресурси багатопроцесорних систем. Підсистема WIN32 є досить потужним засобом для виконання широкого кола завдань, а зокрема для паралельної обробки даних. При цьому немає необхідності розподіляти завдання по процесорам, виділяти на них процесорний час – все це функції ОС.

Введення в багатозадачність Windows NT

Для ОС Windows NT має місце наступна класифікація багатозадачності:

При роботі багатозадачних програм потрібно забезпечувати синхронізацію виконання окремих дій, таких як доступ різних завдань (мається на увазі абстрактна самостійно виконувана задача) до загальних даними в певній послідовності. Це може вити почерговий доступ до даних на запис і зчитування, почергова запис в одні і ті ж файли і т.д.

Для ОС Windows95/NT найбільш поширені у використанні наступні об’єкти синхронізації:

При необхідності змінити значення будь-якої змінної, не побоюючись, що за час звернення одного потоку – інші потоки можуть також спробувати змінити значення, немає необхідності використовувати перераховані вище об’єкти синхронізації. WIN32 має відповідні функції, що дозволяють відповідно збільшити, зменшити або присвоїти конкретне значення змінної: InterlockedDecrement, InterlockedIncrement, InterlockedExchange.

Функції, призначені для управління багатозадачністю, як і інші системні, знаходяться у динамічній бібліотеці Kernel32.dll. Список все стандартних функцій вказано в WIN32 API.

Реалізація паралельної обробки запитів

Використання процесів для забезпечення паралельної обробки даних сервером СУБД дозволяє уникнути їх взаємного впливу один на одного в разі виникнення виняткової ситуації (збою) в одному з них. ОС забезпечує ізоляцію адресного простору процесів, тобто при збої пам’яті (запис / читання невиділеної пам’яті) в адресному просторі одного процесу не виявляється впливу на інші процеси. ОС забезпечить коректне завершення збійного процесу. Для ОС процеси є об’єктом, на який витрачається досить багато системних ресурсів. Час створення об’єкта типу процес також велике. Таким чином, побудова паралельної обробки даних на процесах виправдовує себе лише у випадках довгих за часом запитів або необхідності забезпечити високий рівень захищеності програми від збоїв. Структура програми виходить досить складна. Всі дані ізольовані, однак ОС надає можливість мати процесам єдині дані за допомогою механізму проектування файлів в пам’ять. Передача даних від одного процесу до іншого можлива лише з використанням механізмів синхронізації, інакше не буде можливості визначити момент зміни даних. Ідеальний варіант для використання процесів – Випадок, коли єдине, що необхідно – спільний доступ на читання до файлів бази даних (БД). Це можуть бути, наприклад пошукові запити, або запити на читання вмісту БД.

Використання потоків для забезпечення паралельної обробки даних сервером СУБД принципово не дозволяє виключити взаємний вплив при збої потоків один на одного. Однак потоки розташовуються адресному просторі одного процесу. Це дозволяє їм мати загальні дані, що значно спрощує побудову програми. Програма виходить більш економічною за витратами пам’яті. Зростає також швидкість роботи – Створення потоку менш тривала операція, ніж створення процесу.

При зміні загальних даних також як і у випадку з процесами, необхідно застосовувати механізми синхронізації. Але для потоків застосуємо механізм синхронізації посередництвом критичних секцій. Це найбільш швидкий механізм синхронізації, оскільки підтримується на рівні процесора. Для потоків також існує механізм захисту від збоїв. Мова С + + забезпечує механізм структурної обробки виключень (блоки try {:} catch (..) {throw (…);}) [3]. Також існує механізм обробки необроблених винятків Windows. Він дозволяє встановити власний фільтр обробки винятків і коректно обробити помилку. Забезпечується це функціями AbnormalTermination, GetExceptionCode, GetExceptionInformation, RaiseException, SetUnhandledExceptionFilter, UnhandledExceptionFilter.

Волокна, як уже згадувалося вище, є найбільш простими об’єктами. Таким чином, саме за допомогою волокон можна домогтися найвищої продуктивності програми. Волокна за своїми властивостями подібні до потокам. Однак, з причини необхідності ручного адміністрування, значно зростає складність програми.

Програмний продукт ODB-Jupiter for Windows 95/NT розроблявся як багатокористувацька СУБД. Для організації паралельної обробки запитів розглядалися варіанти використання процесів і потоків. Використання волокон не передбачалося, оскільки була потрібна сумісність з ОС Windows 95. Використання процесів раціонально тільки при вимозі стійкості програми і порівняно великому часу виконання запиту (значно перевищує час запуску самого потоку). СУБД ODB-Jupiter організована таким чином, щоб час виконання одного дійсного запиту було мінімальним (це було необхідно при послідовній обробці запитів, щоб при роботі не було помітного часу очікування відповіді клієнтами). Таким чином для СУБД ODB-Jupiter найбільш раціональним виявилося використання потоків.

Класифікація запитів для сервера ODB

Сервер ODB забезпечує обробку двох основних типів запитів: запити на читання даних і запити на модифікацію даних БД.

До запитів на читання даних відносяться пошукові запити, запити на зчитування різних структур БД таких як запити на читання документів або читання структури рубрикатора.

До запитів на модифікацію даних БД відносяться запити на додавання, видалення або зміни документів БД, додавання нових типів документів та інші.

Реалізація паралельної обробки запитів сервером ODB

СУБД ODB-Jupiter є чисто об’єктної СУБД. Програми, що реалізують її внутрішні механізми задовольняють принципам об’єктно-орієнтованого програмування (ООП).

В загальному вигляді організація взаємодії об’єктів така:

Сервер OBD забезпечує паралельну обробку запитів на читання в межах однієї бази даних і повну паралельність роботи запитів для різних баз даних (включаючи модифікацію БД).

Повна паралельність обробки запитів на одній БД не забезпечується через складність алгоритмів одночасної модифікації файлів БД, зокрема індексного файлу. Оскільки основна маса запитів, надходять на сервер – запити на читання даних, то розробка і застосування алгоритмів, що дозволяють вести одночасну модифікацію даних, на даній стадії розвитку проекту визнана недоцільною. Один із способів одночасної модифікації даних спільно з читанням даних – копіювання ділянки, яка підлягає модифікації, його модифікація і подальша заміна старих даних. Цей спосіб застосовний тільки в тому випадку, якщо не надійшло кілька запитів на модифікацію даних у взаімоперекривающіхся ділянках даних. Тоді обробка запитів виливається в послідовну обробку.

Отже, у випадках, якщо монопольне використання БД одним клієнтом протягом декількох секунд не є проблемою (при відносно невеликому числі модифікуючих дані клієнтів), доцільно застосовувати повне блокування БД для інших клієнтів на час модифікації.

Повна паралельність обробки запитів досягається на різних БД. Це забезпечується взаємної незалежністю файлів БД на різних БД. Модифікація файлів однієї БД не впливає на обслуговування запитів в інший БД.

Поділ обслуговування запитів клієнтів проводиться на рівні отримання даних від транспортного протоколу мережі. На цьому рівні дані є “сирими”, тобто це блок даних, що містить крім основних даних службову інформацію. Їх обробка вже може проводитися паралельно.

На етапі первинної обробки даних проводиться визначення імені БД, до якої адресовано запит. Диспетчер БД направляє цей запит об’єкту конкретної БД, де запит піддається остаточній обробці. На цьому рівні виробляється блокування запитів, які не можуть бути виконані одночасно (при блокуванні обробка запитів проводиться послідовно в порядку їх надходження на сервер).

Як уже згадувалося вище, запити модифікації БД блокують початок обробки інших запитів аж до завершення їх обробки. Для цих цілей кожен об’єкт БД має поле критичну секцію. Конструктор об’єкта БД виробляє ініціалізацію критичної секції, деструктор – видалення.

Фрагмент файлу класу БД:

WINSOCKDATA* TSdbDatabase::Dispatch(:)
{
:EnterCriticalSection(&Critical_section); : Switch (iCmd) {/ / вибираємо за кодом операції метод-приймач
{
:case dbCmd_Del:
for(;wRead_operations_Count;);
Del (lpTransfer,i);
LeaveCriticalSection(&Critical_section);
break;
case dbCmd_Read:
wRead_operations_Count++;
LeaveCriticalSection(&Critical_section);
Read (lpTransfer,i);
InterlockedDecrement(
reinterpret_cast<LPLONG>(&wRead_operations_Count));
break;
}

У наведеному фрагменті програми показана реалізація методу класу TSdbDatabase, що виконує розподіл виконуваних дій за кодом операції, що прийшов від клієнта.

Мінлива wRead_operations_Count є полем класу TsdbDatabase. Її призначення – визначення факту виконання операцій читання в даний момент (якщо нуль – нічого не виконується). Початкове значення – Нуль.

Вхід в критичну секцію виконується в безпосередній близькості від виклику методів, що виконують обробку даних. Це зроблено для мінімізації часу перебування потоку у критичній секції при операціях читання, а також для забезпечення блокування в операціях запису.

У наведеному прикладі випадок dbCmd_Del – операція запису. Перша дія перед початком видалення даних – очікування в нескінченному циклі for (; wRead_operations_Count ;); обнулення змінної wRead_operations_Count. Тільки після завершення виконання всіх операцій читання з це БД в інших потоках, проводиться виклик методу, що виконує видалення даних. І лише після його завершення відбувається вихід із критичної секції.

В випадок операції читання (в наведеному прикладі – випадок dbCmd_Read) в межах критичної секції проводиться збільшення значення змінної wRead_operations_Count (користуючись тим, що інші потоки в даній ділянці програми в цей час виконуватися не можуть). Відразу за операцією інкремента слід вихід з критичної секції. І тільки після цього – виклик методу обробки даних.

Після завершення обробки даних необхідно зменшити значення змінної wRead_operations_Count, однак виконуваний код знаходиться вже за межами критичної секції. Таким чином для гарантованого правильного зміни цієї змінної застосовується функція InterlockedDecrement.

Принцип роботи блокування в одній БД пояснює наведена нижче Таблиця. Вона відображає діаграму процесу обробки різних запитів потоками при одночасному з виконання (за вирахуванням часу надходження повідомлення від транспортного протоколу).

1-й потік 2-й потік
Випадок 1: 1-й потік-запит на читання, 2-й запит на читання
Виклик методу Dispatch —————-
Вхід в критичну секцію
(EnterCriticalSection(&Critical_section);)
Виклик методу Dispatch
Визначено тип запиту:
case dbCmd_Read:
Вхід в критичну секцію
(EnterCriticalSection(&Critical_section);)
Вихід з критичної секції
LeaveCriticalSection(&Critical_section);
Визначено тип запиту:
case dbCmd_Read:
Обробка запиту Вихід з критичної секції
LeaveCriticalSection(&Critical_section);
Завершення обробки, видача відповіді користувачеві Обробка запиту
  Завершення обробки, видача відповіді користувачеві
Випадок 2: 1-й потік-запит на запис, 2-й запит на читання
Виклик методу Dispatch ———-
Вхід в критичну секцію
(EnterCriticalSection(&Critical_section);)
Виклик методу Dispatch
Визначено тип запиту:
case dbCmd_Del
Вхід в критичну секцію
(EnterCriticalSection(&Critical_section);)
Обробка запиту Очікування виходу з критичної секції 1-го потоку
Вихід з критичної секції
LeaveCriticalSection(&Critical_section);
Очікування виходу з критичної секції 1-го потоку
Завершення обробки, видача відповіді користувачеві Визначено тип запиту:
case dbCmd_Read:
  Вихід з критичної секції
LeaveCriticalSection(&Critical_section);
  Обробка запиту
  Завершення обробки, видача відповіді користувачеві
Випадок 3: 1-й потік-запит на читання, 2-й запит на запис
Виклик методу Dispatch ————–
Вхід в критичну секцію
(EnterCriticalSection(&Critical_section);)
Виклик методу Dispatch
Визначено тип запиту:
case dbCmd_Read:
Вхід в критичну секцію
(EnterCriticalSection(&Critical_section);)
Вихід з критичної секції
LeaveCriticalSection(&Critical_section);
Визначено тип запиту:
case dbCmd_Del
Обробка запиту Очікування закінчення обробки запиту 1-м потоком
(for(;wRead_operations_Count;);)
Завершення обробки, видача відповіді користувачеві Обробка запиту
  Вихід з критичної секції
LeaveCriticalSection(&Critical_section);
  Завершення обробки, видача відповіді користувачеві
Випадок 4: 1-й потік-запит на запис, 2-й запит на запис
Виклик методу Dispatch ————–
Вхід в критичну секцію
(EnterCriticalSection(&Critical_section);)
Виклик методу Dispatch
Визначено тип запиту:
case dbCmd_Del
Вхід в критичну секцію
(EnterCriticalSection(&Critical_section);)
Обробка запиту Очікування виходу з критичної секції 1-го потоку
Вихід з критичної секції
LeaveCriticalSection(&Critical_section);
Очікування виходу з критичної секції 1-го потоку
Завершення обробки, видача відповіді користувачеві Визначено тип запиту:
case dbCmd_Del
  Обробка запиту
  Вихід з критичної секції
LeaveCriticalSection(&Critical_section);
  Завершення обробки, видача відповіді користувачеві

В організованому таким чином ділянці програми забезпечується мінімальна затримка на операціях читання (до того ж треба враховувати, що одночасне надходження запитів від декількох клієнтів неможливо унаслідок послідовної роботи транспортного протоколу мережі).

Обчислювальна навантаження ЕОМ

При одночасному обслуговуванні декількох клієнтів необхідно пам’ятати про обмежені обчислювальних потужностях системи, тобто при певній кількості одночасно виконуваних запитів відбувається значне зниження продуктивності ЕОМ. Обумовлено це тим, що ОС намагається рівномірно розподілити системні ресурси по виконуваних потокам. Однак, у випадку великої кількості ресурсоємних потоків ОС не може виділити “одночасно” необхідної кількості ресурсів, наприклад, достатнього процесорного часу або фізичної пам’яті.

Для запобігання перевантаження обчислювальних потужностей системи необхідно забезпечувати одночасне виконання обмеженого числа запитів. WIN32 містить механізм, що дозволяє це здійснити – семафори.

Семафор, як уже говорилося вище, допускає в що обмежується секцію потоки до тих пір, поки лічильник не обнулений. Всі інші потоки, які намагаються виконати код, що знаходиться в обмеженій секції, чекають звільнення семафора. Зміна лічильника можливо на будь-яке значення.

Таким чином крім обмеження загальної кількості одночасно виконуваних запитів, можливо поділ запитів на більш і менш ресурсомісткі.

В СУБД ODB-Jupiter проводиться обмеження одночасного виконання запитів на рівні диспетчера баз. В принципі, можна було б виробляти це обмеження на рівні прийому даних від транспортного протоколу, але, оскільки потрібно звільнення семафора після завершення виконання запиту, з програмною точки зору раціональніше робити це в межах однієї функції.

Висновок

При розробці сучасної СУБД необхідно пам’ятати про зростаючих вимогах до швидкості роботи СУБД, а також ефективно використовувати існуючі засоби операційних систем. Розглянутий метод реалізації паралельної обробки, заснований на використанні потоків для забезпечення паралельної обробки даних сервером СУБД, є досить простим і універсальним для реалізації.

Література

  1. Андрєєв А.М., Березкін Д.В., кантоністів Ю.А., Смирнов Ю.М. “Об’єктно-орієнтована база даних ODB-Jupiter” / / Приладобудування “, № 1, 1998 р.
  2. К.Івенс. Windows NT Workstation 4. Внутрішній світ. Діасофт-Київ, 1997 р.
  3. Керівництво програміста по Microsoft Windows 95, Microsoft Press, Російська редакція, 1998 р.

 

 

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*