sh -c "ETCD_API=3 etcdctl --endpoints=${ETCD_ENDPOINTS} \ set --ttl=10 --swap-with-value alice my-lock alice" Может показаться странным, что Алиса постоянно записывает свое имя в блокировку, но именно так она сможет продлить ее на очередные 10 секунд.
Глава 9. Выбор владельца 167
Если TTL по какой-то причине истечет, возобновления блоки-ровки не произойдет и Алисе придется вновь ее устанавливать. В это время Боб тоже может установить блокировку. Для под-держки владения Бобу также потребуется устанавливать и воз-обновлять блокировку каждые 10 секунд.
Параллельный доступ к данным Даже при использовании описанных ранее механизмов бло-кировки остается возможность того, что в течение небольшого промежутка времени два экземпляра сервиса будут думать, что они оба установили блокировку. Чтобы понять, как это может произойти, представьте, что текущий владелец блокировки ста-новится настолько перегруженным, что перестает работать по нескольку минут подряд. Такое может случиться на машинах, где одновременно исполняется слишком много задач. В этом случае блокировка истечет и ее перехватит другая копия сервиса. Процессор освобождает копию сервиса, которая была заблокиро-вана предыдущим владельцем. Очевидно, вскоре будет вызвана функция handleLockLost() , но в течение небольшого периода времени копия будет считать, что все еще владеет блокировкой. Хотя вероятность такого события невелика, системы необ-ходимо создавать с учетом указанных обстоятельств. Сперва убедимся, что блокировка все еще активна: func (Lock l) isLocked() boolean {
return l.locked && l.lockTime + 0.75 * l.ttl > now() }
Если данная функция выполняется раньше любого кода, требу-ющего блокировок, вероятность одновременного существования двух владельцев значительно снижается, но, что важно, не ис-чезает полностью. Блокировка может истечь между проверкой наличия блокировки и выполнением защищенного кода. Чтобы 168Часть II. Паттерны проектирования обслуживающих систем защититься от таких случаев, система, к которой обращается ко-пия сервиса, должна проверить, что отправивший запрос сервис действительно является владельцем. Для этого в хранилище, кроме состояния блокировки, должно фиксироваться имя хоста копии — владельца блокировки. Так другие участники процесса смогут проверить, что копия, называющая себя владельцем, на самом деле им является.
Схема системы показана на рис. 9.2. Блокировкой владеет shard2 , и, когда рабочему узлу отправляется запрос, тот уточ-няет на сервере блокировок, действительно ли shard2 является текущим владельцем.
Во втором случае shard2 утратил владение блокировкой, но еще не осознал этого и поэтому продолжает отправлять запросы рабочему узлу. На сей раз, получив запрос от shard2 , рабочий узел уточняет его статус на сервере блокировок. Узнав, что он больше не является владельцем, рабочий узел отвергает этот и последующие его запросы.
Еще одна сложность состоит в том, что владение может быть получено, потеряно и затем получено заново. В таком случае за-прос выполнится успешно, хотя должен быть отвергнут. Чтобы понять, как такое возможно, рассмотрим последовательность событий.
1. Шард 1 становится владельцем.
2. Шард 1 отправляет в качестве мастера запрос в момент вре-мени T1 .
3. Сеть зависает, и доставка R1 задерживается.
4. Шард 1 превышает время действия блокировки, и ее пере-хватывает шард 2.
5. Шард 2 становится владельцем и отправляет запрос R2 в мо-мент времени T2 .
Глава 9. Выбор владельца 169
Рис. 9.2. Рабочий узел проверяет, что отправитель запроса действительноявляется текущим владельцем шарда
6. Запрос R2 получен и обработан.
7. Шард 2 отказывает, и владение возвращается к шарду 1.
8. Запрос R1 наконец достигает цели. Шард 1 в данный момент является владельцем, поэтому его запрос принимается к ис-полнению.
Эта последовательность событий кажется коварной, но в ре-альной большой системе такие вещи случаются с пуга ющей частотой. К счастью, ситуация похожа на ранее рассмотренную 170Часть II. Паттерны проектирования обслуживающих систем
проблему, которая была решена с использованием версиониро-вания ресурсов в etcd. Здесь можно поступить так же. Вдобавок фиксации текущего владельца можно с каждым запросом отправлять версию ресурса. R1 из предыдущего примера ста-
Читать дальше