Наверх ▲

Pconnect: граната в руках обезьяны

Сергей Аверин Сергей Аверин Руковожу разработкой проектов в Баду. Программирую server-side. Конференционный маньяк. Среди проектов, которые я делал — Хабрахабр, dirty.ru, trendclub.ru. Специализируюсь на больших/сложных веб-проектах.

twitter: twitter.com/ryba_xek
facebook: facebook.com/ryba.xek

Сергей Аверин: Здравствуйте, меня зовут Сергей Аверин, я работаю в компании Badoo. Сегодня я расскажу о постоянных соединениях (англ. persistent connect) и о том, как мы с ними справляемся.

Badoo - это "смесь" социальной сети, сети знакомств и сервиса для общего доступа к фотографиям. У нас достаточно крупный сайт, с 2007-го года он входит в Top-200 Alexa. У нас примерно 127 миллионов пользователей. Пользователи сайта в день загружают порядка 2 миллионов фотографий. Показатель DAU у нас в районе десяти миллионов пользователей в день.

С точки зрения программиста это 30 тысяч запросов в секунду к серверам back-end. Ясно, что на 1 запрос может приходиться еще несколько подзапросов. Мы используем много ПО с открытыми исходниками – типа MySQL, PHP, PHP-FPM. Также у нас много C/C++ демонов.

Что это такое постоянное соединение?

Еще иногда называют поддерживаемым (англ. keep-alive) соединением или подключением повторного использования (англ. connection reuse). По сути, это одно и то же, хотя есть небольшая разница. Это не технология – это методика использования соединения для отправки нескольких команд и получения нескольких ответов в одном соединении. На самом деле, изначально все так и работали. Просто в мире веба появилось «кликовое сознание» с идеей о том, что должен использоваться один запрос и одно подключение, которое мы потом закрываем, чтобы открыть новое.

Постараюсь объяснить на примере PHP-FPM. У вас есть несколько процессов-обработчиков. В каждом процессе – один поток. Этот поток будет "хозяином", держателем ваших постоянных соединений, он будет передавать их между запросами. 

В один конкретный момент времени в одном обработчике будет обрабатываться только один веб-запрос. Соединение, которое вы открыли, передастся следующему запросу только после того, как текущий полностью закончится (это очень важно).

Какие плюсы нам дает эта методика?

• Экономим ресурсы сервера, потому что подключение – это затратная операция, для нее нужно переслать 3 пакета. 

• Экономим время, потому что нам нужно переслать 3 пакета, дождаться подтверждения (сеть может быть медленной), а так мы считаем, что соединение у нас всегда открыто.

• Конечно, меньше нагрузка на сеть, на ядро, на память и на сетевую подсистему.

Во всех руководствах, которые я прочитал, когда стал близко с этим работать, была такая идея (особенно в PHP хорошо написано). Постоянные соединения были придуманы как замена обычным подключениям. В руководствах советуют просто поменять функции типа mysql_connect, например, на mysql_pconnect – после этого якобы все начнет "летать", и ваше приложение ускорится в 3 раза. На самом деле, все далеко не так. Я утверждаю, что это маркетинг и не более того.

Во-первых, на опыте моего проекта выяснилось, что чаще всего это не самодостаточная технология, не Plug’n’Play. На постоянное соединение должен быть рассчитан протокол, по которому вы общаетесь, ваша клиентская библиотека, клиентский код и серверное ПО зачастую тоже.

Во-вторых, это далеко не «серебряная пуля». Увеличивается если не количество ошибок, то вероятность ошибок и "падений".

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

Теперь будет немного теории.

Какие проблемы являются самым распространенными? 

Первый блок проблем

Первое. Банальная проблема. Программное обеспечение, с которым вы "общаетесь", может быть просто не рассчитано на большое количество соединений. Например, одно инициализированное подключение тратит много памяти. Или открытие и удержание подключения приводит к большой нагрузке на сервер.

