Шукаєте схему розподіленого блокування


10

Мені потрібно придумати спеціальний рекурсивний механізм блокування об'єктів \ шаблон для розподіленої системи в C #. По суті, у мене є багатовузлова система. Кожен вузол має ексклюзивні дозволи на запис для n -численних фрагментів стану. Цей же стан також доступний у формі лише для читання принаймні на одному іншому вузлі. Деякі записи / оновлення повинні бути атомними для всіх вузлів, тоді як інші оновлення згодом стануть послідовними через фонові процеси реплікації, черги тощо ...

Для атомних оновлень я шукаю шаблон або зразки, які ефективно дозволяють мені позначити об'єкт як заблокований для запису, який я можу потім поширювати, виконувати, відкатати і т. Д. ... Оскільки система має високі рівні одночасності, я Я припускаю, що мені потрібно мати можливість складати блоки, які будуть або очікуватими, або будуть розкручені, як тільки блокування будуть звільнені.

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

Ось неясний зразок того, що я передбачав, хоча я відкритий до будь-яких нових ідей, окрім впровадження цілих нових продуктів

thing.AquireLock(LockLevel.Write);

//Do work

thing.ReleaseLock();

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

public static void AquireLock(this IThing instance, TupleLockLevel lockLevel)
{ 
    //TODO: Add aquisition wait, retry, recursion count, timeout support, etc...  
    //TODO: Disallow read lock requests if the 'thing' is already write locked
    //TODO: Throw exception when aquisition fails
    instance.Lock = lockLevel;
}

public static void ReleaseLock(this IThing instance)
{
    instance.Lock = TupleLockLevel.None;
}

Щоб уточнити пару деталей ...

  • Усі комунікації є TCP / IP, використовуючи протокол двійкового запиту / відповіді
  • Не існує посередницьких технологій, таких як черги або бази даних
  • Немає центрального головного вузла. У цьому випадку механізм блокування визначається ініціатором блокування та партнером, який виконає запит з деякою формою таймауту, щоб керувати його поведінкою.

У когось є якісь пропозиції?


Замки, як правило, є стандартною особливістю у більшості систем. Я думаю, що він є і для C #. (Результат пошуку Google: albahari.com/threading/part2.aspx ) Ви намагаєтесь досягти чогось, що перевищує основні Mutex або семафори?
Діпан Мехта

2
@DipanMehta Вибачте, я мав би вирішити це більш чітко. У вузлах я назвати машини по мережі. Я розумію, що Mutex і Semaphores полягає в тому, що вони є замковими замки ( наприклад, перехресні процеси ), а не замки, які можуть поширюватися між машинами в мережі.
JoeGeeky

@JoeGeeky Ваше запитання є тематичним і може бути занадто теоретичним для переповнення стека . Якщо ви хочете повторно запитати його там, ви можете, але ви хочете більш сфокусоване на коді фразування.
Адам Лір

Відповіді:


4

Дякую за роз’яснення

У цьому випадку я рекомендую використовувати модель публікації / підписки. Chubby Google розповсюджує протокол блокування (реалізація Paxos )

Я ніколи не використовував Paxos (або Chubby), але , як видається , реалізація з відкритим вихідним кодом тут .

Якщо це не працює, ви можете реалізувати власну версію Paxos, використовуючи, наприклад, одного з звичайних підозрюваних щодо бібліотек обміну повідомленнями: бібліотеку черги з нульовим повідомленням , RabbitMQ або ActiveMQ .


Попередній відповідь:

Більшість пропозицій щодо SO ( [A] , [B] ) стосуються використання черги повідомлень, щоб досягти перехресної машини.

Ваш AcquireLockметод підштовхне щось до ідентифікації об’єкта блокування у чергу, перевіряючи наявність попередніх примірників блокування перед успіхом. Ваш ReleaseLockметод видалить об'єкт блокування з черги.

