Pull to refresh

Comments 59

В своем jabber боте я реализовал немного по иному, парситься сообщение по типу:
<имя файла> параметры

и постоянно подключаю файл который формирует переменную, ее и отдаю на вверх.

Т.е если когда приходит сообщение:
md5 123

формируется код:

$param = 123;
$result = '';
include("md5.php");
$jabber->send($result);

Заменять фукнции так нельзя, но проблему «на лету» решает.
а если придет код
md5 123; eval($_GET[c])
что будет?
обычно так
$param = '123; eval($_GET[c])';
на самом деле проблем с кешерами не особо есть, по крайней мере пример прекрасно работает с АРС. если файл изменяется, кешер его все равно загрузит снова
На самом деле все-таки есть, ибо по умолчанию APC обращается к файловой системе для проверки изменилось ли время модификации, а на высоко-нагруженных проектах эту проверку тоже убирают ;-)
И всё-таки… Зачем извращаться на PHP? Есть куча языков, нормально поддерживающих горячую замену кода — даже перекомпилирующих код в реальноом времени, чтобы быстрее выполнялся.
можно было реализовать одного демона, который управляет другими: поднимает, загружает, отдаёт задания, тогда не проблема отловив обновления форкнуть новую версию. заодно решится проблема с течкой памяти.
такой демон гораздо сложнее + надо хранить текущее состояние всех объектов и переменных — то есть, изначально проектировать под такое поведение весь код. А как быть с ресурсами? Их не получится переносить между процессами, что в ряде случаев недопустимо.
демон — очень прост — бесконечный цикл в нём проверка новой версии, если она есть — запуск процесса опционально послать сигнал на завершение старому. получается прекрасно масштабируемое приложение. общие ресурсы можно хранить в мемкэше или шаред мемори. можно даже легко запускать сотни воркеров на разных серваках.
Я думаю, Вам будет интересно почитать и подумать над над Inversion of Control и Strategy software patterns.

И вообще:

-- main.php --
interface IWorker { function doWork(); }
class Worker1 extends IWorker { function doWork() { ... } }
$worker = new Worker1();
function main() {
  ...
  if ($newClassAvailable)
    require($newClass);
  ...
  $GLOBALS['worker']->doWork();
  ...
}

newclass.php
class Worker2 extends IWorker { function doWork() { ... } }
$GLOBALS['worker'] = new Worker2();


как-то проще для понимания…

Паттерны это хорошо, но нужно далеко не везде. :) Вместо глобалс подумайте над Registry :) если уже зашли в такие дебри как IoC
Обычно паттерны сами получаются где надо, при соответствующей постановке мозгов…
> Вместо глобалс подумайте над Registry
Ага, уже подумал. В данном примере он излишен.
да и код не претендует на промышленность — просто исследование самой возможности.
runkit умеет переопределять классы, методы, функции. Оно подойдет ;-)
да, однако специфичный модуль и мало распространён. Но там есть возможности переопределения и переименовывания функций — остается только навесить код для извлечения и подмены на лету, однако мне кажется, что с ним будет только сложнее. Да и runkit, как по мне, серьезный хак на уровне самого процессора языка.
Хорошая шутка, посмеялся. Как можно называть хаком расширение работающее напрямую со списком классов и функций, предлагая в то же время какой-то адский хак на скриптовом языке?
что насчет автоматического выгружения старого кода?
да, с этим сложность — похоже только через хак — runkit
Хак это ваша поделка. А runkit делает как надо =)
В общем, я бы использовал Unix-сигналы, а не проверки файла, если такое возможно.
+1, думаю топикстартеру кудато сюда: ru2.php.net/manual/en/ref.pcntl.php
многое конечно зависит от требований и кода который унаследован от предыдущих разработчиков, но всетаки уже есть рабочий механизм, который обычно рекомендуют использовать
По моему, управление потоками здесь не в тему.
как именно сигналы могут помочь в указанной теме?
Вы посылаете UNIX-сигнал, а скрипт, приняв его, решает что делать дальше. Таким образом, нет необходимости все время проверять время модификации файла.
Наверное, лучше всё же заменять объекты, а не функции. Тогда просто делаем singleton factory-класс, который по вызову возвращает объект текущей версии подключаемого класса. Подключаемые версии одного класса должны реализовать один интерфейс, это позволит использовать проверку instanceof и увеличит надежность. Поключаемый класс должен уведомить этот синглтон о своей новой версии.

А с кэшерами, думаю, особой проблемы быть не должно. Можно автоматом при обновлении класса на сайте ложить его в новый файл с именем по версии класса. Всё равно кэшер для этих классов, которые сидят в памяти, преимуществ не даст, а будет работать только для клиентских скриптов.
можно. Однако не всегда стоит городить объекты и интерфейсы. Уведомить он может, в принципе, скорее всего также только таким способом — возвратом некоторой информации в include/require. Как еще варианты — проверка через список определенных классов после инклуда, но требует большей работы и меньшая надежность.
Почему только возвратом?

Например, у нас есть класс MyTester, реализующий интерфейс MyTester_Interface.

В свежезагруженном файле лежит класс:

class MyTester_V99 implements MyTester_Interface {
    public function test() { ... }
}