Второе. Есть некоторые демоны, которые выделяют по процессу на соединение. Ядро Linux рано или поздно выйдет из строя, если вы попытаетесь открыть 10 тысяч "висящих" соединений, потому что у вас будет 10 тысяч процессов.

Третье. Существуют вполне конкретные ограничения ядра Linux на количество открытых исходящих сокетов с одного IP-адреса. Существуют вполне конкретные буферы, которые не бесконечны. Есть ограничения на количество одновременно открытых файлов в процессе (сокет там тоже является файлом, насколько я понимаю). 

Четвертое. В некотором ПО вводится принудительное ограничение на количество соединений с одного адреса. Это сделано как раз для того, чтобы экономить потоки/процессы. Поэтому в первую очередь посмотрите, что за ПО вы используете, проверьте, выдержит ли оно большое количество одновременно открытых соединений.

 

Второй блок проблем

Функция fclose закрывает постоянное соединение, а функция mysql_close не закрывает постоянное соединение. Мало того, во многих расширениях (англ. extension) PHP вообще нельзя закрыть соединение, если оно имеет статус постоянного. Оно просто делает вид, что закрылось. На самом деле это не так. 

Я предлагаю вам проверить, как работает ваше расширение. Особенное внимание стоит уделить предупреждениям и ошибкам. Научитесь обнаруживать, открылось новое соединение или было постоянное соединение. Разберитесь с ошибками. Вполне вероятно, что при каком-то типе ошибок соединение нужно принудительно закрывать (и это так).

Есть достаточно глупая ошибка, хочу вас о ней предупредить. Во встроенном в PHP memcached и клиентской библиотеке есть функция add_servers, которая добавляет серверы в некий пул. Если у вас "свежеоткрытое" соединение, то вы добавите серверы в пустой список. Допустим, добавили 3 сервера. В следующий раз для этого же открытого соединения эти же серверы опять добавятся. У вас пул был 3, потом пул стал 6, потом будет 9. Он будет бесконечно расти в памяти.

Нужно писать идеальный код, который будет проверять, открыто это постоянное соединение или нет, добавлять серверы заново или не добавлять (в зависимости от того, что добавлено в этот пул). Аккуратно относитесь к коду.

Третий блок проблем

Когда вы посылаете запрос на чтение через какой-либо сокет, и вам возвращается ответ, ваше ПО должно уметь "нарезать" эти ответы на кадры. Понимать, что пришла одна команда: вот ее начало, вот ее конец. 

Зачастую (а я с этим сталкивался) в некоторых демонах бывают проблемы. Например, появляются лишние байты. Например, байты "бьются". У меня есть демон, который периодически "бьет" байты.

Если у вас обычное соединение, и вы получили ошибку, то вы закроете его и откроете заново, потом повторите запрос на чтение – отлично, у вас все придет.

При постоянных соединениях будет совершенно другая ситуация. Вы послали запрос на чтение, вам в ответ пришел "битый" кадр. Вы прочитали его половину, считая, что байт "побился" или пришли нули. Вы прочитали 00, потом начался какой-то текст. От этого текста взяли первую букву – будете считать, что это длина команды. Отрезали кусок команды, а в сокете еще лежит недочитанный "кусок". Вы получите ошибку и не сможете распарсить синтаксис этого ответа. 

Вы пошлете следующий запрос на чтение в этом же соединении (возможно, даже в этом же веб-запросe, возможно, в следующем). Вы получите второй недочитанный кусок текста – опять будет синтаксис с ошибкой, и так далее.

Получается так, что нарезка на эти кадры ответов у вас "бьется". Если у вас постоянное соединение, то вы получаете полосы – так называемый «эффект трека», когда в видеосигнале получается шум на несколько кадров вперед, потом опять появляется синхрокадр, и дальше идет нормальная телекартинка.

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

Есть и менее затратный вариант – закрыть соединение и открыть новое. Иначе вы получите полосу. Ваш сокет будет передан следующему обработчику, он опять пошлет команду на чтение или на запись, и опять получит "битый" ответ. У вас будет такая бесконечная полоса до тех пор, пока вы не исчерпаете количество запросов (в PHP-FPM в районе 1 тысячи, по-моему, стоит обычно), после которых полностью гасится этот поток, загружается и "форкается" новый.

