...

суббота, 22 декабря 2018 г.

[Из песочницы] Эксплуатация кроликов (RabbitMQ) в режиме «Выжить любой ценой»

«Компания» — оператор связи ПАО «Мегафон»
«Нода» — сервер RabbitMQ.
«Кластер» — совокупность, в нашем случае трех, нод RabbitMQ работающих как единое целое.
«Контур» — совокупность кластеров RabbitMQ, правила работы с которыми определяются на стоящем перед ними балансировщике.
«Балансировщик», «хап» — Haproxy – балансировщик, выполняющий функции переключения нагрузки на кластеры в рамках контура. Для каждого контура используется пара серверов Haproxy, работающих параллельно.
«Подсистема» — публикатор и/или потребитель сообщений, передаваемых через кролика
«СИСТЕМА» — совокупность Подсистем, являющая собой единое программно-аппаратное решение, используемое в Компании, характеризующееся распределённостью по всей территории России, но обладающее несколькими центрами, куда стекается вся информация и где происходят основные расчёты и вычисления.
СИСТЕМА – географически распределённая система – от Хабаровска и Владивостока до Санкт-Петербурга и Краснодара. Архитектурно это несколько центральных Контуров, разделенных по особенностям подсистем, к ним подключённым.

Какая задача возлагается на транспорт в реалиях телекома?


В двух словах: на каждое действие абонента следует реакция Подсистем, которые в свою очередь информирует другие Подсистемы о событиях и последующих изменениях. Сообщения порождаются любыми действиями с СИСТЕМОЙ, не только со стороны абонентов, но и со стороны сотрудников Компании, и со стороны Подсистем (очень большое количество задач выполняется в автоматическом режиме).

Особенности транспорта в телекоме: большой, нет не так, БОЛЬШОЙ поток разнообразных данных, передаваемых через асинхронный транспорт.

Некоторые Подсистемы живут на отдельных Кластерах в силу тяжеловесности потоков сообщений – ни на кого другого на кластере просто не остаётся ресурсов, например, при потоке сообщений 5-6 тыс. сообщений / секунду, объём передаваемых данных может доходить до 170-190 Мегабайт / секунду. При таком профиле нагрузки попытка приземлить на этот кластер ещё кого ни будь, приведёт к печальным последствиям: поскольку ресурсов для обработки всех данных одновременно не хватает, кролик начнёт загонять входящие соединения во flow – начнётся простой публикаторов, со всеми последствиями для всех Подсистем и СИСТЕМЫ в целом.

Основные требования к транспорту:

  1. Доступность транспорта должны быть 99,99%. На практике это выливается в требование обеспечения работы 24/7 и способность автоматически реагировать на любые аварийные ситуации.
  2. Обеспечение сохранности данных: % потерянных сообщений на транспорте должен стремиться к 0.

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

Кроме событий, вызванных действиями абонента, через транспорт ходят сообщения служебные, которыми обмениваются Подсистемы. Таким образом получается несколько тысяч разных маршрутов передачи сообщений, некоторые пересекаются, некоторые существуют изолированно. Достаточно назвать количество вовлечённых в маршруты очередей на разных Контурах, чтобы понять примерный масштаб транспортной карты: На центральных контурах 600, 200, 260, 15…и на удалённых Контурах по 80-100…

При такой вовлечённости транспорта требования о 100% доступности всех транспортных узлов уже не кажутся чрезмерными. К реализации этих требований мы и переходим.

Как мы решаем поставленные задачи


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

Несколько слов об программно-аппаратном окружении, в котором существуют наши кролики:

  • Все сервера кроликов виртуальные, с параметрами 8-12 CPU, 16 Gb Mem, 200 Gb HDD. Как показал опыт, даже использование жутких не виртуальных серверов на 90 ядер и кучей ОЗУ обеспечивает небольшой прирост производительности при значительно больших затратах. Используемые версии: 3.6.6 (на практике – самая устойчивая из 3.6) с эрлангом 18.3, 3.7.6 с эрлангом 20.1.
  • Для Haproxy требования значительно ниже: 2 CPU, 4 Gb Mem, версия haproxy – 1.8 stable. Загруженность по ресурсам, на всех серверах haproxy, не превышает 15% CPU/Mem.
  • Располагается весь зоопарк в 14 ЦОДах на 7 площадках по всей стране, объединённых в единую сеть. В каждом из ЦОДов располагается Кластер из трёх Нод и один Хап.
  • Для удалённых Контуров используются по 2 ЦОДа, для каждого из центральных Контуров — по 4.
  • Центральные Контуры взаимодействуют как между собой, так и с удалёнными Контурами, в свою очередь удалённые Контуры работают только с центральными, прямой связи между собой не имеют.
  • Конфигурации Хапов и Кластеров в рамках одного Контура полностью идентичны. Точкой входа для каждого Контура псевдоним на несколько A-DNS записей. Таким образом, чтобы не произошло, будет доступен хотя бы один хап и хотя бы один из Кластеров (хотя бы одна нода в Кластере) в каждом Контуре. Поскольку случай выхода из строя даже 6 серверов в двух ЦОДах одновременно крайне маловероятен, принимается доступность близкая к 100%.

Выглядит задуманное (и реализованное) всё это примерно так:

image

image

Теперь немного конфигов.

Конфигурация haproxy
frontend center-rmq_5672
bind *:5672
mode tcp
maxconn 10000
timeout client 3h
option tcpka
option tcplog
default_backend center-rmq_5672
frontend center-rmq_5672_lvl_1
bind localhost:56721
mode tcp
maxconn 10000
timeout client 3h
option tcpka
option tcplog
default_backend center-rmq_5672_lvl_1
backend center-rmq_5672
balance leastconn
mode tcp
fullconn 10000
timeout server 3h
server srv-rmq01 10.10.10.10:5672 check inter 5s rise 2 fall 3 on-marked-up shutdown-backup-sessions
server srv-rmq03 10.10.10.11:5672 check inter 5s rise 2 fall 3 on-marked-up shutdown-backup-sessions
server srv-rmq05 10.10.10.12:5672 check inter 5s rise 2 fall 3 on-marked-up shutdown-backup-sessions
server localhost 127.0.0.1:56721 check inter 5s rise 2 fall 3 backup on-marked-down shutdown-sessions
backend center-rmq_5672_lvl_1
balance leastconn
mode tcp
fullconn 10000
timeout server 3h
server srv-rmq02 10.10.10.13:5672 check inter 5s rise 2 fall 3 on-marked-up shutdown-backup-sessions
server srv-rmq04 10.10.10.14:5672 check inter 5s rise 2 fall 3 on-marked-up shutdown-backup-sessions
server srv-rmq06 10.10.10.5:5672 check inter 5s rise 2 fall 3 on-marked-up shutdown-backup-sessions


Первая секция фронта описывает точку входа – ведущую на основной Кластер, вторая секция предназначена для балансировки резервного уровня. Если просто описать в секции backend все резервные сервера кроликов (инструкция backup), то срабатывать будет так же – при полной недоступности основного кластера, соединения пойдут на резервный, однако, все соединения пойдут на ПЕРВЫЙ в списке backup сервер. Для обеспечения балансировки нагрузки на все резервные ноды, как раз и вводим ещё один фронт, который делаем доступным только с localhost и именно его назначаем backup сервером.

Приведённый пример описывает балансировку удалённого Контура – который работает в рамках двух ЦОДов: сервера srv-rmq{01,03,05} – живут в ЦОД №1, srv-rmq{02,04,06} – в ЦОД №2. Таким образом для реализации четырёх-цодового решения нам требуется только дописать ещё два локальных фронта и две backend секции соответствующих серверов кроликов.

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

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

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

Каждый Кластер создан из трёх нод, как показывает практика – наиболее оптимальное количество нод, при котором обеспечивается оптимальный баланс доступность / отказоустойчивость / скорость работы. Поскольку кролик горизонтально не масштабируется (производительность кластера равна производительности самого медленного сервера), создаём все ноды с одинаковыми, оптимальными параметрами по CPU/Mem/Hdd. Располагаем сервера как можно ближе друг к другу – в нашем случае запиливаем виртуальные машины в рамках одной фермы.

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

  1. Работа с кроликом идёт только по протоколу amqp/amqps – через балансировку. Авторизация под локальными учётками — в рамках каждого Кластера (ну и Контура в целом)
  2. Подсистемы подключаются к кролику в пассивном режиме: Никаких манипуляций с сущностями кролей (создание очередей/эксченджей/биндов) не допускается и ограничивается на уровне прав учётных записей – на конфигурирование прав просто не даём.
  3. Все необходимые сущности создаются централизовано, не средствами Подсистем и на всех Кластерах Контура делаются одинаково – для обеспечения автоматического переключения на резервный Кластер и обратно. Иначе можем получить картину: на резерв переключились, а очереди или бинда там нет, и можем получить на выбор либо ошибку подключения, либо пропажу сообщений.

