Снова задачка для программистов
11.10.2010 технологии
Давайте подумаем вместе, при каких условиях и на каком этапе данный алгоритм не отрабатывает как задумано и данные всё равно слетают? Я в тупике, честно говоря. Не понимаю.
Да, у меня снова epic fail, иронизируйте.
Эта статья начинает цикл заметок из четырёх частей об эволюции функций чтения/записи в Daos. В следующем материале я рассказываю о стресс-тестировании. Задача не решена, но решена проблема.
Комментарии
Комментирование этой статьи закрыто.
« Локальное нагрузочное тестирование PHP-скриптов 10.10.10! Мимикрия? Симбиоз? AvisoDaos! »
Хватит мучаться с файлами. Заюзай нормальную СУБД.
Думаешь, ты первый? Я знаю про СУБД и что их можно заюзать. Мне нужно рабочее решение с файлами.
Говорил уже, но придётся и тут повторить, а то замучают вопросами. Почему файлы? Главный фактор — простота установки.
я бы записал в отдельный файл(при условии, что файл не пустой) случайный код (который будет являться кодом блокирования). подождал некоторое время(это нужно чтобы в файл дописали другие процессы апачи, которые тоже в этом месте выполнения. время должно быть раз в десять больше, чем время отработки операции чтения-записи) и посмотрел в файл. Если в файле мой код, то спокойно бы работал с файлом. Он точно заблокирован. После работы в этом файле стираем содержимое. Если файл занят другим случайным числом, то ждём другой очереди. Вместо числа лучше использовать md5(microtime()).
Удачного дня!
Заюзай хотя-бы SQLite. Он есть в PHP из коробки.
IAD, сдаётся мне, что ничего хорошего в таких ожиданиях нет, тормозить будет и нагрузку давать не хилую. Да и не очень понятно, чем твой вариант принципиально отличается от моего.
Именно поэтому я прошу подумать, что именно происходит с моим кодом, на каком участке есть сбой и почему.
А то вариантов куча, все предлагают что-то. Я вот в этом тоже был уверен, а вышло так, что не работает как надо. Нужно добить алгоритм, вернее, добиться понимания, где именно узкое место.
Sam, SQLite будет, но в Daos 2.0, в нынешнем Daos слишком много придется переписывать для этого.
А что тут иронизировать?
Когда Alek$ тебе на узкие места твоего кода указывал (http://brokenbrake.biz/2010/10/08/Daos-1.9.2#c013204) – ты уверенно отметал все «неудобные» замечания – «В любом случае», «вряд ли будет существенным, чтобы сильно заморачиваться и менять рекурсию на цикл», «крайне маловероятно»…
Не надоело каждые 2 недели открывать для себя новые горизонты? ;)
Конкретнее. Внимательное рассмотрение какого именно замечания гарантированно решает проблему?
Те замечания касались не надёжности, а скорее красоты кода, его производительности.
Так и скажи, что заморачиваться не хочется и нужен костыль :)
И вообще, я счастлив, что регулярно открываю новые горизонты :) Век живи — век учись. Мне лично приятно чувствовать пополнение багажа знаний.
? clearstatcache — Clears file status cache
Тормоз, там слетели и стили. Внутренние, конечно, которые пишутся у тебя в аттрибутах.
По ходу, слетела не только статистика, но и настройки. Хотя настройки биллингов на месте.
Имхо лучше все-таки сделать вставку через яваскрипт полноценного <style></style> вместе с самой строкой.
И довольно неудобно реализованы стили в целом – у меня три разных стиля страниц (сам сайт, форум и сервисы). Везде стили разные. Пришлось рыться в движке, что бы очистить аттрибуты, а потом заморачиваться с !important в родных стилях. Но это дело наживное…
Пока пришлось откатить до предыдущей версии =(
Кстати, зачем изобретать велосипед на тему include_flock, если есть нативный flock, который отлично работает?
Уважаемый Надёжный SEO хостинг, спасибо за помощь, но это же не повод спамить. Ссылку удалил, в следующий раз придётся внести в чёрный список.
Почему вы считаете, что clearstatcache спасёт? Очистка кэша играет роль во второй функции, там используется кэшируемая file_exists. Однако, если файл действительно существует, file_exists не вызывается второй раз.
Заморачиваться не хотят те, кто предлагает СУБД. Разобраться, что конкретно и почему происходит — та ещё заморочка.
Зачем? И где ты её взял? :)
Который нихрена не работал.
Тормоз, пополнять багаж знаний можно по-разному.
Можно при появлении проблемы прочитать необходимый минимум для решения текущей проблемы и на неделю-две успокоиться.
А можно при появлении проблемы полностью изучить предметную область, советы «старших товарищей» и потом на подобные проблемы не натыкаться.
Это я к тому, что если придуманное тобой решение (костыль ;) ) заработало как надо у тебя и еще на 1 компе, это вовсе не значит, что это решение – правильное.
Но в одном точно сходимся – получение новых знаний – благо. :)
Да изучал я дофига решений. Сперва был flock, который вроде бы для таких случаев и предназначен. Не работало оно.
Сейчас вот, видишь, атомарные операции переименования вроде бы должны были спасать, это рекомендации от разработчиков различных счётчиков и т. п. Видишь, сбрасывается.
Я хочу понять. И пока не понимаю. Мне нужно чётко знать, на каком месте происходит этот сброс и почему.
Одна голова хорошо, а несколько — лучше. Про то, что в PHP кэшируются результаты многих функций для работы с файловой системой, я, к своему стыду, вообще не знал, например. За это знание спамеру спасибо уже сказал :)
flock можно использовать по-разному. Если одним способом не заработало, мб просто где-то была ошибка.
Да, была ошибка, но я её устранил.
Мне не понятны некоторые моменты:
1. Зачем использовать рекурсию? Как вариант можно использовать цикл for и в случае успешного чтения сделать break.
2. Что у тебя хранится в это файле, php-код? Я, просто, никогда не юзал var_export. Для удобного хранения данных использую serialize/unserialize и file_put/get_contents.
3. В чем собственно проблема? Оказывается пустой файл?
Про рекурсию мне уже говорили в прошлый раз, два раза даже. Я согласен, что не лучший вариант, рекурсия там скорее просто потому что мне хотелось написать первую в жизни рекурсивную функцию :) Теперь узнал, что цикл лучше, но может ли быть причина сбросов в рекурсивной функции? Если считаешь, что да, объясни, как и почему это возможно.
В файле действительно хранится PHP-код. Serialize/unserialize не оправдали возлагаемого на них доверия, много было глюков, в частности если кавычка в данные попадает. То есть нужно делать всякие дополнительные обработки, а зачем, если можно решить махом все проблемы с сериализацией, отказавшись от неё полностью? +Тогда файлы с данными можно просматривать и редактировать.
Проблема в том, что исчезают данные из файла. Он сам не становится пустым, обнуляется массив с данными.
Не пойму что это: global ${$name};
ты эту переменную делаешь глобальной? Смысл не ясен, т.к. ты и так передаешь ее include_flock($name, $count). Может у тебя она существует вне функции и таким образом перезаписывается? Что бы сделал я:
1. заменил рекурсию циклом;
2. вместо кучи костылей использовал бы tmpfile();
Код бы стал в 2 раза меньше. Меньше кода – меньше шансов спрятаться багу :)
Можешь привести пример данных, содержащихся в файле? Т.е. что содержится здесь $data = «<?php \$$type = $data;»?
до сих пор не решена проблема :), попробуй так :
1 способ, основан на cron‘е, делаешь папочку (очередь) туда записываешь файлы, по команде cron‘ом читаешь все файлы в папке (очереди) и заносишь в главный файл все изменения. т.е. получается 1 writer.
2 способ, основан на создании демона. создаешь демона, вешаешь его на определенный порт и когда надо записать файл спрашиваешь у демона, а можно ли записать или ещё кто-то пишет, или кидаешь демону задание он пишет в файл изменения, т.е. получается 1 writer.
вроде всё ;)
в строке
24 rename($file, $tmpFile);
у тебя нет проверки на успешность переименовки отсюда и проблема (другой процесс уже переименовал его)
хотя переимновка не решит твою проблему, как узнать что файл уже переименован?
Polonskiy, в PHP просто не видны глобальные переменные внутри функций, если не поставить перед именем global. Она, естественно, существует вне функции.
Про tmpfile() не знал, спасибо! Пример файла можно скачать.
Но переписывать наугад всё я не хочу. Повторяю, я хочу понять, где именно происходит сброс, почему точно это происходит.
Иначе так можно бесконечно переписывать, меня и так уже матерят, наверно, из-за постоянных обновлений.
Jungle, твои варианты вообще никуда не годятся. CRON, демоны. Может ещё вообще всё на Ассемблере или Си написать и продавать рекламный модуль к PHP? :)
А про 24 строку ты не прав. Как другой процесс мог переименовать файл? Откуда этот другой процесс про $tmpFile узнает?
ой точнее 18 строка, перепутал
ну вам батенька вообще не угодишь, изобретайте велосипед дальше
ну нету атомарной функции в PHP для файла
И каждый вместо решения задачки стремится избежать её решения :) Относись к этому как к логической игре. Что происходит, почему и в каком моменте?
ОК, 18 строка. Допустим даже, что к моменту rename файла нет. И что? Всё равно данные запишутся во временный файл и после временный переименуется в stat.php.
spectator.ru/technol…
Не благодари меня!
А не за что. Я это читал полгода назад. А вот ты не посмотрел на реализацию моих функций. Используется как раз rename.
да, точно, хотя не понятно как ты обновляешь данные?
и потом 24 строка если 2 процесса переименуют друг за другом файлы, то значения одного из них потеряются
значит ошибка где-то в другом месте
Я знаю, что переменные не видны вне функций :) В том-то и дело, что в данном случае нет надобности делать переменную $name глобальной. Смотри: ф-ции include_flock передается имя файла, т.к. ты объявил $name глобально, то при рекурсивном вызове include_flock (строка 63) ты перезапишешь (??? я не уверен) $name во всем скрипте (т.е. и за пределами функции).
Почему ты не закрываешь пхп-код? т.е. $data = «<?php \$$type = $data;»; здесь не должно быть ?> в конце?
Зачем записывать имя массива? Обычно делается так: file_put_content(‘file.php’, ‘<?php return(’. var_export($array, true) .’); ?>’);
Потом получаем так: $array = include(‘file.php’);
Можно юзать serialize, можно XML (тоже удобно редактировать).
Jungle, данные в глобальной переменной, так что они в коде и обновляются. Сброс там крайне-крайне маловероятен, не буду же я делать unset всему массиву. А примерно это и происходит почему-то.
Как это всё устроено, можешь посмотреть в AvisoDaos, там код такой же, просто удалены некоторые куски (в которых, опять же, не может быть такого сбоя).
Polonskiy, можешь пояснить, чем именно перепишется $name при рекурсивной функции? Все остальные замечания несущественные, к проблеме точно не относятся.
Кстати, замыкающая фигурная скобка не обязательна, а в Zend даже запрещён. Проще говоря, она не нужна.
Тормоз: у тебя проблема вынесена в ф-цию (или в две). Ты пишешь данные в связке «ключ+данные», передавая их в ф-цию. Тут же ты утверждаешь, что под sqlite надо много переписывать. Для конкретного этого случая элементарно пофиксить, записывая всё это в sqlite.
Мы обсуждаем здесь конкретную задачу, а не методы её обхода. Мы здесь не лечим головную боль отрубанием головы.
Тем более, что работа с данными внутри Daos далеко не так тривиальна, как тебе кажется, перевести это всё на СУБД далеко не «элементарно». SQLite и MySQL в нынешней версии в любом случае не будет.
Ты сказал что обнуляется массив с данными. В файл записывается пустой массив? Может в ф-цию vw приходит уже пустой масиив?
Да не факт, что туда пустой массив записывается. Возможно, файл просто корёжится, и потому создаётся новый, но данные ему брать неоткуда.
В vw скорей всего пустой и приходит, но не потому что где-то там внутри он обнулился, просто после include_flock правильных данных не было.
Парни, я понимаю ваши подозрения о том, что сброс может быть на других участках кода, а не в этих функциях. И на 100% утверждать не буду, но на 99% я уверен.
Хотя бы потому, что после первого обновления, связанного с этой проблемой, сбросы стали происходить гораздо реже.
значит твой инклуд не гарантирует что все данные прочитаются, т.к. rename может переименовать файл в процессе чтения или перед чтением инклудом
Я так понимаю, что это происходит иногда, но воспроизвести ситуацию ты не можешь. Верно?
Jungle, а это может быть зацепочкой! Буду ещё думать.
Polonsky, конечно, иначе я бы давно уже нашёл причину.
У меня вообще только один раз был сброс за весь год.
надо еще делать блокировку на rename, пока данные читаются
Спасибо. Сейчас попробую. А ещё попробую сделать мощный локальный тест, чтобы он хотя бы за 10 минут вызывал сброс.
Хм, NoSql ?
Хм. Есть у меня одна идея на тему блокировок в PHP. Сегодня постараюсь успеть проверить ее жизнеспособность.
Тормоз, не читал комменты, извини. Но ты серьезно должен, обязан заюзать СУБД. Решение которые ты предоставляешь уже выросло из штанишек, если люди хотят ставить его на высоконагруженные проекты то файлы ну никак не подходят. Разделяемая память, мемкеш, базы данных, ключ-значение хранилище, но не файлы.
Простота установки это иллюзия. Для простоты установки существуют проверки системных требований и системы автоматического развертывания. А для всех остальных будет сервис.
Флоки не всегда работают и не везде, ничего у тебя не получится, хватит тратить время, понял? Проект из штанишек вырос а ты все еще за них держишься, давай уже, снимай штаны блеать, будь мужиком!
Миша, зря ты комменты не читал.
Про СУБД Тормозу уже успели только раз посоветовать, что я бы на его месте слово СУБД объявил матерным =)
@Alek$: я понимаю, но он должен понять что другого решения не будет. не будет. не будет и точка. разве я не прав? или возрастает нагрузка изза сложных алгоритмов, когда каждый чих в свои файлы, или пишется свое подобие хранилища, или используется хранилище. помоему тут уже даже чинка вычинки не стоит, не то что непрактично…
Опять таки, коменты не читал, но можно было бы задействовать еще один файл для лока
Тоесть, грубо говоря, создавать пустой файл и его присутствие уже служило бы признаком лока, если бы в PHP были какие-то более менее атомарные функции для этого, но со всеми этими zend_parse_parameters увы
А использовать system ты врядли захочешь, доступ к нему закрыт, наверное, чаще чем отсутствует БД
Хотя можно было б touch & unlink попробовать, но я бы так не делал
Короче, хватит биться головой о стену, да
думаю это решение твоей проблемы
seodiver.ru/2010/10/…
точно сказать не могу поможет или нет, но попробуй потестить вдруг поможет ;)
не поможет
@Миша проверял?
Jungle, ну вот, у тебя там премодерация зачем-то. В общем, тестить лучше своё решение, и я сейчас не могу выразить, что именно не нравится в твоём.
Миша, давай с аргументами.
Тормоз, давая я напишу модифицированную версию твоих функций чтений и записи, а ты просто заменишь и протестируешь.
Ладно. Только ты скачай AvisoDaos и проверь свои функции по крайней мере на работоспособность.
ок, как напишу сразу, отпишу здесь
вот написал и протестировал seodiver.ru/2010/10/…
а твой код должен так выглядеть pastie.org/1216327 вырезка функций
А я сейчас как раз свой вариант продумываю, перечёркиваю одно решение за другим.
мой метод работает, бери на вооружение ;)
Спасибо. Мой тоже! На время сна запускал тест — «ни единого разрыва!!!» :) Классно. Твой потестирую позже, но почему-то заранее уверен, что у него время выполнения больше будет. Проверим.
опиши свой метод, интересно знать какой он. как сравнишь оба метода дай знать, ну очень интересно каковы результаты :)
У меня теперь создаётся и удаляется блокирующий пустой микрофайлик для каждой операции чтения или записи. Вот так сейчас — http://pastie.org/1217232
Я ещё отдельно в блоге напишу про этот рефакторинг.
так у тебя получается что только 1 читает или пишет, а надо только 1 может писать или N могут читать если нет записи.
Нет, не надо. Тогда статистика просмотров будет неточной.
Наконец довел до ума метод, о котором упоминал выше. Кто-то назовет решение извращением, но ведь так оно и есть ;-)
Интересный способ, я не знал про такую особенность сессий. Впрочем, я про них вообще нифига не знаю, никогда в жизни их не использовал даже. И мне почему-то изначально сессии казались чем-то сильно сложным, непонятным и даже каким-то устаревшим, поэтому я их не изучал.
На самом деле это очень удобная и используемая технология.
Например, простую авторизацию с их помощью делать – милое дело.
Почитаю доки, спасибо.