Rails не имеет общего доступа или может отдельным запросам обращаться к одним и тем же переменным времени выполнения?
PHP работает в среде без общего доступа, что в этом контексте означает, что каждый веб-запрос выполняется в чистой среде. Вы не можете получить доступ к данным другого запроса, кроме как через отдельный уровень персистентности (файловая система, база данных и т. Д.).
Что насчет Ruby on Rails? Я только что прочитал сообщение в блоге о том, что отдельные запросы могут обращаться к одной и той же переменной класса.
Мне пришло в голову, что это, вероятно, зависит от веб-сервера. Часто задаваемые вопросы Mongrel гласят, что Mongrel использует один поток на запрос, предлагая среду без общего доступа. Далее в FAQ говорится, что RoR не является потокобезопасным, что также предполагает, что RoR не будет существовать в общей среде, если в новом запросе не используются объекты в памяти, созданные из предыдущего запроса.
Очевидно, что это имеет огромные последствия для безопасности. Итак, у меня есть два вопроса:
- Разделяют ли среду RoR ничего?
- Если RoR работает (или может работать в некоторых обстоятельствах) в совместно используемой среде, о каких переменных и других хранилищах данных я должен быть параноиком?
Обновление: уточню дальше. В контейнере сервлетов Java вы можете иметь объекты, которые сохраняются в нескольких запросах. Обычно это делается для кэширования данных, к которым могут иметь доступ несколько пользователей, соединений с базами данных и т. Д. В PHP это не может быть сделано на уровне приложений, это должно быть сделано на отдельном уровне персистентности, таком как Memcached. Итак, двойной вопрос: какой сценарий похож на RoR (PHP или Java) и, если похож на Java, какие типы данных сохраняются в нескольких запросах?
4 ответа
Короче:
- Нет, Rails никогда не работает в среде без общего доступа.
- Будьте параноиком относительно переменных класса и переменных экземпляра класса.
Более длинная версия:
Процессы Rails начинают свой жизненный цикл, загружая фреймворк и приложение. Как правило, они будут запускать только один поток, который будет обрабатывать много запросов в течение срока его службы. Поэтому запросы будут отправляться строго последовательно.
Тем не менее, все классы сохраняются в разных запросах. Это означает, что любой объект, на который ссылаются ваши классы и метаклассы (например, переменные класса и переменные экземпляра класса), будет совместно использоваться запросами. Это может укусить вас, например, если вы попытаетесь запомнить методы (@var ||= expensive_calculation
) в методах вашего класса, ожидая, что оно будет сохраняться только во время текущего запроса. В действительности, расчет будет выполняться только по первому запросу.
На первый взгляд может показаться приятным реализовать кэширование или другое поведение, которое зависит от постоянства запросов. Как правило, это не так. Это связано с тем, что в большинстве стратегий развертывания используется несколько процессов Rails для противодействия их собственной однопоточной природе. Просто не круто блокировать все запросы во время ожидания медленного запроса к базе данных, поэтому самый простой выход - порождать больше процессов. Естественно, эти процессы не разделяют ничего (кроме некоторой памяти, которую вы не заметите). Это может вас укусить, если вы сохраняете вещи в своих переменных класса или переменных экземпляра класса во время запросов. Затем, так или иначе, иногда материал кажется присутствующим, а иногда, кажется, ушел. (В действительности, конечно, данные могут присутствовать или не присутствовать в одном процессе, а отсутствовать в других).
Некоторые конфигурации развертывания (особенно JRuby + Glassfish) на самом деле являются многопоточными. Rails является потокобезопасным, поэтому он может справиться с этим. Но ваше приложение не может быть потокобезопасным. Все экземпляры контроллера выбрасываются после каждого запроса, но, как мы знаем, классы являются общими. Это может вас укусить, если вы передадите информацию в переменные класса или в переменные экземпляра класса. Если вы не используете методы синхронизации должным образом, вы вполне можете оказаться в аду состояния гонки.
В качестве примечания: Rails обычно запускается в однопоточных процессах, потому что реализация потоков в Ruby отстой. К счастью, в Ruby 1.9 дела обстоят немного лучше. И намного лучше в JRuby.
По мере того, как обе эти реализации Ruby набирают популярность, представляется вероятным, что многопоточные стратегии развертывания Rails также приобретут популярность и количество. Хорошей идеей будет написать приложение с учетом многопоточной диспетчеризации запросов.
Вот сравнительно простой пример, который иллюстрирует, что может произойти, если вы не будете осторожны при изменении общих объектов.
Создайте новый проект 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)
Строки увеличиваются на одну на каждой последующей перезагрузке страницы.
В среднем развертывании с использованием Passenger у вас, вероятно, есть несколько процессов приложения, которые не разделяют между ними ничего, кроме классов в каждом процессе, которые поддерживают свое (статическое) состояние от запроса к запросу. Каждый запрос, тем не менее, создает новый экземпляр ваших контроллеров.
Вы могли бы назвать это кластером отдельных сред общего состояния.
Чтобы использовать аналогию с Java, вы можете выполнять кэширование, и оно будет работать от запроса к запросу, вы просто не можете предположить, что оно будет доступно при каждом запросе.
Разделили - ничего, иногда хорошая идея. Но не тогда, когда вам нужно загружать большую инфраструктуру приложения и модель большого домена и большое количество конфигурации для каждого запроса.
Для эффективности Rails хранит некоторые данные, доступные в памяти, для совместного использования всеми запросами в течение всего срока службы приложения. Большая часть этих данных доступна только для чтения, поэтому не стоит беспокоиться.
Когда вы пишете свое приложение, держитесь подальше от записи в общие объекты (за исключением, например, базы данных, которая поставляется из коробки с хорошим контролем параллелизма), и у вас все будет хорошо.