Избранное сообщение

Фетісов В. С. Комп’ютерні технології в тестуванні. Навчально-методичний посібник. 2-ге видання, перероблене та доповнене / Мои публикации

В 10-х годах я принимал участие в программе Европейского Союза Tempus "Освітні вимірювання, адаптовані до стандартів ЄС". В рамк...

Благодаря Интернету количество писателей и поэтов увеличивается в геометрической прогрессии. Поголовье читателей начинает заметно отставать.

четверг, 31 мая 2018 г.

Астранция большая / Фото из личного архива

Смотри также:

Как установить XPS Viewer в Windows 10 1803 / Программное обеспечение. Система

Поддержка документов XPS по умолчанию появилась еще в Windows Vista и была доступной вплоть до апрельского обновления Windows 10. Сам XPS представляет собой основанный на XML формат для работы с графическими документами, позиционируемый Microsoft как альтернатива более «тяжелому» PDF. Особой популярности формат так и не приобрел, и в Windows 10 1083 Microsoft приняла решение не включать XPS Viewer, если установка операционной системы выполняется с нуля, то есть с установочного диска.
Если вы хотите использовать в чисто установленной Windows 1083 XPS Viewer, вам нужно будет добавить его вручную.
Если установка новой версии выполнялась через Центр обновления, никаких действий не потребуется, так как компонент для работы с документами XPS по-прежнему будет доступен.
В «чистой» системе модуль устанавливается следующим образом.
Откройте приложение «Параметры», зайдите в раздел «Приложения и возможности» и нажмите ссылку «Управление дополнительными компонентами».

Параметры
В следующем окне нажмите «Добавить компонент».
Добавить компонент
Выберите из списка XPS Viewer (он должен идти в списке первым) и нажмите кнопку «Установить».
XPS Viewer
Если при установке вдруг возникнут какие-то проблемы, воспользуйтесь консольной утилитой DISM.
Откройте командную строку от имени администратора и выполните в ней такую команду:
dism /Online /Add-Capability /CapabilityName:XPS.Viewer~~~~0.0.1.0
DISM
Через пару минут компонент будет установлен, и вы сможете запустить его командой xpsrchvw.exe в окошке «Выполнить» или просто открыв привычным способом любой файл XPS.
Если Вам нравятся статьи, заметки и другой интересный материал представленный на сайте Белые окошки и у вас есть непреодолимое желание поддержать этот скромный проект тогда выберите один из двух видов стратегии поддержки на специальной странице - Страница с донатом


Смотри также:


Программы для обновления программ в Windows. http://fetisovvs.blogspot.com/2017/02/windows.html
Более 60 инструментов для мониторинга Windows. http://fetisovvs.blogspot.com/2016/05/60-windows.html
3 утилиты для удаления заблокированных в Windows файлов. http://fetisovvs.blogspot.com/2017/04/3-windows.html
Деинсталляторы с функцией отслеживания данных устанавливаемых программ. http://fetisovvs.blogspot.com/2017/03/blog-post_1.html
Aida64: проверяем стабильность системы. http://fetisovvs.blogspot.com/2016/08/aida64.html
Удаляем предустановленные в Windows 10 приложения, используя App Remover. http://fetisovvs.blogspot.com/2015/08/windows-10-app-remover.html
CCleaner превратился в Adware. http://fetisovvs.blogspot.com/2017/11/ccleaner-adware.html
EaseUS Partition Master Free — бесплатный менеджер разделов и оптимизатор системы. http://fetisovvs.blogspot.com/2018/04/easeus-partition-master-free.html
HDD Scanner — как узнать размер папок и файлов. http://fetisovvs.blogspot.com/2017/03/hdd-scanner.html
Как полностью отключить слежку в Windows 10. http://fetisovvs.blogspot.com/2015/08/windows-10_81.html
Перенос Windows на SSD-накопитель с помощью программы Paragon Hard Disk Manager 15. http://fetisovvs.blogspot.com/2015/10/windows-ssd-paragon-hard-disk-manager-15.html
ShutUp10 — еще одна бесплатная утилита для отключения следящих функций Windows 10. http://fetisovvs.blogspot.com/2018/05/shutup10-windows-10.html

Sysprep – штатная утилита адаптации Windows к новому железу. http://fetisovvs.blogspot.com/2018/03/sysprep-windows.html
TaskSpace - Программа для компоновки программ Windows в одном окне. http://fetisovvs.blogspot.com/2014/06/taskspace-windows.html
Расширяем функциональность Total Commander. http://fetisovvs.blogspot.com/2017/07/total-commander.html
WinAero Tweaker: инструмент «все-в-одном» для настройки Windows. http://fetisovvs.blogspot.com/2015/03/winaero-tweaker-windows.html
Инструкция по использованию WinSetupFromUSB. http://fetisovvs.blogspot.com/2017/07/winsetupfromusb.html
Как поменять цвет заголовка окна в Windows 10. http://fetisovvs.blogspot.com/2015/08/windows-10_8.html
Утилита Wise PC 1stAid для автоматического устранения неполадок в Windows. http://fetisovvs.blogspot.com/2016/07/wise-pc-1staid-windows.html

