Вообще говоря, помимо Stateless+Shared Db+(Mem)Cache нужно рассмотреть более сложную архитектуру, где вместо простого кэша, используется полноценная память и/или сервис для синхронизации. Например, можно для синхронизации использовать ДБ. Это актуально, ибо простое кэширование не всегда приемлимо. Но я все же хотел бы рассматривать эти усложнения попозже, совместно с репликацией и обработкой сбоев.
Пусть у нас теперь процессам будет положено иметь состояние - Stateful схема. Будет использоваться shared DB или нет - это отдельный вопрос, но пусть для начала будет shared db. Stateful сервера позволяют сильно увеличить производительность обработки запросов - ведь можно хранить состояние на сервере, т.е. не надо ходить ни в мемкэш на другом хосте, ни в БД - разумеется, периодически надо будет, имеется в виду, что много запросов можно обслуживать, используя локальную память. Правда, тут желательно, чтобы процессы были многопоточными - ведь если на одном хосте будет много однопоточных процессов, то часть состояния будет дублироваться, что может привести к нежелательному расходу памяти.
Однако, в Stateful архитектуре возникает проблема синхронизации состояния в разных процессах. В принципе, в случае веп-процессов, это часто можно избежать. Рассмотрим сперва виды состояния. Можно грубо разделить состояние на три вида: состояние, актуальное для запроса - request-level, для веб-сессии (последовательности запросов) и для приложения в целом. В распределенной системе еще можно выделить состояние в контексте отдельного процесса и распределенного приложения в целом (единое на все процессы). Бывают и более хитрые контексты - например, может быть контекст для длительных транзакций/воркфлоу и ассоциированное с ним состояние. Но не суть важно.
С состояния уровня запросов проблем не вызывает, ибо оно короткоживущее. Поэтому в случае Stateful подхода нам нужно держать в памяти процесса session и application состояние. Разумеется, для восстановления после сбоев часть из него нужно хранить в персистентном хранилище, но это мы обсудим позже. Если у нас всего один Stateful процесс, то проблем с состоянием нет, а если несколько? Тогда информация, нужная для обработки запроса может оказаться на другом сервере. Для session уровня есть стандартное решение - использовать load-balancing с поддержкой sticky sessions, т.е. распределеять нагрузку по веб-серверам с учетом того, где какая сессия расположена. Иными словами, запросы в рамках одной сессии обслуживаются всегда на одном сервере. Таким образом, не нужно заботиться о хождении на другой сервак, репликациях и проч.
Вот с состоянием Application уровня сложнее оно может быть разнесено по нескольким распределенным процессам. В любом случае, нужно будет так или иначе передавать управление на другой сервер (или сервера). Конечно, тут можно предложить два стандартных варианта: хранить Application состояние в Shared DB и другой вариант - выделить отдельный процесс для состояния Application уровня. Т.е. Stateful веб-сервера хранят только состояние своих сессий, а остальное делается в Stateless виде, ну или точнее с использованием Shared сервиса (DB либо App server, либо комбинация и того и другого). Отмечу, что вариант когда мы храним сессии в распределенной разделяемой памяти, я отношу к Stateless подходу (в кэше хранить сесссии очень не рекомендуется, хотя можно кэшировать сессии которые храняться в БД).
На самом деле, конечно, при использовании RPC или передачи сообщений нет особых проблем хранить состояние на любом из распределенных процессов системы. Просто передача управления по сети может существенно снизить производительность приложения. Так что логично продумать процесс обработки запросов, как он будет распределен по слоям/процессам, ну и разместить состояние соответствующим образом. Собственно говоря, пока это был вводный обзор типовых архитектур распределенных веб-приложений. В следующих постах я буду разбирать проблему более систематическим образом. В частности, мы рассмотрим репликацию состояния, координацию процессов, восстановление после сбоев, кэширование, персистентное хранилище, ну и некоторые рекомендации по построению архитектуры системы для эффективной обработки запросов.