Re: stateless/full web service
От: Sinclair Россия https://github.com/evilguest/
Дата: 15.05.08 13:21
Оценка: 88 (8) +1
Здравствуйте, frёёm, Вы писали:

ёё>Прокоментируйте пажалуйста...

Тут, в общем, есть некоторая сложность.
Грубо говоря, термины stateless и stateful все трактуют несколько по-своему.

С одной стороны, протокол HTTP — stateless. Понять, почему это так, можно путем взгляда в RFC какого-либо stateful протокола, например SMTP.
Типичным элементом такого RFC является диаграмма состояний пользовательской сессии.
В HTTP подразумевается, что вся сессия — атомарный это запрос/ответ, и нет никаких переходов между состояниями.

С другой стороны, даже в базовой спецификации HTTP 1/1 заложены элементы session state. К примеру, клиент может первым действием послать Expect: 100-Continue, после чего получит либо Expectation Failed , либо 100 continue и тогда сделает следуюший ход в рамках той же сессии.

С третьей стороны, у опытных философов сразу возникает вопрос: а как же ухитряются делать stateful сервисы при помощи stateless протокола?

Посмотрим на сервис с несколько большей дистанции. Классическое определение: стейтлесс сервер обрабатывает каждый запрос независимо от предыдущего.
Понятно, что полная независимость достижима только для даосистского сервиса, т.е. такого, для выполнения которого достаточно той информации, которую передает клиент.

К примеру, сервис, который выполняет сложение переданных ему чисел, удовлетворяет требованию statelessness. Совершенно неважно, в каком порядке мы передадим ему запросы (2, 2) и (3, 3) — он всегда вернет 4 и 6 соответственно.

Это может показаться совсем бесполезным — ведь такому сервису всё равно где работать, раз у него нет состояния, то с тем же успехом он может стоять на локальной машине и отвечать на запросы. Однако есть непустой класс сервисов, которые могут быть всё еще полезны — к примеру, если они возвращают что-то более сложное, чем простая арифметика.
Например, сервис, который принимает очень длинное целое и возвращает набор его простых делителей, может оказаться вполне востребован.

Прелесть такого сервиса в том, что его работа не имеет побочных эффектов. Это означает, что результаты всегда можно кэшировать, повышая таким образом масштабируемость нашего раскладывателя на множители.

Однако как правило сервисы создаются в сети как раз для того, чтобы управлять некоторым разделяемым состоянием. К примеру, cервер RSDN имеет в качестве состояния наполнение его форумов и статей, и это состояние является непосредственным результатом действий его клиентов.

Мы можем попробовать применять критерий stateless/stateful не к сервису в целом, а к некоторой его части.

Например, если мы отделим ту часть сервиса, которая отдает HTML странички пользователям, от той, которая хранит собственно данные форумных сообщений, то мы сможем сделать эту первую часть stateless. В целом сервис остался stateful (иначе он был бы бесполезен), но одна из его частей стала stateless.

Это позволяет отдельно масштабировать именно эту stateless часть, хотя очевидно, что ей придется обращаться к stateful, которая рано или поздно станет узким местом.

В примере про веб магазин всё так и происходит — пока корзина между запросами сохраняется в памяти web application, оно остается stateful со всеми трудностями с масштабированием. Как только мы вынесли ее во внешний session storage — оп! приложение стало stateless, хотя с точки зрения пользователя ничего не поменялось.

Это может быть и не преимуществом, а недостатком. Состояние один хрен существует, его необходимо использовать при обработке запросов, но в stateful веб приложении оно лежало "прямо здесь", на расстоянии одного обращения к hash map, а теперь будь любезен сбегать на диск и обратно. Зато, конечно, появляется некоторая надежность: если питание сервера вдруг пропадет, содержимое stateful корзины рассеется словно дым, а stateless корзинка сбегает после включения в СУБД и всё вернет в лучшем виде.

В общем, такая statelessness сама по себе не даст особых преимуществ.