среда, 30 мая 2018 г.

Интеллектуальные системы поддержки принятия решений — краткий обзор / Блог компании Open Data Science / Хабр

image

Дисклеймер

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

Введение

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

Зачем нужны СППР:
  1. Сложность в принятии решений
  2. Необходимость в точной оценке различных альтернатив
  3. Необходимость предсказательного функционала
  4. Необходимость мультипотокового входа (для принятия решения нужны выводы на основе данных, экспертные оценки, известные ограничения и т.п.)
Первые СППР (тогда еще без И) выросли из СПТ (Систем Процессинга Транзакций), в середине 60-х — начале 70-х. Тогда эти системы не обладали никакой интерактивностью, представляя собой, по сути, надстройки над РСУБД, с некоторым (совсем не большим) функционалом численного моделирования. Одной из первых систем можно назвать DYNAMO, разработанную в недрах MIT и представлявшую собой систему симуляции каких-либо процессов на основе исторических транзакций. После выхода на рынок мейнфреймов IBM 360 стали появляться и условно-коммерческие системы, применявшиеся в оборонке, спецслужбах и НИИ.

С начала 80-х уже можно говорить о формировании подклассов СППР, таких как MIS (Management Information System), EIS (Executive Information System), GDSS (Group Decision Support Systems), ODSS (Organization Decision Support Systems) и др. По сути, эти системы представляли собой фреймворки, спососбные работать с данными на различных уровнях иерархии (от индивидуального до общеорганизационного), а внутрь можно было внедрить какую угодно логику. Примером может служить разработанная Texas Instruments для United Airlines система GADS (Gate Assignment Display System), которая поодерживала принятие решений в Field Operations — назначение гейтов, определение оптимального времени стоянки и т.п.

В конце 80-х появились ПСППР (Продвинутые — Advanced), которые позволяли осуществлять «what-if» анализ и использовали более продвинутый инструментарий для моделирования.

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

Многообразие СППР

На данных момент существует несколько способов классификации СППР, опишем 3 популярных:

По области применения

  • Бизнес и менеджмент (прайсинг, рабочая сила, продукты, стратегия и т.п.)
  • Инжиниринг (дизайн продукта, контроль качества...)
  • Финансы (кредитование и займы)
  • Медицина (лекарства, виды лечения, диагностика)
  • Окружающая среда

По соотношению данные\модели (методика Стивена Альтера)

  • FDS (File Drawer Systems — системы предоставления доступа к нужным данным)
  • DAS (Data Analysis Systems — системы для быстрого манипулирования данными)
  • AIS (Analysis Information Systems — системы доступа к данным по типу необходимого решения)
  • AFM(s) (Accounting & Financial models (systems) — системы рассчета финансовых последствий)
  • RM(s) (Representation models (systems) — системы симуляции, AnyLogic как пример)
  • OM(s) (Optimization models (systems) — системы, решающие задачи оптимизации)
  • SM(s) (Suggestion models (systems) — системы построения логических выводов на основе правил)

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

  • Model Driven — в основе лежат классические модели (линейные модели, модели управления запасами, транспортные, финансовые и т.п.)
  • Data Driven — на основе исторических данных
  • Communication Driven — системы на оснвое группового принятия решений экспертами (системы фасилитации обмена мнениями и подсчета средних экспертных значений)
  • Document Driven — по сути проиндексированное (часто — многомерное) хранилище документов
  • Knowledge Driven — внезапно, на основе знаний. При чем знаний как экспертных, так и выводимых машинно

Я требую жалобную книгу! нормальную СППР

Несмотря на такое многообразие вариантов классификаций, требования и атрибуты СППР хорошо ложаться в 4 сегмента:
  1. Качество
  2. Организация
  3. Ограничения
  4. Модель
На схеме ниже покажем, какие именно требовани и в какие сегменты ложаться:
image

Отдельно отметим такие важные атрибуты, как масштабируемость (в ныне одном подходе agile никуда без этого), способность обрабатывать плохие данные, юзабилити и user-friendly interface, нетребовательность к ресурсам.

Архитектура и дизайн ИСППР

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

Действительно, СППР вполне можно разделить на 4 больших слоя:

  1. Интерфейс
  2. Моделирование
  3. Data Mining
  4. Data collection
А уж в эти слои можно напихать какие угодно инструменты.

На схеме ниже представляю мое видение архитектуры, с описанием функционала и примерами инструментов:

image

С архитектурой более или менее понятно, перейдем к дизайну и собственно построению СППР.