Так користувач atlantis пропонує, в цій публікації , повідомлення Джеффа Кі за деякими деталями.


Дякую, але ці рішення не підходять, оскільки я не маю центрального майстра, бази даних чи черги. Я оновив питання з деякими додатковими подробицями, щоб уточнити деякі з цих деталей.
JoeGeeky

Я не зможу використовувати ці продукти безпосередньо, оскільки вже є чітко визначений протокол, який я повинен використовувати для всіх комунікацій між вузлами, але Chubby і Paxos можуть мати чітко визначені зразки, з яких я можу навчитися. Я погляну.
JoeGeeky

@JoeGeeky Так, посилання Paxos має діаграми послідовності, які можуть дозволити вам реалізувати його за допомогою бажаного зв'язку.
Петро К.

Хоча не пряма відповідь, читання всіх матеріалів Chubby і Paxos допомогло мені визначити власне рішення. Я не використовував ці інструменти, але міг визначити розумний зразок на основі деяких їх концепцій. Дякую.
JoeGeeky

@JoeGeeky: Приємно почути, що це була якась допомога. Дякую за галочку.
Петро К.

4

Мені здається, у вас тут є кілька змішаних технологій:

  • комунікації (на які ви покладаєтесь як на 100% надійні ... що може бути фатально)

  • блокування / взаємне виключення

  • тайм-аути (з якою метою)?

Слово попередження: тайм-аути в розподілених системах можуть бути загрожені небезпекою та труднощами. Якщо вони використовуються, вони повинні бути встановлені та використані дуже обережно, оскільки нерозбірливе використання тайм-аутів не усуває проблеми, воно просто відкладає катастрофу. (Якщо ви хочете побачити, як слід використовувати тайм-аути , прочитайте та зрозумійте документацію протоколу зв’язку HDLC. Це хороший приклад підходящого та розумного використання в поєднанні з розумною бітовою системою кодування, що дозволяє виявити такі речі, як лінія IDLE) .

Деякий час я працював у багатопроцесорних розподілених системах, підключених за допомогою зв’язків зв'язку (не TCP, щось інше). Одне з речей, яке я дізнався, - це те, що як грубе узагальнення, є кілька небезпечних місць для програмування:

  • залежність від черг зазвичай закінчується сльозами (якщо черга заповнюється, ви переживаєте проблеми. БІЛЬШЕ ви можете обчислити розмір черги, який ніколи не заповниться; у такому випадку ви, ймовірно, могли б використовувати рішення без черги)

  • покладатися на блокування болісно, ​​спробуйте подумати, чи є інший спосіб (якщо вам потрібно застосувати блокування, подивіться на літературу, багатопроцесорне розподілене блокування було предметом багатьох асемістичних робіт за останні 2-3 десятиліття)

I ви повинні продовжити використання блокування, то:

Я ЗАБУДУЮ, що ви використовуватимете тайм-аути лише як засіб відновлення в крайньому випадку - тобто для виявлення несправності базової системи зв'язку. Далі я буду припускати, що ваша система зв'язку TCP / IP має високу пропускну здатність і її можна вважати низькою затримкою (в ідеалі - нульовою, але цього ніколи не буває).