Теперь непосредственно настройки на кроликах:


  1. У локальных УЗ нет доступа к Web интерфейсу
  2. Доступ к Web организуется чрез LDAP – интегрируем с AD и получаем логирование кто и куда на вебке ходил. На уровне конфигурации ограничиваем права у учёток AD, мало того, что требуем нахождение в определённой группе, так и права даём только на «посмотреть». Группы monitoring более чем достаточно. А права администратора навешиваем на другую группу в AD, таким образом сильно ограничивается круг влияния на транспорт.
  3. Для облегчения администрирования и отслеживания:
    На всех VHOST сразу вешаем политику 0 уровня с применением на все очереди (pattern: .*):
    • ha-mode: all – хранить все данные на всех нодах кластера, снижается скорость обработки сообщений, но обеспечивается их сохранность и доступность.
    • ha-sync-mode: automatic – поручаем кролю автоматически синхронизировать данные на всех нодах кластера: так же увеличивается сохранность и доступность данных.
    • queue-mode: lazy – пожалуй одна из самых полезных опций, появившаяся в кроликах с версии 3.6 – немедленная запись сообщений на HDD. Данная опция кардинально снижает потребление ОЗУ и увеличивает сохранность данных при остановках/падениях нод или кластера в целом.

  4. Настройки в файле конфигурации (rabbitmq-main/conf/rabbitmq.config):
    • Секция rabbit: {vm_memory_high_watermark_paging_ratio, 0.5} – порог выгрузки сообщений на диск 50%. При включённом lazy служит больше как страховка, когда нарисуем политику, например, 1 уровня, в которую забудем включить lazy.
    • {vm_memory_high_watermark, 0.95} – ограничиваем кроля 95% всей ОЗУ, поскольку на серверах живёт только кролик, нет смысла вводить более жёсткие ограничения. 5% «широким жестом» так и быть – оставляем ОС, мониторингу и прочим полезным мелочам. Поскольку это значение является верхней границей — ресурсов хватает всем.
    • {cluster_partition_handling, pause_minority} – описывает поведение кластера при возникновении Network Partition, для трёх и более нодового кластера рекомендуется именно такой флаг – позволяет кластеру самому восстановиться.
    • {disk_free_limit, «500MB»} – тут всё просто, когда останется свободного места на диске 500 MB – публикация сообщений будет остановлена, будет доступно только вычитывание.
    • {auth_backends, [rabbit_auth_backend_internal, rabbit_auth_backend_ldap]} — порядок авторизации на кроликах: Сначала проверяется наличие УЗ в локальной базе и если не находится – идём на сервера LDAP.
    • Секция rabbitmq_auth_backend_ldap – конфигурация взаимодействия с AD: {servers, [«srv_dc1»,«srv_dc2»]} – список контроллеров домена, на которых и будет проходить аутентификация.
    • Параметры, непосредственно описывающие пользователя в AD, порт LDAP и прочее, сугубо индивидуальны и подробно описаны в документации.
    • Самое важное для нас – описание прав и ограничений на администрирование и доступ к Web интерфейсу кролей: tag_queries:
      [{administrator,{in_group, «cn=rabbitmq-admins,ou=GRP,ou=GRP_MAIN,dc=My_domain,dc=ru»}},
      {monitoring,
      {in_group, «cn=rabbitmq-web,ou=GRP,ou=GRP_MAIN, dc=My_domain,dc=ru»}
      }]
      – данная конструкция обеспечивает административные привилегии всем пользователям группы rabbitmq-admins и права monitoring (минимально достаточные для доступа на просмотр) для группы rabbitmq-web.
    • resource_access_query:
      {for,
      [{permission,configure, {in_group, «cn=rabbitmq-admins,ou=GRP,ou=GRP_MAIN,dc=My_domain,dc=ru»}},
      {permission,write, {in_group, «cn=rabbitmq-admins,ou=GRP,ou=GRP_MAIN,dc=My_domain,dc=ru»}},
      {permission, read, {constant, true}}
      ]
      }
      – обеспечиваем права на конфигурирование и запись только группе администраторов, всем остальным, кто успешно авторизуется права доступны только на чтение – вполне может вычитать сообщения через Web интерфейс.

Получаем сконфигурированный (на уровне файла конфигурации и настроек в самом кролике) кластер, который максимально обеспечивает доступность и сохранность данных. Этим мы реализуем требование – обеспечение доступности и сохранности данных…в большинстве случаев.

Есть несколько моментов, которые стоит учитывать при эксплуатации подобных высоко нагруженных систем:

  1. Все дополнительные свойства очередей (TTL, expire, max-length и прочее) лучше организовывать политиками, а не вешать параметрами при создании очередей. Получается гибко настраиваемая структура, которую можно подгонять на лету под изменяющиеся реалии.
  2. Использование TTL. Чем длиннее очередь, тем выше будет нагрузка на CPU. Чтобы не допустить «пробивания потолка» лучше длину очереди так же ограничить через max-length.
  3. Кроме самого кролика на сервере крутится ещё некоторое количество служебных приложений, которым, как ни странно, тоже требуются ресурсы CPU. А прожорливый кролик, по умолчанию, занимает все доступные ядра…Может получиться неприятная ситуация: борьба за ресурсы, которая запросто приведёт к тормозам на кролике. Избежать возникновения такой ситуации можно, например, так: Изменить параметры запуска эрланга – ввести принудительное ограничение на количество используемых ядер. Делаем это следующим образом: находим файл rabbitmq-env, ищем параметр SERVER_ERL_ARGS= и добавляем к нему +sct L0-Xc0-X +S Y:Y. Где X-число ядер-1 (отсчёт начинается с 0), Y – Количество ядер -1 (отсчёт с 1). +sct L0-Xc0-X – меняет привязку к ядрам, +S Y:Y – изменяет число шедулеров, запускаемых эрлангом. Так для системы из 8 ядер добавляемые параметры примут вид: +sct L0-6c0-6 +S 7:7. Этим мы отдаём кролику только 7 ядер и рассчитываем, что ОС запуская другие процессы поступит оптимально и повесит их на не нагруженное ядро.

Нюансы эксплуатации получившегося зоопарка


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

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

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

Одну – две такие итерации кластер ещё выдержать может, но больше – уже шансы минимальны. В такой ситуации может спасти остановка выпавшей ноды, но успеть это сделать вручную практически невозможно. Чаще результатом является не просто выпадение ноды из кластера с сообщением «Network Partition», но и картина, когда данные по части очередей жили как раз этой ноде и не успели синхронизироваться на оставшиеся. Визуально – в данных по очередям стоит NaN.

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

Для того, чтобы вывести из-под нагрузки повреждённый кластер, с целью не допустить дальнейшей деградации, самое простое: заставить кролика работать на портах отличных от 5672. Поскольку Хапы у нас следят за кроликами именно по штатному порту, смещение его, например, на 5673 в настройках кролика позволит полностью безболезненно запускать кластер и попытаться восстановить его работоспособность и сообщения, оставшиеся на нём.

Делаем в несколько шагов:

  1. Останавливаем все ноды сбойного кластера – хап переключит нагрузку на резервный Кластер
  2. Добавляем RABBITMQ_NODE_PORT=5673 в файл rabbitmq-env – при запуске кролика настройки эти настройки подтянутся, при этом Web интерфейс по-прежнему будет работать на 15672.
  3. Указываем новый порт на всех нодах безвременно почившего кластера и запускаем их.

При запуске произойдёт перестройка индексов и в подавляющем большинстве случаев все данные восстанавливаются в полном объёме. К сожалению, случаются сбои в результате которых приходится физически удалять все сообщения с диска, оставляя только конфигурацию – в папке с базой удаляются директории msg_store_persistent, msg_store_transient, queues (для версии 3.6) или msg_stores (для версии 3.7).

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

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

Для удобства управления и обновления кроликов используется не готовая сборка в rpm, а разобранный с помощью cpio и переконфигурированный (изменили пути в скриптах) кролик. Основное отличие: не требует для установки/настройки прав root, не устанавливается в систему (пересобранный кролик отлично запаковывается в tgz) и запускается от любого пользователя. Такой подход позволяет гибко проводить обновление версий (если это не требует полной остановки кластера – в таком случае просто переключаем на резервный кластер и обновляемся, не забывая указать смещённый порт для работы). Возможен даже запуск нескольких экземпляров RabbitMQ на одной машине – для тестов вариант очень удобен – можно развернуть уменьшенную архитектурную копию боевого зоопарка.

В результате шаманства с cpio и путями в скриптах получили вариант сборки: две папки rabbitmq-base (в оригинальной сборке – папка mnesia) и rabbimq-main – сюда поместил все необходимые скрипты и самого кролика и эрланга.

В rabbimq-main/bin – симлинки к скриптам кролика и эрланга и скрипт слежения за кроликом (описание ниже).

В rabbimq-main/init.d – скрипт rabbitmq-server через который происходит старт/стоп/ротирование логов; в lib – сам кролик; в lib64 – erlang (используется урезанная, только для работы кролика, версия эрланга).

Получившуюся сборку крайне просто обновлять при выходе новых версий – добавить содержимое rabbimq-main/lib и rabbimq-main/lib64 из новых версий и заменить симлинки в bin. Если обновление затрагивает и управляющие скрипты – достаточно просто изменить в них пути на наши.

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

Поскольку падения кроликов событие хоть и редкое, но происходящее, требовалось воплотить механизм слежения за их самочувствием — поднятие в случае падения (с сохранением логов причин падения). Падение ноды в 99% случаев сопровождается записью в лог, даже kill и тот оставляет следы, это позволило реализовать с помощью простого скрипта слежение за состоянием кроля.