В прицнипе, тут нет никакого rocket science. При построении ИСППР необходимо придерживаться следующих шагов:
  1. Анализ домена (собственно, где мы будем нашу ИСППР использовать)
  2. Сбор данных
  3. Анализ данных
  4. Выбор моделей
  5. Экспертный анализ\интерпретация моделей
  6. Внедрение моделей
  7. Оценка ИСППР
  8. Внедрение ИСППР
  9. Сбор обратной свзяи (на любом этапе, на самом деле)

На схеме это выглядит так:

image

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

image

Подчеркну, что это только ИМХО и вы можете сами сделать удобный для себя чек-лист.

А где тут машинное обучение и теория игр?

Да практически везде! По крайней мере в слое, связанном с моделированием.

С одной стороны, есть классические домены, назовем их «тяжелыми», вроде управления цепями поставок, производства, запасов ТМЦ и проч. В тяжелых доменах наши с вами любимые алгоритмы могут привнести дополнительные инсайты для зарекомендовавших себя классических моделей. Пример: предиктивная аналитика по выходам из строя оборудования (машинное обучение) отлично сработается с каким-нибудь FMEA анализом (классика).

С другой стороны, в «легких» доменах, вроде клиентской аналитики, предсказании churn, выплаты кредитов — алгоритмы машинного обучения будут на первых ролях. А в скоринге, например, можно совмещать классику с NLP, когда решаем выдавать ли кредит на основе пакета документов (как раз-таки document driven СППР).

Классические алгоритмы машинного обучения

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

Поступаем очень просто:

Шаг 0. Определяем целевую переменную (ну, например, содержание оксида титана в готовой продукции)
Шаг 1. Определяемся с данными (выгружаем из SAP, Access и вообще ото всюду, куда дотянемся)
Шаг 2. Собираем фичи\генерим новые
Шаг 3. Рисуем процесс data flow и запускаем его в продакшн
Шаг 4. Выбираем и обучаем модельку, запускаем ее крутиться на сервере
Шаг 5. Определяем feature importances
Шаг 6. Определяемся со вводом новых данных. Пусть наш менеджер их вводит, например, руками.
Шаг 7. Пишем на коленке простой web-based интерфейс, куда менеджер вводит ручками значения важных фич, это крутится на серваке с моделькой, и в тот же интерфейс выплевываестя прогнозируемое качество продукции

Вуа-ля, ИСППР уровня детсад готова, можно пользоваться.

Подобные «простые» алгоритмы также использует IBM в своей СППР Tivoli, которая позволяет определять состояние своих супер-компьютеров (Watson в первую очередь): на основе логов выводится информация по перформансу Watson, прогнозируется доступность ресурсов, баланс cost vs profit, необходимость обслуживания и т.п.

Компания ABB предлагает своим клиентам DSS800 для анализа работы электродвигателей той же ABB на бумагоделательной линии.

Финская Vaisala, производитель сенсоров для минтранса Финляндии использует ИСППР для предсказания того, в какие периоды необходимо применять анти-обледенитель на дорогах во избежания ДТП.

Опять-таки финская Foredata предлагает ИСППР для HR, которая помогает принимать решения по годности кандидата на позицию еще на этапе отбора резюме.

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

Тысячи их!

Обычные нейронные сети

Кроме простого ML, в СППР отлично ложиться и Deep Learning.

Некоторые примеры можно найти в ВПК, например в американской TACDSS (Tactical Air Combat Decision Support System). Там внутри крутятся нейронки и эволюционные алгоритмы, помогающие в определении свой-чужой, в оценке вероятности попадания при залпе в данный конкретный момент и прочие задачки.

В немного более реальном мире можно рассмотреть такой пример: в сегменте B2B необходимо определить, выдавать ли кредит организации на основе пакета документов. Это в B2C вас оператор замучает вопросами по телефону, проставит значения фич у себя в системе и озвучит решение алгоритма, в B2B несколько посложнее.

ИСППР там может строиться так: потенциальный заемщик приносит заранее согласованный пакет документов в офис (ну или по email присылает сканы, с подписями и печатями, как положено), документы скармливаются в OCR, затем передаются в NLP-алгоритм, который дальше уже делит слова на фичи и скармливает их в NN. Клиента просят попить кофе (в лучшем случае), или вот где карту оформляли туда и идите прийти после обеда, за это время как раз все и обсчитается и выведет на экран девочке-операционисту зеленый или красный смайлик. Ну или желтый, если вроде ок, но нужно больше справок богу справок.

Подобными алгоритмами пользуются также в МИД: анкета на визу + прочие справки анализируются прямо в посольстве \ консульстве, после чего сотруднику на экране высвечивается один из 3 смайликов: зеленый (визу выдать), желтый (есть вопросы), красный (соискатель в стоп-листе). Если вы когда-нибудь получали визу в США, то то решение, которое озвучивает вам сотрудник консульства — это именно результат работы алгоритма в совокупности с правилами, а никак не его личное субъективное мнение о вас:)

