-Поиск по дневнику

Поиск сообщений в rss_rss_hh_new

 -Подписка по e-mail

 

 -Статистика

Статистика LiveInternet.ru: показано количество хитов и посетителей
Создан: 17.03.2011
Записей:
Комментариев:
Написано: 51

Habrahabr/New








Добавить любой RSS - источник (включая журнал LiveJournal) в свою ленту друзей вы можете на странице синдикации.

Исходная информация - http://habrahabr.ru/rss/new/.
Данный дневник сформирован из открытого RSS-источника по адресу http://feeds.feedburner.com/xtmb/hh-new-full, и дополняется в соответствии с дополнением данного источника. Он может не соответствовать содержимому оригинальной страницы. Трансляция создана автоматически по запросу читателей этой RSS ленты.
По всем вопросам о работе данного сервиса обращаться со страницы контактной информации.

[Обновить трансляцию]

[Перевод] Что делает язык программирования «модным»?

Суббота, 08 Июля 2017 г. 00:38 + в цитатник


Сейчас много языков программирования, соперничающих за ваше внимание, особенно «горячие» (или «модные!») новые языки, такие как Ceylon, Crystal, Dart, Elixir, Elm, Go, Haxe, Julia, Kotlin, Rust, Swift, TypeScript. И новые, кажется, появляются каждый месяц!

Даже некоторые из не совсем новых языков привлекают внимание, такие языки, как Clojure, Erlang, F#, Haskell, Lua, OCaml, Scala. Некоторым из них исполнилось несколько десятилетий!

Поэтому мне стало интересно: что делает язык программирования, независимо от возраста, модным и захватывающим? Почему 27-летний Haskell все еще вызывает разговоры? Как может 31-летний Erlang вызывать эмоции после стольких лет? Какова любовь к 24-летнему Lua? Об F# до сих пор говорят очень нежно, хотя за ним стоит 12-летняя история.

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

Erlang примечателен OTP (Open Telecom Platform) и его системой времени исполнения (BEAM), что делает Erlang замечательным для написания распределённых отказоустойчивых приложений.

Clojure использует силу и элегантность Lisp. Lua популярен в игровой индустрии как встраиваемый язык. Kotlin и Scala пытаются улучшить Java, самый популярный язык программирования на планете.

Dart и Elm предлагают альтернативы широко презираемому JavaScript. Rust обещает безопасность памяти. Julia – технологический tour de force.

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

Тогда как мы объясним тот факт, что языку Smalltalk сегодня так мало уделяют внимания? У него есть все нужное…

  1. Smalltalk – один из самых маленьких, простых и элегантных языков программирования, когда-либо созданных. Весь его синтаксис может поместиться на обратной стороне открытки! Это делает Smalltalk очень, очень лёгким в изучении. У Smalltalk крайне низкое, если не несущественное когнитивное трение при кодировании. Вам не нужно задумываться над языком, вы просто решаете вашу проблему.
  2. Smalltalk имеет великолепные «живое кодирование и отладку» в IDE, которые делают программирование чрезвычайно быстрым и продуктивным. Вы можете внести изменения в текущую программу и сразу увидеть результаты. Это почти полностью исключает цикл edit-compile-test-debug, который затрудняет работу почти со всеми иными языками программирования. И это удивительно просто в использовании. В современном мире программирования нет ничего подобного.
  3. Smalltalk – один из самых продуктивных языков программирования. Более чем в два раза производительнее, чем Python и Ruby. Более чем в 3 раза эффективнее JavaScript!
  4. Несмотря на свою относительную безвестность, Smalltalk – очень практичный промышленный язык! Он коммерчески использовался более трёх десятилетий. Его известные пользователи включают JPMorgan, Desjardins, UBS, Florida Power & Light, Texas Instruments, Telecom Argentina, Orient Overseas Container Lines, Siemens AG и прочие. Проверьте ALLSTOCKER и банкоматы на улицах Москвы.
  5. Smalltalk прекрасно масштабируем. В начале 2000-х годов объединённые военные силы США использовали Smalltalk для написания программы в миллион строк по моделированию боевых действий под названием JWARS. Фактически он превзошел аналогичное моделирование под названием STORM, написанное на C++ в ВВС США.
  6. Smalltalk имеет богатое наследие. Это был первый язык программирования для популяризации ООП, и он остается прекрасным примером языка ООП (именно поэтому он породил целое поколение языков ООП, таких как Java, Python, PHP, Ruby, Perl, Objective-C, CLOS, Dart, Scala, Groovy и прочие). Smalltalk – это правильное ООП
  7. Smalltalk продолжает активно развиваться, особенно в рамках проекта Pharo и Inria. И Amber для сети, и Redline для JVM. Smalltalk был модернизирован для двадцать первого века.
  8. Кстати, у Smalltalk также есть функции первого класса и замыкания, поэтому он хорош и для функционального программирования!

Smalltalk чрезвычайно универсален (является языком программирования общего назначения):


И я лишь начал!

Smalltalk заслуживает место среди «горячих» (или «модных!») новых языков, таких как Elixir, Elm, Go, Julia, Kotlin, Rust. В конце концов, Pharo всего 9 лет.

Об авторе


Ричард Энг – отставной разработчик программного обеспечения из Канады с более чем 30-летним опытом работы в ИТ-индустрии. Он работал в сфере видео графики, баз данных и финансов, программного обеспечения реального времени, мобильных приложений для iOS и Android, а также в веб-разработке. Он писал в основном на C, но также использовал FORTRAN, Tandem TAL, C++, C#, Objective-C, Java, Smalltalk, Python и Go. Сейчас он возглавляет кампанию Smalltalk Renaissance. Большую часть времени Ричард проводит за написанием статей и эссе.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332650/


Метки:  

Тандем офисной и мобильной телефонии. Как мы разрабатывали FMC

Пятница, 07 Июля 2017 г. 19:35 + в цитатник
FMC (fixed mobile convergence) — это новое технологическое решение на стыке разных типов сетей связи: фиксированной и мобильной. С его помощью любая организация может создать единую сеть офисных и мобильных телефонов с общим планом короткой нумерации. Технология дает возможность созваниваться напрямую по коротким внутренним номерам сотрудникам вне офисов в разных регионах страны или мира без использования реальной офисной АТС.

Использование в компании FMC приводит к упрощению бизнес-процессов организаций вкупе с возможностью оптимизации затрат. По предварительным подсчетам экономия на одной SIM-карте составит до 30% за счет сокращения времени дозвона и низких тарифов IP-телефонии.

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

FMC в МТТ Бизнес


Проект FMC является дополнительным способом приема и инициации голосовых вызовов Виртуальной АТС. Это происходит без использования сети Интернет, только через радиоканалы GSM. При этом FMC является составной частью услуги Виртуальная АТС и не подключается вне ее. Также FMC предоставляется только с использованием SIM МТТ, ассоциированных с Виртуальной АТС.

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

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

Business-API


В реализации «SIM-карта (FMC)» участвовали следующие инфраструктурные элементы компании МТТ:

  • Billing платформа — для тарификации вызовов.
  • WebAPI — для реализации функций по подключению номера и провиженинга на мобильной составляющей сети.
  • BusinessAPI — в часть реализации бизнес-логики подключения/управления услугами.
  • Инфраструктурные элементы мобильной сети (SRF, STP и т.п.).


Схема реализации услуги на уровне Business-API



image
Для FMC было необходимо доработать Business-API. В частности были добавлены в API следующие функции:
  • получения цены за услугу FMC;
  • получения списка тарифов для номеров FMC (для выбора);
  • получения цены по тарифу;
  • добавления/удаления услуги FMC;
  • добавления/удаления/изменения номера FMC клиенту ВАТС;
  • получения списка номеров FMC (со статусом активации).

Также были преобразованы Workplace в совокупность сущностей – WPsi (sip_id), WPsf (softphone), WPsim (FMC). В дальнейшем будут добавляться другие типы рабочих мест, что обеспечит гибкость в настройке переадресаций и добавлении новых типов. Для разделения сущностей рабочего места в структуру добавляется параметр type, который может принимать значения – sip, softphone, sim (в дальнейшем другие типы).

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

Для подключения номеров DEF используются уже существующие функции подключения номеров и установка определяемого номера. При внедрении номеров DEF в MTT-Business в рамках проекта FMC они не прописываются непосредственно на SIM-карты, а просто выполняют роль идентификатора при исходящих вызовах и через них будет также осуществляться переадресация входящих звонков на соответствующих сотрудников или группы.

В Business-API добавляется функция генерации проверочного кода и его закрепление за конкретным ЛС. Генерация и проверка кода осуществляется через личный кабинет клиента, а также посредством SMS сообщений.

Схема реализации услуги на уровне мобильной составляющей


Для того, чтобы в личном кабинете администратора можно было указать SIM FMC в качестве одного из вариантов дозвона до абонента, были сделаны доработки на стороне Business-API, WebAPI, портала MTT-Business и настроено оборудование на стороне мобильной сети.

Доработки провиженинга в HLR


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

В дальнейшем предполагается использовать HLR-API для управления всеми процедурами провиженинга информации в HLR.

Сценарии прохождения голосовых вызовов


Сценарии прохождения вызовов представлены на схемах:

Конкурентные преимущества FMC «МТТ Бизнес»?


Базовый пакет FMC от «МТТ Бизнес» включает ряд бесплатных услуг:
  • бесплатная доставка необходимого вам количества SIM-карт в офис или на указанный адрес;
  • отсутствие абонентской платы как за услугу FMC в целом, так и за использование каждой SIM-карты;
  • 1Гб мобильного интернета в подарок и бесплатный мобильный интернет в роуминге;
  • бесплатная связь между сотрудниками внутри корпоративной Виртуальной АТС;
  • бесплатные входящие звонки в домашней зоне услуги FMC.

В данный момент единая домашняя безроуминговая зона FMC «МТТ Бизнес» включает Москву, Санкт-Петербург и области столиц. Однако уже до конца 2017 года в географию присутствия FMC «МТТ Бизнес» войдут следующие города и области:
  • Екатеринбург и область;
  • Новосибирск и область;
  • Ростов-на-Дону и область;
  • Краснодар и область;
  • Нижний Новгород и область.

Со временем мы планируем развернуть единую домашнюю зону FMC «МТТ Бизнес» на всей территории России.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332644/


Метки:  

Security Week 27: ExPetr = BlackEnergy, более 90% сайтов небезопасны, в Linux закрыли RCE-уязвимость

Пятница, 07 Июля 2017 г. 19:19 + в цитатник
Зловещий ExPetr, поставивший на колени несколько весьма солидных учреждений, продолжает преподносить сюрпризы. Наши аналитики из команды GReAT обнаружили его родство со стирателем, атаковавшим пару лет назад украинские электростанции в рамках кампании BlackEnergy.

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

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



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



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

Исследователи нашли еще несколько строк, присутствующих и в ExPetr и в семпле из BlackEnergy:

— exe /r /f
— ComSpec
— InitiateSystemShutdown.

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

Большинство сайтов в Интернете не поддерживают технологии безопасности

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

А причина, между тем, очевидна – многие модные технологии, появляющиеся в браузерах чуть ли не ежемесячно, требуют поддержки на стороне сайтов. И с этим традиционно все плохо – админ не проснется, пока его площадку не взломают и не рассадят тысячам ее посетителей троянца-банкера или чего похуже. Судя по результатам исследования Mozilla Foundation, таких несознательных админов аж 93.45%.

Эту цифру они получили следующим образом: взяли верхний миллион сайтов из рейтинга Alexa, и проверили поддержку ИБ-технологий, вроде CSP, CORS и HPKP. Результаты, мягко говоря, не обрадовали – 9 из 10 сайтов получили низшую оценку (F), то есть безопасность пользователей администрацию популярных сайтов по большому счету не интересует.



Однако есть и едва заметный положительный тренд: по сравнению с прошлогодним исследованием, F-сайтов стало на 2,8% меньше. То есть еще каких-нибудь 34 года (при сохранении той же динамики), и веб станет безопасным!

В Linux закрыли RCE-баг

Новость. Из Canonical сообщают, что запатчили в Ubuntu уязвимость, позволявшую обрушить систему или заставить ее выполнить код посредством сконструированного DNS-ответа. Проблема крылась в systemd-resolved, системном DNS-резолвере в Убунте и еще нескольких дистрибутивах.

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

Хотя Canonical присвоила багу высокую степень угрозы, ситуация выглядит не очень страшно. Для эксплуатации этой уязвимости требуется заставить систему обратиться к вредоносному DNS-серверу, что уже само по себе успех. Зато она присутствует в Ubuntu с июня 2015 года (в systemd, начиная с версий 223). И в силу того, что systemd идет еще и с дистрибутивами Debian, Fedora и openSUSE, баг стоит поискать и там. Впрочем, Федора уже запатчилась.

Древности


Семейство «Ieronim»

Резидентные неопасные вирусы, записываются в .COM-файлы (кроме COMMAND.COM) при их загрузке в память. В зависимости от своей версии вирусы внедряются либо в конец, либо в начало файла Примерно раз в час выводят текст типа «Mulier pulchra est janua diaboli, via iniquitatis,scorpionis percussio. St. Ieronim». Помимо этой, содержат строку «comcommand». Перехватывают int 8, 21h.

Цитата по книге «Компьютерные вирусы в MS-DOS» Евгения Касперского. 1992 год. Страницa 32.

Disclaimer: Данная колонка отражает лишь частное мнение ее автора. Оно может совпадать с позицией компании «Лаборатория Касперского», а может и не совпадать. Тут уж как повезет.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332646/


Расширение, изменение и создание элементов управления на платформе UWP. Часть 2

Пятница, 07 Июля 2017 г. 19:16 + в цитатник

Метки:  

Simple Field Validation

Пятница, 07 Июля 2017 г. 19:00 + в цитатник
В процессе профессиональной деятельности приходится постоянно сталкиваться с валидацией полей ввода текста на экранах мобильных устройств. Чаще всего это пара-тройка экранов на приложение (SignIn, SignUp, Profile).

Ради этого подтягивать внешние зависимости представляется избыточным. Например, тот же Hibernate Validator добавляет порядка 8000 методов и 1 мб к весу финальной apk, что выглядит… избыточным )

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

Реализацией этой идеи под Android и хочу поделиться. Но предупрежу сразу, что в угоду простоте восприятия идеи примеры использования будут приводится в крайне простом варианте, возможно даже наивном. Плюс, в текущей реализации идеи не используется Rx.

Итак. Начинается все с очень простого интерфейса:

public interface Validator {

    boolean isValid(T value);

    String getDescription();
}

Собственно, это все )

Дальше нам понадобится его реализация для компоновки однотипных атомарных валидаторов воедино:

public class ValidatorsComposer implements Validator {
    private final List> validators;
    private String description;

    public ValidatorsComposer(Validator... validators) {
        this.validators = Arrays.asList(validators);
    }

    @Override
    public boolean isValid(T value) {
        for (Validator validator : validators) {
            if (!validator.isValid(value)) {
                description = validator.getDescription();
                return false;
            }
        }
        return true;
    }

    @Override
    public String getDescription() {
        return description;
    }
}

В качестве примера можно привести валидацию email поля. Для этого создадим два атомарных валидатора:

public class EmptyValidator implements Validator {

    @Override
    public boolean isValid(String value) {
        return !TextUtils.isEmpty(value);
    }

    @Override
    public String getDescription() {
        return "Field must not be empty";
    }
}

public class EmailValidator implements Validator {

    @Override
    public boolean isValid(String value) {
        return Patterns.EMAIL_ADDRESS.matcher(value).matches();
    }

    @Override
    public String getDescription() {
        return "Email should be in 'a@a.com' format";
    }
}

И, собственно, вариант использования:

final ValidatorsComposer emailValidatorsComposer =
        new ValidatorsComposer<>(new EmptyValidator(), new EmailValidator());