То же самое касается тайм-аутов. Если вы послали запрос на чтение, и у вас сработал тайм-аут, это значит, что ответ придет тому, кто будет обрабатывать соединение после вас. Мы один раз "залогинили" 10 тысяч пользователей не в свои аккаунты из-за этого. 

Схема была очень простая. В этом соединении были только команды на чтение. Но ответы всегда были одинаковые. Грубо говоря, select vr_id= и дальше шло число. В ответ мы иногда при тайм-ауте получали два ответа – от предыдущего запроса и от текущего. Поскольку мы не проверяли вообще ничего, то получилось так, что мы считали предыдущий ответ валидным нам, и он был абсолютно синтаксически валиден. Поэтому пользователь "логинился" в аккаунт предыдущего – того, кто авторизовался раньше. 

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

 

Четвертый блок проблем

Есть протоколы с контролем состояния (англ. stateful). Что это такое? Протокол, у которого есть состояние переменной и прочее. Проблема тут простая. Когда вы получаете постоянное соединение, которым кто-то раньше пользовался, от MySQL, можете ли вы сказать, открыта ли транзакция, какие у вас переменные, какой текущий чарт-сет, какая текущая таймзона выставлена у этого соединения? Зачастую нет. 

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

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

Посмотрите обязательно, пожалуйста, для PHP есть два расширения: MySQL MySQLi. MySQLi имеет режим, когда  постоянное соединение реинициализируется. Оно вызывает эту функцию. Если у вас другой язык программирования, у вас своя клиентская библиотека, обязательно разберитесь с состоянием. Сделайте так, чтобы вы всегда гарантированно точно знали или сами вручную приводили соединение в то состояние, которое вам нужно. Иногда в MySQL (но это не очень правильно, я сразу скажу) делают откат (англ. rollback) в начале получения каждого соединения. 

 

Пятый блок проблем

 Дело в том, что сокеты асинхронны. Никакой гарантии, что вы получите ответ в ближайшую 1 секунду (2, 5, 10 секунд) нет. Мало того, нет никакой гарантии, что вы сейчас просили демон – и вам в ответ придет ответ на ваш запрос. Никакой гарантии. Может быть, это ответ на запрос, который он обрабатывал 10 секунд, и теперь "выплюнул" вам ответ. Это не вы спрашивали. Протокол должен быть рассчитан на постоянные соединения, иначе у вас будут большие проблемы.

В протоколе должна быть возможность сопоставления запроса и ответа. Это значит, что вы послали команду, где какой-то случайным образом определенный идентификатор вписан, и ответ вам возвращается с этим идентификатором. Реализовать это можно по-разному. Но единственный протокол, где это вписано исконно, это протокол MongoDB. Может быть, база спорная, но MongoDB отлично работает с постоянными соединениями. Мало того, в расширении PHP соединения постоянны и отлично работают. 

Второе – это мало кому известная альтернатива Google Protobuf. Называется она MessagePack-RPC. Это способ, которым мы пользуемся. Поскольку нам нужно было все это внедрять, делать было нечего, были вполне очевидные проблемы - пользователи попали в чужие аккаунты и начали жаловаться. 

Мы делали очень хитро. Мы делали запросы на чтение так, чтобы нам вернулись и ключ, и данные. У нас была Key-value база данных. Мы делали select_key,data_vr_key=1 и проверяли, что ключ в ответе соответствует тому, который мы просили. В случае, если не соответствует, мы закрывали соединение, открывали заново и повторяли запрос. Это не дает стопроцентной гарантии, но это позволит вам спокойно спать. 

 

Шестой блок проблем

Вполне очевидная проблема PHP, про которую все забывают. Во-первых, существует мало кому известная терминация запроса пользователем в PHP. Вы можете в браузере, пока он грузится, нажать кнопку «стоп». Этот сигнал придет в ваш PHP FastCGI-демон и будет им обработан. Причем вы с этой обработкой можете работать по-разному. По умолчанию она вызывает обработчик Shutdown. 