В тяжелых доменах известны также СППР на основе нейронок, определяющие места накопления буфера на производственных линиях (см, напимер, Tsadiras AK, Papadopoulos CT, O’Kelly MEJ (2013) An artificial neural network based decision support system for solving the buffer allocation problem in reliable production lines. Comput Ind Eng 66(4):1150–1162), Общие Нечеткие Нейронные Сети на основе мин-макса (GFMMNN) для кластеризации потребителей воды (Arsene CTC, Gabrys B, Al-Dabass D (2012) Decision support system for water distribution systems based on neural networks and graphs theory for leakage detection. Expert Syst Appl 39(18):13214–13224) и другие.

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

Байесовские сети

Бывает иногда и так, что данные у нас неоднородны по видам появления. Приведем пример из медицины. Поступил к нам больной. Что-то мы про него знаем из анкеты (пол, возраст, вес, рост и т.п.) и анамнеза (перенесенные инфаркты, например). Назовем эти данные статическими. А что-то мы про него узнаем в процессе периодического обследования и лечения (несколько раз в день меряем температуру, состав крови и проч). Эти данные назовем динамическими. Понятно, что хорошая СППР должна уметь учитывать все эти данные и выдавать рекомендации, основываясь на всей полноте информации.

Динамические данные обновляются во времени, соответственно, паттерн работы модели будет такой: обучение-решение-обучение, что в общем похоже на работу врача: примерно определить диагноз, прокапать лекарство, посмотреть за реакцией. Таким образом, мы постоянно пребываем в состоянии неопределенности, подействует лечение или нет. И состояние пациента меняется динамически. Т.е. нам надо построить динамическую СППР, причем еще и knowledge driven.

В таких случаях нам отлично помогут Динамические Байесовские Сети (ДБС) — обобщение моделей на основе фильтров Калмана и Скрытой Марковской Модели.

Разделим данные по пациенту на статические и динамические.

image

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

p=P(Vi|C)
,

где Vi— узел нашей сетки (вершина графа, по сути), т.е. значение каждой переменной (пол, возраст....), а С — предсказываемый класс (болезнь).

Статическая сетка выглядит так:

image

Но это не айс. Состояние пациента меняется, время идет, надо решать, как же его лечить.

Вот для этого и применим ДБС.

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

image

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

image

Таким, образом, результат мы расчитаем по следующей формуле:

P(result1:T)=Пt=1TПi=1NP(resultit|Pa(resultit))
, где T — совокупное время госпитализации, N — количество переменных на каждом из шагов ДБС.

Внедрить эту модель в СППР необходимо несколько иначе — скорее тут надо идти от обратного, сначала эту модель зафиксировать, а потом строить интерфейс вокруг. Т.е., по сути, мы сделали хард модель, внутри которой динамические элементы.

Теория игр

Теория игр, в свою очередь, гораздо лучше подойдет для ИСППР, созданных для принятия стратегических решений. Приведем пример.

Допустим, на рынке существует олигополия (малое количество соперников), есть определенный лидер и это (увы) не наша компания. Нам необходимо помочь менеджменту принять решение об объемах выпускаемой нами продукции: если мы будем выпускать продукцию в объеме q2, а наш соперник — q1, уйдем мы в минус или нет? Для упрощения возьмем частный случай олигополии — дуополию (2 игрока). Пока вы думаете, RandomForest тут или CatBoost, я вам предложу использовать классику — равновесие Штакельберга. В этой модели поведение фирм описывается динамической игрой с полной совершенной информацией, при этом особенностью игры является наличие лидирующей фирмы, которая первой устанавливает объём выпуска товаров, а остальные фирмы ориентируются в своих расчетах на неё.
Для решения нашей задачи нам надо всего-то посчитать такое q2, при котором решиться задача оптимизации следующего вида: Profit2=max(Price(q1+q2)q2Cost2(q2))

Для ее решения (сюрприз-сюрприз!) надо лишь приравнять первую производную по q2к нулю.

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

Для таких моделей и СППР на их основе подойдет и Excel. Конечно, если вводимые данные надо посчитать, то нужно что-то посложнее, но не сильно. Тот же Power BI справится.

Искать победителя в битве ML vs ToG бессмысленно. Слишком разные подходы к решению задачи, со своими плюсами и минусами.

image

Что дальше?

С современным состоянием ИСППР вроде бы разобрались, куда идти дальше?

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

Скорее всего, вангую, через лет 10 мы перестанем жестко хардкодить модели, и начнем вместо этого повсеместно обучать компьютеры в создаваемых симулируемых средах. Наверное, по этому пути и пойдет реализация ИСППР — по пути AI и прочих скайнетов и WAPR'ов.