button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        if (emailValidatorsComposer.isValid(emailEditText.getText().toString())) {
            errorTextView.setText(null);
        } else {
            errorTextView.setText(emailValidatorsComposer.getDescription());
        }
    }
});

Также эта идея подходит и для валидации DataObject’ов с последующим выводом ошибки на экран. Например, создадим валидатор для DataObject’а User:

public class User {
    public final String name;
    public final Integer age;
    public final Gender gender;

    public enum Gender {MALE, FEMALE}

    public User(String name, Integer age, Gender gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }
}

public class UserValidator implements Validator {
    private String description;

    @Override
    public boolean isValid(User value) {
        if (value == null) {
            description = "User must not be null";
            return false;
        }

        final String name = value.name;
        if (TextUtils.isEmpty(name)) {
            description = "User name must not be blank";
            return false;
        }

        final Integer age = value.age;
        if (age == null) {
            description = "User age must not be blank";
            return false;
        } else if (age < 0) {
            description = "User age must be above zero";
            return false;
        } else if (age > 100) {
            description = "User age is to much";
            return false;
        }

        final User.Gender gender = value.gender;
        if (gender == null) {
            description = "User gender must not be blank";
            return false;
        }

        return true;
    }

    @Override
    public String getDescription() {
        return description;
    }
}

И вариант использования:

final Validator userValidator = new UserValidator();
button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        User user = new User(null, 12, User.Gender.MALE);
        if (userValidator.isValid(user)) {
            errorTextView.setText(null);
        } else {
            errorTextView.setText(userValidator.getDescription());
        }
    }
});

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

emailEditText.addTextChangedListener(new SimpleTextWatcher() {
    @Override
    public void afterTextChanged(Editable s) {
        if (!emailValidatorsComposer.isValid(s.toString())) {
            errorTextView.setText(emailValidatorsComposer.getDescription());
        }
    }
});

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

По итогу получаем крайне простую и вместе с тем гибкую идею валидации полей и объектов данных, которая по-сути ни капли не утяжеляет проект и реализуется по щелчку пальцев. Как бэ профит )
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332642/


Метки:  

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

Пятница, 07 Июля 2017 г. 18:27 + в цитатник
Фото очереди в мавзолей Мао Цзэдуна —  BrokenSphere / Wikimedia Commons

В проекте, над которым я сейчас работаю, применяется распределённая система обработки данных: сначала несколько десятков машин одновременно производят некоторые сообщения, затем эти сообщения отправляются в очередь, из очереди три потока извлекают сообщения и после финальной обработки выкладывают данные в базу Redis. При этом имеется требование: от «зарождения» события в машине, производящей сообщение, до выкладывания обработанных данных в базу должно проходить не более четырёх секунд в 90% случаев.

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

Я начал с того, что измерил время, которое сообщение проводит на разных стадиях обработки, и получил примерно такой результат (в секундах):



Таким образом, невзирая на то, что обработка сообщения занимает довольно мало времени, больше всего сообщение простаивает в очереди. Как такое может быть? И главное: как с этим бороться? Ведь если бы картинка выше была результатом профайлинга обычного кода, то всё было бы очевидно: надо оптимизировать «красную» процедуру. Но вся беда в том, что в нашем случае в «красной» зоне ничего не происходит, сообщение просто ждёт в очереди! Ясно, что оптимизировать надо обработку сообщения («жёлтую» часть диаграммы), но возникают вопросы: каким образом эта оптимизация может повлиять на длину очереди и где гарантии, что мы вообще сможем добиться желаемого результата?

Я помнил, что вероятностными оценками времени ожидания в очереди занимается теория очередей (подраздел исследования операций). В своё время в университете, впрочем, мы это не проходили, поэтому на некоторое время мне предстояло погрузиться в Википедию, PlanetMath и онлайн-лекции, откуда на меня как из рога изобилия посыпались сведения о нотации Кендалла, законе Литтла, формулах Эрланга, и т. д., и т. п.

Аналитические результаты теории очередей изобилуют довольно серьёзной математикой, основная часть этих результатов была получена в XX веке. Изучать всё это — занятие хотя и увлекательное, но довольно долгое и кропотливое. Не могу сказать, что я смог хоть сколько-нибудь глубоко туда погрузиться.

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



Здесь

  • $\mu$ — пропускная способность сервера (в сообщениях за секунду)
  • $\lambda$ — средняя частота поступления запросов (в сообщениях за секунду)
  • По оси ординат отложено среднее время обработки сообщения.

Точный аналитический вид этого графика является предметом изучения теории очередей, и для очередей M/M/1, M/D/1, M/D/c и т. д. (если не знаете, что это такое — см. нотация Кендалла) эта кривая описывается совершенно различными формулами. Тем не менее, какой бы моделью ни описывалась очередь, внешний вид и асимптотическое поведение этой функции будет одинаковым. Доказать это можно простыми рассуждениями, что мы и проделаем.

Во-первых, посмотрим на левую часть графика. Совершенно очевидно, что система не будет стабильной, если $\mu$ (пропускная способность) меньше $\lambda$ (частоты поступления): сообщения на обработку приходят с большей частотой, чем мы их можем обработать, очередь неограниченно растёт, а у нас возникают серьёзные неприятности. В общем, случай $\mu<\lambda$ — аварийный всегда.

Тот факт, что в правой части график асимптотически стремится к $1/\mu$, тоже довольно прост и для своего доказательства глубокого анализа не требует. Если сервер работает очень быстро, то мы практически не ждём в очереди, и общее время, которое мы проводим в системе, равно времени, которое сервер затрачивает на обработку сообщения, а это как раз $1/\mu$.

Не сразу очевидным может показаться лишь тот факт, что при приближении $\mu$ к $\lambda$ с правой стороны время ожидания в очереди растёт до бесконечности. В самом деле: если $\mu = \lambda$, то это значит, что средняя скорость обработки сообщений равна средней скорости поступления сообщений, и интуитивно кажется, что при таком раскладе система должна справляться. Почему же график зависимости времени от производительности сервера в точке $\lambda$ вылетает в бесконечность, а не ведёт себя как-нибудь так:



Но и этот факт можно установить, не прибегая к серьёзному математическому анализу! Для этого достаточно понять, что сервер, обрабатывая сообщения, может находиться в двух состояниях: 1) он занят работой 2) он простаивает, потому что обработал все задачи, а новые в очередь ещё не поступили.

Задания в очередь приходят неравномерно, где «погуще, где пореже»: количество событий в единицу времени является случайной величиной, описываемой так называемым распределением Пуассона. Если задания в какой-то интервал времени шли редко и сервер простаивал, то время, в течение которого он простаивал, он не может «сберечь» для того, чтобы использовать его для обработки будущих сообщений.

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

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

$\frac{1}{\mu-\lambda}.$


Выводы


Итак, разглядывая наш график, мы приходим к следующим выводам.

  • Время обработки сообщения в системе с очередью есть функция пропускной способности сервера $\mu$ и средней частоты поступления сообщений $\lambda$, и даже проще: это функция отношения этих величин $\rho = \lambda / \mu$.
  • Проектируя систему с очередью, нужно оценить среднюю частоту прибытия сообщений $\lambda$ и заложить пропускную способность сервера $\mu >> \lambda$.
  • Всякая система с очередью в зависимости от соотношения $\lambda$ и $\mu$ может находиться в одном из трёх режимов:

    1. Аварийный режим — $\mu \leq \lambda$. Очередь и время обработки в системе неограниченно растут.
    2. Режим, близкий к насыщению: $\mu > \lambda$, но ненамного. В этой ситуации небольшие изменения пропускной способности сервера очень сильно влияют на параметры производительности системы, как в ту, так и в другую сторону. Нужно оптимизировать сервер! При этом даже небольшая оптимизация может оказаться очень благотворной для всей системы. Оценки скоростей поступления сообщений и пропускной способности нашего сервиса показали, что наша система работала как раз около «точки насыщения».
    3. Режим «почти без ожидания в очереди»: $\mu >> \lambda$. Общее время нахождения в системе примерно равно времени, которое сервер тратит на обработку. Необходимость оптимизации сервера определяется уже внешними факторами, такими как вклад подсистемы с очередью в общее время обработки.

Разобравшись с этими вопросами, я приступил к оптимизации обработчика событий. Вот какой расклад получился в итоге:


ОК, вот теперь можно и время производства сообщения подоптимизировать!

Также рекомендую прочесть: Принципы и приёмы обработки очередей.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332634/


Метки:  

[Из песочницы] Как generic-и нас спасают от упаковки

Пятница, 07 Июля 2017 г. 18:16 + в цитатник

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


        public void ThrowIfNull(object obj)
        {
            if(obj == null)
            {
                throw new ArgumentNullException();
            }
        }

И что интересно при такой проверке, я массово вижу использование именно object атрибута, можно ведь воспользоватся generic-ом. Давайте попробуем заменить наш метод на generic и сравнить производительность.


Перед тестированием нужно учесть ещё один недостаток object аргумента. Значимые типы(value types) никогда не могут быть равны null(Nullable тип не в счёт). Вызов метода, вроде ThrowIfNull(5), бессмыслен, однако, поскольку тип аргумента у нас object, компилятор позволит вызвать метод. Как по мне, это снижает качество кода, что в некоторых ситуациях гораздо важнее производительности. Для того что бы избавится от такого поведения, и улучшить сигнатуру метода, generic метод придётся разделить на два, с указанием ограничений(constraints). Беда в том что нельзя указать Nullable ограничение, однако, можно указать nullable аргумент, с ограничением struct.


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


