...

суббота, 28 января 2017 г.

[Из песочницы] Subversion vs. Git: Развенчивание мифов о развенчивании мифов

[Из песочницы] Подмена провайдером DNS-запросов

[Из песочницы] Практика метапрограммирования на C++: бинарное дерево поиска на этапе компиляции

[Из песочницы] Немного о Swift runtime или куда пропал NSObject

iOS: Работа с галереей (Photos framework). Часть 2

Привет, Хабр! В этой статье я расскажу про работу с видео, live-фото и отслеживание изменений в галерее с помощью Photos framework. Для лучшего понимания статьи рекомендую ознакомиться с предыдущей статьей.

Работа с видео


Для того, чтобы загрузить видео из галереи необходимо задать параметры(опции) запроса — PHVideoRequestOptions.
let options = PHVideoRequestOptions()
// Указываем, что PHImageManager может загружать видео из iCloud
options.isNetworkAccessAllowed = true
// Выбираем автоматическое качество видео
options.deliveryMode = .automatic
options.progressHandler = { progress, _, _, _ in
    // Здесь можно отобразить текущий прогресс загрузки
}


Далее эти параметры вместе с объектом PHAsset(см. предыдущую статью) передаем в следующую функцию:
PHImageManager.default().requestPlayerItem(forVideo: asset, 
                                           options: options, 
                                           resultHandler: { playerItem, info in
    // Можем использовать объект playerItem
}


После того как получили playerItem, можно его воспроизвести. Для проигрывания видео используется класс AVPlayerLayer и AVPlayer:
let player = AVPlayer(playerItem: playerItem)
let playerLayer = AVPlayerLayer(player: player)

// Конфигурируем playerLayer и добавляем его во view
 
player.play() 

Работа с live-фото


Работа с live-фото и видео похожи. Для начала мы также задаем параметры запроса, только теперь с помощью класса PHLivePhotoRequestOptions:
let options = PHLivePhotoRequestOptions()
options.isNetworkAccessAllowed = true
options.deliveryMode = .highQualityFormat
options.progressHandler = { progress, _, _, _ in
    // Здесь можно отобразить текущий прогресс загрузки
}


Затем загружаем live-фото:
PHImageManager.default().requestLivePhoto(for: asset, 
                                           targetSize: targetSize,
                                           contentMode: .aspectFit,
                                           options: options,
                                           resultHandler: { livePhoto, info in
    // Можем использовать объект livePhoto
})


После получения объекта livePhoto можем отобразить его в специальном компоненте PHLivePhotoView. Этот компонент очень похож на UIImageView. Он предназначен для отображения live-фото.
livePhotoView.livePhoto = livePhoto

// Показываем live-фото
livePhotoView.startPlayback(with: .full)

Отслеживание изменений в галерее


Photos framework позволяет следить за изменениями, произошедшими как с альбомами так и с отдельными фото или видео. Чтобы начать отслеживать изменения нужно добавить обсервер в PHPhotoLibrary:
PHPhotoLibrary.shared().register(observer)


Обсервер должен реализовывать протокол PHPhotoLibraryChangeObserver, в котором есть всего один метод — photoLibraryDidChange(_ changeInstance: PHChange). Параметр changeInstance — это объект, который содержит список изменений, произошедших в галерее.
В качестве примера покажу как обновить collectionView в соответствии с произошедшими изменениями.
func photoLibraryDidChange(_ changeInstance: PHChange) {
    // Получаем изменения списка PHAsset (переменная fetchResult)
    // Если изменений не было, то выходим из функции
    guard let changes = changeInstance.changeDetails(for: fetchResult) else { return }

    // Уведомление могло прийти не из main queue, поэтому перестраховываемся
    DispatchQueue.main.sync {
        // Присваиваем новый список PHAsset
        fetchResult = changes.fetchResultAfterChanges
        // Проверяем - можно ли получить список изменений по группам
        // (удалены, добавлены, изменены, перемещены)
        if changes.hasIncrementalChanges {
            // Анимируем изменения в collectionView
            collectionView.performBatchUpdates({
                // Тут важно соблюсти порядок изменений:
                // delete, insert, reload, move
                if let removed = changes.removedIndexes where removed.count > 0 {
                    collectionView.deleteItems(at: removed.map { IndexPath(item: $0, section:0) })
                }
                if let inserted = changes.insertedIndexes where inserted.count > 0 {
                    collectionView.insertItems(at: inserted.map { IndexPath(item: $0, section:0) })
                }
                if let changed = changes.changedIndexes where changed.count > 0 {
                    collectionView.reloadItems(at: changed.map { IndexPath(item: $0, section:0) })
                }
                changes.enumerateMoves { fromIndex, toIndex in
                    collectionView.moveItem(at: IndexPath(item: fromIndex, section: 0),
                                            to: IndexPath(item: toIndex, section: 0))
                }
            })
        } else {
            // Просто перезагружаем collectionView
            collectionView.reloadData()
        }
    }
}

Заключение


Приведенный в статьях код не является идеальным. Главная его задача — показать механизм работы Photos framework. Если что-то было непонятно — спрашивайте в комментариях. Возможно, немного позже сделаю на github репозиторий с проектом, где соберу весь код вместе.
Спасибо за прочтение.

Комментарии (0)

    Let's block ads! (Why?)

    [Из песочницы] Вложенные логические выражения

    Правильное обнаружение проблем с помощью Zabbix

    Алексей Владышев

    Алексей Владышев ( alexvl )


    Меня зовут Алексей Владышев, я являюсь создателем Zabbix, и в данный момент я отвечаю за его архитектуру и roadmap.

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

    Что такое Zabbix? Если вы зайдете на нашу страницу, вы увидите, что Zabbix – это Enterprise level Open Source monitoring solution, т.е. это открытое решение уровня enterprise для мониторинга. Я понимаю слово «enterprise» таким образом, что решение должно легко интегрироваться в существующую инфраструктуру. Т.е. если у вас есть большое предприятие, вы уже используете какие-то продукты – Helpdesk, Configuration Management, еще что-то – система должна легко интегрироваться.


    Что отличает Zabbix от других продуктов? Я думаю, что большое отличие и достаточно большое преимущество заключается в том, что Zabbix – открытый продукт, это реально open source, т.е. у нас нет никаких закрытых версий, мы 24/7 строим продукт и отдаем его бесплатно. Пожалуйста, заходите на нашу страницу и используйте наш продукт. Есть и другие преимущества. Я не в целях рекламы, а просто в образовательном смысле – чем мы отличаемся? Zabbix является решением «все в одном», т.е. визуализация, обнаружение проблем, сбор информации различными способами – все это есть в Zabbix, вам не нужно строить вашу систему мониторинга из разных отдельных блоков.

    Как работает Zabbix? Для тех, кто еще не очень представляет, мы собираем данные. Zabbix-сервер – это ядро Zabbix, в котором вся логика. Мы собираем данные различными способами. Эти данные, информация складывается в базе данных, история, потом используется информация для визуализации, и ядро занимается в том числе и анализом потока информации. Мы обнаруживаем проблемы и каким-то образом реагируем на проблемы. Есть различные виды реакции на проблемы.

    Что касается сбора данных, какие проблемы можем отлавливать? Это проблемы связанные с производительностью, с доступностью – доступен сервис или нет. Мы сразу же можем определить, насколько быстро работает система. Это проблемы с производительностью, с целостностью данных. Если у нас изменился какой-то конфигурационный файл или, может быть, image нашей системы, которую мы деплоим в cloude. Изменилась чек-сумма – Zabbix вам тут же скажет, что изменился наш файл.

    Zabbix также немножко покрывает область бизнес-level мониторинга, т.е. фактически мы можем обнаруживать проблемы на различных уровнях нашей IT-инфраструктуры:

    • это уровень железа – что-то произошло с какой-то железякой, мы тут же об этом узнаем,
    • уровень операционной системы,
    • сеть, в основном это мониторинг через SNMP,
    • виртуальный уровень, т.е. «из коробки» мы предлагаем, например, мониторинг WMware инфраструктуры, vCenter и vSphere,
    • дальше идет middleware,
    • бизнес-приложения.

    Т.е. если мы знаем, как обнаруживать проблемы в нашем приложении, и Zabbix это не поддерживает «из коробки», можно к Zabbix это прикрутить с помощью скриптов.

    Каким образом можно собирать данные? Есть два способа: Pull – когда мы вытягиваем данные из устройства; и Push-метод – это когда само устройство сообщает нам, выдает нам информацию. Pull – это обычные проверки, допустим, SNMP, либо сервисные проверки, когда мы подключаемся к сервису, скажем, SSH, и если веб-страница корректно отвечает, значит все в порядке. Push – это в основном активный агент, различного вида Traps и SNMP Traps. Сбор данных работает через Push-метод.

    Что касается Zabbix агента. Агент – это такая программа, которую можно установить на Linux, Windows или другие операционные системы. Я хотел поговорить об активном режиме. Какие у него преимущества в плане обнаружения проблем? Если пассивный режим – это мы подключаемся к агенту, запрашиваем информацию, агент возвращает нам, скажем, загрузку процессора, то в активном режиме сам агент отправляет данные на сервер мониторинга. Здесь такие преимущества:

    • это работает быстро, по крайней мере, быстрее, т.к. сервер не должен заниматься постоянным опрашиванием устройств, сами устройства отправляют информацию,
    • более безопасно с точки зрения агента, потому что агент не должен слушать никакие TCP-соединения или сетевые соединения.
    • небольшое преимущество, то, что нам сегодня важно – в активном режиме есть буферизация. Если Zabbix-сервер недоступен по каким-то причинам, допустим, мы делаем апгрейд Zabbix-сервера, у нас downtime несколько минут, то данные будут накапливаться на стороне агента. Как только мы сервер запускаем, данные тут же отправятся на сторону сервера, и мы их сможем обрабатывать.

    Вообще, стоит вопрос: как часто опрашивать устройство, как часто собирать данные?

    Конечно, хотелось бы опрашивать данные раз в секунду, любые устройства опрашивать раз в секунду, собирать какие-то Гбайты информации. Как только проблема возникает, тут же, через секунду, мы понимаем, что у нас есть проблема. Но тут есть такой принцип: мы не должны навредить системе. Как в квантовой физике – наблюдатель влияет на процесс. Если мы зачем-то наблюдаем, мы уже влияем на процесс. То же самое с системой мониторинга. Если мы начинаем собирать слишком много информации слишком часто, мы системой мониторинга влияем на ту систему, которую мы мониторим. Поэтому здесь нужно выбирать золотую середину – с одной стороны не навредить, с другой стороны мы должны обнаружить проблему как можно быстрее.

    В Zabbix есть несколько вариантов. Во-первых, мы можем мониторить какое-то устройство или метрику раз в n секунд или раз в минуту мы запускаем какую-то проверку. Есть возможность использовать различную частоту в зависимости от интервала времени. Предположим, нам не важно, что система не работает в нерабочее время, нам важно, чтобы система работала в рабочее время. Тогда мы говорим Zabbix: «Проверяй, пожалуйста, рабочие станции с 9:00 до 18:00» и все. Есть такая возможность.

    В Zabbix 3.0 появится новая возможность проверки типа «готово к работе», «ready for business». Это означает, что мы сможем сказать Zabbix: «Сделай эти проверки в конкретное время». Допустим, открывается филиал банка в 9 часов утра, а в 8:55 у нас есть некий чек-лист, и мы проверяем, что филиал действительно готов к работе, что с workstation’ами все в порядке, что сервисы, от которых мы зависим, в порядке, что удачно закрылся предыдущий и открылся новый финансовый день и т.д. Т.е. начиная с Zabbix 3.0 можно запускать проверки в определенное время. Если мы скажем Zabbix: «Проверь, пожалуйста, этот сервис в 8:55», Zabbix проверит сервис в 8:55. Также будет возможность совершать эти проверки с периодичностью, например, в 9:00, в 10:00, в 11:00 – ровно в это время, как мы указали.

    Как в потоке информации отлавливать проблемы? Идет огромный поток информации, Zabbix совершает в секунду тысячи, десятки тысяч различных проверок производительности, доступности, и мы предлагаем решение – триггеры. Если вы используете Zabbix, вы знаете, что такое триггеры. Это, по сути, описание проблемы, т.е. мы формулируем проблему. Сначала, может быть на своем языке, а потом мы это пытаемся перевести в формат триггерных выражений.

    Простое триггерное выражение, например, загрузка процессора на сервере больше 5:

    Это простейший триггер. Триггеры могут быть намного более сложные, мы можем использовать арифметически-логические операции. Здесь очень важно то, что мы не ограничены одной метрикой: мы анализируем не только одну метрику, мы можем анализировать практически все, т.е. брать данные с различных устройств, использовать их в описании проблемы. Также Zabbix’у для анализа доступна как real-time информация, которую мы только-только получили, так и информация, которая у нас есть в базе данных, т.н. историческая информация.

    С чего обычно начинают новички, неофиты от мониторинга? Попадает им в руки Zabbix, такой мощный инструмент для того, чтобы обнаруживать проблемы, и сразу хочется узнать все обо всем. Хочется, как только что-то где-то произошло, сразу получить e-mail либо SMS. К чему это приводит? Приводит к тому, что, казалось бы, вроде бы все в порядке, вот система перегружена, создается триггер «текущая загрузка процессора больше 5», для доступности создаются триггеры, типа «сервис http недоступен», т.е. последняя проверка сказала, что веб-сервер недоступен, и мы считаем, что у нас есть проблема. Почему так делать не очень хорошо?

    Такие триггеры, такие описания проблем слишком чувствительны. Загрузка процессора превысила 5, и мы считаем, что система перегружена, подключаемся через SSH, запускаем TOP, а загрузка процессора уже 4.5. Или Zabbix нам сказал, что веб-сервер не работает, на самом деле мы подключились, а он работает. Так что же происходит?

    Такие вещи, эта чувствительность приводит к привыканию. Система мониторинга генерирует слишком много сообщений, и мы перестаем на них реагировать, потому что это не совсем реальные проблемы.

    Просто приведу пример. В Латвии какое-то время назад был такой случай – в супермаркете сработала сигнализация, и люди, как обычно, на это не среагировали. Персонал, который должен вывести всех людей из супермаркета, подумал,: «А, сигнализация сработала, ничего страшного». Те люди, которые находились в супермаркете, подумали: «Ну, ладно, я завершу покупки и выйду». На самом деле это был предвестник того, что обрушилось все перекрытие, и погибло очень много людей.

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

    Что же делать? Здесь важно правильно формулировать проблему и понимать суть. Нужно задаться вопросом: какую действительно проблему мы пытаемся описать. Что значит «система перегружена» или что означает «сервис недоступен»?

    Система перегружена, скажем, загрузка процессора больше 5 (я сегодня сознательно применяю очень простые примеры, т.е. примеры могут быть более сложными, но это такой простой пример). Система перегружена – это не то, что вот сейчас у нас загрузка процессора = 5, а все-таки система должна быть нагружена в течение какого-то периода времени. И здесь мы можем анализировать историю. Т.е. скажем, последние 10 минут загрузка процессора была больше 5, тогда да, действительно система перегружена, может быть, какой-нибудь процесс завис, или еще что-то произошло.

    В случае доступности сервиса что мы можем сделать? То же самое – посмотреть в историю: «А как было до этого?». Если последние 5 минут система нам отвечала, что http недоступен, то, наверное, http недоступен.

    Тут еще важно понимать суть проверки. Как мы проверяем, что веб-сервер доступен? Проверяем мы следующим образом: мы делаем TCP-соединение, get-запрос, потом отсчитываем результат, и здесь что-то может пойти не так. Например, произошел timeout. Да, timeout произошел, но означает ли это, что веб-сервер не работает? Нет, не обязательно. Но если три раза у нас произошел timeout, как здесь, (#3) – это означает, что три последние проверки ответили, что сервер недоступен, тогда уже очень высока вероятность, что наш веб-сервер действительно недоступен, либо проблемы с сетью, например.

    Поэтому мы все-таки должны смотреть на историю, т.е. основывать свое решение на последнем значении – это не совсем правильно в большинстве случаев.

    О чем мне еще бы хотелось поговорить? Решение проблемы – не эквивалентно ее отсутствию. Что это означает?

    Например, не хватает нам свободного места на диске (опять же, беру очень простые примеры). Как можно это сформулировать? Скажем, свободного места на диске осталось менее 10%. Вопрос в том: как только у нас появится 11% свободного места на диске, эта проблема решилась или не решилась? Мне кажется, что проблема все-таки не совсем решилась. Или у нас там 10% плюс 1 байт.

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

    Свободного места на диске, скажем, меньше 10%, но я хочу выйти из этой проблемной ситуации только в том случае, когда у меня будет свободного места на диске 30%, не 10%, а 30%. Только в этом случае я на 100% уверен, что кто-то что-то сделал, что кто-то удалил какие-то файлы, системный администратор подключился, расширил нашу файловую систему, увеличил ее размер, в этом случае проблемы больше нет.

    Для этого мы используем гистерезис. Гистерезис, по сути, означает следующее. Предположим, что мы смотрим на фотографию машины. Машина будто бы стоит, но мы не знаем ее состояния – она может стоять, может двигаться вперед, назад… Не зная историю, что до этого происходило, мы не можем сказать, в каком состоянии находится машина сейчас. Т.е. мы должны все-таки посмотреть немножечко на историю, что до этого произошло. И используя различные условия для входа в состояние проблемы и выхода из состояния проблемы, мы избавляемся от т.н. флаппинга.

    Что такое флаппинг? Флаппинг – это когда используются очень простые описания проблем. Напрмер, загрузка процессора больше 5, или свободного места на диске меньше 10. Что произойдет, если у нас то 10%, то 8%, то 11%, то 7%? Это флаппинг. Проблема возникает, пропадает, возникает, пропадает. Мы приходим утром, открываем свой inbox и видим массу сообщений от Zabbix о том, что проблема была, проблема исчезла, была, исчезла, была, исчезла… К чему это приводит? К тому, что мы уже системе мониторинга не доверяем. Вот это флаппинг. И если мы используем различные условия для входа в проблему и выхода из проблемы, то мы избавляемся полностью от флаппинга.

    Несколько примеров:

    Система перегружена, загрузка процессора за последние 5 минут превышала тройку, но мы выходим из проблемы только тогда, когда последние 2 минуты загрузка процессора была меньше единицы. Здесь используется немножко другая логика, здесь первое условие – это вход в проблему, второе условие – мы остаемся в состоянии проблемы, пока есть хотя бы одно значение больше единицы.

    Нет свободного места на диске. Мой пример – меньше 10%. Проблема, потому что это действительно проблема, мы тут на 100% уверены. И выходим из состояния проблемы, когда в течение последних 10-ти минут у нас было более 30% свободного места на диске. Потому что системный администратор может начать, скажем, двигать какие-то большие файлы с одной файловой системы в другую, копировать что-то. Если в течение 10-ти минут у нас есть стабильная ситуация, что свободного места на диске более 30%, мы считаем, что проблема пропала.

    То же самое, SSH сервер не доступен. Три последние проверки сказали нам, что сервер недоступен, мы считаем, что сервер недоступен. Но восстановиться мы хотим только тогда, когда 10 последних проверок сказали нам, что сервер наконец-то доступен. Что происходит в реальности? У нас проблемы с сервисом, начинают его чинить, перезапускать, может быть, откатываться на какие-то старые версии, то система работает, то не работает. И вот здесь мы, как раз, описываем этот период доступности. 10 последних проверок все в порядке, вот сейчас мы точно можем пойти спать, потому что мы знаем, что сервисом все хорошо.

    Аномалии. Вообще, что такое аномалии? Аномалия – это греческое слово, означающее отклонение от нормы, т.е. есть некая норма, и как только мы отклоняемся от нормы, это считается аномалией.

    Как можно обнаружить аномалию? В Zabbix есть такая функциональность, когда мы за норму берем состояние системы в прошлом и сравниваем с тем, что есть в данный момент. Допустим, если средняя загрузка процессора за последний час превышает вдвое загрузку процессора за тот же период ровно неделю назад… Ни вчера, потому что если мы сравниваем со вчерашним днем, это можем быть сравнение воскресения с понедельником, пятницы с субботой – не совсем корректно. Скажем, если сегодня четверг, то мы сравниваем с тем, что было в прошлый четверг. Считаем, что это норма – то, что было в прошлый четверг. Если сегодня мы видим, что нагрузка процессора увеличилась, то Zabbix может отправить сообщение администраторам и сказать: «Эй, ребята, посмотрите, пожалуйста, какие-то у нас есть тут проблемы». Возможно, проблемы, не обязательно, конечно. Потому что это информативное сообщение. Может быть, сделали апгрейд какой-либо системы, появились регрессии в плане производительности, может быть еще что-то… Это важно.

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

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

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

    В Zabbix мы просто определяем зависимости одной проблемы от другой. Скажем, CRM система зависит от базы данных, зависит от сети, зависит от какого-то middleware, еще чего-то. Сама база данных зависит от места на диске. В случае, если у нас нет места на диске, мы получим единственное сообщение о том, что у нас нет места на диске.

    Как реагировать на проблемы?

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

    Иногда мы знаем, что у нас есть приложение, которое поедает память. Поедает память и все, решения у нас нет, и это приложение просто надо время от времени перезапускать. Иногда возникает ситуация с тем, что мы получили какой-то kernel panic. Вот, время от времени получаем kernel panic, нам нужно перезапускать этот сервер. Это все можно поручить системе мониторинга. Т.е. в случае kernel panic по IPMI мы можем автоматически на уровне железа делать reset. В случае с сервисами мы можем перезапускать наши сервисы также в автоматическом режиме.

    Реакция может быть просто сообщение пользователю или группе пользователей. Также реакцией может быть открытие каких-то тикетов в Helpdesk системе. Т.е. реагировать можно абсолютно по-разному, и конечно, способ реакции зависит от того, насколько серьезная проблема. Если проблема не очень серьезная, не стоит тут же отправлять SMS сообщение в 4 часа ночи, если эту проблему все равно будут решать в 10 часов утра на следующий день.

    Еще есть возможность эскалировать проблемы. Т.е. возникла проблема, и дальше мы задаемся вопросом: что с этим делать, кого оповещать? Конечно, мы отправляем сообщение администраторам, администраторы что-то делают, но, я думаю, что очень эффективный вариант, это когда проблема эскалируется на уровень бюрократического дерева, на более высокий уровень, менеджеру, может быть, еще менеджеру, может быть, дальше – CEO. И CEO не будет получать сообщения вида «отвалился какой-то там диск volume, что-то там еще», CEO получит от Zabbix просто сообщение вида «последние полчаса мы теряем деньги». И все, и тогда все понятно.

    Можно реагировать сразу – возникла проблема, мы тут же что-то делаем. Можно реагировать с задержкой, т.е. возникла проблема, ну, давайте подождем еще, может быть, 5 минут, и через 5 минут мы отправим сообщение. И тут еще важно следующее. Оповещение, если автоматика не сработала. Предположим, у нас есть сервис, он не работает, мы сконфигурировали Zabbix, чтобы этот сервер автоматически перезапускался. Как мы настраиваем эскалацию в этом случае? Настраиваем следующим образом: сначала мы перезапускаем сервис, и через 10 минут, если проблема все еще существует, отправляем оповещение пользователям. Либо идет дальше эскалация на более высокие уровни.

    И повторные оповещения – это тоже очень важно, особенно для серьезных проблем. Какие-то вещи можно легко пропустить, можно пропустить SMS, можно пропустить e-mail сообщение, но для важных проблем надо все-таки использовать повторные сообщения. Это тоже очень хороший стимул, мы видим, что да, проблема существует, и нам надо работать, чтобы решить ее как можно раньше.

    И, может быть, такой итог. Что тут важно?

    • История. Мы основываем свое решение не только на real-time мониторинге, на оперативной информации, которую мы только-только получили, но и смотрим в историю. В историю нужно обязательно смотреть. Это важно.
    • Отсутствие проблемы – не есть ее решение. Я привел несколько примеров, но на самом деле, вы многие используете Zabbix, посмотрите критическим взглядом на те триггеры, которые у вас сейчас есть. Комбинация анализа истории с гистерезисом, с разными условиями для проблемы и для выхода из проблемы – она на самом деле творит чудеса. Получается такое очень-очень умное обнаружение проблем. Нужно обязательно использовать эту функциональность.
    • С аномалиями, опять же, мне трудно сказать, принесет ли это какую-то практическую пользу, но, по крайней мере, стоит попробовать. Что касается аномалии, в Zabbix 3.0, возможно, мы реализуем baseline monitoring. Что это означает? Baseline – это некая норма, т.е. этот baseline будет высчитываться из трендов. Если сейчас все триггеры работают с историей, то для baseline мониторинга мы будем брать информацию из трендов, из тенденций, и будет возможность, например, сравнивать поведение системы в рабочее время на прошлой неделе с поведением системы на этой неделе или сегодня. Т.е. мы что-то будем брать за основу, за нормальную ситуацию и сравнивать с тем, что есть сейчас. Это такой статистический анализ, основанный на тенденциях, на трендах.
    • Автоматическое решение проблем. Наверное, у каждого из вас, если вы используете систему мониторинга, есть такой класс проблем, про который вы знаете, что эта проблема произойдет рано или поздно, и с этим ничего нельзя сделать. Я привел примеры каких-то случайных падений операционной системы – мы знаем, что такая проблема есть, она может произойти в любое время, соответственно, автоматическое решение проблем – это хорошее решение.
    • И эскалируем проблемы. Отличный стимул для администраторов, если мы делаем эскалирование. Эскалирование не означает, что вот есть администратор, начальник, начальник начальника, начальник начальника начальника… Эскалирование – это означает, что мы сможем среагировать на проблему сразу одним способом, дальше попытаться, может быть, автоматически решить ее другим способом, и дальше, может быть, через 5 минут, если проблема все еще существует, попытаться решить ее следующим способом. Сначала мы перезапускаем сервис, проблема все еще существует, скажем, exchange не завелся с первого раза, что мы тогда можем сделать? Мы можем перезапустить сервер на физическом уровне и тогда уже смотреть, что произойдет.

    Контакты


    » alexvl
    » alex@zabbix.com
    » twitter
    » Zabbix
    Этот доклад — расшифровка одного из лучших выступлений на профессиональной конференции по эксплуатации и devops RootConf.

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

    Ну и главная новость — мы начали подготовку весеннего фестиваля "Российские интернет-технологии", в который входит восемь конференций, включая RootConf.

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

    Комментарии (0)

      Let's block ads! (Why?)

      [Перевод] Борьба с читерами в онлайн-играх: 22 «нужно» и «нельзя»

      image

      Почти невозможно найти успешную многопользовательскую онлайн-игру (кроме тех, в которые играют только друзья разработчика), в которой нет читеров. Другими словами, если в вашей публичной игре нет читеров, она или недостаточно популярна, или распознавание мошенников работает не слишком хорошо. Во всех остальных случаях вам придётся иметь дело с читерством. Изучите список шагов которые НУЖНО и НЕЛЬЗЯ совершать (подробное обсуждение темы читерства приведено в моей трёхтомной книге, см. примечание в конце статьи) при борьбе с мошенничеством в играх.

      1. Добавьте в условия использования своё право блокировать читеров


      Нужно не просто добавить это право, но и указать, что такие решения принимаются «на собственное усмотрение». Однако здесь могут возникнуть трудности:
      • Примечание: я не юрист, и эту статью не нужно считать юридическим советом. Лучше проконсультируйтесь со своим юристом.
      • Это сложная юридическая проблема, решение которой зависит от страны или территории пребывания. НЕ принимайте решений по этому вопросу самостоятельно, узнайте у юриста (лучше, чтобы он был из вашей страны), будут ли ваши условия использования считаться действительным договором в суде. На GDC 2016 я разговаривал с человеком из одной крупной компании. Самой большой сложностью для них являлись юридические проблемы с баном обнаруженных читеров.
      • Смысл здесь прост — иметь возможность банить игроков, если они жульничают. Однако нужно сформулировать условия использования таким образом, чтобы было ясно, что вам не нужно доказывать факт жульничества в суде, иначе преследование читеров становится слишком дорогой затеей. Самый важный юридический вопрос здесь в «бремени доказывания» — должны ли вы доказывать, что читеры мошенничают, или обвинённые читеры должны доказывать свою невиновность. Как вы понимаете, последнее гораздо выгоднее для вас и хуже для читеров. Я уже упомянул выше, что один из возможных способов решения этой проблемы — вставка оговорки «такие решения принимаются на наше собственное усмотрение». Однако нужно обязательно проверить действительность такой оговорки в соответствующей юрисдикции.
      • Если это возможно, лучше всего иметь возможность блокировки игроков без возврата средств, чтобы жульничать было невыгодно. Но если это невозможно, то блокировка с возвратом денег НАМНОГО лучше, чем невозможность блокировать читеров вообще.
      • НЕ слишком волнуйтесь о жалобах игроков на такие формулировки в условиях использования. Большинство игроков всё равно не читает условия, и при сравнении условий разных игр они всё равно мало отличимы друг от друга для широкой публики. Однако НЕ пытайтесь злоупотреблять этой формулировкой, иначе у вас очень быстро возникнут проблемы.

      2. Если вы используете движок защиты стороннего изготовителя, НУЖНО добавить поверх него как можно больше своей защиты (или, что ещё лучше, объединить стороннюю и свою защиту)


      Конечно, сторонние разработчики движков защиты часто знают, что делают, но в условиях взлома систем с принципом security-by-obscurity такие движки становятся мишенью для всего хакерского сообщества. Поэтому добавление защиты сверху значительно улучшает ваше положение.

      3. НУЖНО бороться с читерами с самого начала


      Как только появится сообщество читеров, делающих деньги на продаже читов к вашей игре, любое действие с вашей стороны будет вызывать ГОРАЗДО большее сопротивление. Другими словами, НЕЛЬЗЯ кормить читеров.

      4. НУЖНО использовать полномочный сервер


      Это действительно очень важно. Более того, НУЖНО переместить всю логику на сервер. В идеальной ситуации в клиенте вообще не должно приниматься решений. Поскольку это не всегда возможно на 100% (в основном потому, что игры имеют очень высокий темп) НУЖНО бороться против каждого решения, принимаемого в клиенте. Чем больше расчётов выполняется в клиенте, тем больше возможностей у атакующего. Если вы не будете агрессивно переносить всё на серверную сторону, это постепенно станет Очень Большой Проблемой (я сталкивался с этим, и по такой теме было один доклад на GDC 2016).

      НЕЛЬЗЯ придерживаться deterministic-lockstep архитектур (в которых синхронизация происходит только отправкой данных ввода). Хотя игры с deterministic-lockstep архитектурой меньше страдают от решений, выполняемых на стороне клиента, они довольно уязвимы к атакам утечки информации (таких как Wallhack и Maphack. См. ниже пункт о том, как ограничить клиент только необходимой для него информацией).

      5. НУЖНО шифровать трафик


      Шифрование трафика позволяет оградиться от нескольких типов атак, в том числе на основе прокси (с которыми иначе почти невозможно справиться) и некоторых типов ботов.

      Замечания в этой связи:

      • Нет, использование UDP не является оправданием отсутствия шифрования трафика. Здесь на помощь приходит DTLS и некоторые другие протоколы.
      • НУЖНО привязывать библиотеку OpenSSL статически. OpenSSL.DLL (или любая другая реализация шифрования через DLL) предоставляет атакующему гарантированный способ обхода шифрования.
      • НЕЛЬЗЯ использовать анонимный протокол Диффи-Хелмана (Anonymous Diffie-Hellman, ADH). Да, об этом написано в каждой книге, но всё ещё находятся игры, в которых разработчики применяют его (как минимум одна игра даже упомянула ADH в окне About!).
      • НУЖНО проверять сохранённый корневой сертификат на стороне клиента (да, некоторые разработчики этого по-прежнему не делают).
      • НЕЛЬЗЯ использовать сертификат с машины пользователя. Вместо этого создайте свой центр сертификации и встройте корневой сертификат в клиент (иначе повышается вероятность MITM-атак).
      • Обфусцируйте корневой сертификат в исполняемых файлах клиента, иначе подвергнетесь другому типу MITM-атак.

      6. НУЖНО отслеживать публично доступные и платные читы


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

      Не забывайте при анализе читов следующие принципы:

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

      • Что ещё более очевидно, НЕЛЬЗЯ запускать читы внутри собственной сети (иначе исполнится мечта очень многих читеров). Вместо этого создайте надёжно огороженную файрволлом «песочницу» (в идеале — в отдельном месте, не имеющем VPN между внутренней сетью и «песочницей»).

      7. НЕЛЬЗЯ нанимать известных читеров

      На самом деле у этого правила есть исключения. Во-первых, оно не касается white-hat-хакеров. Во-вторых, МОЖЕТ быть приемлемо нанять известных читеров, с соблюдением ВСЕХ следующих условий:

      • У них НЕ будет специального доступа к «внутренностям» программы (в особенности к исходному коду)
      • Они должны заниматься ТОЛЬКО «black-box-взломом» (которым они и так бы занялись). Единственная разница в том, что они будут сообщать о результатах взлома, а не использовать его.
      • Всю связь с нанятыми читерами НУЖНО вести с отдельной учётной записи электронной почты (полностью изолированной от остальной части системы).
      • Доступ ко всей информации с этой отдельной учётной записи ДОЛЖЕН быть только внутри «песочницы», настроенной для анализа читерских программ

      8. НУЖНО думать о политике блокировок


      Будете ли вы банить читеров навечно, или всего на несколько дней? А как насчёт рецидивистов (на GDC 2016 говорили, что 72% читеров снова пробуют жульничать)? Какая у вас есть защита от читеров, открывающих новую учётную запись (подсказка: в F2P-играх на ПК такая защита почти отсутствует)?

      9. Проверенное правило: НЕЛЬЗЯ полагаться на игроков в жалобах на читерство


      Кроме того, НЕЛЬЗЯ поощрять игроков жаловаться на читеров. Они в любом случае будут жаловаться на то, что посчитают читерством, но подталкивание к жалобам может легко превратить достаточно большое количество игроков в параноиков. Хотя в некоторых случаях блокировка голосованием МОЖЕТ быть необходимой, разрешение игрокам выкидывать противников редко бывает хорошей идеей.

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

      10. НУЖНО передавать клиенту только необходимую информацию


      Другими словами, реализуйте так называемую систему «управления интересами». Любые данные в клиенте могут быть взломаны, поэтому передача уязвимой информации, которая не должна быть публичной — это Очень Плохая Идея. Если не реализовать управление интересами, то вы почти гарантировано подвергнете игру читам, делающим стены прозрачными (Wallhacks) и убирающим туман войны (Maphacks), а также ESP-читам (позволяющим видеть здоровье и другие параметры противника).

      11. НУЖНО использовать C++ в клиенте


      C++ можно взломать, но я обычно разделяю «крекинг» и «полномасштабную обратную разработку». По моему опыту, хотя программу на C++ можно «крякнуть» (т.е. можно найти и отключить ограниченный набор проверок, и найти и изменить ограниченное количество мест в памяти), полномасштабная обратная разработка для C++ гораздо более сложна, чем для C#/Java (я даже не говорю о JavaScript без компиляции Emscripten).

      Если уж мы упомянули об этом, то надо заметить, что для этой цели Emscripten почти также хорош, как и C++. Сейчас я не могу ничем подтвердить свои слова, но судя по тому, что я знаю, всё выглядит именно так.

      12. НИ В КОЕМ СЛУЧАЕ не выпускайте игру с отладочной информацией внутри


      Нужно ли это объяснять?

      13. НУЖНО пройтись по коду и отфильтровать все сообщения об ошибках


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

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

      14. НУЖНО беречь исходный код как свою жизнь


      Если исходники утекут к хакерам, то 99% обфускации не будут иметь смысла.

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

      15. НУЖНО проверять целостность клиента (в том числе ресурсов и/или конфигурации)


      Это нужно выполнять хотя бы при запуске клиента (в идеале — и в процессе его работы, но это уже другая история).

      Также обеспечьте отправку отчётов на сервер в случае компрометации сервера — если это и не чит, то определённо тревожный сигнал.

      16. НУЖНО установить в клиенте «ловушки» для читеров


      Вот типичные примеры ловушек:
      • Одно и то же значение хранится дважды в памяти клиента и время от времени проверяется на равенство. Для эффективности такой ловушки нужно обфусцировать память хотя бы в одном месте хранения (например, XOR какой-нибудь константой, хотя можно реализовать это ГОРАЗДО более сложно). Если значения не одинаковы, то это Очень Тревожный Сигнал, хотя я и против того, чтобы один случай такого несовпадения считался убедительным доказательством. Когда у игры миллион игроков, с некоторыми из них иногда происходят Очень Странные Вещи.
      • Проверка целостности критичных частей кода клиента и проверка на наличие подозрительных внедрённых DLL.
      • Измерение времени исполнения кода для поиска необычно долгих задержек, указывающих на отладку клиента. В этом случае интересным вариантом является инструкция RDTSC платформ x86/x64, потому что она часто позволяет измерять время даже при наличии отладчика ядра.
      • Ещё один особый тип ловушек — «honey pot». Технически «honey pot» отличается от обычной ловушки в том, что для читера создаётся искусственная мишень.

      17. НЕЛЬЗЯ сообщать читеру сразу же, что вы его поймали


      Вместо того, чтобы сразу же блокировать читера, обычно всегда лучше отложить бан. Лично я не большой поклонник «волн блокировок» и предпочитаю случайные задержки для каждого игрока, но даже волны блокировок НАМНОГО лучше немедленных банов.

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


      Практически невозможно отличить действительную задержку от внесённой взломанным клиентом (искусственный лаг). С другой стороны, если вы достаточно аккуратны, то сможете ограничить воздействие таких читов на геймплей.

      19. НЕЛЬЗЯ полагаться ни на какие способы идентификации компьютера


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

      20. НУЖНО собирать статистику, которая может быть связана с читерством


      Например, если игрок попадает в занимающий 5 процентов тела центр тяжести противника 95% времени, то можно что-то заподозрить.

      Железное правило: статистика НЕ ДОЛЖНА использоваться как прямое доказательство, это только тревожный сигнал. Как выяснить, что делать с этим сигналом — отдельная история.

      Последнее по порядку, но не по значимости: НЕЛЬЗЯ полагаться только на статистику, собранную клиентом. Другими словами, собирайте как можно больше статистики на стороне сервера.

      21. НУЖНО обдумать CAPTCHA для распознавания гриндящих ботов


      Один из типов защиты, достаточно хорошо работающий против нежелательных ботов (обычно занятых гриндом) — это CAPTCHA. Надо признать, что она довольно раздражает, но я видел примеры того, как разработчик объяснял необходимость CAPTCHA сообществу игроков, и те признавали её «необходимым злом», если она проверяет пользователей только изредка.

      Учтите, чтобы она работала, это НЕ ДОЛЖНА быть CAPTCHA для всех, её НАДО активировать только при срабатывании одного из тревожных сигналов.

      22. НУЖНО быть готовыми к сканированию компьютеров игроков на предмет известного читерского ПО


      Это довольно противоречивый вопрос, но скорее надо реализовать эту функцию, чем нет. Для большинства игр сканирование компьютеров игроков — это ГОРАЗДО меньшее зло, чем беспрепятственное читерство (оно разрушает для игроков интересность процесса, а значит, убивает вашу игру).

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

      • НУЖНО добавить в условия использования раздел о праве на такое сканирование.
      • НЕЛЬЗЯ получать любую другую информацию, кроме необходимой для распознавания читов.
      • Если вы проигрываете читерам в этой битве, НУЖНО рассмотреть варианты решений на основании драйверов устройств (подсказка: существуют такие коммерческие продукты)
      • При использовании сканирования НУЖНО распознавать запуск в виртуальной машине (ВМ часто используются для сокрытия программного обеспечения, и их обычно НУЖНО считать тревожным сигналом)
      • НУЖНО рассмотреть необходимость ДВУХ уровней распознавания. Первый уровень может быть простым распознаванием самых очевидных и популярных читов, и должен приводит к дружелюбному сообщению: «Ты используешь кое-что, запрещённое правилами. Не нужно этого делать, А НЕ ТО...». На втором уровне выполняется полномасштабное распознавание, приводящее к бану. Задумка здесь в том, чтобы остановить потенциального читера, пока не будет слишком поздно, по возможности не потеряв при этом игрока. Я видел примеры того, что такой подход хорошо работал в довольно большой многопользовательской игре (но тут, как говорится, нет никаких гарантий)

      Заключение


      Конечно же, приведённый выше список неполон. Если ваша игра успешна, вам придётся самому узнавать на этом пути что-то новое (иногда это даётся очень тяжко). Однако вот самый важный принцип, который СИЛЬНО помог нам в этом отношении:
      «Чтобы спастись от медведя, не нужно бежать быстрее него. Достаточно бежать быстрее, чем человек рядом с тобой».
      Джим Батчер
      В нашем случае это можно перефразировать так:
      «Не обязательно защищаться от читерства на 100%, чтобы спасти игру от читеров. Достаточно быть лучше своих конкурентов».
      «No Bugs» Hare

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

      image

      В этой статье вкратце рассмотрена тематика читерства на основании материалов из книги «Development and Deployment of Multiplayer Online Games» («Разработка и выпуск многопользовательских онлайн-игр»).

      Комментарии (0)

        Let's block ads! (Why?)

        Автоматическая визуализация python-кода. Часть вторая: реализация

        Хитрости и трюки Netbeans на живых примерах

        [Из песочницы] PHP 7.1.1 FPM vs Node.js 7.4.0 в качестве web backend сервера

        [Из песочницы] А если без JavaScript?

        [Перевод] 33 iOS библиотеки с открытым исходным кодом, которые будут популярны в 2017 году

        GraphQL как универсальный RPC

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

        Итак, GraphQL — это язык запросов, применяемый Facebook для извлечения данных из графовых СУБД. Язык оказался настолько удачным, что потенциальная сфера его применения значительно шире — его называют «убийцей REST» и даже прикрутили его к реакту в качестве очередного движка управлениями моделями данных. Совсем вкратце о том, что такое GraphQL:
        — запрос — это список полей, которые нужно вернуть в ответе. Возвращаются только те поля, которые были запрошены
        — поле может оказаться методом с одноименным названием — тогда прямо в запросе указываются параметры этого метода: {name, surname, age, getLikesCount(since: «01.01.2016»)}
        — если значение поля или метода — объект, то для него так же надо явно указать список полей: {name, surname, age, bestFriend: {name}}

        Есть много разных мнений о том, что именно в нем такое инновационное, но, я думаю, что самая интересная идея вот в чем:

        Модель данных — это частный случай модели API

        И в самом деле, если в произвольном json заменить поля на методы с пустым списком параметров, то получится какое-то урезанное API:

        {
          name: "John",
          age: 25
          friends: [{
            name: "Jenny",
            age: 24
          }]
        }
        

        превращается в
        interface Human {
          name(): string
          age(): int
          friends: Human[]
        }
        
        

        Этот пример демонструет важное следствие — если API и данные — это одно и то же, то API может возвращать ссылки на другие API, т.е. на объекты, предоставляющие методы, возвращающие данные (или другие API). Важный момент — методы вовсе не обязаны быть идемпотентными, это вполне может быть update/delete или просто вызов некой бизнес-логики.

        Ничего не напоминает? Например, граф объектов в работающей программе? С учетом того, что запрос GraphQL, по сути, представляет из себя список методов на корневом объекте, которые нужно вызвать, а также прочих фич:
        — строгая типизация
        — поддержка интерфейсов
        — документированное представление схемы в виде структуры данных
        мы получаем, что GraphQL позволяет выставить в сеть произвольный объект! И, в частности, активную доменную модель, самописную или собранную при помощи ORM типа Hibernate

        Многим не нравится избыточность GraphQL, его набор фишек и примочек, которыми он оброс за время использования его в фейсбуке. Многие из них имеют смысл только в контексте node.js и конкретного стиля разработки. Но — если у нас есть схема и мы сказали RPC, то очевидное решение — кодогенерация. Если многословные запросы со всем этим `query`, `mutation` и объявлением переменных будут скрыты за API, то этот недостаток нивелируется.

        Итого, на выходе получается RPC фреймворк, позволяющий выставить в сеть типизированный, документированный, объектный API, использующий в качестве транспорта json и примитивное (парсер пишется с полплевка) подмножество GraphQL. А также клиентский кодогенератор, предоставляющий удобный интерфейс для вызова.

        Осталось его написать :-)

        Спасибо.

        Комментарии (0)

          Let's block ads! (Why?)

          [Перевод] Чистая архитектура в Python: пошаговая демонстрация. Часть 4

          Содержание

          Сценарии (часть 3)


          Git tag: Step09


          Наша реализация ответов и запросов, наконец, завершена. И теперь мы можем реализовать последнюю версию нашего сценария. Сценарий корректно возвращает объект ResponseSuccess, но до сих пор не проверяет корректность входящего запроса.


          Давайте изменим тест в файле tests/use_cases/test_storageroom_list_use_case.py и добавим ещё 2 теста. Полученный набор тестов (после фикстуры domain_storagerooms) выглядит следующим образом:


          import pytest
          from unittest import mock
          
          from rentomatic.domain.storageroom import StorageRoom
          from rentomatic.shared import response_object as res
          from rentomatic.use_cases import request_objects as req
          from rentomatic.use_cases import storageroom_use_cases as uc
          
          
          @pytest.fixture
          def domain_storagerooms():
              […]
          
          def test_storageroom_list_without_parameters(domain_storagerooms):
              repo = mock.Mock()
              repo.list.return_value = domain_storagerooms
          
              storageroom_list_use_case = uc.StorageRoomListUseCase(repo)
              request_object = req.StorageRoomListRequestObject.from_dict({})
          
              response_object = storageroom_list_use_case.execute(request_object)
          
              assert bool(response_object) is True
              repo.list.assert_called_with(filters=None)
          
              assert response_object.value == domain_storagerooms
          
          

          Этот тест отличается от предыдущего тем, что теперь метод assert_called_with() вызывается с параметром filters=None. В строках с импортом появились response_objects и request_objects. Фикстура Domain_storagerooms была исключена из кода для лаконичности.


          def test_storageroom_list_with_filters(domain_storagerooms):
              repo = mock.Mock()
              repo.list.return_value = domain_storagerooms
          
              storageroom_list_use_case = uc.StorageRoomListUseCase(repo)
              qry_filters = {'a': 5}
              request_object = req.StorageRoomListRequestObject.from_dict({'filters': qry_filters})
          
              response_object = storageroom_list_use_case.execute(request_object)
          
              assert bool(response_object) is True
              repo.list.assert_called_with(filters=qry_filters)
              assert response_object.value == domain_storagerooms
          
          

          Этот тест проверяет, что при вызове хранилища используется значение ключа filters, который используется и для создания запросов.


          def test_storageroom_list_handles_generic_error():
              repo = mock.Mock()
              repo.list.side_effect = Exception('Just an error message')
          
              storageroom_list_use_case = uc.StorageRoomListUseCase(repo)
              request_object = req.StorageRoomListRequestObject.from_dict({})
          
              response_object = storageroom_list_use_case.execute(request_object)
          
              assert bool(response_object) is False
              assert response_object.value == {
                  'type': res.ResponseFailure.SYSTEM_ERROR,
                  'message': "Exception: Just an error message"
              }
          
          
          def test_storageroom_list_handles_bad_request():
              repo = mock.Mock()
          
              storageroom_list_use_case = uc.StorageRoomListUseCase(repo)
              request_object = req.StorageRoomListRequestObject.from_dict({'filters': 5})
          
              response_object = storageroom_list_use_case.execute(request_object)
          
              assert bool(response_object) is False
              assert response_object.value == {
                  'type': res.ResponseFailure.PARAMETERS_ERROR,
                  'message': "filters: Is not iterable"
              }
          
          

          Эти последние два теста проверяют поведение сценария при возникновении исключения в хранилище или при неверно сформированном запросе.


          Изменим файл rentomatic/use_cases/storageroom_use_cases.py так, чтобы он содержал новую реализацию сценария, позволяющую тестам проходить успешно.


          from rentomatic.shared import response_object as res
          
          
          class StorageRoomListUseCase(object):
          
              def __init__(self, repo):
                  self.repo = repo
          
              def execute(self, request_object):
                  if not request_object:
                      return res.ResponseFailure.build_from_invalid_request_object(request_object)
          
                  try:
                      storage_rooms = self.repo.list(filters=request_object.filters)
                      return res.ResponseSuccess(storage_rooms)
                  except Exception as exc:
                      return res.ResponseFailure.build_system_error(
                          "{}: {}".format(exc.__class__.__name__, "{}".format(exc)))
          
          

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


          Антракт: рефакторинг


          Git tag: Step10


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


          Мы уже выделили объект ответа. Так как test_valid_request_object_cannot_be_used проверяет общее поведение, а не связь между моделью StorageRoom и сценариями, то мы можем переместить его из tests/use_cases/test_storageroom_list_request_objects.py в tests/shared/test_response_object.py.


          Затем мы можем переместить классы InvalidRequestObject и ValidRequestObject из rentomatic/use_cases/request_objects.py в rentomatic/shared/request_object.py, внося необходимые изменения в классе StorageRoomListRequestObject, который теперь наследуется от внешнего класса.


          А вот класс сценария подвергается значительным изменениям. Класс UseCase тестируем кодом файла tests/shared/test_use_case.py:


          from unittest import mock
          
          from rentomatic.shared import request_object as req, response_object as res
          from rentomatic.shared import use_case as uc
          
          
          def test_use_case_cannot_process_valid_requests():
              valid_request_object = mock.MagicMock()
              valid_request_object.__bool__.return_value = True
          
              use_case = uc.UseCase()
              response = use_case.execute(valid_request_object)
          
              assert not response
              assert response.type == res.ResponseFailure.SYSTEM_ERROR
              assert response.message == \
                  'NotImplementedError: process_request() not implemented by UseCase class'
          
          

          Этот тест проверяет, что класс UseCase нельзя использовать для обработки входящих запросов.


          def test_use_case_can_process_invalid_requests_and_returns_response_failure():
              invalid_request_object = req.InvalidRequestObject()
              invalid_request_object.add_error('someparam', 'somemessage')
          
              use_case = uc.UseCase()
              response = use_case.execute(invalid_request_object)
          
              assert not response
              assert response.type == res.ResponseFailure.PARAMETERS_ERROR
              assert response.message == 'someparam: somemessage'
          
          

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


          def test_use_case_can_manage_generic_exception_from_process_request():
              use_case = uc.UseCase()
          
              class TestException(Exception):
                  pass
          
              use_case.process_request = mock.Mock()
              use_case.process_request.side_effect = TestException('somemessage')
              response = use_case.execute(mock.Mock)
          
              assert not response
              assert response.type == res.ResponseFailure.SYSTEM_ERROR
              assert response.message == 'TestException: somemessage'
          
          

          Этот тест заставляет сценарий вызывать исключение. Этот тип ошибки классифицируется как SYSTEM_ERROR, который представляет собой общее название для исключения, которое не связано с параметрами запроса или фактическими сущностями.


          Как видно из последнего теста, идея состоит в том, чтобы предоставить метод execute() класса UseCase и вызвать метод process_request(), который определён в каждом дочернем классе, являющимся нашим сценарием.


          Файл rentomatic/shared/use_case.py содержит следующий код для успешного прохождения теста:


          from rentomatic.shared import response_object as res
          
          
          class UseCase(object):
          
              def execute(self, request_object):
                  if not request_object:
                      return res.ResponseFailure.build_from_invalid_request_object(request_object)
                  try:
                      return self.process_request(request_object)
                  except Exception as exc:
                      return res.ResponseFailure.build_system_error(
                          "{}: {}".format(exc.__class__.__name__, "{}".format(exc)))
          
              def process_request(self, request_object):
                  raise NotImplementedError(
                      "process_request() not implemented by UseCase class")
          
          

          И вот теперь файл rentomatic/use_cases/storageroom_use_cases.py содержит следующий код:


          from rentomatic.shared import use_case as uc
          from rentomatic.shared import response_object as res
          
          
          class StorageRoomListUseCase(uc.UseCase):
          
              def __init__(self, repo):
                  self.repo = repo
          
              def process_request(self, request_object):
                  domain_storageroom = self.repo.list(filters=request_object.filters)
                  return res.ResponseSuccess(domain_storageroom)
          
          

          Слой хранилища


          Git tag: Step11


          Слой хранилища — это тот слой, в котором выполняется система хранения данных. Как вы видели, когда мы реализовали сценарий, мы получаем доступ к хранилищу данных через API, в данном случае через метод хранилища list(). Уровень абстракции, предоставляемой уровнем хранилища, выше, чем уровень, предоставляемый ORM или таким инструментом, как SQLAlchemy. Слой хранилища обеспечивает только необходимые для приложения конечные точки с интерфейсом, который адаптирован для конкретных бизнес-задач и целей приложения.


          Для ясности, говоря в терминах конкретных технологий, SQLAlchemy является прекрасным инструментом для абстрактного доступа к базе данных SQL. Внутренняя реализация хранилища слоя может использовать его для доступа к базе данных PostgreSQL. Но внешний API слоя — не то же самое, что даёт SQLAlchemy. API — это (как правило, уменьшенное) множество функций, которые вызывают сценарии для получения данных. В самом деле внутренняя реализация API может использовать сырые запросы SQL. Хранилище даже может не быть основанным на базе данных. Мы можем иметь слой хранилища, который извлекает данные из службы REST или делает удаленные вызовы процедур через RabbitMQ.


          Очень важной особенностью слоя хранилища является то, что он всегда возвращает модели предметной области, и это совпадает с тем, что обычно делают ORM-фреймворки.


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


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


          Первое, что нужно сделать, это написать несколько тестов, которые описывают публичный API хранилища. Файл tests/repository/test_memrepo.py содержит тесты для данной задачи.


          Сначала мы добавим некоторые данные, которые мы будем использовать в тестах. Импортируем доменную модель для проверки корректного типа результата вызова API:


          import pytest
          
          from rentomatic.shared.domain_model import DomainModel
          
          from rentomatic.repository import memrepo
          
          
          storageroom1 = {
              'code': 'f853578c-fc0f-4e65-81b8-566c5dffa35a',
              'size': 215,
              'price': 39,
              'longitude': '-0.09998975',
              'latitude': '51.75436293',
          }
          
          storageroom2 = {
              'code': 'fe2c3195-aeff-487a-a08f-e0bdc0ec6e9a',
              'size': 405,
              'price': 66,
              'longitude': '0.18228006',
              'latitude': '51.74640997',
          }
          
          storageroom3 = {
              'code': '913694c6-435a-4366-ba0d-da5334a611b2',
              'size': 56,
              'price': 60,
              'longitude': '0.27891577',
              'latitude': '51.45994069',
          }
          
          storageroom4 = {
              'code': 'eed76e77-55c1-41ce-985d-ca49bf6c0585',
              'size': 93,
              'price': 48,
              'longitude': '0.33894476',
              'latitude': '51.39916678',
          }
          
          
          @pytest.fixture
          def storagerooms():
              return [storageroom1, storageroom2, storageroom3, storageroom4]
          
          

          Поскольку объект хранилища будет возвращать модели предметной области, нам нужна вспомогательная функция для проверки правильности результатов. Следующая функция проверяет длину двух списков и гарантирует, что все возвращенные элементы модели из предметной области, и сравнивает коды. Обратите внимание, что мы можем использовать встроенную функцию isinstance(), поскольку DomainModel является абстрактным базовым классом и наши модели зарегистрированы (см rentomatic/domian/storagerooms.py)


          def _check_results(domain_models_list, data_list):
              assert len(domain_models_list) == len(data_list)
              assert all([isinstance(dm, DomainModel) for dm in domain_models_list])
              assert set([dm.code for dm in domain_models_list]) == set([d['code'] for d in data_list])
          
          

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


          def test_repository_list_without_parameters(storagerooms):
              repo = memrepo.MemRepo(storagerooms)
          
              assert repo.list() == storagerooms
          
          

          Метод list() должен принимать параметр filters, являющийся словарём. Ключи словаря должны быть в виде <attribute>__<operator>, похожий на синтаксис, используемый в Django ORM. Таким образом, чтобы выразить, что цена должна быть меньше 65, можно записать filters={'price__lt': 60}.


          Нужно проверить несколько условий возникновения ошибки: неизвестный ключ должен вызвать исключение KeyError, а использование неверного оператора должно вызвать исключение ValueError.


          def test_repository_list_with_filters_unknown_key(storagerooms):
              repo = memrepo.MemRepo(storagerooms)
          
              with pytest.raises(KeyError):
                  repo.list(filters={'name': 'aname'})
          
          
          def test_repository_list_with_filters_unknown_operator(storagerooms):
              repo = memrepo.MemRepo(storagerooms)
          
              with pytest.raises(ValueError):
                  repo.list(filters={'price__in': [20, 30]})
          
          

          Давайте затем проверим, что механизм фильтрации работает. Мы хотим, чтобы оператором по умолчанию был __eq, что значит, что если мы не передаём какой-либо оператор, должен выполняться оператор равенства.


          def test_repository_list_with_filters_price(storagerooms):
              repo = memrepo.MemRepo(storagerooms)
          
              _check_results(repo.list(filters={'price': 60}), [storageroom3])
              _check_results(repo.list(filters={'price__eq': 60}), [storageroom3])
              _check_results(repo.list(filters={'price__lt': 60}), [storageroom1, storageroom4])
              _check_results(repo.list(filters={'price__gt': 60}), [storageroom2])
          
          
          def test_repository_list_with_filters_size(storagerooms):
              repo = memrepo.MemRepo(storagerooms)
          
              _check_results(repo.list(filters={'size': 93}), [storageroom4])
              _check_results(repo.list(filters={'size__eq': 93}), [storageroom4])
              _check_results(repo.list(filters={'size__lt': 60}), [storageroom3])
              _check_results(repo.list(filters={'size__gt': 400}), [storageroom2])
          
          
          def test_repository_list_with_filters_code(storagerooms):
              repo = memrepo.MemRepo(storagerooms)
          
              _check_results(
                  repo.list(filters={'code': '913694c6-435a-4366-ba0d-da5334a611b2'}), [storageroom3])
          
          

          Реализация класса MemRepo довольно проста, и я не буду погружаться в нее построчно.


          from rentomatic.domain import storageroom as sr
          
          
          class MemRepo:
          
              def __init__(self, entries=None):
                  self._entries = []
                  if entries:
                      self._entries.extend(entries)
          
              def _check(self, element, key, value):
                  if '__' not in key:
                      key = key + '__eq'
          
                  key, operator = key.split('__')
          
                  if operator not in ['eq', 'lt', 'gt']:
                      raise ValueError('Operator {} is not supported'.format(operator))
          
                  operator = '__{}__'.format(operator)
          
                  return getattr(element[key], operator)(value)
          
              def list(self, filters=None):
                  if not filters:
                      return self._entries
          
                  result = []
                  result.extend(self._entries)
          
                  for key, value in filters.items():
                      result = [e for e in result if self._check(e, key, value)]
          
                  return [sr.StorageRoom.from_dict(r) for r in result]
          
          
          

          Продолжение следует...

          Комментарии (0)

            Let's block ads! (Why?)

            пятница, 27 января 2017 г.

            Чем СХД отличается от курицы?

            Дизассемблируй это: «Лаборатория Касперского» объявляет старт зимнего конкурса CrackMe

            Ломать не строить. Иногда ломать — дело не менее сложное и благородное, чем строить.

            В январе «Лаборатория Касперского» открывает свой сезон зимних айтишных видов спорта и запускает конкурс CrackMe. Скачайте три файла и разреверсите их, чтобы вычислить ключ, который генерируется в зависимости от введенного адреса email. Полученный ключ необходимо зарегистрировать на сайте.

            image

            Задания пронумерованы в порядке повышения сложности, поэтому советуем решать их в той же очередности.

            Первых 15 человек, которые успешно взломают CrackMe, мы возьмем на экскурсию по антивирусной лаборатории. Вы познакомитесь с экспертами ЛК и узнаете, чем мы тут занимаемся. Лучших участников также ждут памятные сувениры.

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

            Если тема реверс-инжиниринга вам интересна, но взломать CrackMe не получилось, не расстраивайтесь. Подробные решения будут опубликованы в нашем Хабраблоге, как только конкурс завершится.

            Комментарии (0)

              Let's block ads! (Why?)

              Лид-магниты, которые магнетизируют


              Фото Nan Palmero (Flickr)

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

              Сегодня мы расскажем, как придумать ценный лид-магнит, который приведет к мгновенному целевому действию.

              Как найти тему для лид-магнита?


              Чтобы лид-магнит выполнял свою задачу, будь то подписка на рассылку или покупка, он должен быть релевантным для пользователя. Поэтому сначала выясните интересы своих потенциальных подписчиков и клиентов. Что они хотят получить: скидку, знания, решение определённой проблемы, а может ваш совет? И вот что вам поможет.

              Поисковой запрос

              Используйте статистику запросов в поисковиках внутри вашей сферы деятельности. В Google AdWords Keyword Planner или Wordstat Yandex введите ключевое слово или словосочетание, связанное с вашей услугой или товаром – так можно определить наиболее популярные запросы пользователей.

              Google Analytics

              Для более детального анализа подключите Google Analytics. Следите, какие ссылки больше всего кликают у вас на сайте. Наиболее посещаемые статьи блога помогут сориентироваться с темой для лид-магнита.

              Социальные сети

              Анализируйте страницы авторитетных сайтов в вашей отрасли: какие темы набирают больше лайков, какие лучше расшариваются. Иногда сами пользователи могут подсказать идею для лид-магнита, поделившись своей проблемой в комментариях.

              Также можно воспользоваться сервисом Buzzsumo, который оценивает количество расшариваний и лайков в социальных сетях Facebook, Twitter, Google+.

              Какие лид-магниты использовать?


              №1 Электронная книга


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

              Взгляните, как Product Psychology с помощью электронной книги планомерно выстраивают отношения с подписчиками и подогревают к себе интерес.

              На главной странице сайта Product Psychology вы увидите форму подписки с указанием: «Изучите психологию потребителя, урок за уроком, прямо в почтовом ящике».

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

              Компания оправдывает заданные ожидания и тем самым демонстрирует стабильную ценность для подписчика.

              Что учитывать при создании электронной книги:

              1. Определите целевую аудиторию и проанализируйте, какую проблему в вашей отрасли будет решать электронная книга.

              2. Добавьте в книгу призыв к действию, чтобы потенциальный клиент мог проявить заинтересованность. После прочтения книги рекомендуйте один из вариантов:

              — записаться на консультацию;
              — связаться с автором;
              — пробная версия продукта;
              — вступить в группу социальных сетей и так далее.

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

              4. Продумайте, какой формат лучше подойдет: PDF или Google Docs. Файл PDF проще скачать и расположение текстовых блоков и картинок останутся неизменными. А факт скачивания файла на компьютер вызывает у людей чувство обладания.

              5. Если вы ведете блог, можно объединить серию статей в книгу и создать на ее основе рассылку.

              №2 Вебинар


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

              Нил Патель – маркетолог, блогер и автор многочисленных книг и статей по интернет-маркетингу, на своем сайте собирает базу с помощью вебинара с заголовком «Как привлечь 195 013 посетителей в месяц, не потратив ни одного доллара ».

              Такая формулировка цепляет пользователя. Проанализируем до мелочей, как работает его лид-магнит:

              1. Яркая кнопка призыва к действию: «Да, я хочу, чтобы Нил научил меня, развивать бизнес».

              2. Ограничение времени. В форме подписки указано, что вебинар начнется через 2 минуты и 3 секунды. Как вариант, можно перенести вебинар на следующий день, но не позже. Таким образом, создается чувство срочности, и вы с большей вероятностью зарегистрируетесь.

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

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

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

              №3 Результаты исследований, отчеты и статистика


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

              Сайт http://ift.tt/2jcfWBU, посвященный заботе о здоровье, собирает email адреса, предлагая взамен загрузить бесплатный отчет. Он включает в себя советы по работе со стрессом всем, кто часто ему подвержен.

              №4 Чек-листы


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

              Пример от студии Дениса Каплунова для всех, кто работает с рекламными текстами. Взамен на электронный адрес вы получаете чек листы по написанию текстов email рассылок, кейсов, домашней страницы сайта и т.д.


              № 5 Инфографика


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

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

              Хорошая инфографика должна быть:

              — полезной;
              — ясной;
              — визуально привлекательной.

              Внизу пример рассылки с инфографикой, тема которой: «10 шагов к тому, чтобы начать свое дело, имея полную занятость».

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

              №6 Розыгрыш


              Людям нравится испытывать удачу в надежде получить что-либо бесплатно. Внизу вы видите рассылку от Yoga Journal, в которой предлагается выиграть бесплатный курс от мастера.

              В письме ничего лишнего: только краткое описание курса и две кнопки «Войдите и выиграйте», с переходом на целевой сайт.

              № 7 Купон на скидку


              Лид-магнит в виде купона на скидку используют как для сбора адресов, так и для разогрева лидов.

              № 8 Бесплатный пробный период


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

              HootSuite предлагает целых 60 дней пользования профессиональной версией. В письме расписаны все ее преимущества, стандартная цена, а предложение действует лишь до конца дня.

              № 9 Видео


              И снова пример рассылки от Product Psychology. На этот раз, в письме предлагают купить билеты на конференцию. В качестве лид-магнита здесь используется видео с прошлогодней конференции. В нем спикеры из Google Ventures рассказывают, как за 5 дней создать прототип и тестировать любую идею.

              Если вы вдохновились на создание такого лид-магнита, читайте нашу статью о том, как лучше использовать видео в email.

              По большому счету, лид-магнитом может стать что угодно, если это полезно, релевантно, решает проблему пользователей и приводит к нужному целевому действию.

              Поэтому включайте креативность и создавайте лид-магниты, которые магнетизируют.

              Комментарии (0)

                Let's block ads! (Why?)

                В поисках перформанса: мониторинг производительности JVM под Linux при помощи BPF

                Специалист по низкоуровневой оптимизации приложений, Саша Гольдштейн, в рамках своего доклада на JPoint немного отклонится от привычной тематики .NET и расскажет об инструментарии, помогающем бороться за производительность Java приложений под Linux. Что это за инструмент, кому он нужен и зачем, мы решили узнать заранее и взяли у Саши интервью.

                JUG.Ru Group: Расскажите, пожалуйста, пару слов о себе и своей работе?

                Саша Гольдштейн: Меня зовут Саша Гольдштейн, последние 10 лет я работаю в израильской консалтинговой компании Sela в качестве CTO.
                Моя работа сфокусирована на вопросах оптимизации производительности, диагностике на продакшн, мониторинге и всевозможных низкоуровневых задачах.
                Моя типичная рабочая неделя наполнена самыми разными задачами: я преподаю, исправляю ошибки или проблемы производительности для клиентов, а также работаю над внутренними проектами. Также я вхожу в программный комитет пары конференций: нашей собственной SDP (Тель-Авив, Израиль), а также DotNext (Москва и Санкт-Петербург, Россия), что на удивление занимает довольно много времени.

                «Производительность большинства приложений определяется не железом или средой исполнения» – Sasha Goldshtein о мониторинге производительности Java под Linux

                JUG.Ru Group: Обычно вы много рассказываете о производительности .NET. Что подтолкнуло вас в сторону Java?

                Саша Гольдштейн: Действительно, большая часть моей работы связана с C# и C++ под Windows. Я провел много времени, оптимизируя и решая выявленные проблемы с производительностью .NET. Однако в работе над низкоуровневой оптимизацией и отладкой в рамках разных технологий прослеживаются общие элементы: инструменты могут иметь разные названия, но общие принципы, методология и мыслительный процесс совпадают. В последние пару лет я близко познакомился с BPF — фреймворком трассировки под Linux, и это подтолкнуло меня к идее использования BPF для анализа производительности JVM.

                JUG.Ru Group: В чем особенности борьбы за производительность в Java на фоне .NET?

                Саша Гольдштейн: Как я сказал, многие вещи идентичны. Производительность большинства приложений определяется не железом или средой исполнения (JVM, CLR, Python или чем-то еще), а окружением: особенностями доступа к базам данных, скоростью поиска на диске и обработки сетевых запросов. Для подобного класса приложений по большому счету не важно, какую среду исполнения вы используете. Когда речь заходит о низкоуровневой оптимизации, например, минимизации потребления памяти, оптимизации отдельных алгоритмов, скорость работы которых определяется процессором (CPU-bound), и тому подобных вещах, есть ситуации, в которых разница между платформами действительно имеет значение, особенно если вам необходимо настроить среду исполнения под ваше приложение. В общем случае JVM настраивается более гибко, нежели CLR; и, мне кажется, в последние годы больше усилий было вложено именно в оптимизацию различных реализаций JVM, чем в Microsoft CLR.

                JUG.Ru Group: В каких случаях борьба за производительность реально требуется, все-таки эта задача «дорогая» в плане временных затрат? Какие факторы явно говорят о том, что с перфомансом есть проблемы?

                Саша Гольдштейн: Зачастую производительность не является функциональным показателем, которого необходимо достичь. Но даже если вы не строите системы реального времени или сверхбыстрые клиентские приложения, есть, вероятно, некоторые минимальные (разумные) границы скорости, которые ваши пользователи не будут готовы пересечь. Например, веб API, которому требуется 5 секунд на обработку запроса на входа в систему, скорее всего разозлит людей. Есть еще и вопрос стоимости: оптимизация производительности обычно означает, что вам потребуется меньше аппаратных ресурсов, а это означает прямую непосредственную экономию затрат, учитывая принятую многими политику «облака в первую очередь» (cloud-first).
                Остается надеяться, что у большинства людей предусмотрен процесс для определения целевых показателей производительности и по крайней мере простейший способ мониторинга и проверки этих показателей по мере продвижения вперед процесса разработки.

                JUG.Ru Group: С чего стоит начинать исследование проблем с производительностью?

                Саша Гольдштейн: Критичным моментом является наличие хорошего описания системы, например, функциональной блок-схемы. Когда вы понимаете, условно говоря, «механику работы»: какие есть основные компоненты и как они между собой связаны, гораздо проще предположить, где искать узкие места, также как гораздо легче понять, с чего стоит начинать поиск проблемы. Инструменты — вторичны. Прежде чем запустить ворох инструментов, вам необходимо понять, какие есть различные ресурсы, как они могут перегружаться, и как проверить предложенные гипотезы, чтобы добиться прогресса. Например, вы можете потратить дни на оптимизацию производительности CPU при выполнении некоторого алгоритма сортировки, но после этого обнаружите, что 99% времени отнимает запрос данных из БД, так что более или менее эффективная сортировка не дает вклада в общее время выполнения.

                JUG.Ru Group: Можете ли вы рассказать об основных возможностях инструментария на примере BPF?

                Саша Гольдштейн: BPF представляет собой мощный механизм ядра, представленный в последних версиях ядер Linux и позволяющий вводить динамические программы трассировки в ядро. Эти программы контролируемо безопасны и не могут привести к сбою в системе, также они не требуют компиляции и загрузки модулей ядра. В результате у нас есть фреймворк трассировки, который может работать очень близко к источнику основных событий, в частности, к обработке сетевых пакетов, отправке запросов к диску, обработке аппаратных прерываний и аналогичных. Предвидя ваш вопрос отмечу, что есть также некоторые специфичные для JVM события, которые я буду рассматривать в рамках доклада на JPoint: сборка мусора, распределение объектов, блокировка на освобождение монитора и многие другие.
                Более того, BPF позволяет создавать инструменты, в которых агрегация происходит на уровне трассировщика — например, если вас беспокоит гистограмма задержек (например, задержек HTTP-запросов), вам не нужно делать дамп миллиона событий, а затем проводить постобработку для вычисления гистограммы. Вместо этого ваша BPF-программа обеспечивает агрегирование в режиме реального времени и выдает на анализ только окончательный результат.
                Существует очень мощный инструментарий, который разрабатывается людьми из Facebook, Netflix, Plumgrid (VMWare) и других компаний (в том числе при моем скромном участии :-)).

                JUG.Ru Group: Насколько он сложен во внедрении в рабочий процесс и освоении?

                Саша Гольдштейн: BPF не сложен в использовании, поскольку есть множество вызываемых всего одной командной строкой инструментов, которые могут быть использованы для выявления проблем с производительностью. Например, есть инструмент под названием mysqld_slower, который выводит медленные запросы MySQL.
                Единственная проблема заключается в том, что вам нужно установить новое ядро Linux, чтобы использовать инструменты BPF. Большая часть функциональности была включена в Linux 4.1 и 4.4 (который у вас есть в Ubuntu 16.04, например), но другие функции требуют еще более новых версий, в частности, 4.9, которых у большинства пока нет в продакшене. Это конечно можно обойти путем обновления только ядра, благодаря такому подходу компании, вроде Facebook, Netflix и других, получили все преимущества BPF.

                JUG.Ru Group: Можно ли привести пример «типичных граблей» в работе с performance, с которыми позволяет бороться инструментарий на основе BPF?

                Саша Гольдштейн: Инструменты BPF полезны для диагностики приложений, ограниченных возможностями процессора, временем блокировки (блокировки, I/O), проблемами доступа к файлам, медленными запросами к базам данных, сетевыми запросами, сборкой мусора — на самом деле очень широким спектром проблем. Многие из них я рассмотрю в своем докладе.

                JUG.Ru Group: Есть ли задачи, с которыми позволяет разобраться только этот инструментарий?

                Саша Гольдштейн: Да. Когда вам нужно обработать трассировщиком большое количество событий, BPF незаменим. Даже довольно простые сценарии, такие как профилирование CPU, можно сделать намного более эффективным при использовании поддержки профилирования в BPF. В большинстве случаев решение задач, вроде обработки каждого входящего запроса и агрегирования информации о задержке, не практично с другими инструментами анализа производительности.
                В моем докладе мы рассмотрим мониторинг блокировок, разрешение DNS, запросы MySQL и кучу других проблем, которые можно назвать типичными для систем на продакшене.

                JUG.Ru Group: Ваш доклад — больше практический. На кого он в первую очередь ориентирован?

                Саша Гольдштейн: Мой доклад предназначен для разработчиков и инженеров по эксплуатации (Ops Engineer), развивающих ПО под Linux. Фокус внимания будет направлен на JVM (потому что это JPoint!), так что все примеры будут на Java. Мы рассмотрим кучу примеров, которые, я надеюсь, будут полезны для диагностики проблем с их собственными системами — и даже если у вас нет достаточно свежей версии ядра Linux сегодня, оно появится в самом ближайшем будущем. Я думаю, каждый разработчик, работающий на Linux, в один прекрасный день найдет применение для инструментов BPF.



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

                P.S. Кроме Саши, на JPoint 2017 о перфомансе будут рассказывать Алексей @shipilev Шипилёв, Сергей Walrus Куксенко, Владимир vladimirsitnikov Ситников и Николай xpinjection Алименков. О чем именно? Смотрите список докладов.

                А если вы живете в Сибири и до Москвы вам не добраться, рекомендуем присмотреться к JBreak 2017.

                Комментарии (0)

                  Let's block ads! (Why?)

                  Рай перфекциониста или каким должен быть кабель-менеджмент

                  Осторожно: HSTS

                  Про HSTS на Хабре уже писали, этот механизм включен в генераторе конфигов для веб-серверов от Mozilla. Написать этот пост я решил за один день столкнувшись с недоступность сразу двух крупных сайтов из-за HSTS.

                  TL;DR: Тщательно проверяйте работу сайта по TLS перед включением HSTS, особенно если это большой портал с кучей субдоменов.

                  Что такое HSTS?


                  HSTS (HTTP Strict Transport Security) — это механизм защиты от даунгрейд-атак на TLS, указывающий браузеру всегда использовать TLS для сайтов с соответствующими политиками. Стандарт описан в RFC6797, а политики бывают двух видов:

                  Динамические


                  Политика применяется из HTTP-заголовка Strict-Transport-Security при первом заходе на сайт по HTTPS, в нём указан срок действия и применимость к субдоменам:
                  Strict-Transport-Security: max-age=15768000; includeSubDomains;

                  Статические


                  Статические политики захардкожены в браузер и для некоторых сайтов включает привязку к вышестоящему CA, выпустевшему сертификат (например: google.com, paypal.com или torproject.org). Причем она может действовать только когда сайт открыт через TLS, разрешая незащищённое соединение, но блокируя MitM с подменой сертификата.
                  Список из Chromium используют все популярные браузер (Firefox, Safari и IE 11+Edge) и любой желающий может отправить заявку на добавление после соответствующей настройки. Веб-сервер также должен отдавать заголовок со сроком действия от двух лет и ключевым словом preload в конце:
                  Strict-Transport-Security: max-age=63072000; includeSubDomains; preload

                  Как выстрелись себе в ногу?


                  На днях коллеги пожаловались на недоступность некоторых разделов сайта 1С (dist.1c.ru и partweb.1c.ru). Поддержка уверяла что всё работает, у меня проблема не воспроизводилась и даже у коллег сайты открылись из всех браузеров, кроме основного Chrome. Тот выдал ERR_CONNECTION_TIMED_OUT спустя 20 секунд и почему-то настойчиво подставлял HTTPS в URL, даже если адрес был написан полностью с HTTP.

                  Решение пришло почти сразу, так как недавно упоминали HSTS в контексте корпоративного MitM. Гуглёж по ключевому слову первой ссылкой подсказал, что посмотреть кэш политик можно в chrome://net-internals/#hsts и догадка подтвердилась:

                  dynamic_sts_domain: 1c.ru
                  Found:
                  static_sts_domain:
                  static_upgrade_mode: UNKNOWN
                  static_sts_include_subdomains:
                  static_sts_observed:
                  static_pkp_domain:
                  static_pkp_include_subdomains:
                  static_pkp_observed:
                  static_spki_hashes:
                  dynamic_sts_domain: 1c.ru
                  dynamic_upgrade_mode: STRICT
                  dynamic_sts_include_subdomains: true

                  dynamic_sts_observed: 1485244696.197302
                  dynamic_pkp_domain:
                  dynamic_pkp_include_subdomains:
                  dynamic_pkp_observed:
                  dynamic_spki_hashes:

                  Политика включала все субдомены, хотя многие из них были доступны только на 80 порту без TLS.
                  После её удаления нужные разделы стали открываться, по дате получения (в формате unix time) в истории браузера нашли страницу с неверными настройками и отправили багрепорт в 1С.

                  Вторым сайтом оказался ask.mcdonalds.ru, который открывался первый раз, однако Chrome всё равно показал предупреждение с уже знакомой четырёхбуквенной аббревиатурой и без привычной кнопки Proceed to (unsafe):

                  ER_CERT_COMMON


                  Ошибка говорит о несоответствии имени сертификата, окончании срока действия или его отзыве, а показ кнопки для открытия сайта прямо запрещён в RFC. При этом политика для mcdonalds.ru оказалась статической, которую нельзя удалить из chrome://net-internals/#hsts.

                  Обойти такую заглушку в Chrome можно набрав badidea (предыдущим было danger) или запустив браузер с ключом --ignore-certificate-errors.
                  В Firefox надо нажать «Forge About This Site» напротив сайта в истории, открыть about:config и создать новый Integer с именем «test.currentTimeOffsetSeconds» и значением 11491200, а затем открыть сайт в новой вкладке.

                  Выводы


                  Не включайте потенциально опасные функции без понимания принципов их работы. Оценку «A» в тесте от SSL Labs вы получите и без HSTS, а включить его можно после проверки всего функционала через TLS. Со статическим листом в браузере пути назад уже не будет, поэтому лучше сразу приобрести сертификат с wildcard.

                  P.S.


                  Chrome и Firefox поддерживают другой механизм защиты: HTTP Public Key Pinning (HPKP), позволяющий привязать хэш сертификата и отправлять уведомления, когда браузеру попадётся сертификат для этого домена с другим хэшем. Он помог раскрыть несколько инциденов с CA, но на практике используется очень редко из-за высокой цены ошибки.

                  Комментарии (0)

                    Let's block ads! (Why?)