Если же посмотреть на более близкую перспективу, то будущее ИСППР за гибкостью решений. Ни один из предложенных способов (классические модели, машинное обучение, DL, теория игр) не универсален с точки зрения эффективности для всех задач. В хорошей СППР должны сочетаться все эти инструменты + RPA, при этом разные модули должны использоваться под разные задачи и иметь разные интерфейсы вывода для разных пользователей. Этакий коктейль, смешанный, но ни в коем случае не взболтанный.

image

Литература

  1. Merkert, Mueller, Hubl, A Survey of the Application of Machine Learning in Decision Support Systems, University of Hoffenhaim 2015
  2. Tariq, Rafi,Intelligent Decision Support Systems- A Framework, India, 2011
  3. Sanzhez i Marre, Gibert, Evolution of Decision Support Systems, University of Catalunya, 2012
  4. Ltifi, Trabelsi, Ayed, Alimi, Dynamic Decision Support System Based on Bayesian Networks, University of Sfax, National School of Engineers (ENIS), 2012

понедельник, 28 мая 2018 г.

Гиацинты / Фото из личного архива


Фото: Николай Мацапула

Смотри также:

Java и Project Reactor. Эпизод 2 / Блог компании FunCorp / Хабр / Программирование на Java

Привет! Удивительно, но первая часть статьи даже кому-то понравилась.
Отдельное спасибо за ваши отзывы и комментарии. У меня для вас
плохая хорошая новость: нам ещё есть о чём поговорить! А если точнее, то о некоторых деталях работы Reactor.

Я отрекаюсь от магии

Для дальнейшего углубления в Reactor не будет лишним описать некоторые принципы его работы. Что же тщательно скрывается от нас за внешним слоем из Flux и Mono?


Официальная документация предлагает сравнивать Reactor с конвейером. Publisher выдаёт какие-то данные (материалы). Данные идут по цепочке из операторов (конвейерной ленте), обрабатываются, в конце получается готовый продукт, который передаётся в нужный Consumer/Subscriber и употребляется уже там.

Как работают операторы Reactor?
Рецепт усреднённый, потому что вариаций масса.Попытаемся дать грубое описание.

У каждого оператора есть какая-то
тактика реализация в виде объекта. Вызов оператора у Flux/Mono возвращает объект, реализующий этот оператор. Например, вызов flatMap() вернёт объект типа FluxFlatMap (наследник Flux).

Т.е. оператор — это Publisher, который, помимо какой-то своей логики, содержит ссылку на исходный (source) Publisher, к которому применяется. Вызовы операторов создают цепочку из Publisher.

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

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

Schedulers | Планировщики


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

Модель исполнения и её детали определяются имплементацией интерфейса Scheduler (т.е. планировщика). Есть статические методы для ряда случаев жизни, позволяющие указать контекст выполнения:

  • Schedulers.immediate(). Выполнение будет происходить в текущем потоке;
  • Schedulers.single(). Выполнение в выделенном потоке. Осторожно! Он и в самом деле single, обращение не создаст новый scheduler/поток, а вернёт кешированное значение. Для создания выделенного потока/scheduler на каждый вызов используйте Schedulers.newSingle();
  • Schedulers.elastic(). Уже упоминался в прошлой статье. Выполнение задач списывает на workers (работяг, «воркеров»), которых сам же и создаёт. В случае idle (бездействия) worker прибивается. В качестве воркера выступает ExecutorService. Используется для блокирующих задач, например I/O. По умолчанию — unbounded, если нужно ограничение на количество воркеров — используйте Schedulers.newElastic();
  • Schedulers.parallel(). N воркеров, оптимизированных для параллельной работы. По умолчанию N = количеству доступных ядер, т.е. Runtime.getRuntime().availableProcessors(). Осторожно! Внутри Docker этот метод может нагло вам врать.

Стоит отметить, что коробочные Schedulers.single() и Schedulers.parallel() выбрасывают IllegalStateException при попытке запустить в них блокирующий оператор: block(), blockLast(), toIterable(), toStream(). Такое нововведение появилось в релизе 3.1.6.

Если всё-таки хотите заниматься подобными извращениями — используйте Shchedulers.newSingle() и Schedulers.newParallel(). Но лучшей практикой для блокирующих операторов считается использование Schedulers.elastic() или Schedulers.newElastic().

Экземпляры Scheduler так же можно инициализировать из ExecutorService с помощью Schedulers.fromExecutorService(). Из Executor тоже можно, но не рекомендуется.

Некоторые операторы из Flux и Mono запускаются сразу на конкретном Scheduler (но можно передать и свой). К примеру, уже знакомый Flux.interval() по умолчанию запускается на Schedulers.parallel().

Flux.interval(Duration.ofMillis(300), Schedulers.newSingle("test"))

Контекст исполнения


Как же сменить контекст исполнения? Нужно прибегнуть к одному из уже знакомых нам операторов:

  • publishOn();
  • subscribeOn().

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

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