Для версий 3.6 и 3.7 скрипт немного отличается в силу отличий записей в логах.

Для версии 3.6
#!/usr/bin/python<br>
import subprocess<br>
import os<br>
import datetime<br>
import zipfile<br>
<br>
def LastRow(fileName,MAX_ROW=200):<br>
&nbsp&nbsp&nbsp&nbspwith open(fileName,'rb') as f:<br>
&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbspf.seek(-min(os.path.getsize(fileName),MAX_ROW),2)<br>
&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbspreturn (f.read().splitlines())[-1]<br>
<br>
if os.path.isfile('/data/logs/rabbitmq/startup_log'):<br>
&nbsp&nbsp&nbsp&nbspif b'FAILED' in LastRow('/data/logs/rabbitmq/startup_log'):<br>
&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbspproc = subprocess.Popen("ps x|grep rabbitmq-server|grep -v 'grep'", shell=True, stdout=subprocess.PIPE)<br>
&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbspout = proc.stdout.readlines()<br>
&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbspif str(out) == '[]':<br>
&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbspcur_dt=datetime.datetime.now()<br>
&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsptry:<br>
&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbspos.stat('/data/logs/rabbitmq/after_crush')<br>
&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbspexcept:<br>
&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbspos.mkdir('/data/logs/rabbitmq/after_crush')<br>
&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbspz=zipfile.ZipFile('/data/logs/rabbitmq/after_crush/repair_log'+'-'+str(cur_dt.day).zfill(2)+str(cur_dt.month).zfill(2)+str(cur_dt.year)+'_'+str(cur_dt.hour).zfill(2)+'-'+str(cur_dt.minute).zfill(2)+'-'+str(cur_dt.second).zfill(2)+'.zip','a')<br>
&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbspz.write('/data/logs/rabbitmq/startup_err','startup_err')<br>
&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbspproc = subprocess.Popen("~/rabbitmq-main/init.d/rabbitmq-server start", shell=True, stdout=subprocess.PIPE)<br>
&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbspout = proc.stdout.readlines()<br>
&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbspz.writestr('res_restart.log',str(out))<br>
&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbspz.close()<br>
&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbspmy_file = open("/data/logs/rabbitmq/run.time", "a")<br>
&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbspmy_file.write(str(cur_dt)+"\n")<br>
&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbspmy_file.close()<br>


Для 3.7 изменяются только две строки
if (os.path.isfile('/data/logs/rabbitmq/startup_log')) and (os.path.isfile('/data/logs/rabbitmq/startup_err')):<br>
&nbsp&nbsp&nbsp&nbspif ((b'  OK  ' in LastRow('/data/logs/rabbitmq/startup_log')) or (b'FAILED' in LastRow('/data/logs/rabbitmq/startup_log'))) and not (b'Gracefully halting Erlang VM' in LastRow('/data/logs/rabbitmq/startup_err')):<br>


Заводим в crontab учётной записи под которой будет работать кролик (по умолчанию rabbitmq) выполнение этого скрипта (имя скрипта: check_and_run) каждую минуту (для начала просим админа выдать учётке права на использование crontab, ну а если права root есть – делаем сами):
*/1 * * * * ~/rabbitmq-main/bin/check_and_run

Второй момент использования пересобранного кроля — ротирование логов.

Поскольку мы не завязываемся на logrotate системы – используем функционал, предоставленный разработчиком: скрипт rabbitmq-server из init.d (для версии 3.6)
Внеся небольшие изменения в rotate_logs_rabbitmq()
Добавляем:

