Реализация серверной части в многопользовательских онлайн играх

    За свою недолгую жинь я ни разу не встречал программиста, который бы не любил игры. И уж тем более, программиста, который никогда их не писал.
    Кто-то начинает с тетриса, кто-то со змейки. У кого-то это увлечение проходит, а кто-то этим «заболевает» и превращает свою болезнь в любимую работу или занятное хобби.

    В эру интернета и социальных сетей играть одному неинтересно, хочется общаться и играть вместе с друзьями.
    И не просто общаться, а ходить группой в подземелье или показать кто на арене хозяин.
    В данной статье я бы хотел рассказать о своем подходе к серверной реализации такого взаимодействия.



    Задача


    Рассмотрим пример простого игрового приложения, в котором игрок управляет персонажем, который может покупать\продавать предметы, сражаться на арене, ходить в подземелья один или с друзьями.

    Концепция


    Например, игрок создает группу для похода в подземелье. Игрок посылает запрос нашему серверу, который добавляет новую группу в список доступных групп для походов, после чего остальные игроки имеют возможность получить этот список и присоединиться. Группа заполняется, и, нажатием кнопки, создатель отправляет ее в подземелье на встречу опасным монстрам.

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

    Логично предположить, что правильной реализацией такого взаимодействия был бы серверный демон, который отвечал за состояние игрового мира, принимал запросы от игроков (в порядке очереди), обрабатывал их и отдавал результат.
    В нашем случае сервер должен хранить текущее состояние игроков, чтобы иметь возможность корректно обрабатывать результаты.

    Схематично это выглядит так:

    Базовая схема нашего демона

    Пожалуй, самое интересное — это обработчик запросов.

    Он содержит в себе основную логику нашего сервера:
    — хранение состояния игроков (свободен, находится в подземелье, в группе и т.д.)
    — хранение и обработка информации об игровом мире (битвы на арене, походы в подземелья, группы для похода)

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

    Кроме того, на сервере должна производиться обработка игрового мира.
    Допустим, мы пошли в подземелье на 5 минут. За 5 минут сервер должен произвести обработку битвы с монстрами, посчитать опыт, дроп, а также изменить состояние игрока.

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

    Реализация



    Для реализации такого демона был выбран язык PHP 5.3 с расширением libevent, как наиболее знакомый автору.
    Про libevent существует много статей, создан небезызвестный phpDaemon, поэтому углубляться в его работу не имеет смысла.
    Стоит только отметить возможность создания отложенных событий (флаг EV_TIMEOUT), что в нашем случае решает очень много проблем.

    Однако, наш сервер должен, кроме всего прочего, достаточно активно работать с БД для записи результатов, покупки вещей и т.д.
    Как известно, БД — узкое место любого серьезного приложения, а наш сервер может «лечь» от большого количества запросов.

    Поэтому для обработки «тяжелых» запросов можно предусмотреть дополнительный серверный демон с необходимым нам числом нитей (thread/workers), которые будут с радостью выполнять всю кропотливую работу.

    Дополнительный демон

    Стоит отметить, что запросы к work daemon так же становятся в очередь и не обрабатываются, пока мы не получим ответ.
    Принцип работы work daemon такой же, как и основного демона, за исключением наличия нескольких нитей (thread/workers) для обработки запросов.

    Выводы



    Достоинства такого подхода заключаются в следующем:
    — всю тяжелую работу мы сбрасываем на work daemon, а обработку состояний игроков оставляем на нашем основном демоне, что делает его очень легковесным и быстрым
    — work daemon можно вынести на отдельный сервер, в случаем необходимости, а количество workers варьировать в зависимости от железа
    — возможность масштабирования

    А теперь о недостатках.
    Самый главный недостаток реализации: PHP течет. Даже новый сборщик мусора в 5.3 не решает всех проблем. И мы имеем:

    Состояние памяти на каждом worker

    Решение: периодически перегружать worker'а, когда объем используемой памяти достигает определенного предела.

    Наиболее правильным решением я вижу использование языка программирования Erlang\OTP.
    Поставленная задача отлично укладывается в его концепцию FSM/gen_server, чем автор и планирует заняться в ближайшее время.

    Надеюсь, что статья поможет начинающим разразботчикам игр не наступать на грабли и правильно проектировать свое приложение.

    P.S. Если такого рода тематика интересна жителям Хабра, то готов более детально рассказать о каждом из компонентов такой системы.

    Комментарии 77

      +11
      Тематика очень интересна. Нещадно плюсую.
        +5
        игрофобы отакуют? кому не интересно — пишите комменты, а не подкожно минусуйте!
          –1
          Господа, вам не кажеться, что это чересчур спускать карму с 10-ти до 2-х? Здесь что троллинг, резкое высказывание, негативная реакция, экстремистские воззвания? Не скажу, что я расстроен, но не приятно.
            –1
            Тут есть такой феномен — чем больше вы вслух беспокоитесь за свою карму и минусы в нее, тем больше вы их получите.
        +2
        да, пожалуй будет интересно почитать продолжение
          0
          Мы используем похожую систему, у нас тоже демон на пхп, который висит по несколько месяцев без перезагрузки, и я Вам скажу не течет ни на байт!!! Хотя держит большие нагрузки!
          Пусть автор уделяет больше времени этому вопросу…
          +28
          Тема интересная, статья ниочём.

          Было бы интересно почитать про реализацию work-демона и то, как он общается с обработчиком запросов.
            +1
            Статья достаточно общая — прощупываю почву.

            К сожалению, в рамках одной статьи рассказать подробно обо всех аспектах практически невозможно, поэтому детально о каждом компоненте я расскажу в следующих постах с примерами кода, само собой.
            +4
            Тема очень интересна, всеми руками за цикл статей.
              +4
              тема очень интересная, хотелось бы увидеть статьи с примерами кода, по возможности.
                0
                Интересно!
                  0
                  Интересно мнение автора о использовании в качестве сервера связки php-java, php для магазинов и прочего, что требует сравнительно небольшого числа запросов к серверу, а java для закликивания монстров, беготне по картам и прочего.
                    +2
                    ИМХО
                    если игра браузерная, java имеет смысл использовать для чата и боёвки. Эти «модули» требовательны к синхронизации и держанию в памяти большого числа объектов. Хотя можно обойтись без java совсем.

                    Если игра имеет клиент — самое просто использовать Servlets — не надо писать сетевую часть — только протокол.

                    Кстати, применение key-value хранилищь с кэшированием избавлят от надобности хранить состояние игроков. При этом нужно не забывать, что если вы в своём приложении храните состояние какого либо объекта — вы должны гарантировать, что при падении приложения состояние будет сохранено. В случае игр — это критично, потому что, если потеряется артовая вещь купленная за реальные деньги, получится как то некрасиво.
                      +2
                      >и этом нужно не забывать, что если вы в своём приложении храните состояние какого либо объекта — вы должны гарантировать, что при падении приложения состояние будет сохранено.

                      Тоже думал над этой проблемой. Причём не только приложения падения, а сервера в целом (электричество кончилось). Пока пришёл к логированию всех запросов (протокол HTTP) на раздел/диск с отключенным кэшированием записи. Если сервер падает, то после подъёма блокируем входящие запросы (клиентам выдаём 503), находим последний обработанный (результаты транзакции сохранены в БД) запрос и обрабатываем оставшиеся, после чего включаем фронтенд.
                      +1
                      Про java ничего не могу сказать, опыт работы с ней не позволяет ее сравнить с другими языками.
                      В целом, используемый язык не сильно решает, гораздо важнее опыт и навыки его использования.
                        +2
                        На чистой джаве если писать — то достаточно быстро и разрабатывать, и по ресурсо-затратам мало выйдет.
                        Если же брать application server (Jboss, Glassfish) — то тут потребуется более дорогой сервер (не сильно, но всё же)
                        Оптимально взять apache, jetty — быстро, надёжно, требуется меньше ресурсов чем для AS.
                          +2
                          Есть решение на Java — RedDwarf Server, в прошлом, когда Оракл еще не купил Сан и не закрыла проект, он назывался Project Darkstar. Достаточно интересная штука.
                            +1
                            Ну есть ещё SmartFox и Electroserver. Последние два проекта, над которыми я работал, использовали как раз Electroserver (и Flash для клиента).
                      +6
                      >Самый главный недостаток реализации: PHP течет.
                      unset делается где нить в коде? ;)
                        –1
                        А сборщик мусора для чего?
                          0
                          почитайте как он работает — в документации все есть
                          +1
                          Сборщик мусора не оказался панацеей от всего :(
                          0
                          Конечно интересно, пишите! Хотелось бы конечно побольше про Эрланг.
                            +7
                            >Самый главный недостаток реализации: PHP течет.
                            Мы одно время тоже боролись с утечкой памяти. В результате уяснили следующее. Сам интрпретатор как приложение не течёт (ну по крайней мере тот, что был у нас). Утекает только память выделяемая для работы скрипта. Мы влючили логирование в конструкторах и деструкторах и обнаружили забавную вещь — не вызываются деструкторы некоторых объектов на которые больше нет ссылок. Однако если сделать unset всем полям объекта, то объект разрушается. Потому мы дописали объектам вот такой метод. Перед unset дёргаем его
                            function _destroy() {
                            foreach($this as $propName => $propValue) {
                            unset($this->$propName);
                            }
                            }

                            После добавления такого костыля память перестала течь
                              0
                              В своей первой версии обработчика у меня, как ни странно, PHP тоже не тек, потом черт дернул его переписать и для работы с БД использовать Doctrine 1.2, после чего началось. На текущий момент произвожу рефакторинг и отказ от ORM — посмотрим, что выйдет.

                              Основной демон (который не работает с БД), кстати, не течет, а вот worker's…
                                +2
                                Прежде чем перекапывать кучу кода, да ограничивать себя утилитах/подходах, может стоит взглянуть на что-то более совершенное? К примеру python+django+celery (задеплоенный под gunicorn). В связке с postgresql+redis будет летать.
                                  +1
                                  согласен, по моему php далеко не лучший выбор для таких задач
                              +14
                              Считаю, реализацию демонов на скриптовых языках не верной: зачем тратить ресурсы на интерпретатор, когда можно написать свой демон на C/C++.

                              Для игры по сути нужены: сокеты, конекшен к базе и мьютексы. В пхп — это обёртки и не всегда хорошо реализованные драйвера БД.
                                +6
                                Согласен, только есть пару минусов:
                                — гораздо большие трудозатраты
                                — для этого нужно, как минимум, несколько лет опыта разработки на языке, чтобы гарантировать качественный код, который не будет падать в segmentation fault от любого чиха
                                  +1
                                  Ну… что бы писать программы надо быть программистом =)
                                  Да и для построения демонов на С++, я думаю, если библиотеки
                                    +1
                                    Для серверной части можно использовать Charm++ — надмножество С++. Тут и Load Balancing, и миграция объектов, и Message Passing, и чего только нет. Плюс очень масштабируемое решение.
                                      +2
                                      А вы попробуйте на достаточно высокоуровневом frameworke написать. Тот же Qt даже.
                                      Был опыт переписывания демона с php на Qt. Итоговый вариант содержал даже меньшее количество строк кода.
                                      0
                                      А мьютексы зачем?
                                        +1
                                        Я думаю имелись в виду не конкретно мьютексы, а средства синхронизации в общем.
                                      0
                                      Да, продолжение было бы интересным.

                                      На данный момент из виденных мною архитектур мне больше всего нравится RedDwarf. Но почитать другой взгляд тоже будет интересно и полезно.

                                      Правильно ли я понимаю, что в вашей игре монстры не двигаются и все происходит пошагово? Если нет, то расскажите, плз, в т.ч. и о том, как вы программировали изменения мира, которые не зависят от игрока. Ну или как программировали бы, если бы стали добавлять это в вашу архитектуру.
                                        +3
                                        Нити в PHP? С каких пор? Или вы подразумеваете все же процессы/форки? Если процессы, то как организовано взаимодействие между ними?
                                          +1
                                          Прощу прощения за некоторую путаницу. Форки, конечно, PCNTL.
                                          Взаимодействие непосредственно между воркерами не происходит, в этом нет необходимости.
                                          Подробнее про взаимодействие я расскажу в следующих постах.
                                          +3
                                          расставить unset, заменить все foreach, отслеживать циклические ссылки, логировать память при всех созданиях и уничтожениях объектов (сколько пришло — столько ушло). и бить разработчикам по рукам, если они не делают этого. при соблюдении таких не хитрых техник все течи исчезнут.
                                          проверено — демоны на либевенте не текут (ничего не могу говорить за phpDaemon, мы делали сами свою реализацию).
                                            +1
                                            У меня тоже своя реализация.
                                            Основной обработчик не течет, текут воркеры, которые работают с БД.
                                            Из тестов — течет используемая ORM (Doctrine 1.2). Пытался ее пилить вдоль и поперек, но никак… оказалось проще от нее отказаться.
                                              +1
                                              зачем орм в таких проектах?? если течет доктрина, почему пишите, что течет РНР?
                                              и вообще — есть ли реальная необходимость форкаться? нам производительности однопоточного демона вполне хватало даже на обсчет боев. к тому же форк не позволяет воспользоваться преимуществом использования уже готовых объектов, которые не нужно инициализировать. мы вообще в базу ходили фактически только при логине нового игрока, да когда сохраняли состояния измененных объектов (раз в N минут).
                                                +1
                                                Согласен, ORM не нужен, но это те грабли, на которые я наступил.
                                                Про текучесть PHP — это скорее крик души, так как были проблемы в других проектах.

                                                А какая нагрузка у вас? Какого рода приложение?
                                                Необходимость форкаться есть тогда, когда повышается нагрузка. Конечно, все зависит от ситуации и наличия узких мест.
                                                  0
                                                  у нас была mmorpg. однопоточный демон вполне способен был держать до нескольких тысяч человек в онлайне (демон боев — может чуть меньше), далее — мы закладывали баллансировку нагрузки по идентичным демонам (например, когда бы демон локаций перестал бы справляться — подняли бы второго и часть локаций обслуживал бы уже новый). а необходимость форкаться — это следствие выбранной архитектуры, для реализаций игр, исходя из нашей практики, она не нужна.
                                                    0
                                                    сам по себе php не течет. Тоже как и другие удивился использованию ORM — это же такой удар по памяти. Я бы даже задумался насчет целесообразности использования PDO, а уж про обертки поверх него можно сразу забыть. Кстати, драйверы PDO тоже надо проверять на лики. MSSQL драйвер для PDO течет как х.з. что — решето.

                                                    в качестве DB можно было что-то не реляционное посмотреть, кстати. Mongo, например, быстрая и гибкая штука, которая легко маcштабируется.
                                                0
                                                … выстрелить себе в ногу.
                                                0
                                                Мне выпало однажды испытание написать сервер для маленькой ММОРПГ на однопоточном PHP-демоне. Мало того что я был полным нубом в этом деле, так эта проблема помножилась на уродство PHP. Не делайте этого, пишите на яве, даже если её не знаете. Ну или на каком-нибудь Go, говорят он заточен для этого.
                                                  +7
                                                  Эх… немного опередили меня… )))
                                                  Готовил похожую статью только с реализацией на Scala…
                                                  Теперь вот даже не знаю, стоит ли постить…
                                                    +6
                                                    Конечно, стоит.
                                                    Очень интересно посмотреть на реализации на других языках.
                                                      +3
                                                      Стоит, стоит!
                                                        +1
                                                        Опередили? Реализация? Описание общего концепта, не более.
                                                          0
                                                          Опередили потому, что никак не мог набрать карму для статьи…
                                                          И еще по Скале очень мало статей… может она никому тут нафиг не нужна?
                                                      +2
                                                      Тема интересная, статья тему не реализует. Вот если бы кто действительно написал про написание серверной части для сетевых игр, с расчётом на _действительно_ высокие нагрузки, используя, мхм, более подходящий язык, объясняя какие-либо неочевидные для выходца из другой специфики вещи… Ах, вот это было бы чтиво!
                                                        0
                                                        проблема не в языке, а в других частях тела :)
                                                          +3
                                                          Не имею ничего против PHP, но, пардон, уместность этого инструмента для высоконагруженного сервера? Мне кажется, на борьбу за производительность уйдёт больше усилий, чем на хороший стиль в том же С++. Опять же, Erlang был бы любопытен.

                                                          Дело, опять же, не в языке, а в том, что сервер как таковой — это скучно и тривиально. Интересно было бы почитать про нюансы архитектуры при нагрузках в духе WoW ( сотрудники Blizzard нигде не распространялись, кстати? :) ).
                                                            +1
                                                            а что для вас "_действительно_ высокие нагрузки" в цифрах? :)
                                                              0
                                                              Думаю, начиная со 100 000 online + real-time gameplay ( браузерные пошаговки априори неинтересно )
                                                                +1
                                                                наши демоны на связке php+libevent способны были держать несколько тысяч в онлайне. 100 тысяч — это десяток серверов, совсем не много. доходы от игры, где 100 тыс в онлайне, позволят сделать корпуса таких серверов из золота ;) технически сделать не так сложно, гораздо сложнее этих 100 тысяч привлечь и удержать.
                                                                а поддержка/отладка + развитие кода такой же игры на С++ обойдется гораздо дороже.
                                                                  0
                                                                  Так организация масштабируемого кластера и есть один из пунктов «интересного»: ) Вне зависимости от языка. Так, чтобы с ростом количества нодов, накладные расходы на коммуникации не испортили всё. Особенность MMORPG при этом ещё в жестких требованиях к времени отклика.
                                                                  Поддержка на С++ обойдётся дороже исключительно на разницу в зарплате программистов — это вполне себе его стихия. Впрочем, заметьте, я предлагал именно в контексте Erlang писать, а не учить С++ :) ( Кто сказал D? )
                                                                0
                                                                100К онлайна есть у очень небольшого количества игр. Обычно такого масштаба проекты или на коммерческом движке, или самописном иногда.

                                                                Снова таки, смотря о каких играх говорить. Браузерки это одно, социалки флешевые — другое. Да и от жанра зависит много — эконом-стратегия может легко работать на обычной LAMP связке, 3д мультиплеерный шутер уже совсем другое.
                                                                  0
                                                                  Именно поэтому про это было бы интересно почитать — практический опыт такого рода на дороге не валяется. Про жанр я же сказал — real-time gameplay, необходимость обновления остояния по нескольку раз в секунду. Шутер, рпг, ртс.
                                                                    +1
                                                                    Почитайте блоги CCP про EVE Online — возможно будет интересно, есть русские переводы на фан-форуме. 100к кажется онлайна не было, но 50к+ было (на одном «сервере» — афаик, многие популярные MMOG считающиеся с онлайном в десятки к на самом деле представляют собой несколько невзаимодействующих миров — в Еве не так, все на одном сервере и могут взаимодействовать, находясь на разных концах вселенной).
                                                                  0
                                                                  Ха-ха-ха. Вы бы сначала озадачились вопросом синхронизации в релтайм играх, а потом уже про балансировку нагрузки и архитектуру спрашивали. Поверьте уж, «сервер как таковой — это скучно и тривиально.» — очень далеко от истины в случае релтаймовых игр. Сделайте хотя бы «сервачок» для 16 игроков релтаймовых и потом уже можно будет начинать разговоры в духе — «хех, сервер это тривиально».

                                                                  вы вообще представляете, что такое синхронизации и десинхронизация? А насколько качественно реализована интерполяция?
                                                                    +1
                                                                    я к тому, что сервер сделать — это нифига не «скучно и тривиально.»
                                                                +3
                                                                Многих от опиания подробностей останавливает количество копипастящей школоты…
                                                                По крайней мере так высказывалось несколько моих знакомых:
                                                                «Сегодня ты им со всеми подробностями разложишь про сервер для игры, а завтра количество говноигр вырастет в разы...»

                                                                «Дело, опять же, не в языке, а в том, что сервер как таковой — это скучно и тривиально.»
                                                                Сам сервер (т.е. часть отвечающая за авторизацию, обмен информацией с клиентами, хранение данных игры) действительно скучно и тривиально…
                                                                А вот когда в реалтайме пытаешься заставить работать тысячи клиентов… и что бы без телепортов и успеть обсчитать кучу всего от читеров и т.д. и т.п. вот тут уже одной статьей просто так не опишешь…

                                                                Борьба с читерами это вообще почти как вид спорта… кто кого )))
                                                                  0
                                                                  Многовато копипастить придётся бедной школоте, может в процессе чему и научится :)
                                                                    0
                                                                    боюсь что про реалтайм сервер даже для сотни игроков одной статьей не отделаться. слишком много моментов которые надо предусмотреть. обычно «напиши статью как сделать сервер» говорят те, кто ни разу не пробовал писать. Пошаговые штуки куда проще делать и для этого общими словами в виде одной статьи можно.
                                                              0
                                                              интересно также прочитать про создание TCP сервера для высого нагружённых серверов (eve-online, WOW, ...). Там тоже всё весьма запутано, я так и не нашел нормального примера на просторах инета.
                                                                0
                                                                Можно взять менее загруженный вариант — Ultima Online, есть открытый эмулятор RunUO на C#
                                                                +4
                                                                Мы в нашей игре используем точно такой же подход. Все операции, для которых строго важна очерёдность, выполняем на едином однопоточном сервере, который назвали sync (сервер для синхронных действий). Надо понимать, что, построив такую архитектуру, мы навсегда гарантированно избавились от гонок, но, с другой стороны, ввели в систему узкое место — теперь если какой-то запрос выполняется с задержкой, то эту задержку испытывают ВСЕ игроки, которые в то же время что-то хотят от игры. Субъективное ощущение у игроков — что игра тормозит.

                                                                Теперь о том, как это получилось. После того, как был сделан прототип архитектуры, синтетические тесты показывали отличные результаты — десяток тысяч пользователей на одном шарде — как нечего делать. Но дело в том, что в онлайн-игре синхронизации потребовало огромное количество самых различных действий. Например, один персонаж нападает на другого, а последний успел в последний момент слинять в соседнюю локацию. Система должна обеспечить жёсткую синхронизацию этих действий — либо успел уйти, либо начался бой. И никак иначе. Другая ситуация — игрок A видит, что игрок B ходит голый и решает его побить. В момент нападения A внезапно одевается. И должна быть чёткая последовательность — либо A успел одеться, либо в бою у него низкие характеристики, и никак иначе. Потом ситуация с ограблением — если один персонаж нападает на другого с целью ограбить, а последний прямо перед боем покупал что-то в магазине. Должна быть соблюдена чёткая очерёдность — либо деньги успели списаться со счёта, товары попасть в инвентарь, и ограбление удалось, либо торговля не успела пройти и грабитель остался без добычи. Ну и т.д.
                                                                Количество различных действий, которые требуют синхронизации, росло в таких немыслимых количествах, что наш sync начал захлёбываться под нагрузкой. И в реальности на онлайне 300 сервер начинает адски тормозить. Точнее работает, но на любой затормозившей операции (транзакции по передаче большого количества предметов, например) сервер блокирует игру на несколько секунд.

                                                                Сейчас приходится разбирать этого монстра обратно — отпиливать все тяжёлые операции, выносить их на фронтенды. Фронтенды синхронизируем мьютексами на memcached. И хотя в систему вносится потенциальная возможность гонок (в редких случаях), успешному оперированию игры это не мешает.

                                                                В общем, архитектура жизнеспособная, но очень важно, чтобы синхронные операции были крайне лёгкими, а при расширении мира можно было этот сервер горизонтально отмасштабировать, перебросив на него часть нагрузки. Возможно, для этого придётся мир разделить на условные зоны, между которыми персонажи взаимодействовать не могут.
                                                                  0
                                                                  Хм… а вы не перемудрили с синхронными операциями, что у вас на 300 ccu сервер не тянет?
                                                                  Какова конфигурация сервера и на чем писали?
                                                                    0
                                                                    У нас вся передача предметов, расчёт характеристик персонажей перед боем (а это тоже загрузка инвентаря, характеристик предметов), появление на локации растений, их удаление, перемещение персонажей, включая рассылку остальным клиентам информации о перемещениях — всё это легло на sync. По поводу «не тянет» я неправильно выразился. Тянуть оно тянет и 1000 — и наверное и выше сможет. Проблема в том, что задержка в 2-3 секунды у одного игрока автоматически блокирует всех остальных игроков на 2-3 секунды. А чем больше игроков, тем чаще эти провисания случаются. И это системный недостаток архитектуры. Железо нормальное — Xeon 3.2 ГГц.

                                                                      0
                                                                      Вообще да, перемудрили однозначно. Я поэтому и предостерегаю читателей, чтобы по нашим граблям не пошли. Ничего тяжёлого, никакого доступа к нагруженной БД там быть не должно.
                                                                    0
                                                                    С интересом жду следующей части.
                                                                      0
                                                                      картинки не видны (

                                                                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                                      Самое читаемое