Но есть таймер. Таймер на 30 секунд по умолчанию, который как раз позволяет вам делать так, чтобы у вас не было бесконечно исполняющихся FastCGI-запросов. Для этого в PHP встроен какой-то таймер. Для этого там есть возможность назначить обратный вызов (англ. callback) на этот таймер. Он обычно вызывается, все хорошо работает. 

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

Седьмой блок проблем

Сразу скажу, что тут, наверное, все спорно, это чисто мой взгляд… Я внедрял по соединению в 3-х или 4-х совершенно разных протоколах. Клиентские библиотеки очень бездумно написаны. Библиотеки и протоколы, рассчитанные "из коробки" на постоянное соединение, хорошо, качественно и – главное – с гарантией (с гарантией, приближающейся к гарантии совпадения двух чисел CRC32), реализованы только в MongoDB и MessagePack-RPC. 

Например, есть стандартное старое расширение memcached, с которым я работал. Оно даже не отличает ошибки синтаксиса от ошибок типа «ключ не найден». Просто выводит "false" – и все. Это может означать что угодно. Это могло означать, что соединение прервалось. Это могло означать, что пришел ответ, но синтаксис в нем "битый", мы не смогли его прочитать. Это могло означать, что ответ пришел правильный, а ключа в memcached нет.

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

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

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

Перейдем от теории к практике...

Я являюсь руководителем разработки приложения, которое называется Badoo Desktop. Есть версия под Windows, есть под Mac. Оно работает в Tray и сильно похоже на нотификатор, которым, по сути, является. Приложение помогает нам попасть на разделы сайта, уведомляет нас о новых событиях, дает нам информацию о вашем местоположении.

С точки зрения программиста: 3 миллиона пользователей в месяц, 1 миллион открытых постоянных соединений, 20 тысяч запросов к серверам back-end. 

Так все устроена у нас на площадке. Программа подключается к серверу front-end. Он случайным образом "раскидывает" запрос на один из серверов PHP back-end, который, в свою очередь, "ходит" в memcache, в базы, в поиск анкет. Там, где есть зеленые стрелки на слайде, мы внедрили постоянные соединения. Давайте рассмотрим подробно, почему они используются именно там.

 

Во-первых, соединение программы с сервером front-end. Здесь задача была очень простая. Она звучала как «нужно ускорить время доставки нотификации», чтобы пользователь 15 минут не ждал нового сообщения, а получал ответ сразу. Мы пошли совершенно другим путем. Не стали делать гарантию, не стали делать сложных синхронизаций. Мы сами себе сказали, что гарантии доставки сообщений нет. Каждый ответ является, по сути,  отдельной командой, поэтому сопоставление запросов с ответами нам не нужно. 

У нас, по сути, получается архитектура типа "сервер-сервер". Сервер программы "гонит" команду на выполнение каких-то действий. Ответов, по сути, нет вообще. Это редкий случай, но… Так что вы можете решить эту проблему оригинально. 

Проблемы синхронности, параллельности и прочие решаются по принципу "одна программа – одно соединение". Программа сама всегда отслеживает, чтобы было только одно подключение. Есть сохранение порядка команд. В каком порядке, в котором послали, в таком порядке и получили. 

Дальше с серверной стороны (чтобы не было проблем с сокетами, с вычитыванием и прочим, с закрыванием коннекта, нарезкой байт на отдельные кадры команд) используется написанный на "Си" демон, который "держит" это постоянное подключение. Он почти никогда не "падает", он работает очень хорошо. Поэтому с точки зрения PHP никакой проблемы нет. Ему всегда приходит четкая команда, один четкий кадр.

 

Соответственно, этим же демоном мы решили проблему того, как на трех серверах держать миллион с лишним одновременно открытых соединений. Связь сервера front-end с сервером back-end, то есть между написанным на "Си" демоном и PHP-FPM. Отдельный http-запрос, где бинарные запросы, является командой, которая пришла из программы. Постоянных соединений пока нет, но мы планируем внедрить их через HTTP Keep-Alive.

 