&nbsp&nbsp&nbsp&nbspfind ${RABBITMQ_LOG_BASE}/http_api/*.log.*  -maxdepth 0 -type f ! -name "*.gz" | xargs -i gzip --force {}<br>
&nbsp&nbsp&nbsp&nbspfind ${RABBITMQ_LOG_BASE}/*.log.*.back  -maxdepth 0 -type f | xargs -i gzip {}<br>
&nbsp&nbsp&nbsp&nbspfind ${RABBITMQ_LOG_BASE}/*.gz -type f -mtime +30 -delete<br>
&nbsp&nbsp&nbsp&nbspfind ${RABBITMQ_LOG_BASE}/http_api/*.gz -type f -mtime +30 -delete<br>


Результат запуска скрипта rabbitmq-server с ключом rotate-logs: логи сжимаются gzip-ом, и хранятся только за последние 30 дней. http_api – путь куда кролик складывает логи http – настраивается в файле конфигурации: {rabbitmq_management, [{rates_mode, detailed},{http_log_dir, path_to_logs/http_api"}]}

Заодно обращаю внимание на {rates_mode, detailed} – опция несколько увеличивает нагрузку, зато позволяет увидеть на WEB интерфейсе (и соответственно получить через API) информацию о том, кто публикует сообщения в эксченджи. Информация крайне нужная, т.к. все подключения идут через балансировщик – мы увидим только IP самих балансировщиков. А если озадачить все Подсистемы, работающие с кроликом, чтобы они заполняли параметры «Client properties» в свойствах своих подключений к кроликам, то можно будет на уровне коннектов детально получать информацию кто именно, куда и с какой интенсивностью публикует сообщения.

С выходом новых версий 3.7 произошёл полный отказ от скрипта rabbimq-server в init.d. С целью облегчения эксплуатации (однообразие команд управления независимо от версии кролика) и более плавного перехода между версиями, в пересобранном кролике мы продолжаем использование данного скрипта. Правда опять: немного изменим rotate_logs_rabbitmq(), поскольку в 3.7 поменялся механизм именования логов после ротирования:

&nbsp&nbsp&nbsp&nbspmv ${RABBITMQ_LOG_BASE}/$NODENAME.log.0 ${RABBITMQ_LOG_BASE}/$NODENAME.log.$(date +%Y%m%d-%H%M%S).back<br>
&nbsp&nbsp&nbsp&nbspmv ${RABBITMQ_LOG_BASE}/$(echo $NODENAME)_upgrade.log.0 ${RABBITMQ_LOG_BASE}/$(echo $NODENAME)_upgrade.log.$(date +%Y%m%d-%H%M%S).back<br>
&nbsp&nbsp&nbsp&nbspfind ${RABBITMQ_LOG_BASE}/http_api/*.log.*  -maxdepth 0 -type f ! -name "*.gz" | xargs -i gzip --force {}<br>
&nbsp&nbsp&nbsp&nbspfind ${RABBITMQ_LOG_BASE}/*.log.* -maxdepth 0 -type f ! -name "*.gz" | xargs -i gzip --force {}<br>
&nbsp&nbsp&nbsp&nbspfind ${RABBITMQ_LOG_BASE}/*.gz -type f -mtime +30 -delete<br>
&nbsp&nbsp&nbsp&nbspfind ${RABBITMQ_LOG_BASE}/http_api/*.gz -type f -mtime +30 -delete<br>


Теперь осталось только внести в crontab задание по ротированию логов – например каждый день в 23-00:
00 23 * * * ~/rabbitmq-main/init.d/rabbitmq-server rotate-logs

Перейдём к задачам, которые требуется решать в рамках эксплуатации «кроличьей фермы»:

  1. Манипуляции с сущностями кроликов — создание/удаление сущностей кролика: эксченджей, очередей, биндов, шовелов, пользователей, политик. Причем делать это абсолютно идентично на всех Кластерах Контура.
  2. После переключения на / с резервного Кластера требуется перенести сообщения, которые на нём остались на текущий Кластер.
  3. Создание резервных копий конфигураций всех Кластеров всех Контуров
  4. Полная синхронизация конфигураций Кластеров в рамках Контура
  5. Производить остановку/запуск кроликов
  6. Производить анализ текущих потоков данных: все ли сообщения ходят и если ходят, то куда надо или…
  7. Найти и отловить проходящие сообщения по каким-либо критериям

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

Let's block ads! (Why?)

[Из песочницы] Системы мониторинга трафика в сетях VoIP. Часть первая — обзорная

В данном материале попытаемся рассмотреть такой интересный и полезный элемент ИТ-инфрастуктуры, как система мониторинга VoIP-трафика.
image

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

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

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

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

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

О системах мониторинга трафика сетей связи


А вместе — делаем общее дело: ты по-своему, а я по-своему.
Ю. Деточкин

Современные сети передачи медиатрафика проектируются и строятся посредством реализации различных концепций, фундамент которых составляет множество телекоммуникационных протоколов: CAS, SS7, INAP, H.323, SIP и т.д. Система мониторинга трафика (СМТ) – это средство, которое призвано производить захват сообщений, перечисленных выше (и не только) протоколов, и обладает набором удобных, интуитивно понятных и информативных интерфейсов для его анализа. Основное назначение СМТ – сделать сигнальные трассировки и дампы за любой промежуток времени доступными для специалистов в любое время (в том числе в режиме реального времени) без использования специализированных программ (например, Wireshark). С другой стороны, каждый квалифицированный специалист обращает пристальное внимание на вопросы, связанные, например, с безопасностью ИТ-инфраструктуры.

При этом немаловажным аспектом, напрямую связанным с данным вопросом, является возможность данного специалиста «держать руку на пульсе», что может быть достигнуто в том числе с помощью своевременного оповещения о том или ином инциденте. Коль скоро упомянуто о вопросах оповещения, то мы говорим о мониторинге сети связи. Возвращаясь к приведённому выше определению, СМТ позволяет выполнять контроль тех сообщений, ответов и активностей, которые могут свидетельствовать о каком-либо аномальном поведении сети (например, ответы 403 или 408 группы 4хх в SIP или резкое увеличение числа сессий на транке), при этом получить соответствующую инфографику, которая наглядно иллюстрирует происходящее.

Однако следует отметить, что система мониторинга трафика VoIP изначально не является той классической Fault Monitoring System, которая позволяет составлять карты сетей, контролировать доступность их элементов, утилизацию ресурсов, периферию и многое другое (например, как Zabbix).

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

Очевидным является тот факт, что сама по себе СМТ не способна собрать Call Flow «по щучьему велению». Для этого необходимо свести соответствующий трафик со всех используемых устройств в одну точку – Capture Server. Таким образом, написанное определяет характерную особенность системы, которая выражается в необходимости обеспечения централизации места сбора сигнального трафика и позволяет ответить на поставленный выше вопрос: что даёт использование комплекса на эксплуатируемой или внедряемой сети.

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

Следовательно, первое, что даёт внедрение СМТ – это та самая, когда-то запланированная, но так и не выполненная ревизия сети. Конечно, вдумчивый читатель сразу задаст вопрос – а при чём тут СМТ? Прямой связи здесь нет и быть не может, но … Психология большинства людей, в том числе и тех, кто связан с миром ИТ, обычно склонна приурочивать такого рода мероприятия к какому-либо событию. Следующий плюс вытекает из предыдущего и заключается в том, что ещё до того, как будет развёрнута СМТ, установлены и настроены Capture Agents, включена отправка RTCP сообщений, вполне могут быть обнаружены какие-либо проблемы, требующие оперативного вмешательства. Например, где-то образовалось «бутылочное горлышко» и это явно видно и без статистики, которую в том числе может предоставлять СМТ, используя данные, предоставляемые, например, RTCP.

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

При этом в большинстве компаний, где желательно использование такого продукта, как СМТ, имеется специальное подразделение, в список задач которого как раз и входит выполнение рутинных операций, с целью разгрузки других специалистов – service desk, helpdesk или техническая поддержка. Также я не сделаю для читателя открытие, если отмечу, что доступ инженеров службы технической поддержки из соображений безопасности и стабильности сети к наиболее критичным узлам нежелателен (хотя вполне возможно, что он и не возбраняется), а ведь именно указанные сетевые элементы содержат наиболее выгодный ракурс с точки зрения дампов. СМТ, ввиду того, что является центральным местом сбора трафика и обладает интуитивным и прозрачным интерфейсом, вполне способен решить ряд обозначенных проблем. Единственное условие – организация доступа к интерфейсу с рабочих мест специалистов технической поддержки и, возможно, написание knowledge base статьи по её использованию.

В заключение отметим наиболее известные и интересные продукты, так или иначе выполняющие рассмотренный выше функционал, среди которых: Voipmonitor, HOMER SIP Capture, Oracle Communications Monitor, СПАЙДЕР. Несмотря на имеющийся общий подход к организации и развёртыванию, каждая имеет свои нюансы, субъективные положительные и отрицательные стороны и все заслуживают их отдельного рассмотрения. Что и станет предметом дальнейших материалов. Спасибо за Ваше внимание!

Let's block ads! (Why?)

Бесплатный PVS-Studio для тех, кто развивает открытые проекты

PVS-Studio free for open source

В канун празднования нового 2019 года команда PVS-Studio решила сделать приятный подарок всем контрибьюторам open-source проектов, хостящихся на GitHub или Bitbucket. Им предоставляется возможность бесплатного использования статического анализатора PVS-Studio для развития открытых проектов.
Мы помогаем делать код открытого программного обеспечения более качественным и надёжным. Хотя, благодаря нашим публикациям, в открытых проектах было исправлено более 10000 ошибок, этого явно недостаточно. Наша команда физически не способна регулярно проверять тысячи открытых проектов. Поэтому в 2016 году мы предложили бесплатный вариант лицензирования PVS-Studio. Единственное условие — наличие в коде комментариев специального вида. Подробнее про этот вид лицензирования рассказано в статье "Как использовать PVS-Studio бесплатно".

Идя навстречу пожеланиям, мы решили предоставить возможность бесплатного использования PVS-Studio всем, кто участвует в развитии открытых проектов, размещённых на GitHub или Bitbucket.

Всем желающим мы выдаём бесплатную лицензию сроком на 1 год. Чтобы получить лицензию, необходимо:

  1. Перейти на страницу: https://www.viva64.com/ru/open-source-license/
  2. Ввести имя и e-mail, на который будет прислан лицензионный ключ;
  3. Ввести ссылку на свой GitHub/Bitbucket профайл;
  4. Отправить запрос на бесплатную лицензию.

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

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

Старый вариант бесплатного использования анализатора при добавлении в код комментариев остаётся в силе. У этого режима есть свои преимущества. Например, он может использоваться студентами для проверки своих проектов без необходимости выкладывать их на GitHub/Bitbucket. Более того, предыдущий вариант позволяет использовать анализатор вообще в закрытых проектах.

Условия

Поддержка бесплатных пользователей осуществляется посредством ответов на сайте StackOverflow. Более подробно это условие описано в статье "Как использовать PVS-Studio бесплатно" (см. главу «Дополнение: Поддержка»). Хотим обратить внимание, что StackOverflow не является багтрекером. Давайте обсуждать там вопросы, связанные именно с работой анализатора, режимами его работы и так далее. Чтобы проинформировать нас о явном баге, просим по-прежнему писать нам в поддержку.

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

Как уже было сказано ранее, возможность использования бесплатной лицензии не распространяется на зеркала проектов, например, на Clang, Chromium, KDE и так далее. Поддержка разработчиков подобных проектов потребует от нашей команды значительной работы и будет справедливо, если компании, где трудоустроены эти разработчики, приобретут платную лицензию :).

Дополнительные ссылки:

  1. Страница продукта PVS-Studio
  2. Скачать PVS-Studio
  3. Получить бесплатную лицензию для открытого проекта
  4. Бесплатное использования PVS-Studio в закрытых проектах

Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Andrey Karpov. Free PVS-Studio for everyone who develops open source projects.

Let's block ads! (Why?)

Инвестиционная подборка: 4 крупнейших IPO технологических компаний-единорогов в 2019 году

Изображение: Unsplash

Технологические компании, чья оценка достигает $1 млрд называют единорогами. В текущем году состоялось 38 IPO таких компаний. Это наибольшее число со времене «краха доткомов» в 2000 году. Аналитики прогнозируют, что этот рекорд будет побит уже в следующем году.

Портал Investopedia отобрал самые перспективыне и крупнейшией IPO компаний-единорогов, которые имеют все шансы состоятся в 2019 году. Посмотрим, в какие акции вскоре смогут вложиться инвесторы со всего мира.

Uber, отрасль: транспорт, оценка: $120 млрд


По данным WSJ, инвестбанки Goldman Sachs и Morgan Stanley оценивают такси-сервис в $120 млрд. Последние пару лет выдались сложными для Uber – за это время комания столкнулась с уходом основателя под давлением после ряда скандалов. Выход на IPO мог бы помочь бизнесу привлечь ресурсы для развития новых направлений деятельности, вроде беспилотных автомобилей.

Airbnb, краткосрочная аренда, $31 млрд


Как сообщает Forbes, компания планирует полностью подготовиться к проведению IPO к июлю 2019 года. Это означает, что с высокой долей вероятности размещение акций состоится летом-осенью, однако есть и вероятность того, что процесс будет отложен на 2020 год. Не так давно на работу в компанию был нанят бывший финансовый директор Amazon – одна из его задач заключается в подготовке к IPO.

При этом, по информации некоторых СМИ, Airbnb изучает возможность проведения не традиционного IPO, а прямого листинга на бирже по примеру Spotify (мы рассказывали об особенностях такого выхода на биржу в нашем блоге). Руководитель Airbnb Брайан Чески хочет сделать размещение акций своей компании как можно менее традиционным, помимо применения опыта Spotify, по данным журналистов, рассматриваются и другие варианты – например, возможно акции компании получат владельцы недвижимости, сдающейся через платформу.

Lyft, транспорт, $15 млрд


Один из главных конкурентов Uber на ключевом рынке США, где его доля составляет 28%. Сейчас Lyft оценивается в $15 млрд. Выручка компании в 2017 году превысила $1 млрд.

По данным FastCompany, компания уже начала подготовку в IPO – подана заявка в американскую комиссию по товарным рынкам и биржам (SEC). Также были наняты финансовые консультанты, перед которыми поставлена задача подготовить размещение акций в марте или апреле 2019 года.

Slack, софт, $7 млрд


Как и Airbnb, руководство Slack задумается о прямом листинге вместо проведения традиционного IPO. По слухам, гендиректор компании Стюарт Баттерфилд заинтересовался этой идеей и сейчас изучает ее, хотя финального решения о формате размещения акций пока не принято.

При этом компания уже наняла Goldman Sachs для подготовки IPO и наметила его на вторую половину следующего года.

О чем говорит опыт «единорогов» в 2018 году


В 2018 году на рынке IPO доминировали компании, которые не особенно известны широкой аудитории. К примеру, самое успешное размещение провела биотех-компания Elanco Animal Health – она привлекла $1.74 млрд. При удачном стечении обстоятельств в следующем году ситуация изменится.

Однако на пути компаний-единорогов к IPO остаются препятствия и риски. Об этом говорит и опыт их предшественников. Например, IPO Dropbox получилос сдержанные оценки – компанию оценивали в $9 млрд до выхода на биржу, и после этого оценка не дотянула до $10 млрд. В настоящий момент акции Dropbox стоят на 2% дороже, чем в момент размещения. Часто в случае IT-компаний цена может падать и ниже уровня размещения, так что это неплохо. Однако, цена все равно ниже своих пиковых значений примерно на 50% – и вот этот факт уже меньше радует инвесторов.

Есть и примеры неудачных размещений – после IPO акции Tencent Music Entertainment Group (TME) упали гораздо ниже цены размещения в $13 за акцию. С другой стороны, сервис DocuSign, смог добиться серьезной прибыли для инвесторов – после размещения акйий по цене в $29 за штуку, они подорожали до почти $42.

Пример Tencent должен показывать руководству крупных ИТ-компаний, что расслабляться нельзя даже в случае успешной подготовки к IPO. В период бума доткомов многие компании провели сверхуспешные IPO, а затем разорились. Тот факт, что до выхода на биржу оценка компании составляла миллиард и больше, не гарантирует дальнейший успех. Кому удастся сохранить и улучшить свои позиции – узнаем уже совсем скоро.

Другие материалы по теме финансов и фондового рынка от ITI Capital:


Let's block ads! (Why?)

[Перевод] Создаем плагин Vuex Undo/Redo для VueJS

image

Есть много преимуществ для централизации состояния вашего приложения в Vuex store. Одним из преимуществ является то, что все транзакции записываются. Это позволяет использовать удобные функции, такие как отладка по времени выполнения, где вы можете переключаться между предыдущими состояниями, чтобы отделять задачи выполнения.

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

Вы можете проверить готовый код здесь, на Github, и попробовать демо в этом Codepen. Я также создал плагин как модуль NPM под названием vuex-undo-redo, если вы хотите использовать его в проекте.


Примечание: эта статья была первоначально размещена здесь, в блоге разработчиков Vue.js 2017/11/13

Настройка плагина

Чтобы сделать эту функцию многократно используемой, мы создадим ее как плагин Vue. Эта функция требует от нас добавления некоторых методов и данных в экземпляр Vue, поэтому мы структурируем плагин как миксин.

module.exports = {
  install(Vue) {
    Vue.mixin({
      // Code goes here
    });
  }
};

Чтобы использовать его в проекте, мы можем просто импортировать плагин и подключить его:

import VuexUndoRedo from './plugin.js';
Vue.use(VuexUndoRedo);

Идея

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


Подход № 1

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

Проблема с этим подходом состоит в том, что состояние хранилища является объектом JavaScript. Когда вы помещаете объект JavaScript в массив, вы просто помещаете ссылку на объект. Наивная реализация, как и следующая, не будет работать:

var state = { ... };
var snapshot = [];

// Push the first state
snapshot.push(state);

// Push the second state
state.val = "new val";
snapshot.push(state);

// Both snapshots are simply a reference to state
console.log(snapshot[0] === snapshot[1]); // true

Подход моментального снимка потребует, чтобы вы сначала сделали клон состояния перед push. Учитывая, что состояние Vue становится реактивным благодаря автоматическому добавлению функций get и set, оно не очень хорошо работает с клонированием.


Подход № 2

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

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


Регистрация мутаций

Vuex предлагает метод API для подписки на мутации, который мы можем использовать для их регистрации. Мы установим это на хук created. В обратном вызове мы просто помещаем мутацию в массив, который позже можно будет повторно запустить.

Vue.mixin({
  data() {
    return {
      done: []
    }
  },
  created() {
    this.$store.subscribe(mutation => {
      this.done.push(mutation);
    }
  }
});

Метод отката

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


  1. Используем pop метод массива, чтобы удалить последнюю мутацию
  2. Очистим состояние store с помощью специальной мутации EMPTY_STATE (объяснено ниже)
  3. Повторяем каждую оставшуюся мутацию, фиксируя ее снова в новом store. Обратите внимание, что метод подписки все еще активен во время этого процесса, то есть каждая мутация будет добавляться повторно. Удалим это сразу с помощью pop.
const EMPTY_STATE = 'emptyState';
Vue.mixin({
  data() { ... },
  created() { ... },
  methods() {
    undo() {
      this.done.pop();
      this.$store.commit(EMPTY_STATE);
      this.done.forEach(mutation => {
        this.$store.commit(`${mutation.type}`, mutation.payload);
        this.done.pop();
      });
    }
  }
});

Очистка store

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

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

new Vuex.Store({
  state: {
    myVal: null
  },
  mutations: {
    emptyState() {
      this.replaceState({ myval: null });       
    }
  }
});

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

Vue.mixin({
  data() { ... },
  created() {
    this.$store.subscribe(mutation => {
      if (mutation.type !== EMPTY_STATE) {
        this.done.push(mutation);
      }
    });
  },
  methods() { ... }
});

Метод возврата

Давайте создадим новое свойство данных, undone которое будет массивом. Когда мы удаляем последнюю мутацию из done в процессе отката, мы помещаем ее в этот массив:

Vue.mixin({
  data() {
    return {
      done: [],
      undone: []
    }
  },
  methods: {
    undo() {
      this.undone.push(this.done.pop());
      ...
    }
  }
});

Теперь мы можем создать redo метод, который будет просто брать последнюю добавленную мутацию undone и повторно фиксировать ее.

methods: {
  undo() { ... },
  redo() {
    let commit = this.undone.pop();
    this.$store.commit(`${commit.type}`, commit.payload);
  }
}

Возврат дальше не возможен

Если пользователь инициирует отмену один или несколько раз, а затем делает новую новую фиксацию, содержимое undone будет признано недействительным. Если это произойдет, мы должны опустошить undone.

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

Самый простой подход — установить флаг newMutation. Он будет true по умолчанию, но методы отката и возврата временно установят для него значение false. Если при фиксации мутации установлено значение true, обратный вызов subscribe очистит массив undone.

module.exports = {
  install(Vue) {
    Vue.mixin({
      data() {
        return {
          done: [],
          undone: [],
          newMutation: true
        };
      },
      created() {
        this.$store.subscribe(mutation => {
          if (mutation.type !== EMPTY_STATE) {
            this.done.push(mutation);
          }
          if (this.newMutation) {
            this.undone = [];
          }
        });
      },
      methods: {
        redo() {
          let commit = this.undone.pop();
          this.newMutation = false;
          this.$store.commit(`${commit.type}`, commit.payload);
          this.newMutation = true;
        },
        undo() {
          this.undone.push(this.done.pop());
          this.newMutation = false;
          this.$store.commit(EMPTY_STATE);
          this.done.forEach(mutation => {
            this.$store.commit(`${mutation.type}`, mutation.payload);
            this.done.pop();
          });
          this.newMutation = true;
        }
      }
    });
  },
}

Основной функционал теперь завершен! Добавьте плагин в свой собственный проект или в мою демку, чтобы протестировать его.


Публичный API

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

Чтобы разрешить это, плагин может предоставлять два вычисляемых свойства canUndo и canRedo как часть публичного API. Это тривиально для реализации:

module.exports = {
  install(Vue) {
    Vue.mixin({
      data() { ... },
      created() { ... },
      methods: { ... },
      computed: {},
      computed: {
        canRedo() {
          return this.undone.length;
        },
        canUndo() {
          return this.done.length;
        }
      },
    });
  },
}

Let's block ads! (Why?)

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


Ракета-носитель Long March 5 на стартовом столе

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

7 декабря очередная ракета отправила в космос важный груз — луноход Chang’e 4. В январе он попытается сесть на обратную сторону Луны. Если все получится, то Китай поставит очередной рекорд — пока что еще ни одна страна не отправляла роверы на обратную сторону естественного спутника Земли.
Причем в Китае одновременно развивается и государственная космонавтика, и частная. В сентябре 2018 года коммерческая компания iSpace запустила в космос три наноспутника. Это был тестовый запуск ракеты-носителя, спутники не выполняют каких-либо технических задач. Еще одна китайская компания, LinkSpace, планирует испытать свою ракету в 2020 году.

Кроме названных компаний, в Китае успешно работают еще и Landspace, OneSpace и ExPace. Есть и другие компании, менее известные. Многие из них — наследие прошлых лет, НИИ, выросшие в компании, которые, впрочем, находятся под государственным управлением. Самая крупная аэрокосмическая организация Китая, Aerospace Science and Technology Corporation (CASC), такая же большая, как и Boeing, в ней насчитывается 140 000 сотрудников. Другая организация, China Academy of Launch Vehicle Technology (CALT), разработала Long March 5, первую китайскую тяжелую ракету-носитель. Сейчас специалисты CALT работают над созданием сверхтяжелой ракеты-носителя, которая должна быть готова в течение нескольких лет.

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

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

Что касается Long March 5, то первый запуск ракеты в 2016 году, в ноябре, был успешным. К сожалению, после внесения ряда усовершенствований в конструкцию, второй полет оказался неудачным. Он продолжался шесть минут, после чего отказал один из элементов двигателя и ракета упала в море.

Но китайцы понимают, что это лишь одна неудача, поэтому не прекращают работу над ракетой. В январе следующего года они собираются запустить Long March 5 снова, причем уже с грузом на борту — речь идет о весьма сложном спутнике связи, который планируется доставить на геостационарную орбиту. Далее китайцы собираются запустить Long March 5 еще раз — на этот раз для того, чтобы отправить ракету на Луну и обратно. В случае успеха, это будет первый успешный возвратный полет на спутник Земли (с образцами лунных пород) со времени «Луны 24», советского аппарата, который доставил на Землю 170 граммов образцов лунных пород.

Далее Long March 5 будет использоваться для запуска на орбиту Земли элементов орбитальной обитаемой станции. Причем не международной, а чисто китайской. Ее размеры составят лишь пятую часть от МКС, но для китайцев это не проблема — у их станции не будет различных модулей, принадлежащих разным странам, а значит, и размер может быть меньшим.

Еще Китай разрабатывает космический телескоп, разрешение которого будет примерно равно «Хабблу», но в вот поле зрение будет в 300 раз больше. Систему разместят на орбите поблизости от орбитальной станции, так что тайконавты смогут быстро наладить работу телескопа, если что-то вдруг случится.

Ну а если у Китая получится разработать Long March 9, сверхтяжелую ракету-носитель, то страна и вовсе станет лидером в аэрокосмической отрасли. Первый полет этой ракеты намечен на 2028 год, она сможет выводить на орбиту около 140 тонн полезного груза. Это в пять раз больше, чем у Long March 5. По своим возможностям ракета будет близка к самой мощной ракете-носителю, из когда-либо построенных человеком — Saturn V. Китайская ракета без проблем сможет отправить человека на Луну.

Let's block ads! (Why?)

«Вымпелком», «Мегафон» и Tele2 поднимут тарифы в начале года, Минкосвязи предлагает отменить роуминг с Белоруссией

Операторы связи «Вымпелком», «Мегафон» и Tele2 заявили о повышении стоимости услуг в начале 2019 года, о чем сообщают «Ведомости». Так, «Вымпелком» разослал абонентам сообщения, в которых говорится о повышении тарифов и стоимости подключенных услуг уже с 1 января. Цена будет увеличена на коэффициент 1,016949.

Компания объясняет свой шаг повышением НДС до 20%. В службе поддержки «Вымпелкома» заявили, что «компания выполняет обязательство по исполнению ФЗ-303 по изменению ставки НДС».

Что касается других операторов связи, то «Мегфон» собирается обновить цены с 9 января. Изменение тарифов компания также объясняется увеличением ставки НДС. Tele2 опубликовал заявление, где говорится, что оператор «не может не учитывать изменения на рынке, в отрасли и экономике», поэтому иногда компании приходится пересматривать стоимость услуг.
Ну а сегодня стало известно о том, что министр цифрового развития, связи и массовых коммуникаций России Константин Носков предложить отменить роуминг между Россией и Белоруссией. Отмена будет осуществляться в три этапа. В 2019 году будут снижены тарифы, а затем планируется отменить плату за входящие звонки, о чем сообщает «Коммерсант».

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

Возможно, роуминг будет вообще отменен внутри Евразийского экономического союза. Ранее об этом заявил заместитель руководителя Федеральной антимонопольной службы Анатолий Голомолзин.

Let's block ads! (Why?)

[Перевод] Почему измеритель глюкозы от Alphabet не взлетел

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


У них были ресурсы двух крупных компаний, лучшие эксперты по биологии и электротехнике. И всё же исследователи из Alphabet и Novartis не смогли создать точный датчик глюкозы в контактных линзах.

Технический директор Брайан Отис, работающий в Verily, научном подразделении холдинга Alphabet, объявил у в блоге компании, что компании решили «заморозить» проект создания датчика глюкозы, вставляющегося в глаз. Им не удалось добиться точных и постоянных результатов работы устройства в клинических испытаниях.

Это объявление наверняка расстроило толпы людей, страдающих от диабета 1 типа, и ждущих появления лёгкого и неинвазивного способа отслеживания уровня сахара (глюкозы) в крови. Пока что этим людям и дальше придётся колоть пальцы по нескольку раз в день, чтобы измерять уровень глюкозы в крови.
Однако это объявление не стало сюрпризом для многих учёных, которые уже несколько десятилетий безуспешно пытались создать подобные датчики [хотя есть некоторые позитивные отзывы о работе российских учёных / прим. перев.]. Исследователи пытались отслеживать глюкозу не только в слёзах, но и в других выделяемых телом жидкостях, таких, как пот, слюна и моча. И все эти проекты провалились один за другим.

Некоторые из учёных пришли к выводу о принципиальной невозможности создания подобного датчика. «По-настоящему неинвазивные биологические измерения – это наш святой Грааль, который очень и очень тяжело найти», — говорит Джейсон Хейкенфелд, директор Лаборатории инновационных устройств при Университете Цинциннати, сооснователь стартапа датчиков пота Eccrine Systems. «К примеру, в нашей работе с потом у нас ушло семь лет академических исследований» просто для того, чтобы продемонстрировать, что по поту можно точно и постоянно измерять другое анализируемое вещество – алкоголь, сказал он.

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

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

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

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

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

Объявление от Alphabet было сделано на следующий день после того, как в журнале IEEE Spectrum появилась статья, в которой эксперт предполагал, что проект по измерению глюкозы от этой компании не был успешным. В статье описывался другой глазной датчик глюкозы, разработанный компанией Noviosense, и продемонстрировавший положительные клинические результаты на ранней стадии.

Let's block ads! (Why?)

Использование QML Map для построения воздушных трасс — Часть 1

Рождественская открытка с Марса. ESA показало большое скопление льда на поверхности красной планеты

Может быть, вы уже видели? Лёд среди пустыни. Это не фотошоп и не природная аномалия. Это новые снимки водяного льда в кратере Королёва на Марсе, только что опубликованные ESA. Они сделаны спутником Mars Express, запущенным Европейским космическим агентством еще в 2003 году. Объект весом 666 кг вращается вокруг планеты уже 15 лет, многократно оправдав свою стоимость в $345 млн (между прочим, в два раза дешевле, чем у похожих спутников США).

Недавно этот «старичок» сделал свои самые впечатляющие снимки. ESA публикует их в честь приближающегося Рождества, 50-летия миссии Аполлон-8, впервые высадившей людей на Луну, и 15-летия вращения самого Mars Express (он запустил свой главный двигатель и вышел на орбиту Марса 25 декабря).


Пять снимков в разные периоды времени были взяты камерой HRSC, сделанной специально под Mars Express, и скомпонованы в одно изображение – позволяя нам увидеть то, как в реальности выглядит знаменитый кратер Королёва. В дополнение также доступны топографические виды объекта.

Кратер, названный в честь отца советской космонавтики, является самой южной точкой северной низменности Марса, региона Olympia Undae, в которой всё еще встречается водяной лед. Его глубина – 1,8 км, и он почти доверху заполнен льдом, это самое южное такое стабильное скопление льда на планете. Вот его локация на Google Mars.



Изображение того же кратера, взятое системой CaSSIS на спутнике ExoMars Trace в апреле 2018-го (первый снимок этого спутника после запуска). Здесь видна 40-километровая дуга у северного края кратера

Лёд в кратере не переходил в газообразное состояние, по всей видимости, несколько миллионов лет. Ученые из ESA причиной этому является эффект «холодной ловушки». Большая глубина кратера и его крутые края остужают проходящий воздух. Он опускается вместе с пылью и становится частью старинных залежей льда. А поскольку в разреженной атмосфере красной планеты нет хороших проводников тепла, испарение минимально. Сейчас ледник разросся до колоссальных масштабов: он занимает почти всю площадь кратера, 82 километра в диаметре.

Стоит ли паниковать?

Новое изображение ESA наделало много шума. Некоторые газеты даже вышли с заголовками а-ля «найдена вода на Марсе». Между тем, наличие воды в форме льда на поверхности красной планеты – давно не новость для ученых. На Марсе есть целых две полярных шапки, ~300 и ~1000 километров в диаметре, которые мы уже сотни лет назад спокойно различали в телескопы. Позже, с помощью спутников, был подтвержден и их состав: вода, пыль и большое количество диоксида углерода. Теперь мы даже имеем представление о количестве воды на Марсе. И уже десять лет как знаем, что, если растопить северную и южную шапки, этого хватит на покрытие всей планеты 20-метровым слоем воды (правда, выделение CO2 также в два раза повысит атмосферное давление на планете).

Когда исследователи говорят о поисках воды на Марсе, они имеют в виду жидкую воду. В которой, с более высокой вероятностью, даже после миллионов лет могла остаться жизнь. Наличие подземных океанов (на глубине от 1,5 км) было засечено радаром MARSIS на борту всё того же Mars Express. На 29 снимках ученые нашли область, где сигнал резко менялся – это было похоже на подледные озера Гренландии и Антарктики.



Результат труда Curiosity

Температура подземной воды на Марсе, вероятно, тоже отрицательная. Но, как и на Земле, примеси в воде и давление массы льда могут снижать температуру ее замерзания, поэтому она остается жидкой. Но гипотезу надо будет подтвердить, а еще лучше – взять пробы. Для этого нужен будет марсоход, способный бурить на такие глубины (которые и на Земле вообще-то непросто даются). Curiosity, запущенный США в 2011-м, пока что пробурил поверхность Марса на глубину 5 сантиметров.

Let's block ads! (Why?)

[Перевод] ECMAScript-модули в Node.js: новый план

Текущий статус поддержки ECMAScript-модулей (ESM) в Node.js:


  • Экспериментальная поддержка ESM была добавлена в Node.js 8.5.0 12 сентября 2017 года.
  • После этого Технический Руководящий Комитет Node.js сформировал команду, ответственную за модули (Modules Team), чтобы она помогла спроектировать недостающие части для грядущего (не экспериментального) релиза. Эта команда состоит из людей из различных отраслей веб-разработки (фронтенд, бекенд, JS-движки, и т.д.).

В октябре Modules Team опубликовала "План по реализации Новых Модулей". Этот пост объясняет, что в нем содержится.


Фазы

Процесс разделен на три фазы:


  • Фаза 1: создать "минимальное" ядро – основной набор правил и возможностей, минимальных и бесспорных, насколько это возможно.
  • Фаза 2 и далее: создание на основе ядра более сложной функциональности.

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


Фаза 1: минимальное ядро поддержки ESM в Node.js


Упрощение идентификаторов модулей

Одна из целей Modules Team это достижение "браузерной эквивалентности": Node.js должна быть близка по поведению к браузерам, насколько это возможно. Ядро достигает этого путем упрощения разбора идентификаторов модулей (URL-ов, указывающих на модули):


  • Каждый идентификатор модуля должен заканчиваться именем файла с расширением. То есть
    • Расширения не добавляются автоматически
    • Импортирование директорий не поддерживается (ни через перенаправление на dir/index.mjs, ни чтение поля main в package.json).
  • ES modules могут импортировать встроенные Node.js-модули (path и подобные). Они являются единственным исключением из предыдущего правила.
  • По умолчанию поддерживаются только файлы с расширением .mjs (смотрите Фазу 2, если вам интересен статус других расширений). Таким образом, другие виды модулей не смогут быть импортированы через import: модули CommonJS, JSON-файлы, нативные модули.

Принесение важных возможностей CommonJS в ES-модули


  • URL текущего модуля (аналогично __filename из CommonJS): import.meta.url
  • Динамический импорт ES-модулей (доступен через require() в CommonJS): оператор import()

Совместимость


  • ES-модули смогут импортировать CommonJS-модули через createRequireFromPath(). Это будет работать следующим образом (есть планы сделать сокращенный способ, например, функцию createRequireFromUrl()):
import {createRequireFromPath as createRequire} from 'module';
import {fileURLToPath as fromPath} from 'url';
const require = createRequire(fromPath(import.meta.url));

const cjsModule = require('./cjs-module.js');

  • CommonJS-модули смогут загружать ES-модули через import().

Фаза 2 и дальнейшие планы


  • Во второй фазе нас ждет:
    • Поддержка "bare" индентификаторов, таких как 'lodash'. Скорее всего, это включит в себя некоторый способ маппинга этих идентификаторов на реальные пути.
    • Поддержка других расширений файлов, помимо .mjs. Это включает в том числе и поддержку ES-модулей в .js файлах.
  • Фаза 3, скорее всего, сосредоточится на загрузчиках модулей с точками расширения, в которых пользователи смогут подключить свою логику.

Когда я смогу пользоваться ES-модулями в Node.js?


  • За флагом: доступно уже сейчас
    • Внимание: поведение еще не соответствует описанному выше в фазе 1 (по состоянию на Node.js 11.5.0)
  • Без флага и в соотвествии с фазой 1: Modules Team старается сделать это возможным как можно скорее. Надеемся, что модули выпустят из под флага в Node.js 14 (апрель 2020 года) и портируют в предыдущие версии, если это будет возможно.

Часто задаваемые вопросы


  • Зачем нужно новое расширение файлов .mjs?
    • Каждое решение по различению форматов ESM и CommonJS имеет свои преимущества и недостатки. Использование отдельного разрешения кажется неплохим вариантом (больше информации).
  • Почему поведение Node.js должно быть похожим на браузерное?
    • Потому что это делает возможным переиспользование кода. Например, чтобы создавать библиотеки, которые работают одновременно и в браузерах, и в Node.js
    • Также это должно облегчить переключение между бекендом и фронтендом во время кодинга.
  • К чему все эти ограничения в целях совместимости?
    • Между ES и CommonJS-модулями есть довольно сильные отличия в структуре (статическая против динамической) и способе загрузки (асинхронная против синхронной). Ограничения помогают сохранить порядок вещей простым – учитывая, что в долгосрочной перспективе подавляющим большинством будут ES-модули.
  • Почему это все тянется так долго?
    • Здесь участвует много заинтересованных сторон и вовлечено много разных платформ (Node.js, npm, браузеры, JS-движки, TypeScript, TC39 и другие). Если мы действительно получим ES-модули, способные работать везде, наверное, это стоит ожидания, ИМХО.

Благодарность

Спасибо Майлсу Боринсу за его отзывы об этом посте.


Дополнительные источники для дальнейшего чтения


Let's block ads! (Why?)

[Из песочницы] Разрушительные исключения

Ещё раз о том, почему плохо бросать исключения в деструкторах

Многие знатоки C++ (например, Герб Саттер) учат нас, что бросать исключения в деструкторах плохо, потому что в деструктор можно попасть во время раскрутки стека при уже выброшенном исключении, и если в этот момент будет выброшено ещё одно исключение, в результате будет вызван std::terminate(). Стандарт языка C++17 (здесь и далее я ссылаюсь на свободно доступную версию драфта N4713) на эту тему сообщает нам следующее:


18.5.1 The std::terminate() function [except.terminate]

1 In some situations exception handling must be abandoned for less subtle error handling techniques. [ Note:

These situations are:

(1.4) when the destruction of an object during stack unwinding (18.2) terminates by throwing an exception, or

— end note ]

Проверим на простом примере:

#include <iostream>

class PrintInDestructor {

public:
    ~PrintInDestructor() noexcept {
        std::cerr << "~PrintInDestructor() invoked\n";
    }

};

void throw_int_func() {
    std::cerr << "throw_int_func() invoked\n";
    throw 1;
}

class ThrowInDestructor {

public:
    ~ThrowInDestructor() noexcept(false) {
        std::cerr << "~ThrowInDestructor() invoked\n";
        throw_int_func();
    }

private:
    PrintInDestructor member_;

};

int main(int, char**) {

    try {
        ThrowInDestructor bad;
        throw "BANG!";
    } catch (int i) {
        std::cerr << "Catched int exception: " << i << "\n";
    } catch (const char* c) {
        std::cerr << "Catched const char* exception: " << c << "\n";
    } catch (...) {
        std::cerr << "Catched unknown exception\n";
    }

    return 0;

}

Результат:

~ThrowInDestructor() invoked
throw_int_func() invoked
~PrintInDestructor() invoked
terminate called after throwing an instance of 'int'
Aborted

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


2… In the situation where no matching handler is found,
it is implementation-defined whether or not the stack is unwound before std::terminate() is called. In
the situation where the search for a handler (18.3) encounters the outermost block of a function with a
non-throwing exception specification (18.4), it is implementation-defined whether the stack is unwound,
unwound partially, or not unwound at all before std::terminate() is called ...

Я проверял этот пример на нескольких версиях GCC (8.2, 7.3) и Clang (6.0, 5.0), везде раскрутка стека продолжается. Если вы встретите компилятор, где implementation-defined по-другому, пожалуйста, напишите об этом в комментариях.

Следует заметить также, что std::terminate() при раскрутке стека вызывается только тогда, когда исключение выбрасывается наружу из деструктора. Если внутри деструктора находится try/catch блок, который ловит исключение и не пробрасывается дальше, это не приводит к прерыванию раскрутки стека внешнего исключения.

class ThrowCatchInDestructor {

public:
    ~ThrowCatchInDestructor() noexcept(false) {
        try {
            throw_int_func();
        } catch (int i) {
            std::cerr << "Catched int in ~ThrowCatchInDestructor(): " << i << "\n";
        }
    }

private:
    PrintInDestructor member_;

};

int main(int, char**) {

    try {
        ThrowCatchInDestructor good;
        std::cerr << "ThrowCatchInDestructor instance created\n";
        throw "BANG!";
    } catch (int i) {
        std::cerr << "Catched int exception: " << i << "\n";
    } catch (const char* s) {
        std::cerr << "Catched const char* exception: " << s << "\n";
    } catch (...) {
        std::cerr << "Catched unknown exception\n";
    }

    return 0;

}

выводит

ThrowCatchInDestructor instance created
throw_int_func() invoked
Catched int in ~ThrowCatchInDestructor(): 1
~PrintInDestructor() invoked
Catched const char* exception: BANG!

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


Если нельзя, но очень хочется...


Сразу отмечу, что я не пытаюсь оправдать выбрасывание исключений из деструктора, и вслед за Саттером, Мейерсом и другими гуру C++ призываю вас постараться никогда этого не делать (по крайней мере, в новом коде). Тем не менее, программист в реальной практике вполне может столкнуться с legacy-кодом, который не так просто привести к высоким стандартам. Кроме того, зачастую описанные ниже методики могут пригодиться в процессе отладки.

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


  • Проигнорировать ошибку. Плохо, потому что мы скрываем проблему, которая может повлиять на другие части системы.
  • Написать в лог. Лучше, чем просто проигнорировать, но всё равно плохо, т.к. наша библиотека ничего не знает о политиках логирования, принятых в системе, которая её использует. Стандартный лог может быть перенаправлен в /dev/null, в результате чего, опять же, ошибку мы не увидим.
  • Вынести освобождение ресурса в отдельную функцию, которая возвращает значение или бросает исключение, и заставлять пользователя класса вызывать её самостоятельно. Плохо, потому что пользователь вообще может забыть это сделать, и мы получим утечку ресурса.
  • Выбросить исключение. Хорошо в обычных случаях, т.к. пользователь класса может поймать исключение и стандартным образом получить информацию о возникшей ошибке. Плохо во время раскрутки стека, т.к. приводит к std::terminate().

Как же понять, находимся ли мы в данный момент в процессе раскрутке стека по исключению или нет? В C++ для этого есть специальная функция std::uncaught_exception(). С её помощью мы можем безопасно кидать исключение в обычной ситуации, либо делать что-либо менее правильное, но не приводящее к выбросу исключения во время раскрутки стека.

class ThrowInDestructor {

public:
    ~ThrowInDestructor() noexcept(false) {
        if (std::uncaught_exception()) {
            std::cerr << "~ThrowInDestructor() stack unwinding, not throwing\n";
        } else {
            std::cerr << "~ThrowInDestructor() normal case, throwing\n";
            throw_int_func();
        }
    }

private:
    PrintInDestructor member_;

};

int main(int, char**) {

    try {
        ThrowInDestructor normal;
        std::cerr << "ThrowInDestructor normal destruction\n";
    } catch (int i) {
        std::cerr << "Catched int exception: " << i << "\n";
    }

    try {
        ThrowInDestructor stack_unwind;
        std::cerr << "ThrowInDestructor stack unwinding\n";
        throw "BANG!";
    } catch (int i) {
        std::cerr << "Catched int exception: " << i << "\n";
    } catch (const char* s) {
        std::cerr << "Catched const char* exception: " << s << "\n";
    } catch (...) {
        std::cerr << "Catched unknown exception\n";
    }

    return 0;

}

Результат:

ThrowInDestructor normal destruction
~ThrowInDestructor() normal case, throwing
throw_int_func() invoked
~PrintInDestructor() invoked
Catched int exception: 1
ThrowInDestructor stack unwinding
~ThrowInDestructor() stack unwinding, not throwing
~PrintInDestructor() invoked
Catched const char* exception: BANG!

Обратите внимание, что функция std::uncaught_exception() является deprecated начиная со Стандарта C++17, поэтому чтобы скомпилировать пример, соответствующий ворнинг приходится подавлять (с.м. репозитарий с примерами из статьи).

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

class MayThrowInDestructor {

public:
    ~MayThrowInDestructor() noexcept(false) {
        if (std::uncaught_exception()) {
            std::cerr << "~MayThrowInDestructor() stack unwinding, not throwing\n";
        } else {
            std::cerr << "~MayThrowInDestructor() normal case, throwing\n";
            throw_int_func();
        }
    }

};

class ThrowCatchInDestructor {

public:
    ~ThrowCatchInDestructor() noexcept(false) {
        try {
            MayThrowInDestructor may_throw;
        } catch (int i) {
            std::cerr << "Catched int in ~ThrowCatchInDestructor(): " << i << "\n";
        }
    }

private:
    PrintInDestructor member_;

};

int main(int, char**) {

    try {
        ThrowCatchInDestructor stack_unwind;
        std::cerr << "ThrowInDestructor stack unwinding\n";
        throw "BANG!";
    } catch (int i) {
        std::cerr << "Catched int exception: " << i << "\n";
    } catch (const char* s) {
        std::cerr << "Catched const char* exception: " << s << "\n";
    } catch (...) {
        std::cerr << "Catched unknown exception\n";
    }

    return 0;

}

Результат:

ThrowInDestructor stack unwinding
~MayThrowInDestructor() stack unwinding, not throwing
~PrintInDestructor() invoked
Catched const char* exception: BANG!

В новом Стандарте C++17 на замену std::uncaught_exception() была представлена функция std::uncaught_exceptions() (обратите внимание на множественное число), которая вместо булевого значения возвращает количество активных в данный момент исключений (вот подробное обоснование).

Вот как описанная выше проблема решается при помощи std::uncaught_exceptions():

class MayThrowInDestructor {

public:
    MayThrowInDestructor() : exceptions_(std::uncaught_exceptions()) {}
    ~MayThrowInDestructor() noexcept(false) {
        if (std::uncaught_exceptions() > exceptions_) {
            std::cerr << "~MayThrowInDestructor() stack unwinding, not throwing\n";
        } else {
            std::cerr << "~MayThrowInDestructor() normal case, throwing\n";
            throw_int_func();
        }
    }

private:
    int exceptions_;

};

class ThrowCatchInDestructor {

public:
    ~ThrowCatchInDestructor() noexcept(false) {
        try {
            MayThrowInDestructor may_throw;
        } catch (int i) {
            std::cerr << "Catched int in ~ThrowCatchInDestructor(): " << i << "\n";
        }
    }

private:
    PrintInDestructor member_;

};

int main(int, char**) {

    try {
        ThrowCatchInDestructor stack_unwind;
        std::cerr << "ThrowInDestructor stack unwinding\n";
        throw "BANG!";
    } catch (int i) {
        std::cerr << "Catched int exception: " << i << "\n";
    } catch (const char* s) {
        std::cerr << "Catched const char* exception: " << s << "\n";
    } catch (...) {
        std::cerr << "Catched unknown exception\n";
    }

    return 0;

}

Результат:

ThrowInDestructor stack unwinding
~MayThrowInDestructor() normal case, throwing
throw_int_func() invoked
Catched int in ~ThrowCatchInDestructor(): 1
~PrintInDestructor() invoked
Catched const char* exception: BANG!

Когда очень-очень хочется выбросить сразу несколько исключений

std::uncaught_exceptions() позволяет избежать вызова std::terminate(), но не помогает корректно обрабатывать множественные исключения. В идеале хотелось бы иметь механизм, который позволял бы сохранять все выброшенные исключения, а затем обработать их в одном месте.


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

Суть идеи состоит в том, чтобы ловить исключения и сохранять их в контейнер, а затем по одному доставать и обрабатывать. Для того, чтобы сохранять объекты исключений, в языке C++ есть специальный тип std::exception_ptr. Структура типа в Стандарте не раскрывается, но говорится, что это по сути своей shared_ptr на объект исключения.

Как же потом обработать эти исключения? Для этого есть функция std::rethrow_exception(), которая принимает указатель std::exception_ptr и выбрасывает соответствующее исключение. Нам нужно только поймать его соответствующей catch-секцией и обработать, после чего можно переходить к следующему объекту исключения.

using exceptions_queue = std::stack<std::exception_ptr>;

// Get exceptions queue for current thread
exceptions_queue& get_queue() {
    thread_local exceptions_queue queue_;
    return queue_;
}

// Invoke functor and save exception in queue
void safe_invoke(std::function<void()> f) noexcept {
    try {
        f();
    } catch (...) {
        get_queue().push(std::current_exception());
    }
}

class ThrowInDestructor {

public:
    ~ThrowInDestructor() noexcept {
        std::cerr << "~ThrowInDestructor() invoked\n";
        safe_invoke([]() {
            throw_int_func();
        });
    }

private:
    PrintInDestructor member_;

};

int main(int, char**) {

    safe_invoke([]() {
        ThrowInDestructor bad;
        throw "BANG!";
    });

    auto& q = get_queue();
    while (!q.empty()) {
        try {
            std::exception_ptr ex = q.top();
            q.pop();
            if (ex != nullptr) {
                std::rethrow_exception(ex);
            }
        } catch (int i) {
            std::cerr << "Catched int exception: " << i << "\n";
        } catch (const char* s) {
            std::cerr << "Catched const char* exception: " << s << "\n";
        } catch (...) {
            std::cerr << "Catched unknown exception\n";
        }
    }

    return 0;

}

Результат:

~ThrowInDestructor() invoked
throw_int_func() invoked
~PrintInDestructor() invoked
Catched const char* exception: BANG!
Catched int exception: 1

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


Выводы

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


Ссылки

Репозитарий с примерами из статьи

Let's block ads! (Why?)