Для того, чтобы понять, как добиться успеха в распределенной среде, нужно отказаться от идеи трактовать сервис как черный ящик.
Поклонники ООП очень любят инкапсуляцию. Вот есть у нас сервис. Это объект, который отвечает на запросы в соответствии со своим состоянием. Остальное типа не ваше, клиентское, дело.
В итоге клиент, который однажды уже получал от сервиса результат 2+2, при повторном вопросе вынужден снова бежать к черному ящику за ответом — ведь состояние сервиса могло измениться после прошлого запроса!

Сервис, который не любит делать лишнюю работу, может приоткрыть завесу тайны над тем, что происходит при обработке запросов.
Запросы к сервису делятся на четыре уровня по отношению к тому, как они взаимодействуют с состоянием сервиса:
1. Запросы, которым для получения ответа состояние вообще не нужно. Вот как с 2+2. Таких, как ни странно, бывает очень много. К примеру, запрос, который формирует PNG картинку с заданным текстом заданным шрифтом заданным цветом.
2. Запросы, которым нужно состояние, но сами по себе они его не меняют (safe requests). Таких еще больше: запрос текущей температуры в Новосибирске, к примеру, на погоду никак не влияет
3. Запросы, которые влияют на состояние, но повторное выполнение имеет тот же эффект, что и однократное (idempotent). К примеру "set a = 5" — это как раз такой запрос. Сколько бы раз мы его не выполняли, наш a будет равен пяти. Их преимущество в том, что если мы не уверены, что запрос дошел, мы можем смело повторять его до тех пор, пока не добъемся однозначного ответа.
4. Все остальные запросы.

Если клиент знает о том, какие запросы как себя ведут, он может построить эффективную стратегию выполнения.


Следующий шаг к успеху — это отказ от "атомарнсти" состояния. Конечно, еще Лейбниц понял, что можно состоянию чего угодно сопоставить одно-единственное число, пусть оно и очень длинное. Вот, допустим, аккаунт пользователя. В нем собрана фотография, имя, фамилия, дата/время последнего входа. Можно сопоставить такому аккаунту число, которое будет изменяться всякий раз, как пользователь входит на сайт.
Но это означает, что все запросы, касающиеся аккаунта, придется повторно выполнять всякий раз.
Если же мы распилим аккаунт на части, то окажется, что некоторые из них изменяются часто, а некоторые — редко.
Это означает, что мы можем придумать эффективную стратегию кэширования состояния, где для обновления копии будет достаточно передавать только относительно небольшую разницу.

Явное описание в протоколе взаимодействия клиент-сервер той части аккаунта, к которой обращен запрос, позволит вообще избежать затрат на синхронизацию состояний в удобных случаях.


Именно на этих идеях построен REST — REpresentational State Transfer. REST — это такое ООП наоборот. В ООП главное — поведение, а состояние вводится только в инкапсулированном виде для того, чтобы обеспечить достаточно сложное поведение.
В REST главное — состояние. Поведение с точки зрения REST классифицируется только с точки зрения того, как оно влияет на это состояние.

Конечно, REST не пытается вообще игнорировать поведение. Даже разбиение состояния системы на составляющие нужно делать с учетом поведения — так, чтобы границы между составляющими максимально соответствовали областям действия нужных нам глаголов.

Например, список статей wikipedia, отсортированный по дате последнего изменения — крайне быстроменяющийся ресурс.
А вот каждая отдельная её статья, как правило, достаточно статична для того, чтобы кэширование стало эффективным.

Другой пример — включать timestamp в тяжеловесную html-страницу. Это примешивает быстроменяющееся состояние к медленно меняющемуся; если уж очень хочется показать часики — вставьте их в iframe; тогда на F5 будет приезжать только минимально необходимая разметка.

Резюме: выбор разработчика вовсе не между stateful и stateless. Выбор — в том, как распределить состояние системы между узлами распределенной гетерогенной сети таким образом, чтобы минимизировать response time и максимизировать throughput.
... << RSDN@Home 1.2.0 alpha rev. 677>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.