Является ли Rails shared-nothing или может отделить запросы от доступа к тем же переменным среды выполнения?
PHP работает в среде без общего доступа, которая в этом контексте означает, что каждый веб-запрос выполняется в чистой среде. Вы не можете получить доступ к другим данным запроса, кроме как через отдельный уровень сохранения (файловая система, база данных и т.д.).
Как насчет Ruby on Rails? Я просто прочитал сообщение в блоге, в котором говорится, что отдельные запросы могут обращаться к одной и той же переменной класса.
Мне пришло в голову, что это, вероятно, зависит от веб-сервера. Mongrel FAQ утверждает, что Mongrel использует один поток для каждого запроса, предлагая среду с общим доступом. В ЧаВо часто говорится, что RoR не является потокобезопасным, что также предполагает, что RoR не будет существовать в общей среде, если новый запрос не повторно использует объекты в памяти, созданные из предыдущего запроса.
Очевидно, что это имеет серьезные последствия для безопасности. Поэтому у меня есть два вопроса:
- Является ли среда RoR shared-nothing?
- Если RoR запускается (или может работать при некоторых обстоятельствах) совместно используемой среде, какие переменные и другое хранилище данных я должен быть параноидальным?
Обновление: я уточню подробнее. В контейнере сервлетов Java вы можете иметь объекты, которые сохраняются в нескольких запросах. Обычно это делается для кэширования данных, к которым у нескольких пользователей будет доступ, к соединениям с базой данных и т.д. В PHP это невозможно сделать на уровне приложения, это должно быть сделано в отдельном уровне сохранения, таком как Memcached. Таким образом, возникает двоякий вопрос: какой сценарий похож на RoR (PHP или Java) и, если он похож на Java, какие типы данных сохраняются в нескольких запросах?
Ответы
Ответ 1
Короче:
- Нет, Rails никогда не запускается в среде с общим доступом.
- Будьте параноидальными относительно переменных класса и переменных экземпляра класса.
Более длинная версия:
Процессы Rails начинают свой жизненный цикл, загружая фреймворк и приложение. Они, как правило, запускают только один поток, который будет обрабатывать множество запросов в течение его жизни. Поэтому запросы будут отправляться строго последовательно.
Тем не менее, все классы сохраняются в разных запросах. Это означает, что любой объект, на который ссылаются ваши классы и метаклассы (такие как переменные класса и переменные экземпляра класса), будет разделяться между запросами. Это может укусить вас, например, если вы попытаетесь memoize методы (@var ||= expensive_calculation
) в своих методах класса, ожидая, что он будет сохраняться только во время текущего запроса. В действительности расчет будет выполняться только по первому запросу.
На первый взгляд может показаться приятным реализовать кэширование или другое поведение, зависящее от персистентности запросов. Как правило, это не так. Это связано с тем, что большинство стратегий развертывания будут использовать несколько процессов Rails для противодействия их собственной однопоточной природе. Просто не здорово блокировать все запросы, ожидая медленного запроса к базе данных, поэтому простой выход - это вызвать больше процессов. Естественно, эти процессы ничего не разделяют (за исключением некоторой памяти, которую вы не заметите). Это может укусить вас, если вы сохраняете данные в переменных класса или переменных экземпляра класса во время запросов. Затем, как-то, иногда кажется, что материал присутствует, а иногда кажется, что его нет. (На самом деле, конечно, данные могут или не могут присутствовать в каком-то процессе и отсутствуют в других).
Некоторые конфигурации развертывания (особенно JRuby + Glassfish) фактически многопоточны.
Rails является потокобезопасным, поэтому он может справиться с этим. Но ваше приложение не может быть потокобезопасным. Все экземпляры контроллера выбрасываются после каждого запроса, но, как известно, классы разделяются. Это может укусить вас, если вы передаете информацию в переменных класса или в переменных экземпляра класса. Если вы неправильно используете методы синхронизации, вы вполне можете оказаться в азартном состоянии гонки.
В качестве побочного примечания: Rails обычно запускается в однопоточных процессах, потому что реализация Ruby thread всасывает задницу. К счастью, в Ruby 1.9 ситуация немного лучше. И намного лучше в JRuby.
При одновременном использовании обеих этих реализаций Ruby кажется вероятным, что многопотоковые стратегии развертывания Rails также получат популярность и количество. Это хорошая идея написать ваше приложение с многопоточным диспетчером запросов уже.
Ответ 2
Вот относительно простой пример, который иллюстрирует, что может произойти, если вы не будете осторожны при изменении общих объектов.
-
Создайте новый проект Rails: rails test
-
Создайте новый файл lib/misc.rb
и вставьте в него следующее:
class Misc
@xxx = 'Hello'
def Misc.contents()
return @xxx
end
end
- Создайте новый контроллер:
ruby script/generate controller Posts index
-
Измените app/views/posts/index.html.erb
, чтобы содержать этот код:
<%
require 'misc'; y = Misc.contents() ; y << ' (goodbye) '
%>
<pre><%= y %></pre>
(Здесь мы модифицируем неявно разделяемый объект.)
- Добавьте маршруты RESTful в
config/routes.rb
.
- Запустите сервер
ruby script/server
и несколько раз загрузите страницу /posts
. Вы увидите количество строк ( goodbye)
, увеличивающихся на единицу при каждой последующей перезагрузке страницы.
Ответ 3
В вашем среднем развертывании с использованием Passenger у вас, вероятно, есть несколько процессов приложений, которые не имеют ничего общего между ними, кроме классов внутри каждого процесса, которые поддерживают свое (статическое) состояние из запроса для запроса. Однако каждый запрос создает новый экземпляр ваших контроллеров.
Вы можете назвать это кластером различных сред общего состояния.
Чтобы использовать свою аналогию Java, вы можете выполнить кэширование и заставить его работать с запросом на запрос, вы просто не можете предположить, что он будет доступен по каждому запросу.
Ответ 4
Общие - ничто иногда не является хорошей идеей. Но не тогда, когда вам приходится загружать большую инфраструктуру приложения и большую модель домена и большую конфигурацию по каждому запросу.
Для повышения эффективности Rails сохраняет некоторые данные в памяти для совместного использования между всеми запросами на всю жизнь приложения. Большинство этих данных доступны только для чтения, поэтому вам не следует беспокоиться.
Когда вы пишете свое приложение, держитесь подальше от записи на общие объекты (исключая, например, базу данных, которая поставляется с готовым элементом управления concurrency), и все должно быть хорошо.