public class ObjectArgVsGenericArg
    {
        public string str = "some string";
        public Nullable num = 5;

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void ThrowIfNullGenericArg(T arg)
            where T : class
        {
            if (arg == null)
            {
                throw new ArgumentNullException();
            }
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void ThrowIfNullGenericArg(Nullable arg)
            where T : struct
        {
            if(arg == null)
            {
                throw new ArgumentNullException();
            }
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void ThrowIfNullObjectArg(object arg)
        {
            if(arg == null)
            {
                throw new ArgumentNullException();
            }
        }

        [Benchmark]
        public void CallMethodWithObjArgString()
        {
            ThrowIfNullObjectArg(str);
        }

        [Benchmark]
        public void CallMethodWithObjArgNullableInt()
        {
            ThrowIfNullObjectArg(num);
        }

        [Benchmark]
        public void CallMethodWithGenericArgString()
        {
            ThrowIfNullGenericArg(str);
        }

        [Benchmark]
        public void CallMethodWithGenericArgNullableInt()
        {
            ThrowIfNullGenericArg(num);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var summary = BenchmarkRunner.Run();
        }
    }

Method Mean Error StdDev
CallMethodWithObjArgString 1.784 ns 0.0166 ns 0.0138 ns
CallMethodWithObjArgNullableInt 124.335 ns 0.2480 ns 0.2320 ns
CallMethodWithGenericArgString 1.746 ns 0.0290 ns 0.0271 ns
CallMethodWithGenericArgNullableInt 2.158 ns 0.0089 ns 0.0083 ns

Наш generic на nullable типе отработал в 2000 раз быстрее! А всё из-за пресловутой упаковки(boxing). Когда мы вызываем CallMethodWithObjArgNullableInt, то наш nullable-int "упаковывается" и размещается в куче. Упаковка очень дорогая операция, от того метод и проседает по производительности. Таким образом использую generic мы можем избежать упаковки.


Итак, generic аргумент лучше object потому что:


  1. Спасает от упаковки
  2. Позволяет улучшить сигнатуру метода, при использовании ограничений

Upd. Спасибо хабраюзеру zelyony за замечание. Методы инлайнились, для более точных замеров добавил атрибут MethodImpl(MethodImplOptions.NoInlining).

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332640/


Метки:  

RAML-роутинг в Play Framework

Пятница, 07 Июля 2017 г. 18:12 + в цитатник
image

Play framework — очень гибкий инструмент, но информации о том, как изменить формат route-файла, на просторах интернета мало. Я расскажу о том, как можно заменить стандартный язык описания маршрутов на основе route-файла на описание в формате RAML. А для этого нам придется создать свой SBT-плагин.

В двух словах о RAML (RESTful API Modeling Language). Как совершенно справедливо сказано на главной странице проекта, этот язык существенно упрощает работу с API приложения на протяжении всего его жизненного цикла. Он лаконичен, легко переиспользуется, и что самое ценное— в равной степени легко читается машиной и человеком. То есть можно воплотить подход documentation as a code, когда один артефакт (RAML-скрипт) становится точкой входа для всех участников процесса разработки — аналитиков, тестировщиков и программистов.

Постановка задачи


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

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

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

Обычно в документации содержится информация о конечных точках REST, описание параметров и тела запроса, HTTP-коды и описание ответов. При этом для всех вышеперечисленных элементов часто указываются примеры. Этой информации вполне достаточно для тестирования работоспособности конечной точки — просто нужно взять пример запроса для неё и отослать на сервер. Если у конечной чтоки есть параметры, то их значения нужно также взять из примеров. Пришедший ответ сравнить с примером ответа или провалидировать его JSON-схему на основании документации. Чтобы примеры ответов соответствовали ответам сервера, тот должен работать с правильными данными в БД. Таким образом, при наличии БД с тестовыми данными и документации API сервиса с описанием ответов и примерами запросов, мы можем обеспечить простое тестирование работоспособности нашего сервиса. О нашей документации, системе тестирования и БД сейчас я упомянул для полноты картины, и мы о них обязательно поговорим в другой раз. Здесь же я расскажу о том, как на основании такой документации генерировать как можно больше полезного серверного кода.

Наш сервер написан на Play 2.5 и предоставляет REST API своим клиентам. Формат обмена данными — JSON. Стандартное описание API в Play framework находится в файле conf/route. Синтаксис этого описания прост и ограничивается описанием имен конечных точек и их параметров, а также привязкой конечных точек к методам контроллера в файле routes. Нашей целью будет замена стандартного синтаксиса на описание в формате RAML. Для этого нам нужно:

  1. Разобраться, как в Play устроена маршрутизация и как обрабатываются route-файлы.
  2. Заменить стандартный механизм маршрутизации на наш механизм, использующий RAML.
  3. Посмотреть на результат и сделать выводы :)

Итак, давайте по порядку.

Роутинг в Play framework


Play framework рассчитан на использование с двумя языками — Scala и Java. Поэтому для описания маршрутов авторы фреймворка не стали использовать DSL на базе какого-то конкретного языка, а написали свой язык и компилятор к нему. Далее я буду говорить про Scala, но всё сказанное справедливо и для Java. Play-приложение собирается с помощью SBT. Во время сборки проекта route-файлы компилируются в файлы на Scala или Java, и далее результат компиляции используется при сборке. За обработку route-файла отвечает SBT-плагин com.typesafe.play.sbt-plugin. Давайте посмотрим, как он работает. Но для начала пару слов об SBT.

Основным понятием SBT является ключ. Ключи бывают двух типов: TaskKey и SettingsKey. Первый тип используется для хранения функций. Каждое обращение к этому ключу приводит к вызову этой функции. Второй тип ключа хранит константу и вычисляется один раз. Compile — это TaskKey, в процессе выполнения он вызывает другой TaskKey, sourceGenerators, для кодогенерации и создания исходных файлов. Собственно SBT-plugin добавляет функцию обработки route-файла к sourceGenerators.

Обычно на основе route создается два основных артефакта — файл target/scala-2.11/routes/main/router/Routes.scala и target/scala-2.11/routes/main/controllers/ReverseRoutes.scala. Класс Routes используется для маршрутизации входящих запросов. ReverseRoutes используется для вызова конечных точек из кода контроллеров и view по имени конечной точки. Давайте проиллюстрируем вышесказанное примером.

conf/routes
image

Тут мы объявляем параметризованную конечную точку и мапим её на метод HomeController.index. В результате компиляции этого файла получается следующий код на Scala:

target/scala-2.11/routes/main/router/Routes.scala

image
image

Этот класс занимается маршрутизацией входящих запросов. В качестве аргументов ему передаются ссылка на контроллер (точнее, инжектор, но это не существенно) и префикс URL пути, который настраивается в конфигурационном файле. Далее в классе объявлена «маска» маршрутизации controllers_HomeController_index0_route. Маска состоит из HTTP-глагола и паттерна маршрута. Последний состоит из частей, каждая соответствует элементу URL пути. StaticPart определяет маску для неизменной части пути, DynamicPart задает шаблон для URL параметра. Каждый входящий запрос попадает в функцию routes, где сопоставляется с доступными масками (в нашем случае она одна). Если совпадений не найдено — клиент получит 404 ошибку, в противном случае будет вызван соответствующий обработчик. В нашем примере обработчик один — это controllers_HomeController_index0_invoker. В обязанности обработчика входит вызов метода контроллера с нужным набором параметров и трансформация результатов этого вызова.

target/scala-2.11/routes/main/controllers/ReverseRoutes.scala

image

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

Итак, чтобы сменить формат описания маршрутов нам достаточно написать свой генератор файла Routes. ReverseRoutes нам не нужен, так как наш сервис отдает JSON и view у него нет. Чтобы наш генератор сработал, нужно включить его. Можно копировать исходники генератора в каждый проект, где он нужен, а далее подключать его в build.sbt. Но правильнее будет оформить генератор в виде плагина к SBT.

Плагин SBT


О плагинах SBT исчерпывающе написано в документации. Тут я упомяну об основных, на мой взгляд, моментах. Плагин — это набор дополнительной функциональности. Обычно плагины добавляют в проект новые ключи и расширяют существующие. Нам, например, нужно будет расширить ключ sourceGenerators. Одни плагины могут зависеть от других, например, мы могли бы использовать в качестве основы плагин com.typesafe.play.sbt-plugin и изменить в нем только то, что нам нужно. Другими словами наш плагин зависит от com.typesafe.play.sbt-plugin. Чтобы SBT автоматически подключал все зависимости для нашего плагина, тот должен быть AutoPlugin'ом. Ну и последнее: из-за вопросов совместимости плагины пишутся на Scala 2.10.

Итак, нам нужно генерировать Routes.scala на основе файла RAML. Пусть этот файл называется conf/api.raml. Чтобы документацию в RAML-формате можно было использовать для маршрутизации, необходимо каким-то способом указать в нем для каждой конечной точки метод контроллера, который необходимо вызвать при получении запроса. RAML 0.8, который мы будем использовать, не имеет средств для указания такой информации, поэтому придется делать грязный хак (RAML 1.0 решает эту проблему с помощью аннотаций, но на момент написания статьи эта версия стандарта еще сыра). Добавим информацию о вызываемом методе контроллера в первую строку description для каждой конечной точки. Наш пример в RAML-формате из предыдущей главы будет выглядеть так:

image

На деталях парсинга RAML останавливаться не буду, скажу лишь, что можно использовать парсер от raml.org. В результате парсинга мы получаем список правил — по одному на каждую конечную точку. Правило задается следующим классом:

image

Названия и типы полей говорят сами за себя. Теперь для каждого правила мы можем в файле Routes.scala создать свою маску, обработчик и элемент case в функции route. Для решения этой задачи можно вручную генерировать строку с кодом Routes.scala на основе списка правил, или применить макросы. Но лучше выбрать промежуточный вариант, который предпочли и разработчики Play — использовать шаблонизатор. PВ Play применяется шаблонизатор twirl, и мы тоже его используем. Вот шаблон из нашего плагина, генерирующий функцию route:

image
Выглядит несколько запутанно, но если присмотреться, то всё становится ясно. Выражения, начинающиеся с @ — это директивы и переменные шаблонизатора. Переменные @ob и @cb будут раскрыты в { и } соответственно. А, например, @(routeIdentifier(route, index)) развернется по следующему правилу:

image

Теперь ясно, как написать код, создающий Routes.scala на основе RAML, и понятно, как подключить его к сборке. Исходники готового плагина лежат на Github.

Планы на будущее


Плагин позволил нам использовать документацию в качестве исходного кода для сервера. Но кодогенерация не использует всей доступной информации из RAML-файла. А именно, мы никак не используем информацию о типе запроса и ответа. В Play парсинг запроса и генерация ответа происходит в методах контроллера, но мы хотим генерировать этот код автоматически. Кроме того, у нас в планах использовать версию RAML 1.0.

На сегодня всё, спасибо за внимание!
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332638/


Метки:  

Rewarded Video: лучшие сценарии показа или как сделать так, чтобы вашу рекламу посмотрели

Пятница, 07 Июля 2017 г. 17:47 + в цитатник


Формат Rewarded Video (RV) стремительно растет: пользователей такая реклама практически не раздражает, так как выбор — смотреть или не смотреть — остается за ними. Однако, видео за вознаграждение работает все по тому же по закону Парето: нужно потратить время на разработку стратегии размещения RV, чтобы выжать из этого типа рекламы максимум.

По идее, реклама в приложении должна инициализироваться как можно раньше, чтобы правильно отобразить максимальное количество видео из загруженных. Казалось бы, в таком случае проще показать Rewarded Video cразу после входа в приложение — но этого как раз делать не рекомендуется. Такое решение ударит по метрикам удержания (Retention Rate) и репутации. Пользователь вряд ли захочет вернуться в приложение, где ему навязывают видеорекламу сразу после экрана загрузки.

Кроме того, Rewarded Video не стоит пускать в начало приложения и с коммерческой точки зрения: для поиска более дорогой рекламы, с более высоким eCPM, нужно время.

Еще одна особенность — креатив (само видео) в формате Rewarded Video весит больше, чем остальные виды рекламы, соответственно, и загружается дольше. “Водопад” из рекламных сетей (см. cхему) опрашивается 15-20 секунд, а на загрузку креатива нужно около 30 секунд.



Магазин приложения

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

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


Сanyon Crash — Fall Down

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


Family Guy — The Quest For Stuff

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


PixelMon Hunter

В процессе игры

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


Blusty Bubs — Brick Breaker

Еще один хороший вариант — показать видеорекламу в обмен на подсказки в игре. К примеру, пользователь долго мучается с одним и тем же уровнем или не может разгадать паззл — тогда после нескольких неудачных попыток можно предложить ему видеорекламу в обмен на ключ к разгадке. Так сделали разработчики из студии Rusty Lake, которые создают мистический паззл, вдохновленный Твин Пиксом. В какой-то момент они поняли, что загадки в игре слишком сложны, и добавили видеорекламу для получения подсказок. Этот шаг поднял и лояльность игроков, и выручку.
Кроме подсказок есть много способов мотивировать пользователя посмотреть Rewarded video — например, предложите ему суперсилу, удваивание наград или другую фишку, облегчающую игру.


Hidden Worlds Adventure

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

Важно: какой бы вариант интеграции видео вы не выбрали, вознаграждение за его просмотр должно быть максимально очевидно и важно для пользователя. Есть много примеров, в которых Display Rate остается низким, несмотря на грамотное расположение рекламы — это говорит о том, что награда не очевидна для игрока.


Сanyon Crash — Fall Down

Помните и о балансе внутриигровой экономики. Те, кто не хочет расставаться с внутриигровыми покупками, могут чередовать видео с вознаграждением: скажем, игрок может посмотреть Rewarded video и получить награду только раз в 2-4 часа, а может не ждать и купить ее за реальные деньги. Для примера, вот несколько подключенных к Appodeal приложений с удачным использованием Rewarded video: Color Six, Lolo App, Block Strike, Hidden Worlds Adventure.

Источник заглавного изображения
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332632/


5 приемов в помощь разработке на vue.js + vuex

Пятница, 07 Июля 2017 г. 16:50 + в цитатник
Недавно решил разобраться с vue.js. Лучший способ изучить технологию — что-нибудь на ней написать. С этой целью был переписан мой старый планировщик маршрутов, и получился вот такой проект. Код получился достаточно большим для того, чтобы столкнуться с задачей масштабирования.

В этой статье приведу ряд приемов, которые, на мой взгляд, помогут в разработке любого крупного проекта. Этот материал для вас, если вы уже написали свой todo лист на vue.js+vuex, но еще не зарылись в крупное велосипедостроение.



1. Централизованная шина событий (Event Bus)


Любой проект на vue.js состоит из вложенных компонентов. Основной принцип — props down, events up. Подкомпонент получает от родителя данные, которые он не может менять, и список событий родителя, которые он может запустить.

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

Разберемся с событиями. Зачастую полезно иметь глобальный event emitter, с которым может общаться любой компонент независимо от иерархии. Его очень легко сделать, дополнительные библиотеки не нужны:

Object.defineProperty(Vue.prototype,"$bus",{
	get: function() {
		return this.$root.bus;
	}
});

new Vue({
	el: '#app',
	data: {
		bus: new Vue({}) // Here we bind our event bus to our $root Vue model.
	}
});

После этого в любом компоненте появляется доступ к this.$bus, можно подписываться на события через this.$bus.$on() и вызывать их через this.$bus.$emit(). Вот пример.

Очень важно понимать, что this.$bus — глобальный объект на все приложение. Если забывать отписываться, компоненты остаются в памяти этого объекта. Поэтому на каждый this.$bus.$on в mounted должен быть соответствующий this.$bus.$off в beforeDestroy. Например, так:

mounted: function() {
	this._someEvent = (..) => {
		..
	}
	this._otherEvent = (..) => {
		..
	}
	this.$bus.$on("someEvent",this._someEvent);
	this.$bus.$on("otherEvent",this._otherEvent);
},
beforeDestroy: function() {
	this._someEvent && this.$bus.$off("someEvent",this._someEvent);
	this._otherEvent && this.$bus.$off("otherEvent",this._otherEvent);
}

2. Централизованная шина промисов (Promises Bus)


Иногда в компоненте нужно инициализировать некую асинхронную штуку (например, инстанц google maps), к которой хочется обращаться из других компонентов. Для этого можно организовать объект, который будет хранить промисы. Например, такой. Как и в случае в event bus, не забываем удаляться при деинициализации компонента. И вообще, указанным выше способом к vue можно прицепить любой внешний объект с любой логикой.

3. Плоские структуры (flatten store)


В сложном проекте данные зачастую сильно вложены. Работать с такими данными неудобно как в vuex, так и в redux. Рекомендуется уменьшать вложенность, например, воспользовавшись утилитой normalizr. Утилита — это хорошо, но еще лучше понимать, что она делает. Я не сразу пришел к пониманию плоской структуры, для таких же типа себя рассмотрю подробный пример.

Имеем проекты, в каждом — массив слоев, в каждом слое — массив страниц: projects > layers > pages. Как организовать хранилище?

Первое, что приходит в голову — обычная вложенная структура:

projects: [{
	id: 1,
	layers: [{
		id: 1,
		pages: [{
			id: 1,
			name: "page1"
		},{
			id: 2,
			name: "page2"
		}]
	}]
}];

Такую структуру легко читать, легко бегать циклом foreach по проектам, рендерить подкомпоненты со списками слоев и так далее. Но предположим, что нужно поменять название страницы с id:1. Внутри некоторого маленького компонента, который отрисовывает страницу, вызывается $store.dispatch(«changePageName»,{id:1,name:«new name»}). Как найти место, где в этой глубоко вложенной структуре лежит нужный page с id:1? Пробегать по всему хранилищу? Не лучшее решение.

Можно указывать полный путь, типа

$store.dispatch("changePageName",{projectId:1,layerId:1,id:1,name:"new name"})

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

Вторая попытка, из sql:

projects: [{id:1}],
layers: [{id:1,projectId:1}],
pages: [{
	id: 1,
	name: "page1",
	layerId: 1,
	projectId: 1
},{
	id: 2,
	name: "page2",
	layerId: 1,
	projectId: 1
}]

Теперь данные легко менять. Но тяжело бегать. Чтобы вывести все страницы в одном слое, нужно пробежать по вообще всем страницам. Это может быть спрятано в getter-е, или в рендеринге шаблона, но пробежка все равно будет.

Третья попытка, подход normalizr:

projects: [{
	id: 1,
	layersIds: [1]
}],
layers: {
	1: {
		pagesIds: [1,2]
	}
},
pages: {
	1: {name:"page1"},
	2: {name:"page2"}
}

Теперь все страницы слоя могут быть получены через тривиальный геттер

layerPages: (state,getters) => (layerId) => {
	const layer = state.layers[layerId];
	if (!layer || !layer.pagesIds || layer.pagesIds.length==0) return [];
	return layer.pagesIds.map(pageId => state.pages[pageId]);
}

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

4. Мутации не нужны


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

Но валидация далеко не всегда синхронна. Следовательно, по крайней мере часть валидационной логики будет находится не в мутациях, а в действиях (actions).

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

5. Ограничение реактивности


Иногда бывает, что данные в хранилище не меняются. Например, это могут быть результаты поиска объектов на карте, запрошенные из внешнего api. Каждый результат — это сложный объект с множеством полей и методов. Нужно выводить список результатов. Нужна реактивность списка. Но данные внутри самих объектов постоянны, и незачем отслеживать изменение каждого свойства. Чтобы ограничить реактивность, можно использовать Object.freeze.

Но я предпочитаю более тупой метод: пусть state хранит только список id-шников, а сами результаты лежат рядом в массиве. Типа:

const results = {};
const state = {resultIds:[]};

const getters = {
	results: function(state) {
		return _.map(state.resultsIds,id => results[id]);
	}
}

const mutations = {
	updateResults: function(state,data) {
		const new = {};
		const newIds = [];
		data.forEach(r => {
			new[r.id] = r;
			newIds.push(r.id);
		});
		results = new;
		state.resultsIds = newIds;
	}
}

Вопросы


Кое-что у меня получилось не настолько красиво, как хотелось. Вот мои вопросы к сообществу:

— Как победить css анимации сложнее изменения opacity? Часто хочется анимировать появление какого-то блока неизвестных размеров, т.е. изменить его высоту с height: 0 до height: auto.
Это легко решается с javascript — просто оборачиваем в контейнер с overflow: hidden, смотрим высоту обернутого элемента и анимируем высоту контейнера. Это можно решить через css?

— Ищу нормальный способ работы с иконками в webpack, пока безуспешно (поэтому продолжаю пользоваться fontello). Нравятся иконки whhg. Вытащил svg, разбил на файлы. Хочу выбрать несколько файлов и автоматически собирать в inline шрифт + классы на основе названий файлов. Чем это можно делать?
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332628/


Метки:  

Обработка многократно возникающих SIGSEGV-подобных ошибок

Пятница, 07 Июля 2017 г. 16:47 + в цитатник

Тема изъезжена и уже не мало копий было сломано из-за неё. Так или иначе люди продолжают задаваться вопросом о том может ли приложение написанное на C/C++ не упасть после разыменования нулевого указателя, например. Краткий ответ — да, даже на Хабре есть статьи на сей счёт.


Одним из наиболее частых ответов на данный вопрос является фраза "А зачем? Такого просто не должно случаться!". Истинные причины того почему люди продолжают интересоваться данной тематикой могут быть разные, одной из них может быть лень. В случая когда лениво или дорого проверять всё и вся, а исключительные ситуации случаются крайне редко можно, не усложняя кода, завернуть потенциально падающие фрагменты кода в некий try/catch который позволит красиво свернуть приложение или даже восстановится и продолжить работу как ни в чём не бывало. Наиболее ненормальным как раз таки может показаться желание снова и снова ловить ошибки, обычно приводящие к падению приложения, обрабатывать их и продолжать работу.


Итак попробуем создать нечто позволяющее решать проблему обработки SIGSEGV-подобных ошибок. Решение должно быть по максимуму кроссплатформенным, работать на всех наиболее распространённых десктопных и мобильных платформах в однопоточных и многопоточных окружениях. Так же сделаем возможным существование вложенных try/catch секций. Обрабатывать будем следующие виды исключительных ситуаций: доступ к памяти по неправильным адресам, выполнение невалидных инструкций и деление на ноль. Апофеозом будет то, что произошедшие аппаратные исключения будут превращаться в обычные C++ исключения.


Наиболее часто для решения аналогичным поставленной задачам рекомендуется использовать POSIX сигналы на не Windows системах, а на Windows Structured Exception Handling (SEH). Поступим примерно следующим образом, но вместо SEH будем использовать Vectored Exception Handling (VEH), которые очень часто обделены вниманием. Вообще, со слов Microsoft, VEH является расширением SEH, т.е. чем-то более функциональным и современным. VEH чем-то схож c POSIX сигналами, для того чтобы начать ловить какие либо события обработчик надо зарегистрировать. Однако в отличии от сигналов для VEH можно регистрировать несколько обработчиков, которые будут вызываться по очереди до тех пор пока один из них не обработает возникшее событие.


В довесок к обработчикам сигналов возьмём на вооружение пару setjmp/longjmp, которые позволят нам возвращаться туда куда нам хочется после возникновения аварийной ситуации и каким-либо способом обрабатывать эту самую исключительную ситуацию. Так же, чтобы наша поделка работала в многопоточных средах нам понадобится старый добрый thread local storage (TLS), который также доступен во всех интересующих нас средах.


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


stack_t ss;
ss.ss_sp = exception_handler_stack;
ss.ss_flags = 0;
ss.ss_size = SIGSTKSZ;
sigaltstack(&ss, 0);

struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_ONSTACK;
sa.sa_handler = signalHandler;

for (int signum : handled_signals)
    sigaction(signum, &sa, &prev_handlers[signum - MIN_SIGNUM]);

Выше приведённый фрагмент кода регистрирует обработчик для следующий сигналов: SIGBUS, SIGFPE, SIGILL, SIGSEGV. Помимо этого с помощью вызова sigaltstack указываться, что обработчик сигнала должен запускаться на альтернативном, своём собственном, стеке. Это позволяет выживать приложению даже в условиях stack overflow, который легко может возникнуть в случае бесконечно рекурсии. Если не задать альтернативный стек, то подобного рода ошибки не возможно будет обработать, приложение будет просто падать, т.к. для вызова и выполнения обработчика просто не будет стека и с этим ничего нельзя будет сделать. Так же сохраняются указатели на ранее зарегистрированные обработчики, что позволит их вызывать, если наш обработчик поймёт, что делать ему нечего.


Для Windows код намного короче:


exception_handler_handle = AddVectoredExceptionHandler(1, vectoredExceptionHandler);

Обработчик один, он ловит сразу все события (не только аппаратные исключения надо сказать) и нет никакой возможности что-либо сделать со стеком как в Linux, например. Единица, подаваемая первым аргументом в функцию AddVectoredExceptionHandler, говорит о том, что наш обработчик должен вызываться первым, перед любыми другими уже имеющимися. Это даёт нам шанс быть первыми и предпринять необходимые нам действия.


Сам обработчик для POSIX систем выглядит следующим образом:


static void signalHandler(int signum)
{
    if (execution_context) {
        sigset_t signals;
        sigemptyset(&signals);
        sigaddset(&signals, signum);
        sigprocmask(SIG_UNBLOCK, &signals, NULL);
        reinterpret_cast(static_cast(execution_context))->exception_type = signum;
        longjmp(execution_context->environment, 0);
    }
    else if (prev_handlers[signum - MIN_SIGNUM].sa_handler) {
        prev_handlers[signum - MIN_SIGNUM].sa_handler(signum);
    }
    else {
        signal(signum, SIG_DFL);
        raise(signum);
    }
}

Надо сказать, что для того чтобы наш обработчик сигналов стал многоразовым, т.е. мог вызываться снова и снова в случае возникновения новых ошибок, мы должны при каждом заходе разблокировать сработавший сигал. Это необходимо в тех случаях, когда обработчик знает, что исключительная ситуация возникла в участке кода, который завёрнут в некие try/catch о которых речь пойдёт позже. Если же аварийная ситуация сложилась там где мы её совсем не ожидали, дела будут переданы ранее зарегистрированному обработчику сигналов, если такового нет, то вызывается обработчик по умолчанию, который завершит терпящее аварию приложение.


Обработчик для Windows выглядит следующим образом:


static LONG WINAPI vectoredExceptionHandler(struct _EXCEPTION_POINTERS *_exception_info)
{
    if (!execution_context ||
        _exception_info->ExceptionRecord->ExceptionCode == DBG_PRINTEXCEPTION_C ||
        _exception_info->ExceptionRecord->ExceptionCode == 0xE06D7363L /* C++ exception */
    )
        return EXCEPTION_CONTINUE_SEARCH;

    reinterpret_cast(static_cast(execution_context))->dirty = true;
    reinterpret_cast(static_cast(execution_context))->exception_type = _exception_info->ExceptionRecord->ExceptionCode;
    longjmp(execution_context->environment, 0);
}

Как уже упоминалось выше VEH обработчик на Windows ловит много чего ещё помимо аппаратных исключений. Например при вызове OutputDebugString возникает исключение с кодом DBG_PRINTEXCEPTION_C. Подобные события мы обрабатывать не будем и просто вернём EXCEPTION_CONTINUE_SEARCH, что приведёт к тому что ОС пойдёт искать следующий обработчик, который обработает данное событие. Также мы не хотим обрабатывать C++ исключения, которым соответствует магический код 0xE06D7363L не имеющий нормального имени.


Как на POSIX-совместимых системах так и на Windows в конце обработчика вызывается longjmp, который позволяет нам вернуться вверх по стеку, до самого начала секции try и обойти её попав в ветку catch, в которой можно будет сделать все необходимые для восстановления работы действия и продолжить работу так как будто ничего страшного не произошло.


Для того, чтобы обычный C++ try начал ловить не свойственные ему исключительные ситуации необходимо в самое начало поместить небольшой макрос HW_TO_SW_CONVERTER:


#define HW_TO_SW_CONVERTER_UNIQUE_NAME(NAME, LINE) NAME ## LINE
#define HW_TO_SW_CONVERTER_INTERNAL(NAME, LINE) ExecutionContext HW_TO_SW_CONVERTER_UNIQUE_NAME(NAME, LINE); if (setjmp(HW_TO_SW_CONVERTER_UNIQUE_NAME(NAME, LINE).environment)) throw HwException(HW_TO_SW_CONVERTER_UNIQUE_NAME(NAME, LINE))
#define HW_TO_SW_CONVERTER() HW_TO_SW_CONVERTER_INTERNAL(execution_context, __LINE__)

Выглядит довольно кудряво, но по факту здесь делается очень простая вещь:


  1. Вызывается setjmp, который позволяет нам запомнить место где мы начали и куда нам надо вернуться в случае аварии.
  2. Если по пути выполнения случилось аппаратное исключение, то setjmp вернёт не нулевое значение, после того как где-то по пути был вызван longjmp. Это приведёт к тому, что будет брошено C++ исключение типа HwException, которое будет содержать информацию о том какого вида ошибка случилась. Брошенное исключение без проблем ловится стандартным catch.

Упрощённо приведённый выше макрос разворачивается в следующий псевдокод:


if (setjmp(environment))
    throw HwException();

У подхода setjmp/longjmp есть один существенный недостаток. В случае обычных C++ исключений, происходит размотка стека при которой вызываются деструкторы всех созданных по пути объектов. В случае же с longjmp мы сразу прыгаем в исходную позицию, никакой размотки стека не происходит. Это накладывает соответствующие ограничения на код, который находится внутри таких секций try, там нельзя выделять какие-либо ресурсы ибо есть риск их навсегда потерять, что приведёт к утечкам.


Ещё одним ограничением является то, что setjmp нельзя использовать в функциях/методах объявленных как inline. Это ограничение самого setjmp. В лучшем случае компилятор просто откажется собирать подобный код, в худшем он его соберёт, но полученный бинарный файл будет просто аварийно завершать свою работу.


Самым ненормальным действием, которое приходится принимать после обработки аппаратного исключения на Windows является необходимость вызова RemoveVectoredExceptionHandler. Если этого не сделать, то после каждого входа в наш обработчик VEH и выполнения longjmp там будет складываться ситуация как-будто наш обработчик был зарегистрирован ещё один раз. Это приводит к тому, что при каждой последующей аварийной ситуации обработчик будет вызываться всё больше и больше раз подряд, что будет приводить к плачевным последствиям. Данное решение было найдено исключительно путём многочисленных магических экспериментов и нигде никак не документировано.


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


Сам контекст исполнения оформлен в виде простого класса имеющего следующие конструктор и деструктор:


ExecutionContext::ExecutionContext() : prev_context(execution_context)
{
#if defined(PLATFORM_OS_WINDOWS)
    dirty = false;
#endif
    execution_context = this;
}

ExecutionContext::~ExecutionContext()
{
#if defined(PLATFORM_OS_WINDOWS)
    if (execution_context->dirty)
        RemoveVectoredExceptionHandler(exception_handler_handle);
#endif
    execution_context = execution_context->prev_context;
}

Данный класс имеет поле prev_context, которое даёт нам возможность создавать цепочки из вложенных секций try/catch.


Полный листинг описанного выше изделия доступен в GitHub'е:
https://github.com/kutelev/hwtrycatch


В доказательство того, что всё работает как описано имеется автоматическая сборка и тесты под платформы Windows, Linux, Mac OS X и Android:


https://ci.appveyor.com/project/kutelev/hwtrycatch
https://travis-ci.org/kutelev/hwtrycatch


Под iOS это тоже работает, но за неимением устройства для тестирования нет и автоматических тестов.


В заключение скажем, что подобный подход можно использовать и в обычном C. Надо лишь написать несколько макросов, которые будут имитировать работу try/catch из C++.


Так же стоит сказать, что использование описанных методов в большинстве случаев является очень плохой идеей, особенно, если учесть, что на уровне сигналов нельзя выяснить, что же привело к возникновению SIGSEGV или SIGBUS. Это равновероятно может быть как и чтение по неправильным адресам так и запись. Если же чтение по произвольным адресам является операцией не деструктивной, то запись может приводить к плачевным результатам таким как разрушением стека, кучи или даже самого кода.

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332626/


Метки:  

Выбор алгоритма вычисления квантилей для распределённой системы

Пятница, 07 Июля 2017 г. 15:58 + в цитатник


Всем привет!


Меня зовут Александр, я руковожу отделом Data Team в Badoo. Сегодня я расскажу вам о том, как мы выбирали оптимальный алгоритм для вычисления квантилей в нашей распределённой системе обработки событий.


Ранее мы рассказывали о том, как устроена наша система обработки событий UDS (Unified Data Stream). Вкратце – у нас есть поток гетерогенных событий, на котором нужно в скользящем окне проводить агрегацию данных в различных разрезах. Каждый тип события характеризуется своим набором агрегатных функций и измерений.


В ходе развития системы нам потребовалось внедрить поддержку агрегатной функции для квантилей. Более подробно о том, что такое перцентили и почему они лучше представляют поведение метрики, чем min/avg/max, вы можете узнать из нашего поста про использование Pinba в Badoo. Вероятно, мы могли бы взять ту же имплементацию, что используется в Pinba, но стоит принять во внимание следующие особенности UDS:


  1. Вычисления «размазаны» по Hadoop-кластеру.
  2. Дизайн системы подразумевает группировку по произвольному набору атрибутов. Это означает, что количество метрик вида «перцентиль» исчисляется миллионами.
  3. Поскольку вычисления производятся с использованием Map/Reduce, то все промежуточные вычисления агрегатных функций должны обладать свойством аддитивности (мы должны иметь возможность «сливать» их с разных нод, производящих вычисления независимо).
  4. Pinba и UDS имеют различные языки имплементации – C и Java соответственно.

Критерии оценки


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


Точность вычисления


Мы решили, что нас устроит точность вычислений вплоть до 1,5%.


Время выполнения


Нам важно минимизировать период времени от возникновения события до визуализации его квантилей на графиках. Этот фактор складывается из трёх других:


  • время «математики» (стадия Map) – накладные расходы на инициализацию структур данных и добавление данных в них;
  • время сериализации – для того чтобы произвести слияние двух структур данных, они должны быть сериализованы и переданы по сети.
  • время слияния (стадия Reduce) – накладные расходы на слияние двух структур данных, вычисленных независимо.

Объём памяти


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


  • RAM – объём, занимаемый структурами данных для квантилей.
  • Shuffle – так как расчёт производится с использованием Map/Reduce, то неизбежен обмен промежуточными результатами между нодами. Для того, чтобы это было возможно, структура данных должна быть сериализована и записана на диск/в сеть.

Также мы выдвигаем следующие условия:


Типы данных


Алгоритм должен поддерживать вычисления для неотрицательных величин, представленных типом double.


Язык программирования


Должна присутствовать имплементация на Java без использования JNI.


Участники исследования



Naive


Чтобы иметь некий референс для сравнения, мы написали реализацию «в лоб», которая хранит все входящие значения в double[]. При необходимости вычисления квантиля массив сортируется, вычисляется ячейка, соответствующая квантилю, и берётся её значение. Слияние двух промежуточных результатов происходит путём конкатенации двух массивов.


Twitter Algebird


Это решение было найдено нами в ходе рассмотрения алгоритмов, заточенных под Spark (используется в основе UDS). Библиотека Twitter Algebird предназначена для расширения алгебраических операций, доступных в языке Scala. Она содержит ряд широко используемых функций ApproximateDistinct, CountMinSketch и, помимо всего прочего, реализацию перцентилей на основании алгоритма Q-Digest. Математическое обоснование алгоритма вы можете найти здесь. Вкратце структура представляет собой бинарное дерево, в котором каждый узел хранит некоторые дополнительные атрибуты.


Ted Dunning T-Digest


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


Airlift Quantile Digest


На этот продукт мы наткнулись при реверс-инжиниринге распределённого SQL-движка Facebook Presto. Было несколько удивительно увидеть реализацию квантилей в REST-фреймворке, но высокая скорость работы и архитектура Presto (схожая с Map/Reduce) подтолкнули нас к тому, чтобы протестировать это решение. В качестве математического аппарата используется опять же Q-Digest.


High Dynamic Range (HDR) Histogram


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


Методика проведения теста



Качественная оценка


Каждое из рассматриваемых программных решений было обёрнуто некоторой прослойкой (моделью) (чтобы адаптировать его под фреймворк для тестирования). Перед проведением performance-тестов для каждой модели были написаны unit-тесты для проверки её достоверности. Эти тесты проверяют, что модель (её нижележащее программное решение) может выдавать квантили с заданной точностью (проверялись точности 1% и 0,5%).


Performance-тесты


Для каждой из моделей были написаны тесты с использованием JMH. Они были разделены на категории, про каждую из которых я расскажу подробно. Не буду «засорять» пост сырыми выводом от JMH – лучше сразу буду визуализировать в виде графиков.


Raw-тест

В этом тесте мы измеряем производительность структур данных на вставку, то есть производятся замеры времени, требуемого на инициализацию структуры и на заполнение её данными. Также мы рассмотрим, как изменяется это время в зависимости от точности и количества элементов. Измерения производились для последовательностей монотонно возрастающих чисел в диапазонах 10, 100, 1000, 10000, 100000, 1000000 при погрешности вычисления 0,5% и 1%. Вставка производилась пачкой (если структура поддерживает) или поэлементно.


В результате мы получили следующую картину (шкала ординат логарифмическая, меньшие значения – лучше):



Результаты приведены для точности 1%, но для точности 0,5% картина принципиально не меняется. Невооружённым глазом видно, что с точки зрения вставки HDR является оптимальным вариантом при условии наличия более чем 1000 элементов в модели.


Volume test

В этом тесте мы производим замеры объёма, занимаемого моделями в памяти и в сериализованном виде. Модель заполняется последовательностями данных, затем производится оценка её размера. Ожидается, что лучшей окажется модель с меньшим объёмом занимаемой памяти. Замер производится с использованием SizeEstimator из Spark.



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


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



Абсолютным чемпионом вновь является HDR.


Map/Reduce-тест

Этот тест наиболее полно отражает поведение системы в боевой ситуации. Методика теста следующая:


  1. Предсоздаётся десять моделей, содержащих n-ное количество значений.
  2. Производится их слияние (эмуляция map-side combine).
  3. Полученное значение сериализуется и десериализуется десять раз (эмуляция передачи по сети с разных воркеров).
  4. Десериализованные модели сливаются (эмуляция финальной reduce-стадии).

Результаты теста (меньшие значения – лучше):



И в данном тесте мы снова отчётливо видим уверенное доминирование HDR в долгосрочной перспективе.


Анализ результатов


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



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



Эта статистика позволила нам принять решение о необходимости посмотреть на поведение метрик с большим количеством измерений. В результате мы выяснили, что 90 перцентиль числа событий на одну метрику (то есть нашу тестовую модель) находится в пределах 2000. Как мы видели ранее, при подобном количестве элементов есть модели, которые ведут себя лучше, чем HDR. Так у нас появилась новая модель – Combined, которая объединяет в себе лучшее от двух миров:


  1. Если модель содержит менее n элементов, используются алгоритмы Naive модели
  2. При превышении порога n инициализируется модель HDR.

Смотрим результаты этого нового участника!





Как видно из приведённых графиков, Combined-модель действительно ведёт себя лучше HDR на малой выборке и сравнивается с ней при увеличении числа элементов.


Если вас интересуют код исследования и примеры API рассмотренных алгоритмов, вы можете найти всё это на GitHub. И если вы знаете реализацию, которую мы могли бы добавить к сравнению, напишите о ней в комментариях!

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332568/


Метки:  

Как выигрывать в конкурсах Вконтакте? Другой подход

Пятница, 07 Июля 2017 г. 15:07 + в цитатник


Привет, Хабр!
Написать этот пост во многом нас побудила одна статья, опубликованная пару недель назад на Хабре. А именно «Как выигрывать в конкурсах репостов Вконтакте»?

Автор считает, что ответ очевиден
«надо участвовать во всех конкурсах и по теории вероятностей, чем больше конкурсов, тем больше шанс выиграть хоть что-то»

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

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

— Один скриншот?
— Да, один скриншот.

— Может ли нечестный организатор нажимать «Выбрать победителя» в такой программе до тех пор, пока не будет выбран нужный ему участник?
— Возможность есть.

— Будет ли где-то опубликован реестр количества отборов победителя в такой программе-выбирателе?
— О таких случаях не известно.

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



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

Отвергаешь – предлагай


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

  1. Условия участия
  2. Точное время определение победителя (в этот момент программа должна сама выбрать победителя по условиям участия).
  3. Публикация результатов конкурсов в реестре.

— Может ли нечестный организатор выбрать победителем нужного ему участника?
— Нет, внести изменения в реестр результатов возможно только для администраторов центра сертификации конкурса.

Форма взаимодействия с центром сертификации может быть реализована также через приложение в социальной сети, возможность выбрать другого победителя — по запросу с указанием причины. Регистрация в подобном центре поднимет популярность конкурса по сравнению с незарегистрированным при достаточной распространённости информации о функции центра сертификации.

Работаем с тем, что есть, но иначе


Что делать, если желание ждать порядка в организации конкурсов нет, но нужно его провести?



В поисках лучшего была переосмыслена механика конкурса.

— Что если добавить в конкурсы игровую механику?
— Никто не будет разбираться в новых правилах игры! В этом «сила» конкурса репостов – все знают, что делать.

— Тогда добавим известную всем с детства механику игры «Морской бой»!
— Хорошо, на сетке 10*10 расположим призы до публикации конкурса. Участники должны в комментариях к посту указать координаты одной клетки, например, Б8. Попавший в клетку, за которой спрятан приз, — победитель. Разные игроки могут называть одни и те же координаты. Узнать о победе можно только по окончании конкурса.



— Стоп, а как доказать, что призы были расположены до начала конкурса и их расположение не менялось?
— Давайте запишем видео с экрана компьютера. В записи мы посетим главную страницу Яндекса, на которой будут новости, и страницу yandex.ru/time. Затем мы зайдём в социальную сеть, в которой будем проводить розыгрыш. Оставим комментарий под какой-либо записью. Этот комментарий также будет иметь отметку времени. В конце мы покажем, где на сетке мы «прячем» приз. В условиях участия мы опишем в какое время записано видео и оставим ссылку на комментарий. Когда конкурс закончится мы опубликуем это видео.

— Всё?
— Если конкурс на широкую аудиторию — да.

— Но что если провести конкурс на Хабрахабре?
— Тогда для записанного видео мы узнаем и заранее опубликуем хеш-сумму файла видеозаписи. Нам поможет утилита CertUtil по умолчанию входящая в комплект Windows. Чтобы узнать хеш сумму файла зайдём в командную строку: (клавиши Win+R и набираем cmd) и выполним команду
certutil -hashfile c:file SHA512

где, c:file — путь до файла. По умолчанию утилита считает хеш-сумму с помощью SHA1, мы выбрали SHA512



— Но как доказать, что победитель не связан с организатором, который рассказал ему, где спрятан приз?
— Для этого конкурс не может быть с ограниченным количеством призов, но может быть ограничен по времени час, день или 10 минут. Решать организатору. Он заранее знает вероятности в игре. Если приз 1, а клеток 100, на 100 участников будет около 1 победителя (точнее смотри распределение ниже). Если организатор сообщает кому-то где приз, он не лишает возможности других его выиграть, а только увеличивает свои затраты на призы. Зная вероятность, конверсию просмотров в участники и трафик в единицу времени, можно исходя из бюджета на призы рассчитать минимальное время конкурса. Продлить время конкурса в разумных пределах организатор имеет право, а сократить нет.



— Интересно, можно попробовать. А какой будет приз?
— Везде новости про атаки новых вирусов-вымогателей. WannaCry, Petya/notPetya, Erebus… Шифровальщики атакуют. Разыграем 100 ГБ Cloud4Y BACKUP в подарок на 2 месяца?

— Да. Попробуем. Комментарии с координатами клетки на карте можно писать под этой статьёй. Один участник может указать координаты только одной клетки. Если указано больше, учитывается только первая попытка участника. Минимальный срок проведения конкурса 1 час с момента публикации, но не более 24 часов.



  1. Расположение приза на сетке записано в видео в 14:34 по московскому времени 7 июля 2017 года. Видео запись будет опубликована по ссылке
  2. Ссылка на комментарий, оставленный в видео
  3. Хэш-сумма файла видеозаписи SHA512


Сыграем?

P.S. Для тех, кто не хочет проверять свою удачу, у нас также есть интересное предложение:
Выгодные условия на услугу резервного копирования в безопасное облако (BaaS). Получите каждый второй гигабайт бэкапа в подарок до конца лета!
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332624/


Метки:  

DPM: Почему он такой?

Пятница, 07 Июля 2017 г. 14:57 + в цитатник
Сегодня мы поговорим о продукте, который вызывает довольно неоднозначное мнение в профессиональном сообществе — Microsoft Data Protection Manager. Под катом вы найдёте первую часть из серии статей, в которой мы погрузимся в историю.



Передаю слово автору статьи, khabarovdaniil (Даниилу Хабарову).

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

План нашего рассказа будет такой:
В первой части мы поговорим про исторический контекст Microsoft Data Protection Manager, и то, почему он такой.
Вовторой — о том, как он работает технически, с заглядыванием на страшного зверя VSS.
Ну а в третьей — текущее положение дел, что он умеет и как мы это сможем использовать, в том числе и с Microsoft Azure Backup.

Microsoft Data Protection Manager. Начало


Как вы, безусловно, помните, во времена Windows Server 2003 все администраторы пользовались Windows Backup и все были счастливы. Потребность в чем-то большем существовала, но в большинстве случаев она покрывалась исключительно сторонними инструментами, такими как NetBackup, BackupExec, иногда это были прекрасные скрипты robocopy с использованием разнообразных архиваторов.



Не то, чтобы это было удобно, но в те времена гигабайт дискового пространства стоил существенно дороже, бэкап размером в Гигабайт вызывал уважение, а еще существовавшая 10 МБит сеть не всегда позволяла передать необходимое количество информации в пределах адекватного времени. Ленточные библиотеки и стримеры активно записывали информацию на старые кассеты LTO-3.

Именно это и определило развитие будущего DPM:
  • он должен был уметь писать на ленту;
  • улучшить существующий Windows Backup;
  • смотреть в будущее, ориентируясь на растущие потребность в больших и быстрых дисковых хранилищах;
  • предоставлять полноценное резервное копирование Windows инфраструктуры. К таковой относилось, в первую очередь AD, SQL, файловые ресурсы.

И вот, 27 сентября далёкого 2005 года, Microsoft впервые представил DPM 2006. Заострять внимание на этом продукте явно не стоит, потому что мало кто решился его использовать, а те кто и решились, не долго им пользовались, потому что появилась новая версия в рамках релиза System Center 2007.

DPM 2007. Per aspera ad astra


Следует справедливо заметить, что DPM, с некоторых пор, стал единственным поддерживаемым продуктом для резервного копирования AD, т.е. Domain Controllers. Ну, кроме, конечно, Windows Server Backup, который пришел на смену Windows Backup.



Итак, новый продукт предоставлял не в пример более широкий функционал резервного копирования:
  • File servers and workstations;
  • Microsoft Exchange;
  • SQL Server database software;
  • Windows SharePoint Services;
  • Virtual Server (с появлением SP1 — ещё и Hyper-V);
  • End User Recovery.

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

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

С появлением Hyper-V, DPM 2010 SP1 также научился делать резервное копирование виртуальных машин. Это был явный шаг вперёд по сравнению с Virtual PC.

С помощью DPM уже можно было строить иерархические структуры резервного копирования посредством географически разнесённых серверов, что было достаточно актуально в тот момент времени.

Большим минусом, с точки зрения администраторов, была невозможность частичного восстановления объектов AD, что вполне логично объясняется политикой Microsoft для целостности резервных копий. И даже несмотря на то, что функционал был востребован, команда AD так и не разрешила инкрементальное восстановления отдельных объектов исходя из принципов безопасности. Эта ситуация сохраняется и по сей день.

DPM 2010


Data Protection Manager 2010 явно долгожитель среди своих собратьев. Вообще, продукты резервного копирования в организациях живут достаточно долго без изменения и обновления.



Основная причина заключается в том, что зачастую существует боязнь обновления подобных продуктов, так как существуют опасения потери резервных копий с одной стороны, а с другой — потенциальное изменения нормативных документов, таких как План Аварийного Восстановления (в народе известном как DRP).



Чем же он так подкупил организации и системных администраторов? Ответ прост: он реально улучшил уже существующие наработки и добавил несколько нововведений:
  • 100 серверов;
  • 3000 Windows клиентов;
  • 2000 БД SQL Server;
  • 25 ТБ SharePoint ферм, с 1 миллионом объектов;
  • 40 ТБ Exchange storage groups и БД.

Кроме увеличенного объёма хранимых данных, появились реально полезные возможности, например, появилась поддержка Exchange 2010 и DAG. Многие, пожалуй, помнят интереснейшую систему отказоустойчивости Exchange 2007 LCR, CCR, SCR (особенно в кошмарах). Так вот DPM 2010 поддерживал и Exchange 2007 и Exchange 2010.

Ну и, конечно же, появилась поддержка новой на тот момент ОС — Windows Server 2008 R2.

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

DPM 2012 & DPM 2012 R2


Ну и последняя на сегодня версия — DPM 2012 R2.



Основными её нововведениями по сравнению с предшественником было:
  • централизованное управление;
  • возможность использования Generic Data Source (мое прямо любимое);
  • защита клиентов с помощью сертификатов;
  • дедупликация.

А также было улучшено:
  • восстановление отдельных сайтов и элементов SharePoint;
  • поддержка SQL FileStream;
  • расширенные возможности колокейшена лент.

Но самое большое нововведение — это, пожалуй, начало дружбы DPM c Azure backup. Начиная с SP1 и DPM 2012 R2 появилась возможность отправлять резервные копии в облачный сервис по подписке. С точки зрения резервного копирования это был реальный прорыв, и это реально было одним и самых больших нововведений в продукте за всю его историю.

Прежде чем раскланяться, терпеливый читатель, автор хочет озвучить несколько оправданий, чтобы правильно выстроить ожидания достопочтенной публики:
  • данная статья первая из серии, как и говорил в самом начале;
  • цель этой заметки познакомить с тем, как эволюционировал этот продукт, но никак не рассказать о глубинном принципе работы;
  • если вы хотите узнать глубинные принципы работы — дождитесь следующей серии этого рассказа. Он будет посвящен тому, как работает DPM Storage Pool, почему и какие у него существуют ограничения.

До следующей встречи на страницах. :)
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332622/