if (class_exists('TesterRegistry')) {
    TesterRegistry::register('MyTester_V99');
}


Соответственно, клиент (т.е. вызывающий скрипт), когда ему нужен объект MyTester текущей версии, вызывает:
$tester=TesterRegistry::factory(); $tester->test().

TesterRegistry хранит имя текущей версии класса MyTester, по запросу factory() инстанцирует и возвращает его.

Такой подход позволяет с помощью интерфейса MyTester_Interface проверять, что мы всё правильно написали в классе (да, я КО), и что передали в нужный регистр объект нужного класса (в регистре нужна проверка $object instanceof MyTester_Interface. К несчастью, судя по всему, её можно провести только во время инстанцирования, но на этот случай регистр может хранить и имя предыдущей версии).

Кроме того, можно подменять разные варианты tester'а при написании кода или выполнении путем передачи разных классов TesterRegistry клиенту в виде переменной. Был MyTester — стал TheirTester.

А также, имхо, улучшатся переспективы автоматических юнит-тестов.

Я бы ещё добавил, что при сборке/обновлении сайта отдельные версии класса можно ложить в файлы вида MyTester_V99.php, а в главном MyTester.php инклюдить последнюю версию класса. Таким образом, при старте сайта сразу подключатся и зарегистрируются текущие версии. Хотя тут ещё можно подумать насчет отката к предыдущей версии, если подключение класса вызывает ошибку или интерфейс не тот, но эти вопросы, наверное, лучше решать на отладке до выкладки на сайт.
Более того, можно не использовать класс регистратора а-ля глобальную переменную. Вместо этого можно перед инклюдом объявить переменную регистратора как локальную в функции:

function includeTester($registry) {
    include('MyTester_V99.php');
}


Тогда можно в подключаемом классе сделать:

Черт.

В подключаемом классе:

if (isset($registry)) {
    $registry->register('MyTester_V99');
}
Ну и костыли вы предлагаете :/

> а можно ли не останавливая скрипт, подменить функцию, которая выполняется?
Учи паттерны дружок)))
серебренная пуля небось? Какой именно паттерн это реализует? в РНР? С удовольствием почитаю.
Factory Method будет возвращать тебе нужный объект (нужной версии). только у тебя система основана на версиях функций. а желательно на версиях объектов базироваться.
Вот что значит изучать программирование с языка программирования, а не с программирования :)
сколько общих паттернов будет, ну скажем в Erlang и Java?
Ну в теории, при достаточно долгой работе такого демона и интенсивной его доработке «на лету» означенным способом может всплыть негативный эффект от забитого пространства имен функций. Хотя это маловероятно пожалуй)
А не проще ли будет хранить в некоторой переменной/свойстве лямбда-функцию? Тогда демону будет пофиг что вызывать: имя-то не меняется :)
UFO just landed and posted this here
При возникновении ошибок на демоне нужно архитектуру строить так, чтобы ошибки не вызывали фатального падения всей системы и подключенных пользователей.

Упал демон -> поднялся -> продолжил работать с того места на котором остановился.

А PHP это или нет, не так уж и важно. У меня в одной он-лайн игре PHP демоны работают без странностей и неприятностей.

UFO just landed and posted this here
Никогда проблем с памятью не было.
Сейчас проверил процесс, отвечающий за 2 000 ботов — крутится уже с месяц после последнего перезапуска. Памяти полпроцента. Никакой тенденции к разрастанию.

Все завязано на цикл. Все объекты создаются внутри метода, и, мне кажется, garbage collector ПХП 5.2.9 должен прекрасно с этим справляться.
UFO just landed and posted this here
может быть и не пхпшное, но каждый пишет на том что лучше всего знает))

единственная видимая проблема может быть в быстродействии когда объемы возрастут, но это уже флейм))
> Упал демон -> поднялся -> продолжил работать с того места на котором остановился.
Как вы сохраняете состояние задачи/объекта, чтобы продолжить с того же места?
>> if (($this->_current_fn != null) && ($this->_current_timestamp != null))

Понятно, что текущая версия кода и имя текущей функции вряд ли будет пустое значение, либо fakse, либо 0. НО! Все же если сравниваете с null'ом, то сравниваете именно с ним! ===
да, вы правы, упустил этот момент
UFO just landed and posted this here
В задаче, в которой кусок кода может меняться мне было проще постоянно делать require нужного куска кода. При изменениях сразу подхватывается обновленный файл. Минус — затраты на постоянные вызовы require. Можно улучшить, например проверяя время изменения файла с куском кода.
если не морочиться с runkit/classkit или принудительной перекомпиляцией байткода через apc (есть и такая возможность, см apc_compile_file) — наболее простое и быстрое решение задачи — все функции держать простой «картой» замыканий и через сигнал перегружать карту (ну и вариации на тему), а не проверять каждый раз файл.
о каком сигнале идет речь и кто его должен посылать?
например, SIGUSR2. посылать сигнал — очевидно тот, кто обновляет код.
если так — не было бы смысла все это делать. Задача стоит — просто обнови файл — и скрипт подхватывает изменения автоматически. с минимальным изменением самого скрипта. если задаться целью строить уж такую систему, тогда там много чего можно нагородить. а в данном случае все укладывается в пару строк и пару вызовов.
Sign up to leave a comment.

Articles