"Я не терпел поражений.
Я просто нашел 10 000 способов, которые не работают.
Наш большой недостаток в том, что мы слишком быстро опускаем руки.
Наиболее верный путь к успеху - все время пробовать сделать что-либо еще один раз."
(с) Эдисон
Планы таковы: каждую неделю, с вашей помощью, я буду определять одну эвристику, в разы повышающую эффективность программной системы по какому-нибудь критерию.
В январе 2013-го десяток таких эвристик пересмотрим, выявим взаимосвязи, и попробуем создать достаточно формальную и менее-более целостную модель. Затем на ее основе создадим тестовый проект -- возможно, это будет веб/мобильная система по экстремальному обучению любого программиста тому, что мы сейчас собственно и изучаем. Опенсорсная, с подробным комментированием процесса. Одна из ее фич -- автоматическая генерация обучающих задач.
В июньском Хакере есть замечательное интервью с Олегом Буниным на тему проектирования highload-систем. Предыдущее было на forbes
(
http://www.forbes.ru/tehno-opinion/internet-i-telekommunikatsii/75410-seichas-servera-stoyat-namnogo-deshevle-chem-um-che), но хакерское гораздо профессиональнее написано, рекомендую.
"Любой высоконагруженный проект переписывается много-много раз. "ВКонтакте" не стали сразу писать сайт, ориентированый на 100 млн пользователей. Пока у них не было большого трафика, они делали маленький проектик.
Олег видимо имеет в виду, что не "проектировать на 100 млн не надо", а что "сразу развертывать инфраструктуру на 100 млн не надо". Про проектирование он как раз пишет, как надо закладываться на масштаб с самого начала.
Разработчик highload-систем представляет, как все процессы на сервере происходят в деталях. Куда пришел запрос от браузера, как попал на сетевую карту, как обработался ОС, как попал в nginx, как обработалось в БД...
Когда PHP-скрипт запустится, многое уже можно выполнить и обработать. Понимание таких вещей и есть сдвиг парадигмы в сторону highload...
Про модульность и слоистость: каждый семантический блок оформлен в отдельный модуль, отдельный сервис.
Слоистость -- это то же самое, что модульность, только в другом разрезе. Слой представления, слой бизнес-логики, слой кеширования, слой хранения. Не должно быть такого, что SQL-код у вас разбросан по всему проекту... Слой кеширования не знает, где хранятся данные, слой логики не знает, какие объекты кешируются, это его не касается...
Внешние ключи, JOIN-запросы очень удобны, но они ставят крест на дальнейшем масштабировании. Нормализация -- не более чем красивая концепция, которая не работает в высоконагруженных системах. Highload -- это про многократное дублирование данных, кода и всего остального. Все в угоду скорости...
40 тысяч интеллектуальных запросов в секунду к страницам "ВКонтакте"... У Цукерберга 11 млн подписчиков. Сколько копий каждого сообщения на его стене? Скорее всего, 11 млн, а может и больше. Иначе это не заработает, появится узкое место в системе, которое быстро откажет.
В многозвенной системе возникают оверхеды при передаче информации между звеньями. Но ответ на фейсбуке вам отдает десяток серверов. Картинка отсюда, блок текста оттуда, еще один блок еще откуда-то. Вы вводите оверхеды на разбивание запроса на части и сливание в целое, но без этого не отмасштабироваться.
Инструментами нужно жонглировать. В Яндексе миллионы разных демонов, обработчиков, баз данных. Это большое-большое лоскутное одеяло...
У highload есть только одно гарантированное свойство -- в нем всегда что-то ломается.
Должна быть система, вы нажимаете на кнопочку, и она сама идет на все сто серверов, сама достает свежий код из определенной ветки SVN или trunk'a, сама выкладывает код в нужное место, делает бэкап старого, сама стартует все, что нужно.
Нужно деплоиться каждый час, ну хотя бы раз в сутки! Скрипты, которые все разворачивают, должны быть отточены до идеального уровня.
Когда вы пишете код, вы кладете все в SVN, от начала до конца. То же самое вы должны делать с базой данных (в виде, например, SQL-скриптов, которые полностью воссоздают БД).
Вы должны мониторить все".
*
Кстати, в статье Бунина не рассказывается про лаги, и что делать, если мир у нас реалтаймовый (типа Eve или World of Warcraft), когда все эти миллионы юзеров вдобавок активно взаимодействуют друг с другом в рамках единой физической модели мира.
Вытаскиваем отсюда нашу первую эвристику: многозвенность + слоистость. В качестве идеологической платформы, напомню, мы пока будем придерживаться
модели REpresentational State Transfer, объединенной с концепцией из Smalltalk "всё есть объекты, обменивающиеся сообщениями", а главное, исходим из того, что какие-то целостно сформулированные требования заказчика мы не знаем.
Модульность:
Любая менее-более законченная семантика выделяется в отдельный объект (класс). Никаких глобальных связей между семантиками нету. Иерархия классов поддерживается, но достаточно аскетично -- если можно без нее обойтись, обходимся.
Каждый класс имеет минимум внешних методов, закрыт от внешней среды. Реализует минимальный интерфейс, типа, только "мое текущее состояние такое-то", возвращая на запрос другого объекта мета-описание того, что сейчас он делает (это неизбежные оверхеды, фактически принуждающие к правильному проектированию).
Все объекты работают асинхронно.
Слоистость:
Клиент -- это равноправный участник слоистого представления, архитектурно ничем от других слоев не отличается.
В каждом слое работает набор объектов (разных!), которые реализуют профильную логику (работу базы данных, обработку клиентских запросов, реализацию UI, ...). Слой представлен в виде абстрактного интерфейса (возможно, единого для всех слоев), который получает на входе некий URI с описанием, что с ним надо сделать. Внутри себя слой может разделить ресурс для обработки на множество кусочков, но передать результат слою выше или ниже он может только в виде единого URI, собранного в одно целое.
Балансировка:
В такой системе все же не обойтись без мета-уровня (и вот это мне пока очень не нравится) -- балансировщиков/мониторов, которые следят за общей стабильностью и производительностью (не логикой!), добавляют/удаляют объекты в разных слоях в горячем режиме, что позволяет наращивать масштабируемость, по возможности востанавливают систему после сбоев, чистят мусор итд.
Может показаться, что еще нужен некий центральный монитор "общей логики", но по идее, если спроектировать объекты правильно, то реализация этой логики будет происходить естественно и без внешнего надзирателя (что неверно в отношении технических моментов реализации, для чего нужны балансировщики и "реализаторы" асинхронной работы всех объектов). Но вот проектировать логику придется в духе ФП, иначе сложность не побороть:
"Чистые функции обладают свойством аддитивной отладки. Это свойство состоит в том, что сложность отладки системы чистых функций при добавлении еще одной чистой функции складывается из сложности тестирования системы и сложности тестирования функции. Для систем не чистых функций это утверждение не верно".
И важно -- дабы получить естественность общего функционирования -- чтобы при проектировании не было завязок на конкретные состояния объектов (в чистом ФП переменные отсутствуют). Иначе сразу вылазит императивный стиль, смены состояний через изменение переменных, последовательная обработка данных, и всё очень быстро запутывается:)