LibGDX + Scene2d (программируем на Kotlin). Часть 1

Пятница, 07 Июля 2017 г. 14:50 + в цитатник
И снова здравствуйте. По результатам прошлой публикации, я пришел к выводу что опять совершаю ошибки. Высокие темп публикации неудобен ни мне, ни вам. И попробую еще подсократить теорию, но приводить больше примеров кода.

Небольшое лирическое отступление. LibGDX в значительной части представляет из себя простую обертку над OpenGL. Просто работа с текстурами. Все что мы делаем — это указываем порядок и способ отрисовки текстур. Базовый инструмент для рисования текстур — Drawable.

Drawable


Drawable, это такая штука, которая встречается в Scene2d буквально на каждом шагу. Картинки, кнопки, фоны элементов, всякие элементы слайдеров, панелей прокруток и т.д. — все они используют Drawable для отображения себя на экране. С практической точки зрения, нас не сильно заботит как он устроен внутри. Потому что работать мы будем с тремя конкретными реализациями Drawable. Это TextureRegionDrawable, TiledDrawable и NinePatchDrawable. Вот текстура которую мы хотим нарисовать на экране:


А вот три варианта Drawable на основе этой текстуры


Первый вариант — TextureRegionDrawable. Он просто растягивает текстуру под заданные координаты. Второй вариант — TiledDrawable. Текстура повторяется множество раз, при этом масштаб не меняется. И третий вариант, это 9-Box или 9-Patch. Чем же он хорош и когда его следует использовать?