Обращение сервера back-end к MySQL. У нас очень много баз, больше 200 штук. Соединения с ними редки, запросы в них редки. Поэтому мы посчитали, что никакого смысла работать с постоянными соединениями нет. Это накладно. Будет много постоянно открытых соединений до баз, которые и так нагружены. До них и так много соединений. 

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

В MySQL один поток на соединение, поэтому количество соединенией в MySQL всегда ограничено. Вы это знаете прекрасно. Поэтому здесь я бы вам экспериментировать не советовал, если у вас много баз и много серверов back-end. Используйте mysqli_connect_sp:, в котором как раз есть режим реинициализации прямо внутри ПО.

HandlerSocket

Наверняка, кто-то о нем знает. Это такая попытка избавиться от SQL в MySQL. Протокол на постоянные соединения совсем не рассчитан, но подключение тратит мало ресурсов. Мы используем постоянное соединение, применяя технику запроса и ключа, и данных и проверки, что ключ вернулся тот, который просили. 

Библиотек было очень мало, одна падала в «корку», другая нам не понравилась. Написали свою. Дальше я буду рассказывать, что мы получили. Получили прирост производительности в 3-5 раз при чтении. Отличные цифры.

Memcached

Я изучал и текстовый протокол, и бинарную его версию. Они не рассчитаны на постоянные соединения. Однако мы используем их. Расширение нормально не работало, его демон периодически "плевался" нулевыми байтами в ответе. В итоге у нас были постоянные 15-минутные "пролежки" из-за того, что memcached вообще не мог ничего прочитать, потому что каждая команда в ответе была "сбитая". Мы написали свое расширение, чтобы вылавливать нулевые байты, обрезать этот кадр и работать дальше. Все стало прекрасно. Тщательно ловим все ошибки – вот в чем суть.

Также приняли меры предосторожности – проверяем ключ. Ура! Это единственный протокол, в котором в ответе возвращается ключ, который просили. Получили прирост производительности в 2 раза.

Связь с сервером backend и in-memory БД. У нас есть своя база данных, которая хранит статусы пользователя. Мы ее раз в минуту "дергаем", говорим, что этот пользователь "жив" – и он на сайте получает статус online. Протокол на постоянные соединения совершенно не рассчитан, он текстовый, очень простой и построен на базе протокола memcache. Однако мы используем постоянные соединения, не имея вообще никакой гарантии на то, что это будет хорошо работать, потому что у нас есть только записи, ответ бывает только либо OK, либо ERROR. Все команды однотипные. У них отличаются только константы user id.

Гарантии записи нет, и мы это учли. В случае больших проблем пользователь не будет online 5 минут. Нам даже не нужна каждая отдельная ошибка, потому мы используем постоянные соединения. Нам важна полоса. Мы всех пользователей "поотмечали", потом опять "поотмечали". Если начинаются проблемы с демоном, то ошибка будет сразу в 15 тысяч в секунду. Я их увижу, мне придет оповещение системы мониторинга. Этот демон об одной ошибке никогда не сообщает. Он либо работает, либо нет.

Что нам дали постоянные соединения (pconnect)?

Во-первых, у нас "ленивая" инициализация. В handlersocket, чтобы работать из таблицы, нужно ее открыть специальной командой open index. Открываете индекс, работаете с ним. Без pconnect у нас, по сути, было 2 команды: сначала открывался индекс, потом шло чтение. 

Сейчас мы используем более хитрый подход. Мы проверяем. Пытаемся читать. Если нам возвращается ошибка, что этот индекс закрыт, мы его открываем. Получается, что если раньше было 2 команды на запрос, то сейчас 1,0009, потому что эта команда выполняется на свежеоткрытое соединение 1 раз – в тот момент, когда мы лезем в этот индекс. Даже если в этом соединении 10 тысяч индексов, мы их откроем только тогда, когда будет реальная попытка доступа к ним.

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

