[unable to retrieve full-text content]
Один: «Ты записал, что я тебе сказал?». Второй: «Я запомнил!». Через час бездонная пропасть в памяти и звенящая тишина в голове.
Читать дальше →[unable to retrieve full-text content]
Один: «Ты записал, что я тебе сказал?». Второй: «Я запомнил!». Через час бездонная пропасть в памяти и звенящая тишина в голове.
Читать дальше →[unable to retrieve full-text content]
В мире JavaScript очень легко набрать свой стек технологий, используя набор небольших пакетов, каждый из которых решают свою конкретную проблему. И это хорошо, c одной стороны, а с другой стороны, у вас особо нет выбора — фреймворки которые выполняют широкий спектр задач в JavaScript не популярны.
В этом цикле статей я хочу поделиться своим практическим опытом построения JS стека.
[unable to retrieve full-text content]
Очередная приятная новость от команды разработчиков Microsoft Azure. Если кто-то из читателей, кто уже использует сервис резервного копирования в Azure ARM портале, то они могли заметить, что управление политиками резервного копирования чрезвычайно затруднено. Действительно, вам давалась возможность выбора существующей или создания новой политики, только при добавлении нового сервера в Recovery Services Vault.Задержка в сети (лаг) — это реальность, которую нужно учитывать в многопользовательских играх. Сообщениям, передаваемым через Интернет, требуется время, чтобы достичь точки назначения. В зависимости от маршрута и его длины передача этих сообщений может занять довольно долгое время. Это может негативно влиять на процесс игры, особенно в динамичных клиент-серверных играх, таких как FPS. То, что кажется очень простой задачей (стреляй, пытаясь попасть в цель), внезапно становится очень сложным в создании плавного игрового процесса для всех игроков. Думаю, не нужно говорить, что создавать многопользовательские игры сложно, при этом возникает множество проблем, которые разработчики должны решить. В этой статье я расскажу, как система вооружения MechWarrior Online справляется с лагом.
Одна из стратегий решения проблемы лага — уменьшить расстояния и таким образом улучшить маршрут между игроками и игровым сервером, чтобы лаг стал не так заметен. Этого можно достичь, создав систему региональных серверов и подключая игроков только к тем серверам, которые находятся к ним ближе; или же можно обеспечить передачу игрового трафика по маршрутам через определённые сети для оптимальной передачи на серверы. К сожалению, такой способ не всегда возможен из-за высокой стоимости и может применяться при ограниченных объёмах данных; но даже и в этом случае он не всегда срабатывает для игроков, находящихся в отдалённых регионах или имеющих низкое качество Интернет-соединения. MechWarrior Online (MWO) подходит к этой проблеме так же и использует региональные выделенные сервера для минимизации задержек для игроков. Однако этого часто недостаточно для большинства игроков, и нам нужно сделать что-то ещё.
Что видит игрок при лаге
Что видит сервер при лаге
С точки зрения игрока, он стопроцентно попадает в свою цель, но сервер видит, что игрок стреляет совершенно мимо. Чтобы решить эту проблему, нужно каким-то образом компенсировать лаг игрока.
Сейчас вы можете задать вопрос: зачем вообще решать эту проблему? Если клиент прогнозирует всё, может ли клиент просто сообщить серверу, попал игрок в цель или нет? Проще всего ответить «да», этот вариант возможен; однако в нём есть большой изъян. Когда игровому клиенту разрешают сообщать что-то серверу, возникает возможность читерства. При игре на компьютере игровой клиент можно с лёгкостью модифицировать, и хитрый читер изменит игровой клиент таким образом, что тот будет отправлять сообщения о попадании, когда захочет читер, даже если он находится далеко от цели или вообще её не видит. Поэтому передача таких прав клиенту, особенно в отношении нанесения ущерба врагам — это невероятно рискованное предложение. Если многопользовательская игра предназначена для платформ, обеспечивающих какую-нибудь «защиту» игрового клиента, например, для консолей, то это может быть вполне приемлемым решением. Но в мире ПК, если вы даёте клиенту любой контроль над состоянием игры, то он почти наверняка будет использован в свою пользу. Существует множество способов жульничать в бою, но в этой статье мы тоже не будем их рассматривать. Для нас самым безопасным решением будет предоставление полного контроля серверу, именно такой подход и реализован в MWO.
В MWO существуют две категории оружия, и для каждой лаг должен компенсироваться по-своему. Вооружение подразделяется на оружие прямолинейного огня (такое как лазеры и пулемёты) и на артиллерийское оружие (например, ракеты и баллистическое оружие). Работать гораздо проще с прямолинейным оружием, так что давайте начнём с него.
Как сервер видит «перемотанное назад» положение цели
В случае MWO недостаточно просто хранить предыдущие положения и направления. Каждый компонент меха может быть повреждён отдельно, и это очень важная игровая механика, так что необходимо ещё состояние анимации. Оно также должно запоминаться и «перематываться» на сервере. На картинке это видно как разница в цикле движения «прошлого» и «настоящего» меха. Итак, мы разобрались с прямолинейным оружием, теперь давайте найдём решение для артиллерийского вооружения.
Упреждающая стрельба артиллерийским оружием на игровом клиенте
Нам бы хотелось перемотать вперёд снаряд на сервере, чтобы определить его положение, прогнозируемое клиентом. Если мы сделаем прогноз клиента правильным, мы скомпенсируем лаг на игровом клиенте. Чтобы достичь этого, мы разобьём цикл «жизни» снаряда на две части.
Ко времени получения сервером запроса на выстрел снарядом проходит половина времени на передачу и подтверждение игрока. Но нужно помнить, что сервер должен опережать клиент на половину времени на передачу и подтверждение, поскольку обрабатываемый сервером снаряд должен был существовать в течение секунд пинга, чтобы соответствовать всему остальному в мире сервера. В течение этого времени снаряд уже может столкнуться с целью в клиенте, и сервер должен на это реагировать, но давайте пока не будем волноваться об этом. Давайте назовём этот первый этап срока жизни снаряда периодом «перемотки назад». Всё после этой первой части срока жизни снаряда будет точкой, в которой симуляция снаряда сервером и прогнозирование поведения снаряда клиентом должны синхронизироваться. Важно заметить, что синхронизированность в данном контексте не означает, что снаряд должен находиться в одинаковых точках в клиенте и на сервере. Клиент должен отставать от сервера на половину времени на передачу и подтверждение. Поэтому в этом случае синхрозирированность означает, что снаряд находится в том же месте относительно других объектов мира и в клиенте, и на сервере. Назовём этот этап срока жизни снаряда синхронизированным периодом. Работа с первой частью немного сложнее, поэтому давайте сначала займёмся второй.
Предположим, что снаряд имеет простую физику и двигается по прямой с постоянной скоростью. В этом случае в момент получения сервером запроса на выстрел снаряд уже пройдёт в игровом клиенте расстояние, равное его скорости, умноженной на пинг клиента. Если снаряд имеет более сложную физику, например, с учётом трения и гравитации, то всё равно должно быть возможно рассчитать пройденное им расстояние с помощью более сложной формулы.
Траектория снаряда на сервере, вид сверху
Если мы пока проигнорируем период «перемотки назад», сервер может сделать прогноз клиента правильным или синхронизироваться с клиентом, просто создав снаряд на расстоянии, которое он прошёл от обычного положения создания. Другими словами, сервер не будет стрелять снарядом из дула оружия, а вместо этого выстрелит им на нужном расстоянии вдоль его траектории. Если снаряд имеет более сложную физику, серверу может потребоваться рассчитать нужную скорость и ускорение, которое должен будет иметь снаряд в соответствующей точке траектории. Полагая, что снаряд движется детерминированно, он должен теперь перемещаться по синхронизированной между клиентом и сервером траектории. Это довольно просто, но как насчёт периода «перемотки назад»?
Если бы мы всегда создавали снаряды на расстоянии периода «перемотки назад», то снаряды бы могли проходить через цели или даже статические объекты. Довольно просто ограничить прохождение через статические объекты с помощью трассировки линий, но как насчёт подвижных целей? Если цель находится в пределах периода «перемотки назад», то снаряд согласно прогнозу клиента может уже столкнуться с целью, прежде чем запрос дойдёт до сервера. В этом и состоит настоящая проблема со снарядами – сервер должен уметь распознавать такие ситуации, чтобы реализовывать прогнозы клиента.
Один из способов решения проблемы — итеративный, сочетающий в себе «перемотку назад» с определённой симуляцией «перемотки вперёд». Когда запрос на выстрел снарядом достигает сервера, он может «перемотать назад» цель, создать снаряд и циклично просчитывать снаряд и цель «перемоткой вперёд» в течение периода «перемотки назад». В случае обнаружения столкновения между целью и снарядом при цикличном просчёте вперёд, мы можем нанести повреждение цели и вообще избежать создания снаряда. Если столкновение не обнаружено, мы можем создать снаряд в его синхронизированный период.
К сожалению, у этого решения есть пара других проблем. Первая заключается в том, что быстро летящие снаряды без какого-нибудь способа непрерывного обнаружения столкновений в каждом цикле смогут проникать через цели в собственном цикле. Вторая состоит в том, что такой тип решения очень затратен для применения на сервере. Количество итераций соотносится с длительностью периода «перемотки назад». Так что если у игроков будут повышенные пинги, обработка каждого выстрела снарядом будет более ресурсоёмкой. В MWO бывает очень много снарядов, и они летят очень быстро; по этим и другим причинам, которые мы не будем здесь рассматривать, такое решение нам не подходило.
Для создания неитеративного решения давайте сначала сделаем некоторые допущения, упрощающие проблему. Мы примем для снарядов следующие допущения: снаряд приблизительно может считаться точкой, так что можно использовать трассирование линий для аппроксимации его траектории; траектория периода «перемотки назад» достаточно хорошо аппроксимируется прямой линией; также можно принять, что снаряды в течение периода «перемотки назад» имеют постоянную скорость. В MWO для снарядов применяется баллистическая физика, поэтому они не всегда движутся по прямым, но относительно мехов они очень малы, движутся очень быстро и не снижают скорости при движении в воздухе, так что эти допущения нам подходят. Также нам нужно принять допущения относительно движения целей. Примем, что движение цели между положением «перемотки назад» и текущим положением на сервере может быть достаточно хорошо аппроксимировано с помощью прямой линии; также примем, что скорость цели в течение этого периода тоже постоянна. Такие допущения могут быть довольно неудобными в некоторых динамичных играх, но к счастью, в MWO мехи поворачиваются, ускоряются и замедляются медленно и плавно, так что эти условия нам подходят.
Приняв эти допущения, мы можем разбить проблему на две части. В первой части мы хотим узнать, возможно ли вообще столкновение с целью. Если столкновение с целью возможно, то мы выполняем расчёт для определения времени, в которое должно случиться столкновение в период «перемотки назад». Во второй части мы используем эту информацию для выполнения обычной «перемотки назад» прямолинейного огня с изменённым значением пинга, чтобы определить, было ли попадание.
Для проверки на наличие возможности попадания, мы сначала определяем положение «перемотанной назад» цели на основании текущего пинга игрока и вектора, представляющего траекторию снаряда в период «перемотки назад». Это похоже на то, что мы делали при «перемотке назад» прямолинейного огня. Мы хотим узнать, пересекаются ли эти два объекта при перемещении по собственным траекториям. Допущения, принятые ранее, позволяют свести эту проблему к трассировке единственной линии, сделав скорость снаряда относительной к стационарной «перемотанной назад» цели. Можно увидеть это на изображении ниже.
Применяя наши допущения, сервер может свести проблему, представленную слева, к проблеме справа, которую гораздо проще решить
Решение этой проблемы аналогично решению проблемы прохождения сквозь объекты, которая упомянута выше. Можно представить, как снаряд перемещается из своего начального положения к концу своего периода «перемотки назад» за один цикл; наличие столкновений во время движения этих объектов между начальной и конечной точкой выполнятся непрерывной проверкой обнаружения столкновений. Важно также заметить, что эта проверка не полностью аналогична обычной трассировке линий. Мы намеренно игнорируем остальную часть мира и рассматриваем только «перемотанную назад» цель и модифицированную траекторию – это очень важная деталь. Если мы не будем игнорировать остальную часть мира, мы можем ошибочно получить результат, сообщающий о невозможности возникновения столкновения, в то время как оно может быть. Например, если игрок стоит рядом со стеной, у нас может получиться ложноотрицательный результат. Можно видеть это на картинке ниже.
Возможная ситуация, в которой объект мира приводит к ошибочному результату при проверке модифицированной траектории
Помните, что эта проверка сообщает нам только о возможности столкновения; она не гарантирует, что столкновение обязательно произойдёт. На пути могут находиться другие движущиеся или статичные объекты. Итак, мы всё ещё хотим выполнять какую-нибудь проверку «перемотки назад» вдоль исходной траектории снаряда, похожую на ту, которую мы делали для оружия прямолинейного огня. Если первая часть этой проверки проходит успешно, она даёт нам точно ту информацию, которая нам нужна для этого. Она сообщает, что столкновение возникло, оно возникло на определённом расстоянии в процентах вдоль пути от «перемотанного назад» положения до текущего положения цели. Поскольку и снаряд, и цель двигаются в течение одного временного промежутка, этот процент равен проценту вдоль модифицированной траектории, где происходит столкновение – иногда он называется «временем попадания». Мы можем использовать время попадания для расчёта значения модифицированного пинга, которое «перематывает назад» цель к этому среднему положению.
Состояния «перемотки назад» снарядов на сервере. Чёрная линия – исходный вектор огня в течение периода «перемотки назад». Синяя линия – модифицированной вектор огня для непрерывной проверки обнаружения столкновений. Чёрный мех – текущее положение меха-цели при выстреле снарядом. Синий мех – «перемотанное назад» положение меха-цели на основании пинга игрока. Жёлтый мех – потенциальное положение для финальной проверки «перемотки назад»
Это среднее положение и есть «перемотанное назад» положение, для которого мы и хотим выполнить проверку «перемотки назад» прямолинейного огня. Мы можем выполнить обычную проверку «перемотки назад» прямолинейного огня с модифицированным значением пинга, чтобы определить, было ли на самом деле попадание. Если попадание было, мы наносим цели соответствующее повреждение и обходимся без создания снаряда. Если попадание отсутствует, мы создаём снаряд в его синхронизированный период, как было описано выше. В случае изображения выше мы видим, что в данной ситуации попадание есть.
Мой доклад предназначен для тех людей, которые знают слово «репликация», даже знают, что в MySQL она есть, и, возможно, один раз ее настроили, 15 минут потратили и забыли. Больше про нее они не знают ничего.
Мы немного пройдемся по теории, попытаемся объяснить, как это все работает внутри, а после этого вы с утроенными силами сможете сами нырнуть в документацию.
Что такое репликация, в принципе? Это копирование изменений. У нас есть одна копия БД, мы хотим с какой-то целью еще одну копию.
Репликация бывает разных видов. Разные оси сравнения:
В докладе не будет:
Все это есть в Интернете, синтаксис разбирать смысла нет.
Забавный факт — если немного задуматься, репликация нам теоретически помогает из принципиальных соображений скейлить только чтение. Вот такой несколько неочевидный вывод. Это потому что, если у нас на одну и ту же копию данных надо налить определенное количество изменений, и эта определенная копия данных обслуживается одним и тем же сервером, то этот сервер способен выдержать определенное количество апдейтов в секунду, и больше туда не залить. Способен сервер обновить 1000 записей в секунду, а 2000 — не способен. Что изменится от того, что ты поставишь к этому серверу реплику, неважно, в режиме мастер-слэйв или мастер-мастер? Сумеешь ты на эту реплику налить вторую тысячу апдейтов? Правильный ответ — нет.
На реплику в режиме мастер-мастер ты, конечно, сумеешь налить дополнительных апдейтов, другое дело, что, когда они не прилетят на первый мастер и попытаются на нем сделать вторую тысячу апдейтов, то емкости уже не хватит. Надо понимать и не смешивать два почти очевидных момента, что репликация, как бы, про одно, а то, что данные надо дробить, и если надо скейлить не чтение, а записи, то придется делать что-то другое, а репликация не очень спасет.
Т.е. репликация — это больше про чтение.
Синхронизация — гарантия наличия и доступности. Доступности в том смысле, что у нас commit прошел, транзакция зафиксировалась, все хорошо, эти данные видно одной или нескольким нодам в кластере, они могут участвовать в следующих запросах. Наличие — это то, что данные, в принципе, есть более чем на одном сервере, но, возможно, транзакция не проигралась и не доступна.
Здесь нет рефрена «commit закончился успешно, что это значит?». Синхронный commit означает, что у нас локальный и удаленный (хотя бы на одной реплике) закончился, т.е. мы что-то закоммитили на машину, если у нас синхронный режим репликации, то эти изменения успешно закоммитились, они видны для последующих запросов на локальной машине, на удаленной машине (хотя бы на одной) тоже видны. Это означает, что если случилась стандартная внештатная ситуация, т.е. в один и серверов прилетел лом и пробил все насквозь — от процессора до самого винта, то, несмотря на это, данные не только скопированы на некий удаленный сервер, но еще, вдобавок, могут мгновенно, без каких-то дополнительных задержек, участвовать в последующих транзакциях.
Это все общая терминология, никак совершенно не связанная с MySQL. В любой распределенной системе оно будет устроено так.
Асинхронный commit — никаких дополнительных гарантий, как повезет.
Полусинхронный commit — приятное промежуточное решение, это когда у нас локальный commit прошел, про удаленный commit ничего не известно — может, слэйв догнал, а, может, и не догнал, но, по меньшей мере, нам пришло подтверждение, что эти данные куда-то улетели и там приняты и, наверное, записались.
Про сервера для записи. Какие бывают виды репликации.
Master-slave classic, изменения все льются на один сервер, после этого копируются на массу реплик.
Master-master true — когда изменения льются на кучу мастеров одновременно и каким-то образом с одного на другой, с другого на третий и между ними всеми, что порождает и ряд радостей, и ряд автоматических проблем. Понятно, что когда у тебя есть одна «золотая копия» и с нее несколько реплик, которые должны (в идеале — мгновенно) повторять эту «золотую копию», то все сравнительно просто с точки зрения того, как данные туда-сюда гонять и что делать на каждой конкретной копии. С master-master начинается интересная «головная боль», причем, подчеркиваю, не конкретно в случае MySQL, а сугубо теоретическая. Как же быть, если на двух нодах одновременно попытались прогнать одну и ту же транзакцию, которая меняет одни и те же данные, причем, меняет их, для простоты примера, по-разному. Понятно, что одновременно эти два изменения мы применить не можем. На момент, когда мы на одной ноде начинаем что-то изменять, на второй ноде еще пока ничего нет. Конфликт. Одну из транзакций придется откатывать. Вдобавок начинаются отдельные «пляски» со сверкой часов и т.п.
Любопытный момент — даже вариант, когда у вас в конечном итоге все изменения со всех мастеров должны постепенно распространиться везде, все равно не поможет тому самому write bandwidth. Обидно, но вот так.
Приятный вариант — под названием«Master-slave + routing запросов». Приятен он тем, что внутри программировать просто, у тебя есть одна основная копия, ты ее реплицируешь на кучу машин. Это намного проще, чем в мастер-мастер среде, когда все равноправны и т.д., но с точки зрения приложения все равно выглядит так, будто у тебя точек записи много. Ты приходишь на любую ноду, она знает, куда тебя зароутить, и успешно роутит. Ну, и чтения масштабируются — вот оно счастье репликации. Читать можно со всех точек все и всегда.
Теперь ближе к базам данных, «волшебным» форматам statement-based, row-based и т.д. Про формат изменений.
Что можно делать? Можно передавать сами запросы, а можно передавать только измененные строки. Подчеркиваю — пока мы еще не нырнули в дебри MySQL, этим может заниматься любая СУБД, в которой есть запросы, порождающие большое (или не очень) количество изменений, т.е. обновляющие много данных. Возникает вопрос — а что конкретно будем копировать? Можно сами запросы туда-сюда между нодами гонять, а можно гонять только измененные данные. Интересно, что и так и эдак очень плохо! Можно еще пытаться смешивать.
Еще один пункт про то, какие бывают репликации. Про модель распространения. Наверное, где-то до сих пор еще не полностью вымерла модель Push-based, когда та нода, которая внесла изменения, та и обязана их рассылать по всем остальным нодам. С точки зрения программирования и отслеживания state'ов это та еще морока. Поэтому рулит Pull-based. Забирать апдейты с той или иной ноды — это намного проще запрограммировать, чем на одной ноде следить за хаотичным кластером своих реплик.
Некие общие термины ввели. Переходим к тому, как сделали в MySQL.
MySQL, сам по себе, это некий обман. Есть логический слой под названием MySQL, который занимается всяким общими и изолированными от хранения данных делами — сеть, оптимизатор, кэши и т.д. Конкретный физический слой, который отвечает за хранение данных, лежит на этаж ниже. Есть несколько встроенных, есть ставящиеся плагинами. Но даже встроенные MyISAM, InnoDB и т.д. живут на физическом слое. Плагинная архитектура — это клево, можно подцепить новый движок, но мгновенно возникает некая неоптимальность. В принципе, транзакционные write-ahead log'и (WAL), которые физический слой хранения все равно пишет, было бы хорошо использовать для репликации, и если система знает о том, что есть некий физический уровень, или достаточно хорошо сопряжена с этим физическим уровнем, то можно было бы отдельный лог на логическом уровне не писать, а использовать тот же самый WAL. Но у MySQL это невозможно концептуально, либо, если поменять интерфейс в PSE так, чтобы стало возможно концептуально, то будет очень много работы.
Репликация реализована на уровне самого MySQL. В этом есть и хорошее — помимо одного лога в виде глубоко внутренних данных движка хранения, есть более-менее логический лог, возможно, на уровне statement'ов, который ведется отдельно от этого движка. А это «лишняя» безопасность и т.д. плюс, поскольку никаких ограничений внутри нет, можно делать всякий креатив типа подмены движка «на лету».
В веденных терминах в MySQL 4.1 было реализовано: master-slave, pull-based, строго async и строго SBR. Если вы застряли в древней эпохе 4.х, то, наверное, у вас все плохо. Версиям 5.х уже чуть ли не 10 лет — пора бы и обновиться.
Забавно прослеживать по версиям, как люди наступали на всяческие грабли и, когда сделать уже ничего было нельзя, прикручивали к этим граблям новые грабли, чтобы жизнь была не такая болезненная. Так, в версии 5.1 прикрутили RBR, чтобы компенсировать неизбежные проблемы с SBR, и прикрутили mixed режим. В версии 5.6 прикрутили еще приятных штук: semi-sync, delayed slave, GTID.
Еще один момент. Поскольку MySQL — это некий общий слой, с одной стороны, и куча pluggable движков, с другой стороны, в том числе, встроенных, там есть с определенного момента божественный NDB cluster, про который рассказывают крутое. Там полностью синхронная мастер-мастер репликация, очень доступная in-memory БД… Но есть один нюанс — как только начинаешь искать людей, которые в продакшене используют NDB cluster, то таких людей находится крайне мало.
Чем занимается мастер в тот момент, когда вы решили включить репликацию? На мастере происходит довольно мало дополнительных движений. Как обычно, мы по сети принимаем запросы, парсим их, гоняем транзакции, фиксируем их и т.д. Вдобавок к этому, на логическом уровне MySQL мастер начинает вести binary log — файл, не совсем текстовый, в который сыплются все подряд изменения. Также мастер умеет рассылать эти логи по сети. Все это очень просто и, вроде как, работает.
Чем занимается слэйв? Изменения на слэйв лучше не слать, потому что можно попасть в непонятное. У слэйва чуть больше работы. Помимо того, чтобы вести один дополнительный лог и по запросу его рассылать, еще есть тред, который ходит к удаленному мастеру, возможно, даже не к одному, и качает оттуда binary log'и. Решение «давайте ходить к нескольким удаленным мастерам и с них качать разные логи» неоднозначно. С одной стороны неплохо, а с другой получается мгновенное расхождение. Просто физически копировать файлы по SCP нельзя, уже получается на сервере один лог, в нем свои позиции, локально мы их по сетке тянем, складываем в отдельный лог, еще отдельный тред бегает и пытается проигрывать эти локальные логи. Самое адское, на мой взгляд, заключается в том, что вплоть до версии 5.6 идентификация той или иной транзакции в логе происходила по имени файла и позиции на мастере. Интересное решение.
Вот путь записи, который простенький insert проходит без репликации:
Приложение сконнектилось к серверу, положило в таблицу и отбой.
С репликацией получается несколько дополнительных шагов:
Приложение-писатель точно так же идет к мастеру, но вдобавок эти данные попадают в том или ином виде в binary log, потом качаются по сети в relay log, потом из relay log'а постепенно реплеются (если нам повезло, и слэйв не лагает, реплеются сразу) в таблицу на слэйве, после этого все доступно в читателе.
Что конкретно попадает в binary log, зависит от настроек SBR/RBR/mixed. Откуда это все растет? Представим себя базой данных. Нам прилетел простой запрос «обнови одну конкретную запись» — UPDATE users SET x=123 WHERE id=456
Что записать в binary log? В принципе, все равно, на самом деле. Можем коротенький запрос записать, либо (а он обновил одну запись) можем записать изменение каким-то образом в том или ином формате.
Другая ситуация. Представим, что нам прилетел тот самый запрос, который сам по себе маленький, а данных меняет много — UPDATE users SET bonus=bonus+100
Тут эффективный вариант один — писать сам запрос, потому что запрос — ровно 32 байта, а записей он может обновить произвольное количество — 1000, 100 000, 1 000 000, сколько угодно… Неэффективно писать измененные записи в лог.
А что произойдет, если мы в лог поместим такой нехитрый запрос «давайте отключим всех юзеров, которые не логинились давно» — UPDATE users SET disabled=1 WHERE last_login < UNIX_TIMESTAMP(NOW())-100*86400
Внезапно наступает ужас. Проблема в том, что если среплицировать идеально сам запрос, то, во-первых, время никогда не синхронно между двумя нодами, кроме этого, за счет того, что путь записи такой длинный, в момент реплея этот «NOW» разойдется-таки. Реплика внезапно расходится с мастером, и все последующие изменения, формально говоря, уже небезопасны, могут привести к чему угодно.
Вообще говоря, для таких запросов, вне зависимости от количества измененных данных, в идеале надо бы копировать сами строчки. В данном конкретном случае можно сами строчки не копировать, а зафиксировать константу и в лог написать не «NOW», а конкретный timestamp, который был использован мастером на момент репликации.
Забавные факты, которые случайно узнаешь, ныряя в дебри репликации. Причем, нырять можно неглубоко — нарываешься на них стразу. В случайном порядке они такие:
Короче говоря, устроено все не хитро — палка, веревка, один лог, второй лог. И даже в этом логе «детские» болезни довольно забавные:
Для человека, который использует репликацию два дня, все это страшно и тяжело. Но, зная, насколько она нехитро устроена, в принципе, понятно, как с ней жить:
И лучше сразу настроить, чтобы потом не разбирать странный фарш.
В ситуации «протух лог, разошлась позиция, неизвестно, что происходит» есть определенный инструментарий — смотрим event'ы, пытаемся понять, какая транзакция уже проскочила, какая — нет, можно ли все это дело спасти или восстановить и т.д. Если GTID«ы заранее сумели включить, то жизнь становится проще.
Другой момент наблюдения за репликацией. Интересно посмотреть, как внутреннее кривое устройство провоцирует не то, что конкуренцию, а создание дополнительных продуктов. „Волшебный“ Tungsten Replicator, говорят, хорошо решает задачу под названием „однопоточный слэйв — это плохо“, а если бы не врожденные сложности, не было бы дополнительного продукта, который позволяет пользоваться этим механизмом, переливать данные в другие системы, с одной стороны, и заодно решать ряд проблем, встроенных в существующую систему, с другой стороны.
Как обычно, советовать невозможно. Кому-то помогает, кто-то будет сильно плеваться. Но, говорят, есть ситуации, в которых с неизбежным однопоточным лагом хорошо справляется Tungsten. Я уверен, есть еще всякие увлекательные фокусы, но внутренний однопоточный слэйв — это тяжело.
Что делать, если вы зачем-то использовали реплики как бэкап? Я считаю, надо биться головой об стену, потому что реплика и бэкап — это две разные штуки. Тем не менее, если вы креативные пацаны и используете достаточно новую версию, delayed replication вас спасает, с одной стороны, но с другой стороны, если вы не делаете полноценных бэкапов, вас все равно ничего не спасет.
Далее еще один элемент креатива. Нетрудно представить ситуацию, когда мастер забил логами весь 10 PB облачный диск или забил рассылкой этих логов всю сеть, при этом 90% этих обновлений нам не нужны, потому что нам интересно реплицировать, например, одну таблицу прицельно или одну базу прицельно, а по умолчанию все валится валом в бинарный лог — все изменения по всем базам, по всем таблицам, по всему. Решение опять поражает своей креативностью. С одной стороны, есть четыре настройки — {binlog|replicate}_{do|ignore}_db, которые позволяют фильтровать на мастере — что запишется в лог, а что проигнорируется. На слэйве, соответственно, позволяет делать то же самое. Т.е. на мастере мы можем отфильтровать то, что попадает в binary log — в эту воронку, которая потом сливается в сеть, а на слэйве, соответственно, мы можем поставить входящий фильтр на то, что прилетает из сети. Или писать на диск только часть данных, а потом на слэйве реплеить, опять же, только часть данных. Внезапно даже в этой нехитрой истории наступает ужас, потому что комбинация — используем одну БД, а апдейтим таблицу в другой БД через интересный синтаксис — она ведет себя как-то… А как конкретно она себя поведет — неизвестно, т.к. разные фильтры срабатывают в разные моменты.
Встроенных приятных штук под названием „перевыборы мастера, если он внезапно сдох“ нет, надо поднимать руками. Отсутствие инструментов для менеджмента кластера — это, по моему мнению, хорошо — порождает конкуренцию, порождает создание дополнительных продуктов. В самом деле, если бы в обычном MySQL идеально работала очень клевая мастер-мастер репликация, или хотя бы автоматическое поднятие после сбоев, то зачем бы была нужна всякая Galera, Рercona/MariaDB Cluster и т.д.?
Еще немного фокусов. Интересна реализация репликации, которая простая как палка и веревка, без всяких проверок, с одной стороны, и без всяких инструментов, чтобы приятнее менеджить кластер реплицирующегося слэйва, с другой стороны. Это плохо. Но зато можно вручную лепить из этого такие интересные конфигурации, что содрогнутся все, кто потом придет и за вами будет это разбирать.
Конфигурация №1. Мастер-мастер «на коленке» в стиле MySQL делается вот так:
Что пугает — сколько в мире идиотов! Погуглите „Мастер-мастер MySQL репликация“ — каждая вторая ссылка вот такая. Ад и холокост.
Фокус №2 — catch-all slave — поприятнее. Никаких ненужных проверок нет — что с кого прилетает, кому попадает, и что с этим делать. За счет этого можно сделать забавные штуки типа слэйва, на который либо прицельно сливается часть данных с кучи серверов, либо прицельно сливаются все данные со всех серверов — сервер со всеми-всеми бэкапами. Но, повторюсь, репликация есть, т.е. есть некий базовый инструмент, который копирует таблицу А вместо В и все.
Ну и, наконец, фокус №3 — подменяем всякое. Вспоминаем, что репликация живет на логическом уровне, никак не связанном с физическим уровнем хранения. За счет этого можно крайне интересно чудить. Можно менять движок «на лету» с непонятными целями — вот true story, что, дескать, репликация из InnoDB баз в MyISAM таблицы просто ради того, чтобы полнотекстовый поиск работал хоть как-то. Есть креативный финт под названием „изменение схемы через репликацию“. В чем жир, понимать отказываюсь, но бывают и такие фокусы. Ну и, есть понятный и интересный режим работы под названием „параноидальный апгрейд версии через репликацию“.
В ходе доклада мы узнали:
Тем не менее, с этим адом можно жить, если хотя бы примерно понимать, как он устроен.
Основной посыл в том, что:
В 2015 году на конференции HighLoad++ Junior Андрей Аксёнов прочитал новую версию своего доклада об устройстве репликации в MySQL. Её мы тоже расшифровали и опубликовали в своём блоге.
Для чего же нужен «зонтичный» мониторинг? Если поинтересоваться историей развития информационных технологий, можно увидеть, что разрозненные средства мониторинга появились как ответ на потребности администраторов тех или иных систем. С появлением новых типов ИТ-решений возникала необходимость отслеживать их состояние и работоспособность. Администраторам были необходимы всё более эффективные средства контроля, поэтому утилиты для мониторинга постоянно совершенствовались. А затем и руководители компаний стали отчетливо осознавать, насколько важна роль информационных технологий в организации их бизнеса. Соответственно, у них тоже появилась потребность в инструментах, позволяющих увидеть и оценить не только то, в каком состоянии находятся ИТ-ресурсы в текущий момент времени, но и как их можно будет использовать для реализации новых задач — открытия офисов, разработки продуктов, вывода услуг и т.д.
Представителям бизнеса нужна в первую очередь предсказуемость: понимание того, какие ИТ-активы имеются в их распоряжении, в какой степени их сотрудники обладают необходимыми знаниями, насколько выгодно сейчас компании с практической точки зрения инвестировать в современные технологии и наращивать компетенции. Когда появились такие вопросы, стало понятно, что отдельные утилиты для мониторинга, о которых мы говорили выше, не помогут дать исчерпывающие ответы. Дополнительные скрипты и надстройки верхнего уровня над техническими утилитами администрирования тоже не позволяли справиться с этой проблемой. Поэтому разработчики начали постепенно наращивать функциональность утилит, добавлять возможности корреляции, выявления и обогащения информации, анализа статистических данных — вплоть до выявления неизвестных проблем и выяснения степени влияния ИТ-метрик на бизнес-метрики. Однако одновременно надо было решить и ещё одну глобальную задачу: интегрировать и консолидировать все разрозненные средства мониторинга, которые уже применялись в компаниях на протяжении многих лет.
В результате поиска способа решения этой задачи и появились системы «зонтичного» мониторинга ИТ-инфраструктуры, например HPE Operations Bridge, которая, по сути, представляет собой набор компонентов. Если заказчик заинтересован в чётком понимании ситуации и снижении фактора неопределённости для принятия управленческих решений, значит, он уже вполне «дорос» до внедрения «зонтичного» мониторинга и выбора соответствующего решения.
Решение HPE Operation Bridge включает в себя несколько ключевых компонентов, или модулей. Остановимся на них поподробнее. Самое главное, что нужно сделать для запуска процесса мониторинга, — собрать информацию. Поэтому сначала расскажем о компоненте, задача которого состоит в консолидации информации, поступающей от различных источников, — Operations Manager I (OMI). В базовом варианте он выполняет свое главное предназначение — консолидирует событийную информацию, приоритизирует, автоматически распределяет по группам и назначает ответственных лиц. Существует множество дополнений к OMI. Одно из них отвечает за обеспечение корреляции. Поддерживается два типа корреляции: поточная (ожидание определенных событий) и топологическая (распространение события по всей топологии инфраструктуры; работает на основе CMDB). Топологическая носит динамический характер, поэтому меняется автоматически, если, например, физическое оборудование или виртуальную среду перемещают из одного хоста в другой. Кроме того, предприятие может создавать собственные корреляционные схемы отдельных участков ИТ-инфраструктуры, а при обнаружении аналогичных участков в целевой сервисно-ресурсной модели эта схема будет автоматически распространяться и на них: на основе их взаимосвязей будет выстроена общая корреляционная схема. Это может быть очень полезно, если у компании имеется разветвлённая территориально-распределённая сеть филиалов, каждый из которых предоставляет одинаковые услуги или продукты. Если схема меняется в одном месте, корреляционные связи будут автоматически перестроены.
Добавление новых элементов в ИТ-инфраструктуру, находящуюся под «зонтиком», должно осуществляться автоматически, это упростит процесс администрирования. Поэтому следующий модуль HPE Operation Bridge — Monitoring Automation — отвечает за автоматизацию настроек мониторинга и аудита. С его помощью новый сервер или другое оборудование, а также виртуальная среда, добавленная в ИТ-инфраструктуру, смогут автоматически подключиться к периметру мониторинга сервисно-ресурсной модели, после чего на неё будут распространены заданные политики по мониторингу. Таким образом сокращается объём рутинных операций по настройке мониторинга, а предприятие получает полную картину всей схемы мониторинга ИТ-ресурсов. В случае изменения роли сервера в сервисно-ресурсной модели от ненужных политик отказываются и подключают новые. В итоге снижается количество ложных сообщений мониторинга.
Неотъемлемым элементом мониторинга ИТ-инфраструктуры является формирование отчётности. За это отвечает модуль Operations Bridge Reporter. Он даёт возможность получать сквозную отчётность по всей услуге — начиная от состояния бизнес-процесса до каждого компонента ИТ-инфраструктуры, обеспечивающей его работу. Заказчик может настроить форму предоставления этих отчётов в соответствии со своими предпочтениями. В основе Operations Bridge Reporter лежит СУБД Vertica, специально предназначенная для обработки Больших данных. Таким образом, руководитель компании может самостоятельно определить степень загруженности оборудования и принять решение, стоит ли пополнить парк устройств в случае увеличения объемов продаж или расширения услуг либо достаточно существующих ресурсов. Подобную отчётность можно получать не только по оборудованию, но и по приложениям, сетевой нагрузке и т.д.
Визуализация данных не менее важна, чем гибкая отчётность. За нее также отвечает специальный модуль — Business Value Dashboard. Он позволяет создавать информационные панели, на которые можно выводить финансовые, технические и иные материалы. Все они визуализируются и подаются в формате, наиболее подходящем для конкретного пользователя, будь это генеральный директор или рядовой сотрудник. Модуль выпущен всего два года назад, но уже есть интересные сценарии его применения. Так, европейская компания WindPark, специализирующаяся на ветряной энергетике, использует HP Operation Bridge для мониторинга работы ветряных генераторов энергии. Визуализированные данные о силе ветра, а также о количестве вырабатываемой электроэнергии компания получает посредством Business Value Dashboard. Дело в том, что HPE Operation Bridge можно использовать для мониторинга не только ИТ-ресурсов, но и производственных и инженерных систем. Разумеется, она не заменит специальные решения для контроля за инженерными системами, но успешно сопоставит информацию об их работе с данными в бизнес-приложениях.
Иногда на предприятиях происходят различные инциденты, связанные с работой ИТ-инфраструктуры, которые требуют расследования и выявления причин их возникновения. В таком случае на помощь придет еще один компонент в составе HPE Operation Bridge — Operations Analytics, отвечающий за выявление нештатных ситуаций. Его можно условно назвать «машиной времени», поскольку, используя поступившую в систему информацию, он анализирует её с точки зрения взаимосвязей и выявляет скрытые тренды. К примеру, при неполадках в системе этот модуль способен проанализировать лог-файл, найти данные, свидетельствующие о причинах сбоя, и предоставить визуализированную информацию о взаимосвязи событий, повлекших неисправности. Подобная функция очень полезна при разборе инцидентов, поскольку позволяет определить, что стало первопричиной «падения» системы. Для этого достаточно откатить «бегунок» на информационной панели назад, до начала цепочки неисправностей. Модуль Operations Analytics использует СУБД Vertica, поэтому он может работать с Большими данными.
Как известно, системные администраторы обязаны регулярно проверять работоспособность ИТ-ресурсов, за которые они несут ответственность. Модуль Operations Orchestration позволяет автоматизировать рутинные операции по проверке информационных систем, он способен полностью заменить ручную проверку или самописные скрипты.
Помимо перечисленных базовых модулей, HPE Operation Bridge включает в себя еще несколько опциональных дополнений, отвечающих за управление мощностями, сетевой мониторинг, организацию сбора данных с помощью агентов или коллекторов. Последнее необходимо в тех случаях, когда для мониторинга всех имеющихся ИТ-ресурсов заказчик использует только решения компании Hewlett Packard Enterprise. Если на нижнем уровне работают системы для ИТ-мониторинга от сторонних производителей (Zabbix, IBM Tivoli и другие), а HPE Operation Bridge применяется в качестве «зонтика», установка коллекторов не нужна. Дело в том, что HPE Operation Bridge получает из внешних систем, как минимум, топологию, события и метрики производительности.
Когда HPE Operation Bridge внедряется с нуля, самый оптимальный вариант — использование комплексного решения, обеспечивающего как сбор данных, так и аналитику. В дальнейшем это позволит компании существенно сэкономить, в том числе на обучении, а также снизить риски. По мере развития бизнеса такое решение можно наращивать. Более сложной является задача развертывания подобной системы в организации, где отдельные утилиты для мониторинга ИТ-ресурсов уже были внедрены. Поскольку сотрудники выработали определенные привычки, придется столкнуться с лоббированием интересов, разного рода опасениями или нежеланием перехода на другие решения. В таких случаях успеха можно добиться не с помощью приказа «сверху», а путем мотивирования каждого сотрудника к переходу на новые технологии. В этой связи полезно сравнить возможности и удобство старых и новых инструментов.
Раньше нередко говорили о том, что бизнес-руководителю нужен «светофор», то есть максимально простое решение. Это не совсем так. Топ-менеджеру необходимо знать несколько вещей: где возникла проблема, кто за неё несет ответственность, работают ли уже над ее устранением и когда будет восстановлена услуга или бизнес-процесс. Все эти задачи поможет решить «зонтичный» мониторинг.
[unable to retrieve full-text content]
Зачем нужна аналитика геймдизайнерам, продюсерам и другим сотрудникам, отвечающим за продуктовую составляющую, качество игры и её контент, за успешность на рынке и бизнес-показатели? В этой статье я расскажу, какие показатели статистики принято анализировать для того, чтобы предотвратить возможные проблемы и повысить выручку.[unable to retrieve full-text content]
Один из аспектов профессии разработчика — посвящение профанов в особенности процесса разработки ПО.
С. Макконнелл, Совершенный код
Полагаю публикация будет полезной:
Резюмируя положение дел на старте, можно выделить следующие балластные категории, которые представляли риск для успеха дальнейшей разработки:
В подобной ситуации вам неизбежно придётся насаждать свои, лучшие практики в команде. Для этого есть два пути: эволюционный — обучить (в первую очередь собственным примером) имеющиеся кадры. Революционный — искать более компетентных исполнителей, а значит планомерно заменять команду.
Если у вас нет в запасе лишнего человека-года, что более чем вероятно, для коммерческой разработки, то шансов плавно эволюционировать у малоопытной команды мало. Лучше сразу готовиться формировать новую команду, предъявляя к соискателям требования выше, чем было до этого. Да, увеличивая таким образом бюджет. И это не раздувание бюджета, это компенсация долга, созданного в прошлом менеджером: может в попытке сэкомить, может из-за недостатка опыта.
Перечисление технологических изменений, которые предстояло произвести, я начал с модульных тестов. Модульные тесты одновременно самая дешёвая во внедрении и сопровождении возможность для поддержания вашего проекта в порядке. И в то же время одна из самых фундаментальных. Начинать с тестов — хорошая привычка. С тестов можно начинать кодирование, использование незнакомой библиотеки или языка, постановку задачи, проверку реализации чего бы-то ни было. Тесты — это способ мышления, при котором вы, прежде чем сделать что-то, формально описываете результат. Если отточить этот навык до должного уровня, то получение любого результата не будет у вас вызывать затруднений, вы забудете про страх белого листа. И это касается не только программирования.
Это мероприятие, как прочие, не чисто техническое решение. Методологии и концепции разработки влияют на другие процессы, не забывайте об этом. Если менеджер привык руководить командой, для которой подготовка релиза сводиться к «**як-**як и в продакшн!», недостаточно настроить агент в TeamCity. Вам придётся донести до менеджера осознание того, что «нельзя просто взять и «пофиксить» это за пять минут на проде до демо». Да, это будет на первых порах доставлять дискомфорт. Менеджеру понадобиться месяц или больше, чтобы привыкнуть что деплой теперь происходит не по пинку, мгновенно, вместе с падением рабочего продакшена. Теперь это осознанная процедура, которая пусть занимает 10 минут, но гарантировано ничего не уронит, и даст предсказуемый результат на любом из серверов, сколько бы их у вас не было.
Для заказчика аргументами необходимости выделение на это ресурсов и времени послужили:
Поскольку система управления проектом — краеугольный камень процесса планирования и разработки, использование «забагованного» и нестабильного решения сказывалось на всех остальных процессах: люди ленились заводить баги в трекере (это было сложно и долго), при планировании наполнение бэклога итерации превращалось в пытку на целый день, задачи терялись, инструменты аналитики и оценки было проще не использовать, менеджеры стремились при любой возможности протолкнуть в работу что-то в обход трекера. Таким образом используемый инструмент явно наносил вред нашим процессам, согласитесь это ужасно.
Благодаря Девпрому и изобретательности привычных к нему менеджеров, я наблюдал некоторые альтернативные способы управления:
Трекер задач — не менее важен в разработке чем IDE. Это краеугольный камень процесса разработки: в нём должна начинаться и заканчиваться любая активность в проекте. Нет задачи — нет кода. Jira, возможно лучшее из решений для коммерческих организаций на рынке.
Если менеджмент не готов прислушиваться к команде разработки, шансов на восстановление — нет. Вы не можете прямо влиять на решение вышестоящего, или сменить руководство проекта. Но вы должны сделать всё возможное чтобы вас услышали, если не хотите отвечать за чужие ошибки.
Иногда ошибка действительно в генах. Тогда лучший способ — отсечь некачественный генотип от вашей популяции и заменить его свежим образцом с необходимыми доминирующими признаками.
После года рефакторинга, мне кажется что порой стоит оценивать разработчиков не большому по количеству принесённой пользы (функциональности + соответствующей кодовой базы), а по минимуму оставляемого вреда, в виде технического долга и неподдерживаемого, или не тестируемого кода. Конечно у вашего менеджера будет альтернативный взгляд на реальность. Но реальность разработки такова, что «запилить фичу» практически любой сложности для среднего разработчика не составляет труда. Гораздо важнее в средне- и долгосрочной перспективе чтобы это добавление не ухудшало архитектуру, сопровождалось не хрупкими и понятными тестами, было спроектировано с оглядкой на принципы SOLID. В этом отношении, я предпочту одно senior'a двум middle'ам и двух middle'ов четырём junior'ам. Чем длиннее дистанция, которую предстоит пройти вам и вашему продукту, тем важнее данный тезис.
Увы, собрать достаточно сильную команду в приемлемые сроки почти никогда не удаётся, даже обладая необходимым бюджетом. Квалифицированных специалистов даже в самых распространённых технологиях и фреймворках катастрофически не хватает. Построение процесса разработки на промышленном уровне поможет вам компенсировать это. Используйте возможные утилиты и техники для анализа и контроля качества кода. Поставив за отлаженный конвейер среднего или начинающего разработчика вы получите более предсказуемый результат и планомерное развитие, чем дав волю «коммитить» в мастер энтузиасту с горящими глазами. Ваша задача должна состоять в организации стабильного процесса и облегчении встраивания в него участников, а не героической ликвидации последствий не менее героических лихих кустарных решений.
Банальная истина, которой мы ежедневно забываем: люди склонны полагать свои мысли очевидными. Несмотря на средний высокий IQ в нашей отрасли, телепаты, увы, всегда в отпуске. Если вы войдёте в режим телепата и сделаете за аналитика его работу, то вы окажетесь виноваты в следующих грехах:
Идеально было бы версионировать эти файлы параллельно с исходным кодом, чтобы понимать актуальное и требуемое состояние системы. К сожалению, с большинством сложных офисным форматов версионирование по аналогии с исходными кодами в Git не применимо.
Бизнес-анализ — это процесс, который важно формализовать и отладить не меньше чем саму разработку. И поскольку в отношениях разработчик-аналитик вы являетесь своего рода потребителем, ваш долг помочь выстроить эти отношения к обоюдному удовольствию.
Другая крайность: установите минимальную оценку. Я использую 1, хотя многие оптимисты склонны давать обещания вроде «это можно сделать за 5 / 10 /15 минут». Да, безусловно есть правки, внесение которых займёт считанные минуты — не считая накладные расходы на взаимодействие с трекером, СКВ, документаций, тестами. Чтобы не огорчать менеджера тем, что «маленький» фикс занимает целый инженерный час, могу порекомендовать связанные мелкие правки объединять в одну задачу.
Другой нюанс: если в команде нет культуры тестирования, менеджер может полагать что ручное тестирование продукта — дело разработчиков. Ваша миссия показать ему простую арифметику: час разработчика как правило в N-раз дороже часа «ручного» тестировщика. За несколько полных дней тестирования имеющимися в команде разработчиками, легко сжигается зарплата зарплата выделенного тестера. Не забываем о простое разработки.
Тестирование — это процесс, который должен проходить планомерно и на регулярной основе, а не мероприятие вроде апрельского субботника. Если у вас в организации ещё нет QA, но вы знаете что это такое — приближайте его появление всеми доступными средствами. Пока нет квалифицированного приёмочного тестирования, команда разработки будет оказываться крайней во всех обнаруженных багах. Если тестирование носит нерегулярный характер, то баги будут находиться редко, зато много и не те. Это значит, что лавина аврально обнаруженных багов будет отравлять вам жизнь, и серьёзно вредить всему процессу разработки. В упомянутом проекте, выбивание штатной единицы для QA-специалиста и поиск подходящего кандидата у меня заняло около полутора лет.
Какие риски несёт нерегулярное и плохо организованное тестирование:
Ещё один нюанс, о котором стоит помнить: по мере роста продукта, стоимость внесения изменений неизбежно растёт. Всегда. Все лучшие практики и прочие примочки, о которых написано выше, лишь уменьшают коэффициент роста этой стоимости. Таким образом, если человек не имел продолжительного опыта работы с командами, которые имеют хорошо поставленные процессы, и плохо эти процессы понимающий, он может сделать следующий вывод:
— было: никакого формализма, **як и в прод, пацаны быстро делали всё.
— стало: куча формализма, CI/QA-какой-то мешает быстро «релизить», пацаны медленно стали кодить…
При этом, не имея личного опыта работы в проектах различных размеров и командах, от него ускользает, что между «было» и «стало» находятся:
Недавно мне потребовалось добавить метрику по uptime сервиса дистанционного обслуживания для расчета SLA. Статистика по вызовам API является косвенным показателем работоспособности, а нужна достоверная проверка всех функций от дозвона из внешней сети, до прохождения пользователя по всему меню обслуживания. В интернете ничего готового не видел, поэтому решил поделиться своими изысканиями.
Есть система дистанционного обслуживания – клиент может позвонить в call-центр и проверить/изменить настройки своей учётной записи без участия оператора. Для перехода по меню и управления настройками используются тональные сигналы (DTMF). АТС в свою очередь взаимодействует с ядром основной системы через API, возвращая результаты пользователю в виде голосовых сообщений.
Задача: настроить автоматизированную проверку системы (правильно отвечает на запросы/выполняет нужные команды).
Главные требования:
максимальная правдоподобность имитации пользователя: т.е. нужно именно звонить и нажимать кнопки, а не вызывать методы API в обход call-центра.
Для данной статьи упростим наш call-центр и API до безобразия: при звонке в call-центр пользователю доступна единственная услуга (клавиша 1); в ней пользователю предлагается ввести ПИН и в случае его корректности выдается статус учетной записи пользователя (ON/OFF); шаг влево или вправо – выдается сообщение об ошибке. Через API доступно три метода: GET ping (инициализация звонка), GET status (получить статус), POST status (установить статус).
Решать задачу будем с помощью Asterisk. По сути нам нужно собрать аналогичный IVR только от лица клиента: нужно описать машину состояний (ждем приветствие, ждем запроса ПИН и т.д.), и при переходе в каждое из состояний выполнять определённые действия.
Команды отправлять понятно как – call-центр ожидает тональные сигналы от пользователя – значит можно воспользоваться командой SendDTMF
и «нажимать» нужные кнопки от лица клиента.
А как изменять своё состояние? Да точно так же! Для этого немного модернизируем dialplan нашего боевого IVR'а вызовом незамысловатого макроса в ключевых местах:
[macro-robot]
exten => s,1,ExecIf($["${CALLERID(name)}"!="Robot"]?MacroExit())
same => n,Wait(1)
same => n,SendDTMF(${ARG1})
В результате в ключевых местах работы IVR, если звонок поступил от робота, в канал будет отправляться выбранная нами DTMF последовательность. Задержка в 1 секунду добавлена, чтобы наш робот успевал перейти в режим ожидания ввода.
Теперь нам пригодится возможность Asterisk отправлять совершаемый звонок в нужный нам локальный контекст – таким образом замкнём между собой IVR call-центра и нашего робота. В самом простом варианте мы можем использовать call-файл и запускать проверку, периодически копируя этот файл в /var/spool/asterisk/outgoing/
.
Процесс проверки у нас будет такой:
1. Звоним в call-центр
2. ждём, пока можно будет выбирать услугу
3. Нажимаем «1»
4. Ждём, пока можно будет вводить ПИН
5. Вводим ПИН
6. Узнаем состояние
— При первой проверке вызовом API меняем состояние на противоположное и заново проверяем состояние (переходим в п.3)
— При второй проверке убеждаемся, что состояние изменилось на противоположное
8. Если состояние изменилось, считаем проверку успешной
9. Во всех остальных случаях сообщаем об ошибке
Ниже я объединил «на одном экране» dialplan'ы обоих Asterisk'ов и показал, как передается управление/изменение состояний:
Спрятал в спойлер, т.к. рвёт экран.
# Call-file
Channel: SIP/cc_peer/ivr >----------\
Callerid: Robot |
/--< Context: robot-test |
| Extension: s |
| |
| |
[robot-test] | | [ivr]
| |
exten => s,1,NoOp(Wait for init) <--/ \-----> exten => s,1,NoOp(IVR Start)
same => n,Set(STATUS=) same => n,Answer()
same => n,WaitExten(10) same => n,GotoIf($["${CURL(localhost/ping)}"!="PONG"]?err,1)
/----------------------< same => n,Macro(robot,00)
exten => 00,1,NoOp(Init done) <------------------------------/ same => n,Background(welcome)
same => n,Wait(1) same => n(again),Background(press_1_for_status)
same => n,SendDTMF(1) ; Press 1 for status >----------------\ same => n,WaitExten(5)
same => n,WaitExten(10) \ same => n,Goto(err,1)
\
exten => 10,1,NoOp(Status check) <---------------------------\ \--------------------> exten => 1,1,NoOp(Status check) <---------------------------------\
same => n,Wait(1) \-------------------------< same => n,Macro(robot,10) |
same => n,SendDTMF(1234) ; Send pin code >----------------------------------------> same => n,Read(PIN,enter_pin,4) |
same => n,WaitExten(10) same => n,Set(STATUS=${CURL(localhost/status?pin=${PIN})}) |
same => n,GotoIf($["${STATUS}"=="ON"]?on) |
exten => _1[12],1,NoOp(Status) <--------------------------------------------------\ same => n,GotoIf($["${STATUS}"=="OFF"]?off) |
same => n,ExecIf($[${EXTEN}==11]?MSet(CURRENT=ON,NEW=OFF)) | same => n,Goto(err,1) |
same => n,ExecIf($[${EXTEN}==12]?MSet(CURRENT=OFF,NEW=ON)) |---< same => n(on),Macro(robot,11) |
same => n,GotoIf($["${STATUS}"==""]?toggle) | same => n,Background(status_on) |
same => n,ExecIf($["${STATUS}"=="${CURRENT}"]?System(echo GOOD >> /cc_check.log)) | same => n,Goto(s,again) |
same => n,ExecIf($["${STATUS}"!="${CURRENT}"]?System(echo BAD >> /cc_check.log)) \---< same => n(off),Macro(robot,12) |
same => n,Hangup() same => n,Background(status_off) |
same => n(toggle),NoOp(Toggle status) same => n,Goto(s,again) |
same => n,Set(STATUS=${NEW}) /--------------------------------------------------------------------------------------------/
same => n,Set(RES=${CURL(localhost/status,status=${NEW})}) / exten => err,1,NoOp(Error occured)
same => n,Wait(1) / /------< same => n,Macro(robot,99)
same => n,SendDTMF(1) ; Press 1 for status >--------------------/ / same => n,Playback(error)
same => n,WaitExten(10) / same => n,Hangup()
/
exten => i,1,System(echo BAD >> /cc_check.log) <---------------------------/ [macro-robot]
exten => s,1,ExecIf($["${CALLERID(name)}"!="Robot"]?MacroExit())
same => n,Wait(1)
same => n,SendDTMF(${ARG1})
В нашем примере результат выполнения проверки записывается в файл /cc_check.log
. В боевой системе вы конечно же эти результаты будете складывать в свою систему мониторинга.
В реальной системе простого CURL-запроса к API для проверки всей системы дистанционного обслуживания скорее всего не хватит, поэтому решение можно расширить для контроля звонка робота через AMI. Для этого нужно модифицировать dialplan робота, чтобы он отправлял UserEvent'ы
в AMI. Нашу демонстрационную конфигурацию можно изменить следующим образом:
[robot-test-ami]
exten => s,1,UserEvent(CC_ROBOT_WAIT_INIT,RobotId: ${RobotId})
same => n,Set(STATUS=)
same => n,WaitExten(10)
exten => 00,1,UserEvent(CC_ROBOT_WAIT_SERVICE,RobotId: ${RobotId})
same => n,Wait(1)
same => n,UserEvent(CC_ROBOT_WAIT_SERVICE,RobotId: ${RobotId},Data: Will press 1 now)
same => n,SendDTMF(1) ; Press 1 for status
same => n,WaitExten(10)
exten => 10,1,UserEvent(CC_ROBOT_WAIT_PIN,RobotId: ${RobotId})
same => n,Wait(1)
same => n,UserEvent(CC_ROBOT_WAIT_PIN,RobotId: ${RobotId},Data: Will send pin (1234) now)
same => n,SendDTMF(1234) ; Send pin code
same => n,WaitExten(10)
exten => _1[12],1,UserEvent(CC_ROBOT_STATUS_CHECK,RobotId: ${RobotId})
same => n,ExecIf($[${EXTEN}==11]?MSet(CURRENT=ON,NEW=OFF))
same => n,ExecIf($[${EXTEN}==12]?MSet(CURRENT=OFF,NEW=ON))
same => n,UserEvent(CC_ROBOT_STATUS_CHECK,RobotId: ${RobotId},Data: Current status is '${CURRENT}')
same => n,GotoIf($["${STATUS}"==""]?toggle)
same => n,ExecIf($["${STATUS}"=="${CURRENT}"]?UserEvent(CC_ROBOT_RESULT,RobotId: ${RobotId},Data: GOOD))
same => n,ExecIf($["${STATUS}"!="${CURRENT}"]?UserEvent(CC_ROBOT_RESULT,RobotId: ${RobotId},Data: BAD))
same => n,Hangup()
same => n(toggle),UserEvent(CC_ROBOT_STATUS_CHECK,RobotId: ${RobotId},Data: Need to toggle state)
same => n,Set(STATUS=${NEW})
same => n,UserEvent(CC_ROBOT_TOGGLE,RobotId: ${RobotId},Data: ${CURRENT})
same => n,Wait(2)
same => n,SendDTMF(1)
same => n,WaitExten(10)
exten => i,1,UserEvent(CC_ROBOT_RESULT,RobotId: ${RobotId},Data: BAD)
Для взаимодействия с таким dialplan'ом необходимо подключиться к Asterisk'у, с которого инициируется звонок робота, через AMI, сделать Originate
и дальше действовать в соответствии с приходящими UserEvent'ами
. Пример реализации скрипта нашей demo-проверки на Python:
#!/usr/bin/python
import os
import time
import string
import random
import sys
import requests
from asterisk.ami import Action, AMIClient
seconds_to_wait = 30
test_result = 'unknown'
host = 'localhost' # Asterisk with AMI and test dialplan
user = 'robot' # AMI user
password = 'MrRobot' # AMI password
call_to = 'Local/ivr' # Call-center
context = { # Robot dialplan context
"context": "robot-test-ami",
"extension": "s",
"priority": 1
}
def toggle_state(new_state):
print 'Will try to toggle state to {}'.format(new_state)
r = requests.post('http://localhost/status', data = {'status':new_state.upper()})
print 'Done! Actual state now: {}'.format(r.text)
def event_notification(source, event):
global test_result
keys = event.keys
if 'RobotId' in keys:
if keys['RobotId'] == robot_id: # it's our RobotId
if 'Data' in keys:
data = keys['Data']
else:
data = 'unknown'
if 'UserEvent' in keys:
name = keys['UserEvent']
else:
name = 'unknown'
if name.startswith('CC_ROBOT'):
print '{}: {}'.format(name, data)
if name == 'CC_ROBOT_TOGGLE':
if data.lower() in ['on','off']:
if data.lower() == 'on':
toggle_state('off')
else:
toggle_state('on')
else:
print 'Unknown state {}'.format(data)
if name == 'CC_ROBOT_RESULT':
test_result = data
# Generate uniq RobotId to distinguish events from different robots
robot_id = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(8))
print 'Current RobotId: {}'.format(robot_id)
# Action to enable user events
aEnableEvents = Action('Events', keys={'EventMask':'user'})
# Action to originate call
aOriginateCall = Action('Originate',
keys={'Channel':call_to, 'Context':context['context'], 'Exten':context['extension'], 'Priority':context['priority'], 'CallerId':'Robot'},
variables={'RobotId':robot_id}
)
# Init AMI client and try to login
client = AMIClient(host)
# Register our event listener
client.add_event_listener(event_notification)
try:
future = client.login(user, password)
# This will wait for 1 second or fail
if future.response.is_error():
raise Exception(str(future.response.keys['Message']))
except Exception as err:
client.logoff()
sys.exit('Error: {}'.format(err.message))
print 'Spawned AMI session to: {}'.format(host)
try:
# Try to enable user events coming
future = client.send_action(aEnableEvents,None)
if future.response.is_error():
raise Exception(str(future.response.keys['Message']))
print 'Logged in as {}'.format(user)
# Try to originate call
future = client.send_action(aOriginateCall,None)
if future.response.is_error():
raise Exception(str(future.response.keys['Message']))
print 'Originated test call'
except Exception as err:
client.logoff()
sys.exit('Error: {}'.format(err.message))
print 'Waiting for events...'
# Wait for events during timelimit interval
for i in range(seconds_to_wait):
time.sleep(1)
# If test_result is changed (via events), then stop waiting
if test_result != 'unknown':
break;
else:
client.logoff()
sys.exit('Error: time limit exceeded')
# Logoff if we still here
client.logoff()
print 'Test result: {}'.format(test_result)
Действия выполняются аналогичные описанному выше сценарию с call-файлом, но звонок инициируется по команде скрипта и вызов API тоже осуществляется из скрипта при получении события CC_ROBOT_TOGGLE
. Результат проверки приходит вместе с событием CC_ROBOT_RESULT
.
В итоге мы решили задачу в рамках требуемых условий: звоним и «нажимаем кнопки», делаем проверки в боевом dialplan'е (выдавая тоны по ходу звонка, если звонит робот).
Удачного тестирования!
Комментарии (0)