9-Patch


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

Table Layout


Как я упоминал вчера, сцена это иерархический набор элементов (потомки класса Actor). Все акторы делятся на две группы — Widget и WidgetGroup. Widget это листья дерева, которые не могут содержать дочерних элементов. WidgetGroup — это узлы. То есть все их различие заключается в том, что WidgetGroup умеют «раскладывать» дочерние элементы в определенном порядке. Все обучение Scene2d сводится к умению комбинировать эти объекты. К примеру кнопка в LibGDX это WidgetGroup, наследник Table. Она может содержать и текст, и изображение. Ну и любую другую верстку как любая другая таблица.

Изображение


Kotlin
class TableStage : Stage() {

    init {

        val stageLayout = Table()
        addActor(stageLayout.apply {
            debugAll()
            setFillParent(true)

            pad(AppConstants.PADDING)
            defaults().expand().space(AppConstants.PADDING)

            row().let {
                add(Image(uiSkin.getDrawable("sample")))
                add(Image(uiSkin.getDrawable("sample"))).top().right()
                add(Image(uiSkin.getDrawable("sample"))).fill()
            }

            row().let {
                add(Image(uiSkin.getTiledDrawable("sample"))).fillY().left().colspan(2)
                add(Image(uiSkin.getTiledDrawable("sample"))).width(64f).height(64f).right().bottom()
            }

            row().let {
                add(Image(uiSkin.getDrawable("sample")))
                add(Image(uiSkin.getTiledDrawable("sample"))).fill().pad(AppConstants.PADDING)
                add(Image(uiSkin.getDrawable("sample"))).width(64f).height(64f)
            }
        })
    }
}