Суть очень простая. Если у вас есть уникальный "запрос-ответ", и ваш серверный демон может работать как-то в несколько процессов (то есть вы можете поставить 3 команды – он вам может вернуть 3 ответа), то используя эти запрос-ответы, вы можете этот демон использовать. Можно посылать 3 запроса одновременно – в Facebook, например, так делают. Они делают даже более интересно – они сначала пытаются высчитать все данные по пользователю, всех его друзей, все его фотки и прочее, а потом начинают проверять разрешения – мог он запрашивать эти фотки или нет. 

Но поскольку 99 страниц, которые пользователи Facebook запрашивают, все-таки рендерятся (503-я, 404-я ошибки), то у них получается так, что ответ приходит от разных демонов одновременно. Они вначале запроса послали 10-20 ответов, через секунду проверили – окей (через 10 миллисекунд). Все вернулось, отлично, сразу все данные обрабатывают, а не по очереди. У них код в этом плане несинхронный.

Используя сопоставление запрос – ответ вы можете делать так, что у вас даже ответы будут возвращаться в разном порядке, и вы будете с ними работать. Это, например, есть в handler-socket. 

 

Создание пула соединений (англ. connection pooling). Это создание некоего массива, который хранит все ваши соединения. Пул бывает двух видов. Внутри процесса или, допустим, внутри вашего FastCGI-демона, хотя таких я не видел никогда – всегда были внутри процесса. Либо в виде отдельного ПО, которая ставится на сервер и решает только эту задачу. 

Плюсы пула соединений

• Уменьшаем время получения соединения, потому что за нас это делает отдельная библиотека. У нас есть этот пул, он всегда доступен. Многие даже преинициализируют эти соединения (как только код или FastCGI-демон запустился – сразу все соединения открылись).

• Упрощает код очень сильно. Библиотека многое берет на себя.

• Дает контроль над использованием ресурса, что на мой взгляд, очень интересно. Если у вас есть MySQL-база, вы к ней можете только через 5 соединений входить, потому что у вас большое ограничение, то вы можете поставить себе ПО, которое будет держать только 5 соединений, а их по очереди отдавать тем, кто просит. Главное – чтобы они отпускали это соединение.

Внутри FastCGI ничего хорошего я не видел. В Java есть хорошее. Что касается PHP – пишите сами, у вас будет хороший навык. У некоторых расширений есть свои пулы. Типа как в memcache, но там пул немного другого плана. В мире Python – SQLAlchemy. Там это решено очень хорошо. Рекомендую.

Вне процесса обычно гораздо такого рода ПО обычно более функционально. Оно решает еще массу параллельных задач. У таких программ есть дополнительная логика, и они вообще очень крутые. Редко кто ими пользуется, а жаль.

 

Что у нас есть для PostgreSQL? Есть большая проблема с тем, что один процесс на соединение. Понимаете сами, в чем проблема. Используется pgpool.

Для MySQL используется обычно MySQL Proxy. Это вообще «швейцарский нож». Там, например, есть разделение чтения и записи по разным серверам.Там есть логирование медленных запросов, анализ запросов. Всячески рекомендую. 

Есть еще SQL Relay, его многие хвалят, но я им не пользовался. Он поддерживает большое количество баз данных. Пробуйте. Или напишите свой.

Контрольный список

Если вы собираетесь внедрить pconnect в высоконагруженном проекте, ответьте себе на эти вопросы. 

1) Что конкретно вы экономите? С какими последствиями примерно вы это экономите? Может, вы ничего не сэкономите. На что вы рассчитываете? Что вы хотите получить? 

2) Потянет ли все это ваша система? Рассчитано ли на это ваше серверное ПО, сеть, таблицы маршрутизации, маршрутизатор и прочее?

3) Вы разобрались, как работает ваше расширение? Проверили, что с протоколом оно работает правильно, прошивки синхронизации как-то обнаруживает? Или вы их можете обнаружить их с помощью ошибки и закрыть соединение? Если вы не можете сами закрыть соединение, такое расширение вообще использовать не стоит.