Я б сказав, що кожен вузол має список підключень інших вузлів, до яких він може підключитися. (Вузлам було б байдуже, звідки відбувається з'єднання.) Населення таблиць, до яких вузлів може підключитися вузол, залишається як окрема річ для впорядкування, ви не сказали, чи буде це статично встановлено чи іншим чином. Також зручно ігнорувати такі речі, як розподіл номерів портів IP, де з'єднання входили б у вузол - можуть бути вагомі причини для прийому запитів лише на одному порту або на декількох портах. Це потрібно ретельно продумати. Фактори включатимуть неявну чергу, замовлення, використання ресурсів, тип операційної системи та можливості.

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

if (get_lock(remote_node) == timeout) then
  {
    take some failure action - the comms network is down
  }

/* Lock is now acquired - do work here */

if (release_lock(remote_node) == timeout) then
  {
    take some failure action - the comms network is down
  }

виклики get_lock та release_lock повинні бути приблизно (в принципі):

send_to_remote_node(lock_request)
get_from_remote_node_or_timeout(lock_reply, time)
if (result was timeout) then
  return timeout
else
  return ok

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

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


Перечитавши питання, схоже, ви насправді не хочете перейматися комунікаційною стороною речей, ви просто хочете вирішити свою проблему з блокуванням.

Тому моя відповідь може здатися трохи поза темою, однак я вважаю, що ви не зможете вирішити свою проблему з блокуванням, не отримавши деталі під нею правильно. Аналогія: Будівництво будинку на поганих фундаментах змушує його впасти ... Зрештою.


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

Щоб говорити з іншими вашими коментарями, я не використовую черги самі по собі (в сенсі зв'язку асинхронізації), хоча я б очікував, що блокування складені та випущені на основі шаблону FIFO. Я не зовсім узгоджував, як це буде працювати з точки зору необхідного шаблону запиту / відповіді, окрім цього, потрібно буде певним чином блокувати та бути частиною більшого рукостискання. На даний момент я працюю через механізм блокування з накопиченням в межах одного вузла, а потім, як це буде працювати через розподілений сценарій. Я трохи більше прочитаю, як ви запропонували. Дякую
JoeGeeky

@JoeGeeky - FIFO - це черга. Остерігайтеся черг. Подумайте про це дуже уважно. Це звучить дуже схоже, що ви не збираєтеся просто дістати щось "з полиці", але доведеться ретельно продумати свою проблему та рішення.
quick_now

Я розумію ... Я намагався уточнити різницю між чергою FIFO, яка використовується в процесах асинхронізації ( наприклад, один процес запитує, а потім інший видає ). У цьому випадку речами потрібно буде керувати в порядку, але процес, що надходить до черги, не залишиться, поки (a) вони не отримають замок, (b) не буде відмовлено у блокуванні, або (c) вони очікують час та покинуть лінію. Більше, як стояти в черзі в банкоматі. Це поводиться як зразок FIFO у випадку успіху, але процеси можуть вийти з ладу до того, як дійти до передньої лінії. Що стосується позашляхових? Ні, але це не нова проблема
JoeGeeky

0

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

Погляньте на наступний код;

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

// Instance of the object used to lock and unlock cache items in NCache
LockHandle lockHandle = new LockHandle();

// Specify time span of 10 sec for which the item remains locked
// NCache will auto release the lock after 10 seconds.
TimeSpan lockSpan = new TimeSpan(0, 0, 10); 

try
{
    // If item fetch is successful, lockHandle object will be populated
    // The lockHandle object will be used to unlock the cache item
    // acquireLock should be true if you want to acquire to the lock.
    // If item does not exists, account will be null
    BankAccount account = cache.Get(key, lockSpan, 
    ref lockHandle, acquireLock) as BankAccount;
    // Lock acquired otherwise it will throw LockingException exception

    if(account != null && account.IsActive)
    {
        // Withdraw money or Deposit
        account.Balance += withdrawAmount;
        // account.Balance -= depositAmount;

        // Insert the data in the cache and release the lock simultaneously 
        // LockHandle initially used to lock the item must be provided
        // releaseLock should be true to release the lock, otherwise false
        cache.Insert("Key", account, lockHandle, releaseLock); 
        //For your case you should use cache.Unlock("Key", lockHandle);
    }
    else
    {
        // Either does not exist or unable to cast
        // Explicitly release the lock in case of errors
        cache.Unlock("Key", lockHandle);
    } 
}
catch(LockingException lockException)
{
    // Lock couldn't be acquired
    // Wait and try again
}

Взято за посиланням: http://blogs.alachisoft.com/ncache/distributed-locking/

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.