Код использует Атлас Текстур/Шкурки для улучшения читаемости. Как настраивать лучше посмотреть в репозитории. Описывать принципы их работы это на целую отдельную статью.

Что мы видим в коде:

        ... 
        val stageLayout = Table()
        addActor(stageLayout.apply { // добавление таблицы в сцену
            debugAll() // Включаем дебаг для всех элементов таблицы
            setFillParent(true) // Указываем что таблица принимает размеры родителя

            pad(AppConstants.PADDING)
            defaults().expand().space(AppConstants.PADDING)

            row().let {
                add(Image(uiSkin.getDrawable("sample")))
                add(Image(uiSkin.getDrawable("sample"))).top().right()
                add(Image(uiSkin.getDrawable("sample"))).fill()
            }

Все, что внутри .apply применяется к объекту, на котором apply был вызван. Метод setFillParent(true) правильно использовать только один раз при добавлении корневого элемента в сцену. Так как он используется очень редко, я про него постоянно забываю и не сразу понимаю почему сцена у меня пустая.

Самая распространенная ошибка: забыть добавить setFillParent(true) в корневой элемент

Тот же пример на java

        ... 
        Table stageLayout = new Table();
        stageLayout.debugAll();
        stageLayout.setFillParent(true);

        stageLayout.pad(AppConstants.PADDING);
        stageLayout.defaults().expand().space(AppConstants.PADDING);

        stageLayout.row();
        stageLayout.add(Image(uiSkin.getDrawable("sample")));
        stageLayout.add(Image(uiSkin.getDrawable("sample"))).top().right();
        stageLayout.add(Image(uiSkin.getDrawable("sample"))).fill();

        addActor(stageLayout);

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

В Kotlin'e я применял к row() функцию сокрытия видимости .let, которую я еще ни разу не видел чтобы использовали как функцию сокрытия видимости. Самый распространенный вариант ее использования — null check. Внутри let поле будет доступно как it и гарантированно не-нулл.

var name: String? = ...
name?.let { 
    if (it == "Alex") ...
}

Методы разметки таблицы




add — добавляет ячейку в ряд. Возвращает Cell к которому можно применять модификаторы
row — добавляет row. Возвращает default Cell для ряда. Модификаторы, примененные к default Cell будут автоматически применены ко все ячейкам этого ряда.

expand/expandX/expandY — «пружинки». Меняют размер ячеек (но не содержимого). По умолчанию содержимое ячеек расположено в центре

width/height — задает размер ячейки фиксировано или в процентном соотношении.

.width(40f)
.width(Value.percentWidth(.4f, stageLayout)

fill/fillX/fillY — заставляет содержимое ячейки принять размер ячейки

left/right/top/bottom — если содержимое ячейки меньше размеров, указывает способ выравнивания

Делаем верстку первого экрана:



Я сделал набор иконок которые поясняют примененные модификаторы к ячейкам
Пружинки — expand/expandX/expandY (раздвигают ячейку)
Стрелки — fill/fillX/fillY (содержимое ячейки заполняет ячейку)
Швеллер — фиксированный размер ширины/высоты (фиксирует размеры ячейки по ширине/высоте)

Container<> Layout


Контейнер может иметь только один Widget. Имеет Drawable background. Поэтому мы будем использовать его чтобы нарисовать на экране header и footer (панель ресурсов/командная панель).

val stageLayout = Table()
addActor(stageLayout.apply {
...
    row().let {
        val headerContainer = Container()
        add(headerContainer.apply {
            background = TextureRegionDrawable(TextureRegion(Texture("images/status-bar-background.png")))
            // здесь в следующей части мы добавим панель ресурсов
        }).height(100f).expandX()
    }

Полный код главной сцены
val stageLayout = Table()
addActor(stageLayout.apply {
    setFillParent(true)

    defaults().fill()

    row().let {
        val headerContainer = Container()
        add(headerContainer.apply {
            background = TextureRegionDrawable(TextureRegion(Texture("images/status-bar-background.png")))
        }).height(100f).expandX()
    }

    row().let {
        add(Image(Texture("backgrounds/main-screen-background.png")).apply {
            setScaling(Scaling.fill)
        }).expand()
    }

    row().let {
        val footerContainer = Container()
        add(footerContainer.apply {
            background = TextureRegionDrawable(TextureRegion(Texture("images/status-bar-background.png")))
            fill()

            actor = CommandPanel()
        }).height(160f).expandX()
    }
})


Верстка Loading Screen


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

Прототип верстки


Пример кода:

val stageLayout = Table()
addActor(stageLayout.apply {
    setFillParent(true)
    background = TextureRegionDrawable(TextureRegion(Texture("backgrounds/loading-logo.png")))
})

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

val stageLayout = Table()
val backgroundImage = Image(Texture("backgrounds/loading-logo.png"))
addActor(backgroundImage.apply {
    setFillParent(true)
    setScaling(Scaling.fill)
})

Допустим вот такой вариант. Мы используем изображение, и говорим что масштабировать его надо сохраняя пропорции до тех пор пока меньшая сторона упрется в край. При этом большая сторона будет обрезана. Другой вариант — Scaling.fit. Масштабирование будет идти пока большая часть не упрется в край, меньшая часть будет иметь незаполненные участки (letterbox).

Но что делать если мы, к примеру, хотим разместить Progress Bar где-то в 20% пространства снизу и чтобы он занимал 60% экрана. Никто не запрещает добавить в сцену несколько actor'ов верхнего уровня. Будет так:

Экран


Код
init {
    val backgroundImage = Image(Texture("backgrounds/loading-logo.png"))
    addActor(backgroundImage.apply {
        setFillParent(true)
        setScaling(Scaling.fill)
    })

    val stageLayout = Table()
    addActor(stageLayout.apply {
        setFillParent(true)

        row().let {
            add().width(Value.percentWidth(.6f, stageLayout)).height(Value.percentHeight(.8f, stageLayout))
        }

        row().let {
            add(progressBar).height(40f).fill() // про progressBar будет в следующих частях
        }
    })
}


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

P.S. На итоговом экране есть командная панель с 4 кнопками. Используя материал из данной статьи вы можете самостоятельно ее реализовать. Ответ в репозитории. Следующая статья через неделю.

Результат части 1

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332298/


Метки:  

Метод восстановления данных с диска, зашифрованного NotPetya с помощью алгоритма Salsa20

Пятница, 07 Июля 2017 г. 14:45 + в цитатник


Атаки с использованием вирусов-шифровальщиков стали настоящим трендом 2017 года. Подобных атак было зафиксировано множество, однако самыми громкими из них оказались WannaCry и NotPetya (также известный под множеством других имен — Petya, Petya.A, ExPetr и другими). Освоив опыт предыдущей эпидемии, специалисты по всему миру оперативно среагировали на новый вызов и в считанные часы после заражения первых компьютеров принялись изучать экземпляры зашифрованных дисков. Уже 27 июня появились первые описания методов заражения и распространения NotPetya, более того — появилась вакцина от заражения.

Если при запуске NotPetya не смог получить административные привилегии, он зашифровывал алгоритмом AES только пользовательские файлы, но операционная система продолжала работать. К сожалению, для восстановления файлов, зашифрованных таким способом, требуется знание секретного ключа RSA (который вроде бы предлагают купить в Даркнете за 100 биткойнов).

Изложенный далее метод восстановления данных применим в тех случаях, если вирус NotPetya имел административные привилегии и зашифровал жесткий диск целиком при помощи алгоритма Salsa20.

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

Однако из-за некоторых особенностей применения алгоритма Salsa20 возможно восстановление данных без знания ключа.

Как работает Salsa20



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

Чтобы гамму можно было вычислять для любого смещения в потоке, генератор гаммы s20_expand32() формирует 64-байтовый массив keystream, в который вперемешку укладываются:

  • 256 бит (32 байта) ключа шифрования,
  • 8 байт несекретной случайной последовательности nonce (number used once),
  • 16 байт константы sigma (“expand 32-byte k” или “-1nvalid s3ct-id”),
  • 64 бита (8 байт) номера блока в потоке.


Вот на этой иллюстрации, взятой из отчета Check Point, наглядно показано, как разложены данные:



64 байта keystream пропускаются через функцию перемешивания, и получившиеся 64 байта используются как фрагмент гаммы.

Следует отметить, что генерируемые фрагменты гаммы всегда выровнены на границу, кратную 64 байтам. И чтобы зашифровать, скажем, 7 байт начиная со смещения 100, надо найти номер блока, в который попадает первый байт (100/64 == 1), вычислить для этого блока гамму и использовать из нее 7 байт начиная со смещения (100%64 == 36). Если байтов в блоке не хватает, тогда генерируется гамма для следующего блока и т.д.

В процессе шифрования одного потока (а диск, с точки зрения NotPetya, это один поток) не происходит смены ключа или nonce. Следовательно, для каждого зашифрованного диска единственная варьируемая величина в keystream — это номер блока.

По задумке авторов шифра Salsa20, 264 блоков по 64 байта позволяют сгенерировать гамму с периодом 270 ~ 1021 байт. Это достаточно большой период для почти любых практических применений, и жестких дисков такого объема не появится еще очень долго.

Однако в реализации не все так гладко.

Реальный период гаммы в NotPetya



Посмотрим на прототип функции s20_crypt32(), через вызовы которой и выполняется шифрование секторов диска.

enum s20_status_t s20_crypt32(uint8_t *key,
                            uint8_t nonce[static 8],
                            uint32_t si,
                            uint8_t *buf,
                            uint32_t buflen)


Через аргумент si (вероятно, Stream Index) передается байтовое смещение в потоке. И по типу аргумента понятно, что там не 64, а только 32 бита. Это значение попадает в keystream после деления на 64, то есть остается максимум 24 бита.

// Set the second-to-highest 4 bytes of n to the block number
    s20_rev_littleendian(n+8, si / 64);


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



На ней серым цветом выделены те байты, которые не влияют на формирование гаммы вследствие ошибки реализации функции s20_rev_littleendian(). Таким образом, из 24 бит номера блока только 16 бит (байты по смещению 0x20-0x21) повлияют на keystream. Следовательно, максимальный период гаммы составит 216=65536 блоков по 64 байта, или 4 мегабайта.

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

И еще одна ошибка



На этом огрехи разработчиков не заканчиваются. При вызове функции s20_crypt32() они вместо значения смещения в байтах передают… номер 512-байтового сектора!

Секторы обычно зашифрованы парами (1024 байта за одно обращение), и значит, гамма применяемая для зашифрования двух соседних пар секторов, будет совпадать в 1022 байтах (со смещением в 2 байта).

Эвристики для Known Plaintext Attack



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

Вступительные 512 байт гаммы



Для проверки правильности ключа шифрования NotPetya зашифровывает сектор 0x21, содержащий предопределенные значения (все байты 0x07). Это дает нам 512 байт гаммы.

Восстановление гаммы по MFT



NotPetya не зашифровывает первые 16 записей в MFT (32 сектора), но зашифровывает все остальные.

Известно, что каждая файловая запись начинается с последовательности “FILE”, затем обычно идут байты 30 00 03 00 (UpdateSequenceArrayOffset = 0x30, UpdateSequenceArrayLength = 3). Теоретически, эти 4 байта могут иметь и другие значения, но они почти всегда одинаковы для всех файловых записей в пределах одного логического тома NTFS.

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

А восстановив фрагменты гаммы, использованные для шифрования MFT, мы сможем полностью восстановить структуру файловой системы.

Восстановление гаммы по известным файлам



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

Если поставить «чистую» Windows XP и заглянуть в папку Windows, там можно найти 8315 файлов. В активно используемой Windows 8.1 файлов будет больше 200 тысяч. Шансов, что многие из них совпадут с файлами на зашифрованном диске, более чем достаточно.
Так что, если проиндексировать DLL- и EXE-файлы из доступных инсталляций Windows (желательно той же версии и с близким набором обновлений), — возможно, этого хватит, чтобы восстановить гамму полностью.

А получив фрагменты гаммы, можно пытаться восстанавливать и уникальные файлы.

Перспективы и подводные камни



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

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

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

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

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

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

Хочется выразить благодарность Александру Песляку (Solar Designer) за наводящие подсказки, позволившие придумать описанный выше метод.

Автор: Дмитрий Скляров, руководитель анализа приложений Positive Technologies.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332618/


Метки:  

[Из песочницы] Спасёт ли Python от казни?

Пятница, 07 Июля 2017 г. 14:02 + в цитатник
Доброго времени суток! При просмотре экшн-фильмов (фильмов с хорошо продуманными динамичными сценами) иногда закрадывается в голову: а реально ли это в действительности? Например, мог ли автомобиль перевернуться на маленькой скорости, как быстро можно раскачаться на верёвке без начальной скорости над пропастью…

image

Что говорит на это физика? Интересно ли писать на бумажечке и потом хвастаться клочком с формулами и парой-тройкой векторов? Давайте сделаем это безопасно и наглядно.

Начнём с обычного математического маятника:

$$display$$\dfrac{m \dot{\theta }^2}{2}+mgl(1-cos \ \theta )=E$$display$$


$$display$$\ddot{\theta}+gl \ sin \ \theta=0$$display$$


$$display$$sin \ x\approx x$$display$$

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

Разделим уравнение на два первого порядка и запишем в стандартном виде

$$display$$\dfrac{d}{dt}\begin{pmatrix} \theta \\ \omega \end{pmatrix}=\begin{pmatrix} \omega \\ -gl \ sin\ \theta \end{pmatrix} $$display$$


И теперь смело это кормим нашему змею (подробнее об scipy.integrate.odeint, увы, в оригинале).

t = linspace(0,15,100)
G = 9.8
L = 1.0

def diffeq(state, t):
    th, w = state
    return [w, -G*L*sin(th)]
dt = 0.05
t = np.arange(0.0, 20, dt)

th1 = 179.0
w1 = 0.0
state = np.radians([th1, w1])
y = odeint(diffeq, state, t)

На первом курсе в лаборатории механики были маятники, возможности каждого из них почти исчерпывало задание к работе. А вот с одним было что-то не то, потому что так было нельзя больше всего привлекал внимание маятник Обербека: «Что будет, если не закрепить грузики?»

И вот спустя N лет я увидела в фильме (очередные пираты Карибского моря) что же произойдёт!

Хмммм а действительно ли это так? Для этого достаточно записать всего два уравнения

$$display$$a = a_{central} - g \ cos \ \theta, a_{central} = \dot{\theta}^2 r \\ \ddot{\theta} = gx \ sin \ \theta$$display$$


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

У нас появилась ещё одна переменная r — это расстояние грузика от центра. Запишем окончательно выражение для scipy:

$$display$$\begin{cases} \ddot{r} = \dot{\theta}^2r-g \ cos \ \theta, \\ \ddot{\theta} = gr \ sin \ \theta \end{cases}$$display$$


image
image
import matplotlib.animation as animation
from pylab import *
from scipy.integrate import *
import matplotlib.pyplot as plt

t = linspace(0,15,100)
G = 9.8
L = 10.0

def derivs(state, t):
    th, w, r, v  = state
    if 0. 0, max = L))*sin(y[:, 0])
y2 = -(L-y[:,2].clip(min = 0, max = L))*cos(y[:, 0])

fig = plt.figure()
ax = fig.add_subplot(111, autoscale_on=False, xlim=(-L-0.2, L+0.2), ylim=(-L-0.2, L+0.2))
ax.grid()

line, = ax.plot([], [], '-', lw=2)
point, = ax.plot(0,0,'o', lw=2)
extra, = ax.plot(x1/L*r1,y1/L*r1,'o', lw=2)
time_template = 'time = %.1fs'
time_text = ax.text(0.05, 0.9, '', transform=ax.transAxes)


def init():
    line.set_data([], [])
    point.set_data(0,0)
    time_text.set_text('')
    extra.set_data(x1/L*r1,y1/L*r1)
    return line, time_text, point, extra

def animate(i):
    thisx = [0, x1[i]]
    thisy = [0, y1[i]]
    thisx2 = x2[i]
    thisy2 = y2[i]
    point.set_data(thisx[0],thisy[0])
    line.set_data(thisx, thisy)
    time_text.set_text(time_template % (i*dt))
    extra.set_data([thisx2,thisy2])
    
    return line, time_text, point, extra

ani = animation.FuncAnimation(fig, animate, np.arange(1, len(y)),
                              interval=25, blit=True, init_func=init, repeat = False)

plt.show()

P.S. здесь начала копать.
P.S.S. поигравшись с начальными параметрами (для маленьких углов отклонения от вертикали или небольших начальных угловых скоростей) можно прийти к «плоскому» пониманию устойчивости гироскопов.

Спасти оранжевую точку мне так и не удалось, поэтому будем добрыми и лежать подальше от гильотин, которые из фильмов кажутся безопасными!
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332620/


Метки:  

[Перевод] Чему я научился, конвертируя проект в Kotlin при помощи Android Studio

Пятница, 07 Июля 2017 г. 14:00 + в цитатник
К большой моей радости, мне наконец выдалась возможность поработать с популярным языком Kotlin — конвертировать простенькое приложение из Java при помощи инструмента Convert Java File to Kotlin из Android Studio. Я опробовал язык и хотел бы рассказать о своем опыте.

Я быстро убедился, что этот инструмент конвертирует большую часть классов в Java практически безукоризненно. Но кое-где пришлось подчистить за ним код, и в процессе я выучил несколько новых ключевых слов!

Ниже я поделюсь своими наблюдениями. Прежде, чем мы начнем, замечу: если вы в какой-то момент захотите взглянуть, что происходит «под капотом», Android Studio позволяет отслеживать все процессы; просто перейдите в панели по следующему пути: Tools -> Kotlin -> Show Kotlin Bytecode.




Константа long


Первое изменение я сперва даже не заметил — настолько оно было незначительным. Волшебным образом конвертер заменил константу long в одном из классов на int и преобразовывал ее обратно в long при каждом обращении. Брр!

companion object {
    private val TIMER_DELAY = 3000
}
//...
handler.postDelayed({
    //...
}, TIMER_DELAY.toLong())


Хорошая новость: Константа все равно распознавалась благодаря ключевому слову val.

Плохая новость: многие процессы сопровождались ненужными преобразованиями. Я ожидал, что безопасность типов в Kotlin будет на более высоком уровне, что там все будет реализовано лучше. Может быть, я переоценил, насколько умен этот конвертер?

Решение оказалось простым: нужно было просто добавить «L» в конце объявления переменой (примерно как в Java).

companion object {
    private val TIMER_DELAY = 3000L
}
//...
handler.postDelayed({
    //...
}, TIMER_DELAY)


Лучше поздно, чем никогда


Одно из главных преимуществ Kotlin — безопасность null, которая устраняет угрозу возникновения нулевых ссылок. Осуществляется это при помощи системы типов, которая различает ссылки, допускающие и не допускающие значение null. В большинстве случаев для вас предпочтительнее ссылки, не допускающие значения null, с которыми нет риска столкнуться с NPE (Null Pointer Exceptions). Однако в некоторых ситуациях нулевые ссылки могут быть полезны, например, при инициализации из события onClick(), такого как AsyncTask.

Существует несколько способов наладить работу с нулевыми ссылками:

  1. Старые-добрые операторы if, которые будут проверять свойства на наличие нулевых ссылок, прежде чем дать к ним доступ (Java должен был вас уже к ним приучить).
  2. Крутой Safe Call Operator (синтаксис ?.), который проводит за вас проверку на нулевые значения в фоновом режиме. Если объект — нулевая ссылка, то он возвращает ноль (не NPE). Никаких больше надоедливых операторов if!
  3. Насильственное возвращение NPE при помощи оператора !!.. В этом случае вы фактически пишете знакомый по Java код и вам необходимо вернуться к первому шагу.

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

Я понимал, что разрешить коду Kotlin выбрасывать null pointer exception — это как-то идет вразрез с преимуществами, которые дает данный язык, и стал копать глубже в надежде найти решение, которое оказалось бы лучше уже имеющихся.

Так я обнаружил мощное ключевое слово lateinit. При помощи lateinit в Kotlin можно инициализировать ненулевые свойства после вызова конструктора, что дает возможность вообще отойти от нулевых свойств.

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

Использование lateinit — это простой способ убрать операторы!!! из кода на Kotlin. Если вам интересны другие советы о том, как от них избавиться и сделать код аккуратнее, рекомендую пост David V'avra.

Internal и его внутренний мир


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

У меня был публичный метод в одном фрагменте, который конвертировался в функцию internal в Kotlin. На Java у него не было никаких модификаторов доступа, и, соответственно, он был package private.

public class ErrorFragment extends Fragment {
    void setErrorContent() {
        //...
    }
}


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

class ErrorFragment : Fragment() {
    internal fun setErrorContent() {
        //...
    }
}


Что означает это новое ключевое слово? Заглянув в декомпилированный биткод мы тут же увидим, что название метода из setErrorContent() превратилось в setErrorContent$production_sources_for_module_app().

public final void setErrorContent$production_sources_for_module_app() {
   //...
}


Хорошая новость: в других классах Kotlin достаточно знать исходное название метода.

mErrorFragment.setErrorContent()


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

// Accesses the ErrorFragment instance and invokes the actual method
ErrorActivity.access$getMErrorFragment$p(ErrorActivity.this)
    .setErrorContent$production_sources_for_module_app();


Таким образом, Kotlin разбирается с изменениями в названиях своими силами. А как насчет остальных классов на Java?

Из Java класса вызвать метод errorFragment.setErrorContent() нельзя — ведь этого «внутреннего» метода на самом деле не существует (так как изменилось название).

Метод setErrorContent() теперь невидим для классов на Java, как можно увидеть и в API, и в окошке Intellisense в Android Studio. Так что придется использовать сгенерированное (и очень громоздкое) название метода.



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

Сложности с компаньоном


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

Если вы создаете константу в классе на Java, а затем конвертируете его в Kotlin, то конвертер не распознает, что переменная static final должна применяться как константа, что может привести к помехам в совместимости Java и Kotlin.

Когда вам нужна константа в классе Java, вы создаете переменную static final:

public class DetailsActivity extends Activity {
    public static final String SHARED_ELEMENT_NAME = "hero";
    public static final String MOVIE = "Movie";

    //...

}


Как видите, после конвертирования, все они оказались в классе компаньона:

class DetailsActivity : Activity() {

    companion object {
        val SHARED_ELEMENT_NAME = "hero"
        val MOVIE = "Movie"
    }

    //...
}


Когда их используют другие классы Kotlin, все происходит так, как и следовало ожидать:

val intent = Intent(context, DetailsActivity::class.java)
intent.putExtra(DetailsActivity.MOVIE, item)


Однако, так как Kotlin, конвертируя константу, помещает ее в собственный класс компаньона, доступ к таким константам из Java класса не интуитивен.

intent.putExtra(DetailsActivity.Companion.getMOVIE(), item)


Декомпилируя класс в Kotlin, мы можем заметить, что константы стали приватными и раскрываются через класс-оболочку компаньона.

public final class DetailsActivity extends Activity {
   @NotNull
   private static final String SHARED_ELEMENT_NAME = "hero";
   @NotNull
   private static final String MOVIE = "Movie";
   public static final DetailsActivity.Companion Companion = new DetailsActivity.Companion((DefaultConstructorMarker)null);
   //...

   public static final class Companion {
      @NotNull
      public final String getSHARED_ELEMENT_NAME() {
         return DetailsActivity.SHARED_ELEMENT_NAME;
      }

      @NotNull
      public final String getMOVIE() {
         return DetailsActivity.MOVIE;
      }

      private Companion() {
      }

      // $FF: synthetic method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }
}


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

Хорошая новость: частично исправить положение и добиться желаемого поведения мы можем, введя ключевое слово const в класс компаньона.

class DetailsActivity : Activity() {

    companion object {
        const val SHARED_ELEMENT_NAME = "hero"
        const val MOVIE = "Movie"
    }

    //...
}


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

public final class DetailsActivity extends Activity {
   @NotNull
   public static final String SHARED_ELEMENT_NAME = "hero";
   @NotNull
   public static final String MOVIE = "Movie";
   public static final DetailsActivity.Companion Companion = new DetailsActivity.Companion((DefaultConstructorMarker)null);
   //...
   public static final class Companion {
      private Companion() {
      }

      // $FF: synthetic method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }
}


Зато доступ из Java классов происходит по обычной схеме!

Прошу заметить, что это метод работает только для примитивов и строк. Чтобы узнать больше о не-примитивах, почитайте JvmField и статью Kotlin’s hidden costs.



Циклы, и как Kotlin их совершенствует


По умолчанию Kotlin конвертирует циклы в диапазоне с границами 0..N-1, чем затрудняет сопровождение кода, увеличивая вероятность возникновения ошибок на единицу.

В моем коде, к примеру, был вложенный цикл for, чтобы добавлять карты в каждый ряд — самый обычный пример цикла for на Android.

for (int i = 0; i < NUM_ROWS; i++) {
    //...
    for (int j = 0; j < NUM_COLS; j++) {
        //...
    }
    //...
}


Конвертирование прошло без особых ухищрений.

for (i in 0..NUM_ROWS - 1) {
    //...
    for (j in 0..NUM_COLS - 1) {
        //...
    }
    //...
}


Код, который получается в итоге, может показаться Java разработчикам непривычным — будто его писали на Ruby или Python.

Как пишет в своем блоге Dan Lew, у Kotlin функция диапазона инклюзивна по умолчанию. Однако, ознакомившись с характеристиками диапазона у Kotlin, я нашел их очень хорошо проработанными и гибкими. Мы можем упростить код и сделать его читабельнее, воспользовавшись возможностями, которые они предлагают.

for (i in 0 until NUM_ROWS) {
    //...
    for (j in 0 until NUM_COLS) {
        //...
    }
    //...
}


Функция until делает циклы неинклюзивными и более простыми для чтения. Можно наконец выкинуть все эти нелепые -1 из головы!

Полезные советы для продвинутых



Для ленивых

Иногда бывает полезно лениво загрузить переменную member. Представьте, что у вас класс типа singleton, который управляет списком данных. Каждый раз создавать этот список заново нет необходимости, так что мы зачастую обращаемся к ленивому геттеру. Паттерн получается в таком духе:

public static List getList() {
    if (list == null) {
        list = createMovies();
    }
    return list;
}


Если конвертер попытается конвертировать этот паттерн, код не скомпилируется, так как list прописан как неизменяемый, при том что у createMovies() изменяемый тип возвращаемого значения. Компилятор не позволит вернуть изменяемый объект, если сигнатура метода задает неизменяемый.



Это очень мощный паттерн для делегирования загрузки объекта, поэтому Kotlin подключает особую функцию, lazy, чтобы упростить загрузку ленивым способом. С ее помощью код компилируется.

val list: List by lazy {
    createMovies()
}


Так как последняя строка — это возвращаемый объект, теперь мы можем создавать объект, который требует меньше кода, чтобы лениво его загрузить!

Деструктуризация

Если вам приходилось деструктурировать массивы или объекты на javascript, то объявления по деструктуризации покажутся вам знакомыми.

На Java мы постоянно создаем и перемещаем объекты. Однако в некоторых случаях нам нужно буквально несколько свойств объекта, и бывает жаль времени на то, чтобы извлекать их в переменные. Если же речь идет о большом количестве свойств, проще получить к ним доступ через геттер. Например, так:

final Movie movie = (Movie) getActivity()
        .getIntent().getSerializableExtra(DetailsActivity.MOVIE);

// Access properties from getters
mMediaPlayerGlue.setTitle(movie.getTitle());
mMediaPlayerGlue.setArtist(movie.getDescription());
mMediaPlayerGlue.setVideoUrl(movie.getVideoUrl());


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

val (_, title, description, _, _, videoUrl) = activity
        .intent.getSerializableExtra(DetailsActivity.MOVIE) as Movie

// Access properties via variables
mMediaPlayerGlue.setTitle(title)
mMediaPlayerGlue.setArtist(description)
mMediaPlayerGlue.setVideoUrl(videoUrl)


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

Serializable var10000 = this.getActivity().getIntent().getSerializableExtra("Movie");
Movie var5 = (Movie)var10000;
String title = var5.component2();
String description = var5.component3();
String videoUrl = var5.component6();


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

Заключение


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

Хорошо, что меня предупредили: после конвертации код нужно обязательно вычитать. Иначе на Kotlin у меня получилось бы нечто маловразумительное! Хотя, честно говоря, у меня и на Java с этим не лучше.

Если вы хотите узнать другую полезную информацию о Kotlin для начинающих, советую прочитать этот пост и посмотреть видео.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332598/


Метки:  

[Перевод] Платформа Node.js обойдёт Java в течение года

Пятница, 07 Июля 2017 г. 13:57 + в цитатник
В конце июня сего года Микеал Роджерс (он участвовал в работе Node.js Foundation со дня основания этой организации, а теперь покидает проект) дал интервью thenewstack.io. Тогда он сказал, что платформа Node.js обойдёт Java в течение года. Тут надо уточнить, что речь идёт о том, что число программистов, которые пишут для Node.js, превзойдёт число тех, кто пишет на Java.

image

Ресурс builtinnode.com, через неделю после публикации интервью, подготовил материал, автор которого, задавшись вопросом: «Действительно ли Node.js обойдёт Java?», проанализировал ситуацию. Представляем вашему вниманию перевод интервью и аналитической статьи и предлагаем поразмыслить о перспективах Node.js и Java.


Интервью с Микеалом Роджерсом


Начнём с самого начала. Вы не собираетесь больше участвовать в работе Node.js Foundation?

Всё верно — ухожу через несколько недель. Я был здесь с самого начала, всякое повидал. Сегодня всё в Node.js Foundation выглядит весьма прилично. Я готов перейти к чему-то новому, хотя пока не решил — что именно это будет, и где именно буду этим заниматься.

Расскажите о вашем наиболее значительном достижении в Node.js Foundation.

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

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

Я начал программировать в 13 лет. Мне хотелось стать хакером, и я, в основном, занимался ассемблером. Это — как раз то, что нужно для того, чтобы устроить нечто вроде переполнения буфера. Учился я, разбираясь с чужим кодом. Хакерское сообщество существовало и до широкого распространения опенсорс-движения. Тогда можно было, например, отправлять патчи в списки рассылок или делиться эксплойтами по IRC.

Всё это было в штате Вашингтон, в маленьком городе около Сиэтла. В Сиэтл я переехал как только мне исполнилось 18. Работал в различных технических компаниях, в том числе — в Mozilla, где, как разработчик на Python и JavaScript, занимался проектами вроде JSBridge и Mozmill. Писал CouchDB-приложения.

Что привело вас в мир Open Source?

По мере того, как я втягивался в программирование и в IT-индустрию, было совершенно естественным примкнуть к опенсорсу. Это движение было очень похоже на мир хакеров, с которого я начинал. С начала до середины 90-х опенсорс-сообщество не отличалось особой дружелюбностью к новичкам. Если человек точно знал — что и почему он делает, он присоединялся к сообществу и участвовал в проектах. Хакеры, с другой стороны, больше напоминали кучку подростков, которые демонстрировали всем желающим свои достижения и помогали тем, кто в этом нуждался.

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

Расскажите об управлении Open Source-сообществами, и о том, почему это важно.

Всё время слышишь, как говорят о масштабировании серверов, а разговоров о масштабировании сообществ как-то маловато. Пожалуй, нет создателя проекта с открытым кодом, который не хотел бы, чтобы его разработка, что называется, «выстрелила». Однако, когда такое случается, далеко не все готовы поддерживать проект. Это и на самом деле непросто. Сначала проект требует нескольких часов в неделю, а внезапно оказывается, что объём работ вырос настолько, что для управления им может не хватить и двух штатных рабочих мест. Для того, чтобы с подобными вещами справляться, вокруг опенсорс-проектов нужно создавать сообщества, которые поддаются масштабированию, которые способны укреплять и поддерживать проект в случае его неожиданного роста. Тут нужна некая структура. Культура разработки в опенсорсе имеется, а управление — это такой подход к организации работы над проектом, который позволит ему расти и развиваться в заданном направлении и сохранять работоспособность.

Все эти идеи оказали на меня влияние благодаря моей работе в Open Source Applications Foundation. Эта организация была основана (и профинансирована) Митчем Капором, основателем Lotus Development Corp и создателем электронной таблицы Lotus 1-2-3. Цель OSAF заключалась в поддержке массового внедрения бесплатного ПО и ПО с открытым исходным кодом.

В OSAF мы всерьёз обсуждали такие темы, как ценности и стимулы, создаваемые вокруг проекта, говорили о том, куда мы направляем энергию людей, поступая так или эдак. Некоторые из старожилов оказывали сопротивление изменениям способов работы, а вот молодёжь воспринимала новшества на ура. То, что называют «проектом с открытым исходным кодом», это, на самом деле, далеко не только код, и код — далеко не главная проблема. Если взглянуть на системы и процессы как на структуры стимулирования, их можно как бы «запрограммировать» для получения определённого результата. Если получилось что-то не то, можно проанализировать события, которые привели к тому, что получилось, понять, что привело к ошибке. Как результат, можно создавать системы, нацеленные на получение нужных результатов.

Работа OSAF, в итоге, успехом не увенчалась. Есть книга «Dreaming in Code», которую написал Скотт Розенберг, посвящённая тому, как всё это было. Я начал работать в Mozilla в тот день, когда перестал сотрудничать с OSAF. Тогда уже было видно, что именно пошло не так. В конце концов Митч прекратил финансирование проекта, а я перешёл в Mozilla.

Как и когда вы занялись работой над Node.js?

Я и мой друг детства Адам Кристиан создали Windmill — опенсорсный фреймворк для тестирования, написанный на Python. Это был конкурент Selenium. Получилось очень хорошо. Даже создатель Selenium говорил, что наша разработка лучше, чем его. Это, так сказать, небольшая предыстория к последовавшим событиям. Буквально сразу после выхода Node.js, в ноябре 2009-го, мне попался твит, в котором был вопрос о том, есть ли у кого-нибудь HTTP-прокси под Node. Ни у кого такой штуки, естественно, не было, платформе-то и нескольких дней не исполнилось. А я, в течение примерно трёх лет, занимался оптимизацией прокси для Windmill. В результате я подумал: «Будет чем занять себя на выходных». Через пару часов у меня уже был рабочий прокси. Меня буквально ошеломило то, что всё это заняло около 80 строк кода, и когда я испытал производительность нового прокси, оказалось, что она многократно превосходит то, над чем я работал годами. Прямо тогда я сказал себе: «На Python я больше не пишу — будущее за Node.js».

Создатель Node.js, Райан Даль, через какое-то время переехал в Сан-Франциско, атмосфера там способствовала быстрой и динамичной разработке API Node.js. В ходе работы были и рассуждения о том, какое сообщество нужно проекту. Я участвовал в работе над ранними элементами ядра Node, над экосистемой библиотек. То же относится и к разного рода делам, связанным с сообществом. Например, я помогал в организации конференции NodeConf, которая притягивала к себе тех, кто был занят работой над Node. Работы было много, но все мы знали, что Node непременно взлетит. Хотя, конечно, тогда мы и подумать не могли о том, что Node.js буквально обгонит Java лет за десять.

Какие технические или бизнес-задачи решает Node.js?

Количество платформ, для которых приходится вести разработку, даже в рамках одного проекта, постоянно расширяется. Если вы, скажем, создаёте некое веб-решение, у вас будет сайт, видимый пользователям. Затем же — не просто серверная часть, но набор API, дальше — мобильный клиент, настольный клиент. Возможно, в проекте понадобятся и технологии интернета вещей. Если команда невелика, сложно вести разработку для некоего количества совершенно разных окружений, но Node даёт универсальную платформу с экосистемой в полмиллиона пакетов. Это больше, чем у других платформ. Вам не нужно изобретать реализацию большинства алгоритмов — вы почти наверняка найдёте готовый пакет. Это позволяет программистам писать приложения, а не заниматься созданием инфраструктуры, на которой они будут работать. Процесс разработки становится быстрее.

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

Что вы можете сказать о текущем состоянии проекта?

Сейчас у Node.js примерно 8 миллионов пользователей, и это показатель продолжает расти примерно на 100% в год. Мы пока не обошли Java в плане пользовательской базы, но, если рост продолжится теми же темпами, Node.js окажется впереди Java в начале лета 2018-го.

В проекте более 100 активных разработчиков. Еженедельный показатель количества коммитов непостоянен. Когда мы выпускаем новые релизы, люди активно включаются в работу или на некоторое время пресыщаются ей, но колебания не так уж и велики. Начиналось всё, до форка io.js, с трёх разработчиков, так что сейчас мы, определённо, движемся в правильном направлении.

Расскажите об участии в проекте и потенциале заработка на этом

Node.js Foundation — организация некоммерческая, поэтому о заработке на уровне опенсорсных проектов я говорить не могу. Однако, лично мне участие в подобных вещах помогло при поиске работы и при обсуждении уровня заработной платы.

Каждый, кто участвует в опенсорс-разработке, в итоге, оказывается более привлекательным, с точки зрения работодателя, разработчиком. Это, безусловно, огромное преимущество. Скажем, за профилем в том же LinkedIn может обнаружиться масса ценной общедоступной информации. Если кто-то вроде технического директора ищет сотрудников, он может, по проектам с открытым кодом, увидеть, как человек общается, как реагирует на критику своего кода, как ведёт себя, если ситуация накаляется — пытается всё уладить, или доводит до скандала вселенских масштабов.

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

Хотите сказать что-нибудь ещё?

Что мне особенно нравится в Node.js — это то, что каждый год программировать под эту платформу начинает примерно столько же людей, сколько уже ей пользуется. Тут виден постоянный приток свежей крови и новых идей. Мы постоянно работаем над тем, чтобы снизить входные барьеры, мы открываем мир программирования для тех, кто раньше не занимался разработкой ПО. Доступность Node.js постоянно повышается благодаря большому количеству новых разработчиков. В любое время примерно 50% сообщества — это люди, которые не писали не только под Node, но и с JavaScript едва знакомы. Поэтому нам нужно сделать платформу такой, чтобы к ней было просто присоединиться и начать кодить.

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

Обойдёт или нет?


За какой платформой будущее? Java это или Node.js? Микеал упомянул, что сейчас имеется около 8 миллионов пользователей Node.js, ежегодный рост этого показателя составляет примерно 100%. В прошлом году Node.js Foundation сообщила о 3.5 миллионах пользователей, то есть, по меньшей мере, в течение года всё происходило именно так.


Простые расчёты показывают, что в 2018-м у Node уже будет 16 миллионов пользователей. А сколько пользователей у Java?

В 2013-м компания Oracle сообщила о 9 миллионах Java-разработчиков. В 2007-м их было около 6 миллионов. Сколько их будет по итогам 2017-го? Сложно сказать.

Oracle не делала никаких заявлений на эту тему, поэтому нам остаётся лишь строить догадки. Однако, если вооружиться вышеприведёнными показателями, предположить, что число Java-разработчиков тоже растёт, окажется, что сейчас их должно быть примерно 12-14 миллионов. Опять же, всё это — очень приблизительно. Для того, чтобы разобраться в ситуации, привлечём некоторые цифры.

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

Java был весьма популярным языком ещё до появления платформы Node.js. Если взглянуть на исторические показатели индекса TIOBE, видно, что Java занимал первое место далеко не раз и не два.


Индекс TIOBE

В этом году Java снова первый по популярности. А вот как менялась популярность Java с течением времени.


Популярность JAVA

Тут может возникнуть вопрос о том, почему в рейтинге нет Node.js. Дело в том, что TIOBE не считает Node самостоятельным языком программирования и учитывает эту платформу в общем разделе JavaScript.

В любом случае, индекс TIOBE говорит нам о зрелости и популярности Java. Однако, стоит учесть, например то, что PayPal и NetFlix перешли с Java на Node.js. Java вполне может быть популярной платформой, но компании готовы менять базовое ПО, если появляется что-то более привлекательное, и у Node.js есть полезные качества, ради которых и переходят на эту платформу. Здесь можно найти и другие компании, которые сделали выбор в пользу Node.

На HackerNews есть раздел со сведениями о вакансиях. Здесь можно найти инструменты для работы с этими данными. Ниже приведено сравнение вакансий, где нужно знать Node.js (синий график) и Java (чёрный). Тут показаны данные с августа 2011-го по июнь 2017-го. Это сравнение, как и предыдущее, субъективно. В нём принимаются во внимание материалы с HackerNews, но нельзя не заметить, что данные эти вполне согласуются с теми, о которых мы говорили выше.

График говорит нам, во-первых, о росте интереса к Node.js, во-вторых — о том, что эта платформа иногда обходит Java.


Node.js и Java: вакансии

Если воспользоваться обзорами Stack Overflow, можно сравнить Java и Node.js напрямую. Однако, надо учитывать, что эти данные репрезентативны лишь для пользователей Stack Overflow.

Если перейти в раздел языков программирования, мы снова будем сравнивать JavaScript и Java, так как Node.js самостоятельным языком программирования не считается. Для полноты картины следует сказать, что JavaScript здесь, в рейтинге популярности языков программирования, занимает первое место, а Java — третье. Если же посмотреть раздел с анализом популярности технологий, можно видеть исторические сведения по Java и Node.js за последние 5 лет — именно столько Stack Overflow проводит подобные исследования.

Популярность технологий по версии Stack Overflow

Популярность Node.js, с 2013-го по 2017-й, выросла с 8% до 26%. Популярность Java за тот же период упала с 42% до 39%. Здесь учитывается процент респондентов, использующих технологию.

Итоги


Если число программистов, которые пишут для платформы Node.js, продолжит расти на 100% в год, то Node, по этому показателю, обойдёт Java. Однако, число пользователей технологии — это ещё не всё. Существуют области, где JavaScript, и, в частности, Node.js, опережают Java. Есть и области (как справедливо отмечено в одном из комментариев к обзору, опубликованному на builtinnode), в применении к которым говорить о превосходстве Node.js над Java ещё очень рано. В любом случае, Node.js — весьма перспективная платформа, за развитием которой, уверены, наблюдать будет очень интересно.

Уважаемые читатели! Как вы думаете, каким окажется будущее Node.js и Java? Платформа Node.js всё-таки обойдёт Java или нет?
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332616/


Метки:  

Резервное копирование томов LVM2 с защитой от перегрузок IO с использованием сигналов SIGSTOP, SIGCONT

Пятница, 07 Июля 2017 г. 13:42 + в цитатник

Настройка резервного копирования уверенно занимает одно из важнейших мест в деятельности администратора. В зависимости от задач резервного копирование и типов приложений и вида данных резервное копирование может осуществляться с помощью различных инструментов, таких как rsync, duplicity, rdiff-backup, bacula и других, коих существует огромное множество.


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


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


И, тем не менее, основной вопрос остается открытым. Как же осуществлять резервное копирование таким образом, чтобы основные приложения получали приемлемое качество обслуживания? Операционные системы семейства UNIX предоставляют штатный механизм управления приоритетами ввода-вывода для приложений, который называется ionice, кроме того, конкретные реализации UNIX предоставляют свои механизмы, которые позволяют наложить дополнительные ограничения. К примеру, в случае GNU/Linux существует механизм cgroups, который позволяет ограничить полосу пропускания (для физически подключенных устройств) и установить относительный приоритет для группы процессов.


Тем не менее, в некоторых случаях таких решений недостаточно и необходимо ориентироваться на фактическое "самочувствие" системных процессов, которое отражают такие параметры системы как Load Average или %IOWait. В этом случае на помощь может прийти подход, который я успешно применяю уже достаточно продолжительное время при осуществлении резервного копирования данных с LVM2 с помощью dd.


Описание задачи


Имеется сервер GNU/Linux, на котором настроено хранилище, использующее LVM2 и для данного сервера каждую ночь осуществляется процедура резервного копирования тома, которая выполняется с помощью создания снимка раздела и запуска dd + gzip:


ionice -c3 dd if=/dev/vg/volume-snap bs=1M | gzip --fast | ncftpput ...

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


Поиск решения


Изначально для решения был применен подход с ionice -с3, но он не давал стабильного результата. Механизмы, основанные на cpipe и cgroups (throttling) были отброшены как не дающие возможности копировать данные быстро, если %IOWait в норме. В итоге было выбрано решение, основанное на мониторинге %IOWait и приостановке/возобновлении процесса dd с помощью сигналов SIGSTOP, SIGCONT совместно с сервисом статистики sar.


Решение


Схематично решение выглядит следующим образом:


  1. Запрашиваем статистику в течение N секунд и получаем среднее значение %IOWait;
  2. Определяем действие:
    a. Если значение %IOWait < 30, то возобновляем процесс (SIGCONT);
    b. Если значение %IOWait > 30, останавливаем процесс (SIGSTOP), увеличиваем счетчик;
  3. Если процесс остановлен дольше чем N x K, возобновляем процесс и останавливаем его снова через 2 секунды

Скорее всего п.3 вызывает вопросы. Зачем такое странное действие? Дело в том, что в рамках резервного копирования осуществляется передача данных по FTP на удаленный сервер и если процесс копирования остановлен достаточно продолжительное время, то мы можем потерять соединение по таймауту. Для того, чтобы этого не произошло, мы выполняем принудительное возобновление и остановку процесса копирования даже в том случае, если находимся в "красной" зоне.

Код решения приведен ниже.


#!/bin/bash

INTERVAL=10
CNTR=0

while :
do
    CUR_LA=`LANG=C sar 1 $INTERVAL | grep Average | awk '{print $6}' | perl -pe 'if ($_ > 30) { print "HIGH "} else {print "LOW "}'`
    echo $CUR_LA
    MARKER=`echo $CUR_LA | awk '{print $1}'`
    if [ "$MARKER" = "LOW" ]
    then
        CNTR=0
        pkill dd -x --signal CONT
        continue
    else
        let "CNTR=$CNTR+1"
                pkill dd -x --signal STOP
    fi
    if [ "$CNTR" = "5" ]
    then
        echo "CNTR = $CNTR - CONT / 2 sec / STOP to avoid socket timeouts"
        CNTR=0
        pkill dd -x --signal CONT
        sleep 2
        pkill dd -x --signal STOP
    fi
done

Данное решение успешно решило проблему с перегрузкой IO на сервере, при этом не ограничивает скорость жестко, и уже несколько месяцев служит верой и правдой, в то время, как решения, основанные на предназначенных для этого механизмах, не дали положительного результата. Стоит отметить, что значение параметра, получаемое sar может быть легко заменено на Load Average и иные параметры, которые коррелируют с деградацией сервиса. Данный скрипт вполне подходит и для задач, в которых применяется не LVM2 + dd, а, к примеру, Rsync или другие инструменты резервного копирования.


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


PS: Скрипт приведен без редактуры в оригинальном виде.

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332614/



Поиск сообщений в rss_rss_hh_new
Страницы: 1437 ... 1041 1040 [1039] 1038 1037 ..
.. 1 Календарь