4) Рассчитан ли протокол на постоянные соединения?

5) Если не рассчитан, используете ли вы какие-то дополнительные методики, чтобы предупредить возникновение проблем?

6) Если у вас протокол с контролем состояния, правильно ли вы с ним работаете? У вас всегда одно состояние, и вы им, по сути, не пользуетесь. Либо вы делаете повторное соединение, либо еще как-то искусственно проверяете, типа: if_a≠1_set_1.

7) Используете ли вы пул соединений для экономии ресурсов?

8) Готовы ли вы "изобретать велосипеды"? Мы написали два "велосипеда".

У меня на этом все. Можно задавать вопросы. 

Вопросы и ответы

Вопрос из зала: Вопрос номер раз. Было ли такое, что где-то вы решили внедрить pconnect, но вы в результате отказались от этой идеи, попробовав? То есть что-то вы там не научились готовить или еще что-то. 

Номер два. Было ли такое, что вы pconnect сделали и не получили, собственно говоря, никакого выигрыша, который ожидали?

Сергей Аверин: Мы всегда точно знали, куда "стреляли". Такого, чтобы сделали – и не получили выигрыша, не было. Это правда. У меня такая специфика, что у приложения очень маленькие запросы, они быстро выполняются и там время на подключение ко всем серверам back-end – MySQL, поиск и прочее – достаточно весомо, сравнимо со временем запроса. Раза в 2 я всегда получаю прирост.

Теперь отвечаю на первый вопрос по поводу «сделали». Один раз была ситуация, когда мы стали использовать внутреннее расширение, которое у нас в компании разрабатывается для memcache. Оно, я так понимаю, основано на коде оригинального, еще старого расширения memcache для PHP. С ним возникали проблемы сбивки синхронизации, и они оборачивались полосами "пролежки" по 15 минут. Я не мог закрыть соединение и сделать так, чтобы соединение заново открылось. С этим была проблема. Там соединения почему-то не закрывались.

Вопрос из зала: Решили проблему-то?

Сергей Аверин: Решили. Выкинули это расширение, написали другое, которое "умеет" делать только 2 команды (set и get), но обнаруживает эти "сбивки".

Вопрос из зала: В результате когда внедряли pconnect – везде получили выигрыши и везде смогли решить все проблемы с поддержкой протоколов и так далее? 

Сергей Аверин: Да. Везде смогли решить все проблемы.

Реплика из зала: Круто! Молодец.

Вопрос из зала: Вопрос про MySQL Proxy. Вы реально его используете? 

Сергей Аверин: Поскольку я говорю, что я не хожу в MySQL особо, у меня очень редкие запросы, я, в основном, хожу через handler-socket. Даже в компании – нет, мы не пробовали. Я бы, наверное, хотел, подумал бы об этом.

Вопрос из зала: Просто вы говорили, что при сколько-нибудь существенных нагрузках очень непредсказуемо себя ведет, "валится" и вообще не очень хороший.

Сергей Аверин: Не могу ничего сказать. Врать не буду. Желание попробовать у меня есть.

Вопрос из зала: Не сталкивался ли с протоколом Postgres и использованием синхронизации?

Сергей Аверин: К сожалению, за 7 лет программирования не сталкивался с Postgres ни разу. Что сказать? Извините.

Вопрос из зала: Интересно, видел ли ты где-нибудь красивый API для асинхронного общения с базой? Когда ты шлешь "пачку" запросов одну за другой.

Сергей Аверин: MessagePack-RPC посмотри – очень интересный проект. Это, по сути, бинарный JSON, к которому идут дополнительные "плюшки" в виде своего RPC-протокола и готовых написанных библиотек. Он не предназначен для баз или не баз. Это протокол, очень сильно заточенный на сжатие коротких данных (именно коротких). Там почти на каждый формат данных есть два варианта. 