В случае с subscribeOn оператор «глобальный», срабатывает сразу на всю цепочку Subscriber. После вызова subscribe() контекстом выполнения будет указанный Scheduler. Далее контекст может изменяться с помощью оператора publishOn. Последующие вызовы subscribeOn игнорируются.


Flux.just("a", "b", "c") 
      
      .doOnNext(v -> System.out.println("before publishOn: " + Thread.currentThread().getName()))
      .publishOn(Schedulers.elastic())
      
      .doOnNext(v -> System.out.println("after publishOn: " + Thread.currentThread().getName()))
      .subscribeOn(Schedulers.parallel())
      .subscribe(v -> System.out.println("received " + v + " on " + Thread.currentThread().getName()));

Thread.sleep(5000);

выведет следующий результат:

before publishOn: parallel-1
before publishOn: parallel-1
after publishOn: elastic-2
before publishOn: parallel-1
received a on elastic-2
after publishOn: elastic-2
received b on elastic-2
after publishOn: elastic-2
received c on elastic-2

Обработка ошибок


В Reactor исключения воспринимаются как terminal event (терминальное событие).
Если где-то произошло исключение, значит, что-то пошло не так, наш конвейер останавливается, а ошибка прокидывается до финального Subscriber и его метода onError.

Любимая картинка

Почему так? Reactor не знает о серьёзности возникшего исключения и понятия не имеет, что с ним делать. Подобные ситуации должны как-то обрабатываться на уровне приложения. Для этого у Subscriber есть прекрасный метод onError(). Reactor вынуждает нас его переопределять и как-то реагировать на исключение, в противном случае мы будем получать UnsupportedOperationException при ошибках.


Философия try/catch


Что обычно делается внутри catch-блока в Java? Ну, не считая всеми любимых пустых catch-блоков.

  1. Static Fallback Value. Вернуть какое-то статическое значение по умолчанию:
    try {
       return fromRemoteAndUnstableSource();
    } catch(Throwable e) {
       return DEFAULT_VALUE;
    }
  2. Fallback Method. Вызов альтернативного метода в случае ошибки:

    try {
       return fromRemoteAndUnstableSource();
    } catch(Throwable e) {
       return loadValueFromCache();
    }
  3. Dynamic Fallback Value. Вернуть какое-то динамическое значение в зависимости от исключения:

    try {
       return fromRemoteAndUnstableSource();
    } catch(Throwable e) {
       if (e instanceof TimeoutException) {
           return loadValueFromCache();
       }
       return DEFAULT_VALUE;
    }
  4. Catch and Rethrow. Обернуть в какое-то доменное исключение и пробросить исключение дальше:

    try {
       return fromRemoteAndUnstableSource();
    } catch(Throwable e) {
       throw new BusinessException(e);
    }
  5. Log or React on the Side. Залогировать ошибку и пробросить исключение дальше:

    try {
       return fromRemoteAndUnstableSource();
    } catch(Throwable e) {
       logger.error(e.getMessage(), e);
       throw e;
    }
  6. Using Resources and the Finally Block. Освобождение ресурсов в finally-блоке или с помощью try-with-resources.
    try {
       return fromRemoteAndUnstableSource();
    } catch(Throwable e) {
       
    } finally {
       cleanAllStuff();
    }

Приятная новость: всё это есть в Reactor в виде эквивалентных операторов.

Менее приятная новость: в случае ошибки ваша прекрасная последовательность данных всё равно завершится (terminal event), несмотря на оператора обработки ошибок.
Подобные операторы используются скорее для создания новой, резервной (fallback) последовательности на замену завершившейся.

Приведём пример:

Flux<String> s = Flux.range(1, 10)
   .map(v -> doSomethingDangerous(v))
   .map(v -> doSecondTransform(v));
s.subscribe(value -> System.out.println("RECEIVED " + value), error -> System.err.println("CAUGHT " + error));

Можно сравнить это с похожим блоком try / catch:

try {
   for (int i = 1; i < 11; i++) {
       String v1 = doSomethingDangerous(i);
       String v2 = doSecondTransform(v1);
       System.out.println("RECEIVED " + v2);
   }
} catch (Throwable t) {
   System.err.println("CAUGHT " + t);
}

Обратите внимание: for прерывается!

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

Flux<String> flux = Flux.interval(Duration.ofMillis(250))
   .map(input -> {
       if (input < 3) return "tick " + input;
       throw new RuntimeException("boom");
   })
   .onErrorReturn("Uh oh");

flux.subscribe(System.out::println);
Thread.sleep(2100);

На экране получим:

tick 0
tick 1
tick 2
Uh oh

Реализация try/catch

Static Fallback Value

Используя оператор onErrorReturn:

Flux.just(10)
   .map(this::doSomethingDangerous)
   .onErrorReturn("RECOVERED");

Можно добавить предикат, чтобы оператор выполнялся не для всех исключений:

Flux.just(10)
   .map(this::doSomethingDangerous)
   .onErrorReturn(e -> e.getMessage().equals("boom10"), "recovered10");

Fallback Method

Используя оператор onErrorResume,

Flux.just("key1", "key2")
   .flatMap(k -> callExternalService(k)) 
   .onErrorResume(e -> getFromCache(k)); 

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

Flux.just("timeout1", "unknown", "key2")
   .flatMap(k -> callExternalService(k))
   .onErrorResume(TimeoutException.class, getFromCache(k))
   .onErrorResume((Predicate<Throwable>) error -> error instanceof UnknownKeyException, registerNewEntry(k, "DEFAULT"));

Аналогично:

Flux.just("timeout1", "unknown", "key2")
   .flatMap(k -> callExternalService(k))
   .onErrorResume(error -> {
       if (error instanceof TimeoutException)
           return getFromCache(k);
       else if (error instanceof UnknownKeyException) 
           return registerNewEntry(k, "DEFAULT");
       else
           return Flux.error(error);
   });

Dynamic Fallback Value

Всё тот же onErrorResume:

erroringFlux.onErrorResume(error -> Mono.just(
   myWrapper.fromError(error); 
));

Catch and Rethrow


Можно сделать двумя способами. Первый — с оператором onErrorResume:

Flux.just("timeout1")
   .flatMap(k -> callExternalService(k))
   .onErrorResume(original -> Flux.error(
       new BusinessException("oops, SLA exceeded", original)
   );

И более лаконично — с помощью onErrorMap:

Flux.just("timeout1")
   .flatMap(k -> callExternalService(k))
   .onErrorMap(original -> new BusinessException("oops, SLA exceeded", original));

Log or React on the Side

Добавить какой-то side effect (метрики, логирование) можно с помощью оператора doOnError

LongAdder failureStat = new LongAdder();
Flux<String> flux = Flux.just("unknown")
   .flatMap(k -> callExternalService(k))
   .doOnError(e -> {
       failureStat.increment();
       log("uh oh, falling back, service failed for key " + k);
   })
   .onErrorResume(e -> getFromCache(k));

Using Resources and the Finally Block

Итак, как же получить аналог try-with-resources или блок finally? На выручку нам приходит оператор Flux.using().

Для начала нужно ознакомиться с интерфейсом Disposable. Он заставляет нас реализовать метод dispose(). Вызов этого метода должен отменять или завершать какую-то задачу или последовательность. Вызовы метода должны быть идемпотентными. Использованные ресурсы должны быть освобождены.

AtomicBoolean isDisposed = new AtomicBoolean();
Disposable disposableInstance = new Disposable() {
   @Override
   public void dispose() {
       isDisposed.set(true);
   }

   @Override
   public String toString() {
       return "DISPOSABLE";
   }
};

Flux<String> flux = Flux.using(
       () -> disposableInstance,  
       disposable -> Flux.just(disposable.toString()), 
       Disposable::dispose  
);

Повторение | Retrying

При повторе (retry) наблюдается похожее поведение, оригинальная последовательность завершается (terminate event), мы повторно подписываемся (re-subscribing) на Flux.

Разберём на примере. Код

Flux.interval(Duration.ofMillis(250))
   .map(input -> {
       if (input < 3) return "tick " + input;
       throw new RuntimeException("boom");
   })
   .elapsed()
   .retry(1)
   .subscribe(System.out::println, System.err::println);

Thread.sleep(2100);

выведет

259,tick 0
249,tick 1
251,tick 2
506,tick 0
248,tick 1
253,tick 2
java.lang.RuntimeException: boom

Более сложная логика повторов доступна с использованием оператора retryWhen().

Заключение

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

Подведём итоги:
  • контекстом выполнения можно манипулировать с помощью операторов publishOn, subscribeOn и Schedulers;
  • для обработки исключительных ситуаций есть множество операторов на все случаи жизни;
  • посылание terminate signal приводит к завершению оригинальной «последовательности»;
  • для освобождения ресурсов используется интерфейс Dispose.
Спасибо за внимание!

Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.
Источник: https://habr.com/company/funcorp/blog/359194/?utm_source=habrahabr&utm_medium=rss&utm_campaign=359194

Смотри также:

Зачем нужна Java. http://fetisovvs.blogspot.com/2014/07/java.html
Разбор основных концепций параллелизма. http://fetisovvs.blogspot.com/2018/04/java.html

Первый контакт с «var» в Java 10. http://fetisovvs.blogspot.com/2018/01/var-java-10-java.html
JAVA 9. Что нового? http://fetisovvs.blogspot.com/2017/10/java-9-java.html
Концепции объектно-ориентированного программирования — ООП в Java. http://fetisovvs.blogspot.com/2017/01/java-java.html
Анимации в Android по полочкам (Часть 1. Базовые анимации). http://fetisovvs.blogspot.com/2018/02/android-1-java.html
Двести пятьдесят русскоязычных обучающих видео докладов и лекций о Java. http://fetisovvs.blogspot.com/2015/12/java-5-java-java.html
Абстрактные классы и методы. http://fetisovvs.blogspot.com/2017/02/java.html
Полное руководство по Java Reflection API. Рефлексия на примерах. http://fetisovvs.blogspot.com/2017/02/java-reflection-api-java.html
Микросервисы для Java программистов. Практическое введение во фреймворки и контейнеры. http://fetisovvs.blogspot.com/2017/10/java-java.html
Микросервисы для Java программистов. Практическое введение во фреймворки и контейнеры. (Часть 3). http://fetisovvs.blogspot.com/2017/10/java-3-java.html
ТОП-3 способа конвертировать массив в ArrayList. Пример на Java. http://fetisovvs.blogspot.com/2016/09/3-arraylist-java-java.html
Ввод–вывод в Java. http://fetisovvs.blogspot.com/2016/05/java-java_28.html
Enum-Всемогущий. http://fetisovvs.blogspot.com/2017/02/enum-java.html
Массивы в Java. Создание и обработка. http://fetisovvs.blogspot.com/2017/10/java-java_18.html
Arrays, Collections: Алгоритмический минимум. http://fetisovvs.blogspot.com/2017/12/arrays-collections.html
Популярные методы для работы с Java массивами. http://fetisovvs.blogspot.com/2016/09/java-java_29.html
Пример использования метода replace в Java. Как заменить символ в строке? http://fetisovvs.blogspot.com/2017/01/replace-java-java.html
Класс Scanner в Java — описание и пример использования. http://fetisovvs.blogspot.com/2017/01/scanner-java-java.html
Пример использования метода trim в Java: как удалить пробелы в начале и конце строки? http://fetisovvs.blogspot.com/2017/01/trim-java-java.html
Spark — Потрясающий веб-микрофреймворк для Java. http://fetisovvs.blogspot.com/2017/10/spark-java-java.html
Чтение и запись CSV файла с помощью SuperCSV. http://fetisovvs.blogspot.com/2017/01/csv-supercsv-java-java.html
Конструкция try/catch/finally (исключения). http://fetisovvs.blogspot.com/2017/01/trycatchfinally-java.html
1000+ часов видео по Java на русском. http://fetisovvs.blogspot.nl/2017/06/1000-java-java.html
Шпаргалка Java программиста 7.1 Типовые задачи: Оптимальный путь преобразования InputStream в строку. http://fetisovvs.blogspot.com/2016/04/java-71-inputstream-java.html
Шпаргалки Java программиста 10: Lombok. http://fetisovvs.blogspot.nl/2017/12/java-10-lombok-java.html
Шпаргалки Java программиста 9: Java SE — Шпаргалка для собеседований и повторений. http://fetisovvs.blogspot.com/2017/12/java-9-java-se-java.html
Шпаргалка Java программиста 8. Библиотеки для работы с Json (Gson, Fastjson,
LoganSquare, Jackson, JsonPath и другие). http://fetisovvs.blogspot.com/2016/04/java-8-json-gson-fastjson-logansquare.html
Java 8 и паттерн Стратегия. http://fetisovvs.blogspot.com/2018/03/java-8-java.html
Реализация ООП-наследования в классах, работающих с SQL и MS Entity Framework. http://fetisovvs.blogspot.com/2017/02/sql-ms-entity-framework.html
Как установить соединение с СУБД MySQL в IntelliJ IDEA в редакции Community. http://fetisovvs.blogspot.com/2016/04/mysql-intellij-idea-community-java.html
Как с помощью maven работать с библиотеками, которых в maven нет. http://fetisovvs.blogspot.com/2017/03/maven-maven-java.html
Проекты по созданию компиляторов из Java в JavaScript и исполняемые файлы. http://fetisovvs.blogspot.com/2018/01/java-javascript-java.html
Диагностика утечек памяти в Java. http://fetisovvs.blogspot.com/2017/03/java-java_18.html
Spring AOP и JavaConfig в плагинах для Atlassian Jira. http://fetisovvs.blogspot.com/2018/04/spring-aop-javaconfig-atlassian-jira.html
Блеск и нищета Java для настольных систем. http://fetisovvs.blogspot.com/2018/04/java-haulmont-java.html
Разбор задачек от Одноклассников на JPoint 2018. http://fetisovvs.blogspot.com/2018/04/jpoint-2018-java.html
Программируем… выход из лабиринта. http://fetisovvs.blogspot.com/2015/10/java.html
Основы работы с IntelliJ IDEA. Интерфейс программы. http://fetisovvs.blogspot.com/2016/09/intellij-idea-java.html
Ускоряем время сборки и доставки java web приложения. http://fetisovvs.blogspot.com/2018/03/java-web-java.html