14.2. Распределенные глобальные состояния
Глобальные состояния и распределенные снимки
Все связанные с параллельными вычислениями проблемы, такие, как взаимоисключения, взаимоблокировки и голодание, встречающиеся в сильносвязанных системах, имеются также в распределенных системах. Стратегии разработки в этом случае осложняются тем, что для таких систем нельзя определить глобальное состояние. Это означает, что в распределенной системе операционная система или процесс не могут получить информацию о текущем состоянии всех процессов системы. Процесс может знать лишь текущее состояние всех процессов локальной системы, осуществив доступ к управляющим блокам процессов, которые находятся в памяти. Что касается удаленных процессов, то информацию об их состоянии процесс может получить только из пришедших сообщений. Следует отметить, что эта информация получена некоторое время назад и может оказаться устаревшей в момент получения. Аналогичная ситуация — в астрономии: знания об удаленной звезде или галактике получены на основании светового и прочего электромагнитного излучения удаленного объекта, и это излучение дает представление о состоянии объекта в прошлом. Например, знания об объекте, находящемся на расстоянии пяти тысяч световых лет, успевают устареть на пять тысяч лет.
Подобная задержка во времени, зависящая от природы распределенных систем, усложняет все проблемы, связанные с параллелизмом. Чтобы пояснить это, приведем пример, взятый из [ANDR90]. Для иллюстрации проблемы воспользуемся графиками процессов и событий (рис. 14.3 и 14.4). Горизонтальными линиями на этих графиках представлены временные оси. Точка на оси соответствует событию (например, событию во внутреннем процессе, отправке или приему сообщения). Обведенный вокруг точки квадрат представляет состояние локального процесса в фиксированный момент времени, соответствующий этой точке. Стрелкой обозначается передача сообщения от одного процесса другому.
В нашем примере клиент банка имеет счет в двух его филиалах. Чтобы определить полную сумму, находящуюся на счету этого клиента, банк должен иметь сведения о количестве его денег в каждом из филиалов. Предположим, что запрос сделан ровно в 3:00. В случае, показанном на рис, 14.3,а, общий счет окажется равным $100.00. Однако возможна также ситуация, представленная на рис. 14.3,6, в которой во время запроса происходит перевод денег из филиала А в филиал В. В результате будет получен неправильный результат, равный $0.00. Эту проблему можно решить, проверяя во время запроса все сообщения о переводе денег. Нужно, чтобы в филиале А хранились записи обо всех переводах из этого филиала, а также о том, кому эти переводы предназначены. Поэтому в "состояние" филиала А нужно включить как учет текущих счетов клиентов, так и записи о денежных переводах. Проверяя эти два пункта, служащий банка обнаружит денежный перевод, покинувший филиал А и предназначенный для филиала В, Так как эта сумма еще не пришла в филиал В, она будет добавлена в общий счет. Любая сумма, которая была переведена и уже получена, учитывается только один раз; она будет входить в текущий счет, содержащийся в филиале-получателе.
Такая стратегия не является полностью надежной. В примере, приведенном на рис. 14.3,в, часы в разных филиалах идут с некоторым относительным сдвигом. Проверка состояния счета клиента в филиале А в момент времени 3:00 дает результат, равный $100.00. Однако впоследствии эта сумма переводится в филиал В. В этот момент времени часы в филиале А показывают время 3:01, но в филиале В в этот же момент на часах — 2:59. Поэтому в запросе, производимом в 3:00, переводимая сумма учитывается дважды.
Чтобы понять, с трудностью какого рода мы столкнулись, и сформулировать решение, определим некоторые понятия,
- Канал. Если два процесса обмениваются сообщениями, между ними устанавливается канал. Канал можно представить как путь или способ, с помощью которого происходит передача сообщений. Для удобства каналы считаются однонаправленными. Таким образом, для обмена сообщениями между двумя процессами необходимы два канала, каждый из которых служит для передачи сообщений в одном направлении.
- Состояние. Состояние процесса — это последовательность сообщений, отправленных и принятых по всем его каналам.
- Снимок. Снимок фиксирует состояние процесса. В каждый снимок входят записи обо всех сообщениях, отправленных и полученных, начиная с того момента, когда был сделан предыдущий снимок.
- Глобальное состояние. Объединенное состояние всех процессов.
- Распределенный снимок. Множество снимков каждого процесса.
Проблема состоит в том, что из-за задержки, необходимой для передачи сообщений, невозможно определить истинное глобальное состояние распределенной системы. Можно попытаться определить глобальное состояние, объединив в одно множество снимки всех процессов. Например, в глобальном состоянии, соответствующем рис. 14.4,а, на момент выполнения снимков будет обнаружено, что одно сообщение передается по каналу <А,В>, одно — по каналу <А,С> и одно — по каналу <С,А>. Сообщения 2 и 4 будут представлены правильно, а сообщение 3 — нет. Распределенный снимок покажет, что оно уже получено, но еще не отправлено.
Хотелось бы, чтобы распределенный снимок отображал глобальное состояние согласованно. Глобальное состояние будет согласованным, если для каждого состояния процесса, в котором есть запись о получении сообщения, имеется соответствующая запись в состоянии процесса-отправителя. На рис. 14.4,6' приведен пример такого состояния. Несогласованное глобальное состояние возникает тогда, когда в процессе имеется запись о получении какого-то сообщения, а соответствующей записи в процессе, отправившем это сообщение, нет (рис. 14.4,а).
Алгоритм распределенного снимка
В [CHAN85] описан алгоритм распределенного снимка, который позволяет получить согласованное глобальное состояние. В этом алгоритме предполагается, что сообщения доставляются в том порядке, в котором они были отправлены, и что они не теряются. Надежный протокол передачи сообщений (например, TCP) удовлетворяет этим требованиям. В алгоритме используются специальные управляющие сообщения, которые называются маркерами (marker).
Инициатором начала работы алгоритма выступает какой-то процесс, записывающий свое состояние и отправляющий маркер по всем своим исходящим каналам. В промежутке между моментом записи состояния и моментом отправки маркера никакие другие сообщения не отправляются. После этого каждый процесс р, первый раз получивший маркер (скажем, от процесса q), выполняет такие действия.
1. Записывает свое состояние Sp.
2. Делает запись о том, что входной канал из q в р является пустым.
3. Рассылает маркер всем соседним процессам, пользуясь для этого своими выходными каналами.
Эти действия должны выполняться атомарно — т.е. до выполнения шага 3 нельзя отправлять или принимать никакие сообщения.
Процесс р, который уже записал свое состояние, получив по входному каналу маркер от какого-то другого процесса (скажем, от процесса г), выполняет следующее.
1. Записывает состояние канала из г в р как последовательность сообщений, полученных от процесса г за интервал времени от момента записи своего локального состояния Sp до момента получения маркера от процесса г.
Выполнение алгоритма процессом прекращается тогда, когда этот процесс получит маркеры по всем своим входным каналам.
В [ANDR90] по поводу этого алгоритма сделаны такие замечания.
1. Любой процесс может начать выполнение алгоритма, отправив маркер. Фактически решение о записи глобального состояния могут независимо принять несколько узлов, и это никак не повлияет на правильность выполнения алгоритма.
2. Если все сообщения (включая маркеры) передаются в течение конечного времени, время выполнения алгоритма тоже будет конечным.
3. Алгоритм является распределенным: каждый процесс сам отвечает за запись своего собственного состояния и состояния всех своих входных каналов.
4. После того как будут записаны все состояния (после завершения работы алгоритма во всех процессах), из них можно будет собрать глобальное состояние. Для этого нужно, чтобы каждый процесс отправил данные о своем состоянии по всем выходным каналам, а каждый процесс, получивший данные о состоянии других процессов, переслал их по всем своим выходным каналам. Можно предложить и другой метод сборки, в котором процесс-инициатор запрашивает в установленном порядке состояния всех других процессов, составляя из них глобальное состояние.
5. Алгоритм не влияет на другие распределенные алгоритмы, в которых принимают участие вовлеченные в него процессы, и не подвержен влиянию этих алгоритмов.
В качестве примера применения описанного выше алгоритма (пример взят из [BEN90]) рассмотрим множество процессов, изображенных на рис. 14.5. Каждый процесс представлен кружком, а каждый однонаправленный канал — соединяющей кружки линией со стрелкой, которая указывает направление. Предположим, что алгоритм снимка начинает работу; пусть при этом по каждому из выходных каналов каждого процесса пересылается по девять сообщений. Решение о записи глобального состояния принимается независимо процессами 1 и 4. Процесс 1 к этому времени отправил шесть сообщений, а процесс 4 — три сообщения. По завершении работы алгоритма собираются снимки всех процессов, в результате чего получается глобальное состояние, показанное на рис. 14.6. Перед тем как записать свое состояние, процесс 2 отправил по каждому из своих выходных каналов по четыре сообщения процессам 3 и 4. До записи своего состояния этот процесс успел получить от процесса 1 четыре сообщения, а сообщения 5 и 6 попали в запись о состоянии каналов. Читатель может убедиться в согласованности моментального снимка: для каждого отправленного сообщения имеется либо запись о его получении, либо запись о том, что оно передается по каналу.
Приведенный алгоритм получения распределенного снимка является мощным и гибким инструментом. Его можно использовать для адаптации любого централизованного алгоритма к условиям распределенной среды, так как в основе любого централизованного алгоритма лежат знания о глобальном состоянии. В качестве частных примеров можно привести алгоритмы выявления взаимоблокировок и обнаружения прекращения процессов (см., например, [BEN90, LYNC96]). Этот алгоритм также можно использовать для работы контрольных точек распределенного алгоритма, с тем, чтобы в случае ошибки можно было восстановить исходное состояние системы.