Допустим, integer, которых больше 20-ти тысяч, и integer меньше. Integer меньше сжимается в 1 байт, а integer больше – в 2 байта или в 4. Там на уровне битов все работает. Очень интересный проект, сжимает очень круто и сильнее, чем Google Protobuf. В Google Protobuf на уровне байтов протокол, а в MessagePack – на уровне битов. 

Там есть параллельная конвейеризация, там есть синхронизация запросов-ответов. В принципе, там это может быть асинхронно. Но только в том случае если ты хранишь некий пул посланных запросов. У тебя есть какой-то массив, в котором ты можешь сопоставить, кому пришел ответ. Это библиотека плюс протокол плюс протокол упаковки.

Вопрос из зала: Если использовать pconnect в PHP с теми же базами данных, это реально без каких-то внешних инструментов или переписывания расширения?

Сергей Аверин: С чем? С pconnect в PHP с чем?

Вопрос из зала: С MySQL. Можно ли использовать постоянные соединения вместо обычных?

Сергей Аверин: Можно использовать. Но дело в том, что там сделано очень хитро. Там тоже run lancing coding. Там сначала дается длина команды, потом сама команда. Но проблема в том, что есть номер кадра, но он с каждым запросом новый. Ты послал запрос, у него приходит ответ с № 0, потом с № 1, потом с № 3… Эти ряды пакуются, по-моему, до 25-ти килобайт, что ли, эти кадры могут быть. Но когда ты посылаешь следующий запрос, ты опять получаешь ответ 0, 1, 2, 3. 

Ты можешь использовать MySQL с pconnect, если тебе это нужно. Для этого нужно использовать расширение MySQLi, ставить вот это sp: в начале хоста, и он тебе будет это соединение реинициализировать, каждый раз авторизировать тебя с логином и паролем заново. Этот момент и будет синхронизацией. Если в ответ на синхронизацию приходит какая-то ерунда, ответ от предыдущего соединения, то у тебя будет ошибка: «Мы пытались авторизоваться, а пришла какая-то ерунда». Ты должен найти эту ошибку, закрыть подключение, открыть заново и работать с ним. Все, тогда у тебя будет все хорошо. 

Во всей серверной логике, которая у меня "ходит" куда-либо, всегда 3-4-5 попыток делается. Должно быть зашито, что если будет проблема, мы будем пытаться повторять. Причем на какие-то попытки соединение закрывается, на какие-то – нет. В зависимости от того, это ошибка синтаксиса синхронизации или просто – не найден ключ, тайм-аут произошел, еще что-то случилось.

Реплика из зала: Спасибо.

Сергей Аверин: Можно. Но надо делать повторы соединений и надо ловить ошибки и при этих ошибках закрывать его.

Вопрос из зала: Пробовал ли ты pconnect с Radis?

Сергей Аверин: Не пробовал. Когда я внедрял Radis, мне это было еще не очень интересно. Это был второй этап проекта, уже ускорение. Radis еще на уровне развития, взлета был "выкинут", к сожалению, по причине того, что мы не научились его "варить".

Вопрос из зала: Вопрос немного не по теме: MongoDB используется?

Сергей Аверин: У нас внедрена MongoDB в одном из проектов внутри компании. Могу даже познакомить вас с тем, кто его использует.

Комментарии

Нет ни одного комментария

Только пользователи могут оставлять комментарии

Возможно, вам будет интересно:

Василий Аксенов

Василий Аксенов

Руководитель группы фронтенд-разработки компании СКБ Контур.

Василий Аксенов (СКБ Контур) рассказывает о техниках создания и возможностях сеток.

Александр Калугин

Александр Калугин

Project Director в компании Mercury Development Russia. Докладчик профильных конференций SEF.BY, ReqLabs, SECR.

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

Ярослав Городецкий

Ярослав Городецкий

Генеральный директор CDNvideo. В Интернете и телекоме с 1996 года. Обожает придумывать и запускать новые продукты и услуги.

Ярослав Городецкий (CDNvideo) делится своим опытом построения сети доставки контента, работающей в России, СНГ и Западной Европе.