Гибридная ИТ-инфраструктура: как прикрутить облака к реальности?

Маркетинговые описания облаков обещают много благ: эти технологии оптимизируют расходы на ИТ, трансформируют инфраструктуру, делают ее более адаптивной. И это — чистая правда. Вот только на практике получить преимущества от облачных технологий в «кровавом Enterprise» оказывается непросто. Почему? Крупные компании при смене платформы чаще всего тянут в облака привычные им подходы и ставят перед своей ИТ-инфраструктурой те же самые задачи — надежная защита критически важных данных, обеспечение высокой производительности для информационных систем. И чаще всего мечты об облаках налетают на суровую реальность и разбиваются вдребезги. Так как же подружить современные тенденции и строгие стандарты крупных корпораций? 

Самый простой и надежный способ выполнить требования бизнеса — полная репликация всех данных с созданием отказоустойчивых конфигураций. Богатые компании чаще всего создают две абсолютно симметричные инфраструктуры в разных ЦОД (или даже строят одинаковыми сами ЦОД) и поверх этой «растянутой» инфраструктуры сразу в двух дата-центрах работают приложения. Случится сбой, а бизнес не почувствует его: виртуальные машины перезапустятся, кластера баз данных переключатся на выжившую половину, и «шестеренки» продолжат крутиться.

Рис. 1. Классическая DR-инфраструктура на базе двух ЦОД
Рис. 1. Классическая DR-инфраструктура на базе двух ЦОД

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

Может не надо так сильно Active?

А откуда вообще появляется потребность в растянутой ИТ-инфраструктуре и резервировании систем в двух ЦОД по модели Active-Active?

На заре времен, когда приложения писались на уже давно забытых языках, очень мало кто задумывался о катастрофоустойчивости и масштабировании системы за пределы серверной комнаты. Но именно оттуда нам по наследству досталось множество ИТ-систем, в своих архитектурах не предполагающих каких-либо излишеств, кроме разделения на уровень Front-End (Web server, App server) и Back-End (DB server). С годами пользователей таких систем становилось все больше, росла их важность для бизнеса, а вместе с этим — и требования к их надежности. Так свет увидели отказоустойчивые кластеры, кластеры виртуализации и, наконец, решения, которые вообще могли спрятать от неповоротливых старичков (или, как их еще называют, Legacy-систем), что там находится ниже уровня операционной системы.

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

Это в теории. Почти в каждой большой компании с историей в продуктиве есть старые Legacy-приложения и новые, причем разной степени модности. А кроме того, эти приложения реализуют разные сервисы, некоторые очень важные — так называемые «золотые коровы» (процессинг в банке, биллинг в телекоме, CRM в ритейле и т.д.) и неважные, вплоть до неиспользуемых и всеми забытых. Так вот именно здесь и кроется ответ на вопрос «А нужно ли строить дорогую ИТ-инфраструктуру?»

Место облаку

Большому кораблю — большое плавание. Эта поговорка хорошо описывает правильный подход к выбору ИТ-архитектуры для разных типов приложений. Дорогие способы организации надежности можно и нужно применять для «золотых коров», особенно если они еще и Legacy, если остановка системы приведет к проблемам в работе важного бизнес-процесса, приносящего деньги или предотвращающего убытки. Ну, или когда приложение очень требовательно к производительности и пожертвовать ею слишком рискованно.

А что же с остальными? Для них можно выбрать что-то простое, не такое дорогое и более гибкое. Требования-то ниже, и есть простор для маневра. Здесь как раз и напрашивается идея использования облаков как инструмента оптимизации расходов на ИТ-инфраструктуру.

Катастрофоустойчивость в облаке и не только

Очень часто я сталкиваюсь с мнением, что перенос ИТ-инфраструктуры в облако сам по себе решает задачу защиты систем от отказа ЦОД. Это заблуждение и опасная иллюзия. И породила ее не алчность сервис-провайдеров (они никогда такого не утверждали), а человеческая склонность к упрощению. Налицо ошибка мышления: если мы чего-то не видим, то этого и нет. В публичном облаке мы не знаем, на каком железе работают виртуальные серверы, но это не значит, что отказывать там нечему. Сервисы в облаке горят так же хорошо, как и в любом другом ЦОД, с такими же последствиями, и недавний пожар в ЦОД во Франции тому подтверждение.

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

Рис. 2. DR-инфраструктура, использующая как ресурсы собственных ЦОД, так и ресурсы публичных облаков
Рис. 2. DR-инфраструктура, использующая как ресурсы собственных ЦОД, так и ресурсы публичных облаков

Как это делается? У нас есть виртуальные серверы, для которых со стороны бизнес-процессов нет требований быстрого рестарта в случае гибели ЦОД. Некритичные сервисы могут и полежать. То есть мы можем держать их в РЦОД в выключенном виде и переносить туда не синхронной репликой, а любой из возможных технологий репликации виртуальных машин на основе snapshot-ов (Snapshot shipping), и запускать по необходимости. При этом ничто не мешает держать такие «холодные» виртуалки в публичном облаке и не резервировать под них собственное железо. У многих сервис-провайдеров даже есть дешевые тарифы (модель Pay as you Go), которые предполагают использование вычислительных ресурсов не более N часов в год. Удобно ведь? РЦОД есть, а платить за него нужно «по факту». Безусловно, тут есть свои подводные камни: и организационно, и технически такое решение сложнее, чем Active-Active. Но финансовая выгода обычно такова, что игра стоит свеч.

В тоже время только ради DR городить гибридные ИТ-инфраструктуры не совсем логично и рентабельно. Вторым шагом использования облаков в Enterprise может стать возможность быстрого получения вычислительных ресурсов под разработку и тестирование новых продуктов. Далеко не каждый результат труда группы разработки увидит свет продуктив, так зачем тратить время и деньги, закупая железо, если можно получить виртуальные серверы в том же облаке, погонять новый продукт в разных конфигурациях и «схлопнуть» ресурсы в случае необходимости. Закупать железо на постоянку можно, когда на руках окажутся результаты тестов.

А что дальше?

Как получить выгоду от возможности облаков выдавать ресурсы по запросу, мы поговорили. Но современные публичные облака — это не магический куб с ресурсами, которые хорошо покупать кусочками по арендной схеме. Сервис-провайдеры предлагают целый набор различных ИТ-услуг: от банального администрирования «переехавших» в облако ОС и приложений до доступа к готовым сервисам вроде СУБД, СРК, SDN и т. д. Но могут ли они принести реальную пользу компаниям с ИТ-инфраструктурой Enterprise-уровня?

Рис. 3. Гибридная ИТ-инфраструктура.
Рис. 3. Гибридная ИТ-инфраструктура.

Когда мы в самом начале поста говорили про решение «для богатых», забыли упомянуть важную вещь, а именно задачу хранения резервных копий (РК). В идеологии Active-Active все резервные копии всех систем нужно не только хранить в ЦОД, где эти системы живут, но и обязательно дублировать на вторую площадку. Иначе в случае потери ЦОД с бэкапами вторая площадка окажется без защиты от логических ошибок на уровне данных, а значит, каждый день жизни в РЦОД будет грозить потерей данных в невероятных объемах.

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

Тут снова применимы облака со своими дешевыми облачными хранилищами. На ресурсах сервис-провайдера действительно можно создать огромные хранилища, стоимость хранения в которых будет очень низкой. Обычной компании такие объемы будут просто не нужны. Чаще всего такие хранилища построены на технологии S3, относительно новой, но прекрасно поддерживаемой основными решениями для резервного копирования. Все, что нужно — арендовать необходимый объем и прописать ссылку в корпоративной СРК. Многие возразят, что доступ к РК через интернет медленный, что сервис-провайдер не гарантирует скорость восстановления. Да, но речь идёт о бэкапах нужных, но неважных сервисов. Важное и срочное хранится в собственном ЦОД.

Минус полкластера

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

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

  • Тип 1 — самые требовательные к инфраструктуре и ее надежности системы. Размещаются только на On-Premises решениях, обеспечивающих синхронную репликацию и быстрое восстановление.   

  • Тип 2 — содержат в своей инфраструктуре требовательные компоненты (СУБД). СУБД размещается On-Premises, а сервера приложений переносятся в IaaS.    

  • Тип 3 — целиком располагаются на виртуальных машинах и не предъявляют особых требований к надежности и доступности. Все компоненты размещаются в IaaS.    

  • Тип 4 — Core Infrastructure Service, такие как DNS, сервисы двухфакторной аутентификации, Active Directory и пр. Они должны быть развернуты во всех средах, без вариантов. Обеспечивают базовый функционал для ИТ-систем и On-Premises, и в облаке.

В результате оказалось, что систем первого типа в инфраструктуре всего 15%, а значит, можно смело резервировать большую часть компонентов в облаке. 

Когда все это разложили на виртуальные машины, в облака могли переселиться больше половины ВМ (61%). Получилось сэкономить 50% бюджета на создание Active-Active кластера между двумя ЦОД, а для остальных — построить вполне себе рабочую Active-Passive конфигурацию с холодным резервированием и скриптами для автоматизированного запуска «по кнопке».

Другой пример — снижение стоимости off-site хранения резервных копий в одном крупном банке. Как и у многих других компаний, исторически для длительного хранения (более 4-х недель) использовались дополнительные ленточные библиотеки, установленные за пределами основных дата-центров. По политикам резервного копирования отдельные резервные копии после записи в основное хранилище дублировались на отдельную площадку и записывались на ленты. С учетом объемов поддержка такой инфраструктуры хранения оборачивалась огромными затратами, так как резервные копии должны были быть доступны и их нельзя было хранить вне библиотеки. К тому же сами библиотеки требовали постоянной модернизации, а ленточные накопители — замены.

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

Путь до облака

Безусловно, идеальный облачный путь — пересоздать свои ИТ-системы «с нуля» в этом самом облаке. Сразу построить их так, чтобы они изначально имели сloud-native архитектуру и основывались не на устаревших принципах, а правильно использовали доступные в облаках сервисы и технологии.

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

Рис. 4. Дорожная карта модернизации ИТ-инфраструктуры
Рис. 4. Дорожная карта модернизации ИТ-инфраструктуры

Вот основные шаги на пути трансформации своей инфраструктуры.

Шаг 1. Провести работы для полноценной классификации информационных систем. Нужно собрать данные по всем компонентам инфраструктуры, определить реальные RTO/RPO для каждой системы, подготовить ресурсно-сервисные модели, чтобы корректно оценить количество ресурсов, необходимое для каждого класса критичности, подготовить списки оборудования, которое можно переиспользовать (например, перенести в РЦОД), а какое нужно закупить. Когда на руках появится вся необходимая информация, можно будет понять реальные возможности по использованию облачных сред и принять экономически обоснованное решение о дальнейшем развитии.

Шаг 2. Выбрать правильного поставщика услуг IaaS/PaaS/SaaS, то есть сформировать ТЗ с учетом потребности «периодического» использования арендуемых ресурсов, договориться об условиях резервирования и запуска ВМ в случае аварийного восстановления. Также важно сразу понять, какие готовые сервисы может предоставить тот или иной провайдер, и как они реализованы.

Шаг 3. Мигрировать в целевую инфраструктуру те ИС, которые по итогам обследования оказались не на своих местах. Здесь главное — подготовить планы миграции, создать Active-Active инфраструктуру для наиболее требовательных систем, максимально использовать существующее оборудование из всех ЦОД, перенести все компоненты систем на соответствующие им платформы.

Шаг 4. Продолжить развитие гибридной инфраструктуры. Когда основные сервисы будут правильно размещены и образуется тесная и надежная связь собственной инфраструктуры и облачной, можно продолжить расширять интеграцию, быстро и легко подключать новые сервисы, предоставить разработчикам больше свободы и возможностей. Не стоит бояться сложности гибридной инфраструктуры. Многие задачи эксплуатации успешно решаются при помощи систем автоматизации и управления, реализованных на базе одной из Cloud Management Platform (CMP), например Morpheus.

Автор: Сергей Терехин, ИТ-архитектор «Инфосистемы Джет»

ссылка на оригинал статьи https://habr.com/ru/company/jetinfosystems/blog/565118/

Реактивное программирование со Spring, часть 2 Project Reactor

Это вторая часть серии заметок о реактивном программировании, в которой представлен обзор Project Reactor, реактивной библиотеки, основанной на спецификации Reactive Streams.

1. Введение в Project Reactor

Реактивное программирование поддерживается Spring Framework, начиная с версии 5. Эта поддержка построена на основе Project Reactor.

Project Reactor (или просто Reactor) — это библиотека Reactive для создания неблокирующих приложений на JVM, основанная на спецификации Reactive Streams. Reactor — это основа реактивного стека в экосистеме Spring, и он разрабатывается в тесном сотрудничестве со Spring. WebFlux, веб-фреймворк с реактивным стеком Spring, использует Reactor в качестве базовой зависимости.

1.1 Модули Reactor

Проект Reactor состоит из набора модулей, перечисленных в документации Reactor. Модули встраиваемы и совместимы. Основным артефактом является Reactor Core, который содержит реактивные типы Flux и Mono, которые реализуют интерфейс Publisher Reactive Stream (подробности см. в первом сообщении этой серии) и набор операторов, которые могут применяться к ним.

Некоторые другие модули:

  • Reactor Test — предоставляет некоторые утилиты для тестирования реактивных потоков

  • Reactor Extra — предоставляет некоторые дополнительные операторы Flux

  • Reactor Netty — неблокирующие клиенты и серверы TCP, HTTP и UDP с поддержкой обратного давления — на основе инфраструктуры Netty

  • Reactor Adapter — адаптер для других реактивных библиотек, таких как RxJava2 и Akka Streams

  • Reactor Kafka — реактивный API для Kafka, который позволяет публиковать и получать сообщения в Kafka.

1.2 Настройка проекта

Прежде чем мы продолжим, если вы хотите настроить проект и запустить некоторые из приведенных ниже примеров кода, сгенерируйте новое приложение Spring Boot с помощью Spring Initializr. В качестве зависимости выберите Spring Reactive Web. После импорта проекта в вашу среду IDE взгляните на файл POM, и вы увидите, что добавлена ​​зависимость spring-boot-starter-webflux, которая также внесет зависимость ядра-реактора. Также в качестве зависимости добавлен тест-реактор. Теперь вы готовы к запуску следующих примеров кода.

...	   <dependencies> 		<dependency> 			<groupId>org.springframework.boot</groupId> 			<artifactId>spring-boot-starter-webflux</artifactId> 		</dependency>  		<dependency> 			<groupId>org.springframework.boot</groupId> 			<artifactId>spring-boot-starter-test</artifactId> 			<scope>test</scope> 			<exclusions> 				<exclusion> 					<groupId>org.junit.vintage</groupId> 					<artifactId>junit-vintage-engine</artifactId> 				</exclusion> 			</exclusions> 		</dependency> 		<dependency> 			<groupId>io.projectreactor</groupId> 			<artifactId>reactor-test</artifactId> 			<scope>test</scope> 		</dependency> 	</dependencies> ... 

2. Возможности Reactor Core

Reactor Core определяет реактивные типы Flux и Mono.

2.1 FLUX и MONO

Flux — это Publisher, который может испускать от 0 до N элементов, а Mono может испускать от 0 до 1 элемента. Оба они завершаются либо сигналом завершения, либо ошибкой, и они вызывают методы onNext, onComplete и onError нижестоящего подписчика. Помимо реализации функций, описанных в спецификации Reactive Streams, Flux и Mono предоставляют набор операторов для поддержки преобразований, фильтрации и обработки ошибок.

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

@Test void simpleFluxExample() {     Flux<String> fluxColors = Flux.just("red", "green", "blue");     fluxColors.subscribe(System.out::println); } 

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

2.2 Объединение операторов

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

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

fluxColors.log().subscribe(System.out::println);

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

2020-09-12 16:16:39.779  INFO 6252 --- [           main] reactor.Flux.Array.1                     : | onSubscribe([Synchronous Fuseable] FluxArray.ArraySubscription) 2020-09-12 16:16:39.781  INFO 6252 --- [           main] reactor.Flux.Array.1                     : | request(unbounded) 2020-09-12 16:16:39.781  INFO 6252 --- [           main] reactor.Flux.Array.1                     : | onNext(red) red 2020-09-12 16:16:39.781  INFO 6252 --- [           main] reactor.Flux.Array.1                     : | onNext(green) green 2020-09-12 16:16:39.781  INFO 6252 --- [           main] reactor.Flux.Array.1                     : | onNext(blue) blue 2020-09-12 16:16:39.782  INFO 6252 --- [           main] reactor.Flux.Array.1                     : | onComplete() 

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

fluxColors.log();

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

2.3 Поиск подходящего оператора

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

КАТЕГОРИЯ ОПЕРАТОРА

ПРИМЕРЫ

Создание новой последовательности

just, fromArray, fromIterable, fromStream

Преобразование существующей последовательности

map, flatMap, startWith, concatWith

Заглядывать в последовательность

doOnNext, doOnComplete, doOnError, doOnCancel

Фильтрация последовательности

filter, ignoreElements, distinct, elementAt, takeLast

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

onErrorReturn, onErrorResume, retry

Работаем со временем

elapsed, interval, timestamp, timeout

Расщепление потока

buffer, groupBy, window

Возвращаясь к синхронному миру

block, blockFirst, blockLast, toIterable, toStream

Многоадресная рассылка потока нескольким подписчикам

publish, cache, replay

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

@Test void mapExample() {     Flux<String> fluxColors = Flux.just("red", "green", "blue");     fluxColors.map(color -> color.charAt(0)).subscribe(System.out::println); }

Или оператор zip, который объединяет несколько источников вместе (ожидая, пока все источники испускают один элемент, и объединяет их в кортеж):

@Test void zipExample() {     Flux<String> fluxFruits = Flux.just("apple", "pear", "plum");     Flux<String> fluxColors = Flux.just("red", "green", "blue");     Flux<Integer> fluxAmounts = Flux.just(10, 20, 30);     Flux.zip(fluxFruits, fluxColors, fluxAmounts).subscribe(System.out::println); }

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

Как описано в предыдущем сообщении в блоге, в Reactive Streams ошибки — это терминальные события. При возникновении ошибки вся последовательность останавливается, и ошибка передается методу onError подписчика, который всегда должен быть определен. Если не определено, onError вызовет исключение UnsupportedOperationException.

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

@Test public void onErrorExample() {     Flux<String> fluxCalc = Flux.just(-1, 0, 1)         .map(i -> "10 / " + i + " = " + (10 / i));          fluxCalc.subscribe(value -> System.out.println("Next: " + value),         error -> System.err.println("Error: " + error)); }

Результат будет выглядеть так:

Next: 10 / -1 = -10 Error: java.lang.ArithmeticException: / by zero

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

Метод onErrorReturn будет выдавать резервное значение, когда наблюдается ошибка указанного типа. Это можно сравнить с перехватом исключения и возвратом статического запасного значения в императивном программировании. См. Пример ниже:

@Test public void onErrorReturnExample() {     Flux<String> fluxCalc = Flux.just(-1, 0, 1) 	    .map(i -> "10 / " + i + " = " + (10 / i)) 		  .onErrorReturn(ArithmeticException.class, "Division by 0 not allowed");      fluxCalc.subscribe(value -> System.out.println("Next: " + value), 	    error -> System.err.println("Error: " + error));  }

и результат:

Next: 10 / -1 = -10 Next: Division by 0 not allowed

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

4. Тестирование

Модуль Reactor Test предоставляет служебные программы, которые могут помочь в тестировании поведения вашего Flux или Mono. В этом помогает API StepVerifier. Вы создаете StepVerifier и передаете его издателю для тестирования. StepVerifier подписывается на Publisher при вызове метода verify, а затем сравнивает выданные значения с вашими определенными ожиданиями.

См. следующий пример:

@Test public void stepVerifierTest() {     Flux<String> fluxCalc = Flux.just(-1, 0, 1)         .map(i -> "10 / " + i + " = " + (10 / i));      StepVerifier.create(fluxCalc)         .expectNextCount(1)         .expectError(ArithmeticException.class)         .verify(); }

Для объекта создается StepVerifier, fluxCalc и определяются два ожидания: сначала ожидается, что будет выдана одна String, а затем должна быть выдана ошибка с типом ArithmeticException. С помощью вызова verify StepVerifier начинает подписываться на Flux, и инициируется поток.

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

Модуль Reactor Test также предоставляет другой API, TestPublisher который представляет собой Publisher, которым вы можете напрямую управлять, инициируя события onNext, onComplete и onError для целей тестирования.

5. Модель параллелизма

Как вы, возможно, уже заметили из вывода журнала simpleFluxExample, до сих пор наш издатель выполнялся в основном потоке так же, как подписчик. Это связано с тем, что Reactor не применяет модель параллелизма. Вместо этого выполнение большинства операторов будет продолжено в том же потоке, оставляя выбор за разработчиком. Модель выполнения определяется тем Scheduler, что используется.

Есть два способа переключения контекста выполнения в реактивной цепочке: publishOn и subscribeOn. Отличается следующее:

  • publishOn(Scheduler scheduler) влияет на выполнение всех последующих операторов (если не указано иное)

  • subscribeOn(Scheduler scheduler) изменяет поток, из которого подписывается вся цепочка операторов, на основе самого раннего вызова subscribeOn в цепочке. Это не влияет на поведение последующих вызовов publishOn

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

  • parallel() — Фиксированный пул воркеров, настроенный для параллельной работы, создавая столько воркеров, сколько ядер ЦП.

  • single() — Одиночная многоразовая нить. Этот метод повторно использует один и тот же поток для всех вызывающих, пока Планировщик не будет удален. Если вместо этого вам нужен выделенный поток для каждого вызова, вы можете использовать Schedulers.newSingle () для каждого вызова.

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

  • immediate() — немедленно запускается в исполняемом потоке, не переключая контекст выполнения

  • fromExecutorService(ExecutorService) — может использоваться для создания Планировщика из любого существующего ExecutorService

Выполните следующий пример и посмотрите на поведение:

@Test public void publishSubscribeExample() {     Scheduler schedulerA = Schedulers.newParallel("Scheduler A");     Scheduler schedulerB = Schedulers.newParallel("Scheduler B");     Scheduler schedulerC = Schedulers.newParallel("Scheduler C");              Flux.just(1)         .map(i -> {             System.out.println("First map: " + Thread.currentThread().getName());             return i;         })         .subscribeOn(schedulerA)         .map(i -> {             System.out.println("Second map: " + Thread.currentThread().getName());             return i;         })         .publishOn(schedulerB)         .map(i -> {             System.out.println("Third map: " + Thread.currentThread().getName());             return i;         })         .subscribeOn(schedulerC)         .map(i -> {             System.out.println("Fourth map: " + Thread.currentThread().getName());             return i;         })         .publishOn(schedulerA)         .map(i -> {             System.out.println("Fifth map: " + Thread.currentThread().getName());             return i;         })         .blockLast(); } 

Взглянув на вывод (показан ниже), вы можете увидеть, что первая и вторая операции map выполняются в потоке из планировщика A, поскольку первый subscribeOn в цепочке переключается на этот планировщик, и это влияет на всю цепочку. Перед третьей операцией map выполняется publishOn, переключающий контекст выполнения на Scheduler B, в результате чего третья и четвертая операции map выполняются в этом контексте (поскольку вторая subscribeOn не будет иметь никакого эффекта). И, наконец, есть новый метод publishOn, который переключает обратно на Планировщик A перед последней операцией map.

First map: Scheduler A-4 Second map: Scheduler A-4 Third map: Scheduler B-3 Fourth map: Scheduler B-3 Fifth map: Scheduler A-1

6. BACKPRESSURE (противодавление)

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

В приведенном ниже примере показано, как подписчик может контролировать скорость передачи, вызывая request(n) метод в Subscription.

@Test public void backpressureExample() {     Flux.range(1,5)         .subscribe(new Subscriber<Integer>() {             private Subscription s;             int counter;                          @Override             public void onSubscribe(Subscription s) {                 System.out.println("onSubscribe");                 this.s = s;                 System.out.println("Requesting 2 emissions");                 s.request(2);             }                          @Override             public void onNext(Integer i) {                 System.out.println("onNext " + i);                 counter++;                 if (counter % 2 == 0) {                     System.out.println("Requesting 2 emissions");                     s.request(2);                 }             }              @Override             public void onError(Throwable t) {                 System.err.println("onError");             }              @Override             public void onComplete() {                 System.out.println("onComplete");             }     }); }

Запустите его, и вы увидите, что по запросу одновременно генерируются два значения:

onSubscribe Requesting 2 emissions onNext 1 onNext 2 Requesting 2 emissions onNext 3 onNext 4 Requesting 2 emissions onNext 5 onComplete

В Subscription также есть cancelметод, позволяющий запросить Издателя остановить эмиссию и очистить ресурсы.

7. Холодные и горячие Publisher

Доступны два типа Publisher — cold и hot (холодные и горячие). Пока что мы сосредоточились на холодных Publisher. Как мы заявляли ранее, ничего не происходит, пока мы не подпишемся — но на самом деле это верно только для холодных издателей.

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

Publisher в Reactor по умолчанию не работают. Один из способов создания горячего Publisher — это вызвать publish() метод в Flux. Это вернет ConnectableFlux<T>, у которого есть метод connect() для запуска передачи значений. Подписчики должны затем подписаться на этот ConnectableFlux вместо исходного Flux.

Давайте посмотрим на простой холодный и горячий Publisher, чтобы увидеть различное поведение. В приведенном ниже примере coldPublisherExample оператор interval используется для создания потока, который генерирует значения long, начинающиеся с 0.

@Test public void coldPublisherExample() throws InterruptedException {     Flux<Long> intervalFlux = Flux.interval(Duration.ofSeconds(1));     Thread.sleep(2000);     intervalFlux.subscribe(i -> System.out.println(String.format("Subscriber A, value: %d", i)));     Thread.sleep(2000);     intervalFlux.subscribe(i -> System.out.println(String.format("Subscriber B, value: %d", i)));     Thread.sleep(3000); } 

При запуске будет получен следующий результат:

Subscriber A, value: 0 Subscriber A, value: 1 Subscriber A, value: 2 Subscriber B, value: 0 Subscriber A, value: 3 Subscriber B, value: 1 Subscriber A, value: 4 Subscriber B, value: 2

Теперь вы можете задаться вопросом, почему что-то происходит, когда основной поток спит, но это потому, что оператор интервала по умолчанию выполняется в планировщике Schedulers.parallel(). Как видите, оба подписчика получат значения, начинающиеся с 0.

Теперь давайте посмотрим, что происходит, когда мы используем ConnectableFlux:

@Test public void hotPublisherExample() throws InterruptedException {     Flux<Long> intervalFlux = Flux.interval(Duration.ofSeconds(1));     ConnectableFlux<Long> intervalCF = intervalFlux.publish();     intervalCF.connect();     Thread.sleep(2000);     intervalCF.subscribe(i -> System.out.println(String.format("Subscriber A, value: %d", i)));     Thread.sleep(2000);     intervalCF.subscribe(i -> System.out.println(String.format("Subscriber B, value: %d", i)));     Thread.sleep(3000); }

На этот раз мы получаем следующий результат:

Subscriber A, value: 2 Subscriber A, value: 3 Subscriber A, value: 4 Subscriber B, value: 4 Subscriber A, value: 5 Subscriber B, value: 5 Subscriber A, value: 6 Subscriber B, value: 6

Как мы видим, на этот раз ни один из подписчиков не получает исходные значения 0 и 1. Они получают значения, которые отправляются после подписки. Вместо того, чтобы вручную запускать публикацию, с помощью этого autoConnect(n)метода также можно настроить ConnectableFlux так, чтобы он запускался после n подписок.

8. Прочие возможности

8.1 Завершение синхронного, блокирующего вызова

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

Mono blockingWrapper = Mono.fromCallable(() -> {      return /* make a remote synchronous call */  }); blockingWrapper = blockingWrapper.subscribeOn(Schedulers.boundedElastic()); 

Метод fromCallable создает Mono, который производит его значение с помощью прилагаемого Callable. Используя Schedulers.boundedElastic(), мы гарантируем, что каждая подписка выполняется на выделенном однопоточном работнике, не влияя на другую неблокирующую обработку.

8.2 Контекст

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

Reactor имеет функцию, которая в некоторой степени сравнима с ThreadLocal, но может применяться к Flux или Mono вместо Thread, называемая a Context. Это интерфейс, похожий на Map, где вы можете хранить пары ключ-значение и получать значение по его ключу. Контекст прозрачно распространяется по всему реактивному конвейеру и может быть легко доступен в любой момент, вызвав метод Mono.subscriberContext().

Контекст может быть заполнен во время подписки путем добавления вызова метода subscriberContext(Function) или subscriberContext(Context) метода в конце вашего реактивного конвейера, как показано в методе тестирования ниже..

8.3 SINKS

Rector также предлагает возможность создавать Flux или Mono, программно определяя события onNext, onError и onComplete. Для этого предоставляется так называемый API-интерфейс приемника, запускающий события. Существуют несколько различных вариантов раковин, чтобы узнать больше об этом, читайте далее в справочной документации: Программное создание последовательности

8.4 Отладка

Отладка реактивного кода может стать проблемой из-за его функционального декларативного стиля, в котором фактическое объявление (или «assembly ») и обработка сигнала («execution») не происходят одновременно. Обычная трассировка стека Java, генерируемая приложением Reactor, не будет включать никаких ссылок на ассемблерный код, что затрудняет определение фактической основной причины распространенной ошибки.

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

Для использования в производственной среде Project Reactor предоставляет отдельный Java-агент, который инструментирует ваш код и добавляет отладочную информацию, не требуя больших ресурсов для захвата трассировки стека при каждом вызове оператора. Чтобы использовать его, вам нужно добавить reactor-tools артефакт в свои зависимости и инициализировать его при запуске приложения Spring Boot:

public static void main(String[] args) {     ReactorDebugAgent.init();     SpringApplication.run(Application.class, args); }

8.5 Метрики

Reactor предоставляет встроенную поддержку для включения и отображения показателей как для планировщиков (Schedulers), так и для издателей (Publishers). Дополнительные сведения см. в разделе «Метрики» Справочного руководства.

9. Подводя итог…

В этом сообщении в блоге представлен обзор Project Reactor, в основном сосредоточенный на функциях Reactor Core. Следующий блог в этой серии будет о WebFlux — реактивном фреймворке Spring, который использует Reactor в качестве реактивной библиотеки!

Ссылки

Project Reactor

Spring Web Reactive Framework

Reactor Debugging Experience

Flight of the Flux 1 — Assembly vs Subscription

ссылка на оригинал статьи https://habr.com/ru/post/565050/

Пора запретить рекламу, основанную на слежке

image

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

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

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

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

Норвежский Совет Потребителей делает правильный шаг

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

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

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

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

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

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

Угроза становится достаточно серьёзной, чтобы оправдать запрет на использование подобной практики.

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

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

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

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

Vivaldi всегда выступал против методов, основанных на слежке

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

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

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

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

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

Это просто неправильно.

Гиганты делают миллиарды, общество за это платит

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

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

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

Не верьте, что «это единственный способ»

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

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

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

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

Мы можем вылечить интернет

Неужели есть надежда?

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

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

Точно так же реклама, основанная на слежке за пользователями, опасна для здоровья интернета и всего общества.

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

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

Это безумие должно прекратиться

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

Осознавая, что пользователи все больше задумываются о приватности, гиганты становятся все более креативными, чтобы не допустить пересыхания широкого потока данных. Например, такие инициативы, как FLOC от Google, которая позиционирует себя как революционная «технология конфиденциальности», а на самом деле разработанная для сбора пользовательских данных в интересах Google — и для обхода настроек браузера, которые могут помешать этому.

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

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

ссылка на оригинал статьи https://habr.com/ru/company/vivaldi/blog/565422/

Менеджер качества, или как не спалить лоу-энд девайсы ультра-графикой

Всем привет, сегодня мы расскажем о том, как мы делим качества и какие инструменты для этого используем в проекте War Robots.

Релиз War Robots состоялся еще в 2014 году, и за 7 лет существования проекта графическая часть в нем постоянно развивалась. Но в то же время команда постоянно сталкивалась с ограничениями из-за минимальных требований к девайсам. Оперируя таким большим проектом, у которого немало устройств входит в low-end сегмент, нельзя просто взять и запилить крутой современный графен, не потеряв при этом часть аудитории.

Так у нас появилась задача: сделать всем красиво и хорошо. Поэтому мы решили делать War Robots Remastered — с блэк-джеком, обновлением графического пайплайна и разделением ассетов на разные качества.

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

На тот момент билд War Robots под Android со всеми ресурсам весил порядка 700 МБ и включал сотни единиц контента. У нас было 13 карт, 81 мех, более ста пушек, десяток дронов и еще куча всякой мелочи. Не то, чтобы все это было категорически необходимо в проекте, но если уж начал пилить контент, то иди в своем увлечении до конца.

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

У нас уже был менеджер качества, состоящий из динамических пресетов и представляющий собой ScriptableObject с кастомным InspectorGUI. Это была длиннющая портянка с настройками и пресетами, почти полностью завязанная на логику «Роботов», и каждое поле в настройке рисовалось кодом. Хочешь добавить параметр в настройку — не забудь отрисовать это в InspectorGUI. Выглядело это монструозно, так как из-за большого количества настроек число фолдаутов в инспекторе достигало более 9000.

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

Все это нас не устраивало, так что мы решили изменить подход к формированию пресетов и запилить новый Quality Manager. А задачу эту передали нашему отделу кросс-проектной разработки, именуемому Platform Team.

Теперь поговорим о том, что же такое новый Quality Manager в War Robots, какие задачи он решает и как устроен.

Так что это за менеджер качества такой

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

Главными сущностями у нас являются:

  • QualitySetting — набор параметров, объединенных в общую группу;
  • Preset, состоящий из выбранных уровней QualitySetting разного типа.

Quality Manager состоит из двух частей: runtime-часть с API для инициализации и переключения качеств и editor-часть с GUI, которые позволяют все это конфигурировать.

Editor

Начнем с editor-части. Ее задача — предоставление интерфейса для конфигурации настроек качества и пресетов и минимизация количества кода, необходимого со стороны клиента. В идеале мы хотели заставить клиентщиков описывать только структуры настроек качества, а всю работу по отрисовке оставить на стороне Quality Manager.

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

Давайте подробнее разберемся, как с этим работать, и начнем с вкладки Quality Settings.

Так выглядят классы «базовых настроек» и «настроек кэша пререндеров» в War Robots. Эти же классы затем используются в рантайме:

Посмотреть код

	public class CommonQualitySettings : WRQualitySetting 	{ 		[IntSliderView(0, 72)] 		public int CorpsesCount { get; private set; }  		[FloatSliderView(5, 600)] 		public float UnloadPeriodImGameplay { get; set; }  		[FloatSliderView(0, 300)] 		public float UnloadPeriodInMenu { get; private set; }  		public bool UseMechCacheInHangar { get; set; }  		public bool HSEnabled { get; private set; }  		public bool BattleAmbientSoundEnabled { get; private set; }  		public HangarCacheSettings CacheSettings { get; private set; }  		public CommonQualitySettings() 		{ 			CorpsesCount = 12; 			UseMechCacheInHangar = true; 			HSEnabled = true; 			BattleAmbientSoundEnabled = true; 		} 	}  	public class ImageCacheSettings : WRQualitySetting 	{ 		[IntSliderView(0, 1000)] 		public int MinCacheSize { get; private set; }  		[IntSliderView(0, 1000)] 		public int MaxCacheSize { get; private set; }  		[IntPopupView(new[] { 128, 256, 512, 1024 })] 		public int RenderSize { get; private set; }  		public string Info 		{ 			get { return $"Cache takes from {(int) (MinCacheSize * 0.1f)} to {(int) (MaxCacheSize * 0.1f)} Mb"; } 		}  		public ImageCacheSettings() 		{ 			MinCacheSize = 150; 			MaxCacheSize = 200; 			RenderSize = 512; 		} 	} 

А вот так это выглядит в окне Quality Manager:

Получается достаточно простая схема. Клиентские разработчики создают класс с набором полей и настраивают уровни качества для него в окне Quality Manager. Он, в свою очередь, «из коробки» умеет отрисовывать примитивные типы, Enum, Nullable, массивы, списки, интерфейсы и собственные классы c полями вышеперечисленных типов, включая другие классы. На случай, когда необходимо отрисовывать для поля кастомный GUI, в QM предусмотрена возможность помечать поля атрибутом, в классе которого реализована отрисовка этого поля.

Так, например, выглядит код класса, меняющего отрисовку для int значения c IntFiled на Slider с параметрами шага, минимального и максимального значений:

Посмотреть код

	[Conditional("UNITY_EDITOR")] 	public class IntSliderViewAttribute : CustomPropertyViewAttribute 	{ 		public int MinValue { get; private set; } 		public int MaxValue { get; private set; } 		public int Step { get; private set; }  		public new int Value 		{ 			get { return (int) base.Value; } 			set { base.Value = value; } 		}  		public IntSliderViewAttribute(int minValue, int maxValue, int step = 1) 		{ 			MinValue = minValue; 			MaxValue = maxValue; 			Step = step; 		}  #if UNITY_EDITOR 		public override void OnGUI() 		{ 			Value = Step * UnityEditor.EditorGUILayout.IntSlider(Value / Step, MinValue / Step, MaxValue / Step); 		} #endif 	}

А так — поле с этим атрибутом:

		[IntSliderView(0, 72)] 		public int CorpsesCount { get; private set; } 

В QM сразу включен ряд реализаций для кастомной отрисовки полей: IntSliderView, FloatSliderView, IntPopupView, PresetIndexView, PresetNameView, QualitySettingIndexView, QualitySettingNameView. Этого скромного набора нам хватило для интеграции QM в War Robots и переезда со старого QM на новый. Кода в проекте стало ощутимо меньше, а тот, который остался, стал заметно проще, понятнее и описывал именно то, что он и должен был описывать — данные и логику работы с ними.

У нас уже есть классы настроек качества и данные для их уровней, так что пора формировать из них пресеты. Для этого отправляемся на первую вкладку окна QM — Presets.

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

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

Третья вкладка — Device Groups.

На этой вкладке мы формируем группы девайсов. Для этого мы используем несколько параметров: объем ОП, частоту процессора, модель GPU и модель девайса для совсем точного попадания. Все параметры не являются обязательными, и можно для группы указать только часть из них. Так, например, для устройств на iOS самый простой вариант — составить карту по модели устройств. На Android же большую часть покроют группы, объеденные по популярным моделям GPU, а в остальных случаях можно указать минимальные требования по объему оперативной памяти и частоте процессора.

Для группы — помимо пресета, который будет выбран по умолчанию — мы также задаем список доступных этой группе устройств пресетов для того, чтобы пользователь на low-end девайсе не смог поменять настройки на ultra high, что может привести к крешам по OOM на старте приложения и блокировать тем самым возможность изменить настройки обратно.

Итак, конфиг QM готов. Сериализуется он в JSON, что дает возможность его легко читать и править без окна QM, а также доставлять на клиент с сервера.

Помимо описанного функционала, QM позволяет:

  • работать с несколькими конфигурациями;
  • добавлять к пресетам кастомные данные, не относящиеся к уровню настроек качеств (мало ли);
  • конфигурировать базовые настройки, не относящиеся к пресетам (раздел Custom Data).

Runtime

API рантайм-части достаточно простой и включает основные методы для работы с QM:

  • инициализация (в том числе и обновление текущего инстанса QM из нового конфига);
  • выбор пресета (по индексу, имени, объекту пресета из конфига);
  • выбор уровня настройки качества (по индексу, имени, объекту настройки качества из конфига);
  • сброс пресета на дефолтное значение;
  • сохранение/удаление данных о выбранном пресете и уровне настроек качества (стейта).

Так инициализируется инстанс QM:

var exampleStateController = new ExampleStateController(); var qualityManager = QualityManager.Initialize<ExampleQualityManager>(exampleStateController);

Помимо основных методов, инстанс содержит ивенты о смене пресета и уровня настройки качества. Логика применения настроек качества лежит на клиентской стороне — для этого у класса QualitySetting есть виртуальный метод Apply, который вызывается при смене уровня настройки качества.

Помимо базовых методов API, в QM есть набор методов для «мягкого» переключения уровней качества. В случае, когда в рантайме мы сталкиваемся с ситуацией, при которой девайс должен тянуть заданный ему пресет, но при этом происходит падение FPS из-за посторонних факторов, можно ослабить нагрузку, понизив одну из настроек качества, или наоборот. Для этого в QM есть отдельный тип настроек качества, у которого есть виртуальный метод CanBeSwitchedTo, позволяющий определить, можно ли сменить текущий уровень настройки на новый. Так мы можем в рантайме поэтапно даунскейлить качество, чтобы стабилизировать FPS, или наоборот — попробовать дать девайсу нарисовать лучшую картинку, пока не начнем ловить падение FPS.

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

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

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

С QM разобрались. Давайте теперь более предметно рассмотрим, как мы сформировали список настроек качества, пресетов и групп устройств на проекте War Robots.

Как все это работает в игре

Мы решили зафиксировать несколько четко установленных пресетов, а динамики достигать так же, как и в консольных играх, — за счет скейлинга картинки. Современные девайсы обладают экранами с высоким разрешением, однако GPU в них стоят, конечно, далеко не RTX 3090, так что было бы наивно полагать, что они будут справляться с 60 FPS в нативных Quad HD или даже 4k. Собственно, мы сразу ограничили плотность пикселей сверху, проитерировавшись до значения в 350 ppi.

Изначально при работе над ремастером мы фокусировались на двух качествах — HD (high definition) и LD (low definition). Весь контент, который мы переделывали, основывался на них, и все инструменты по автоматической генерации исходили тоже из них. Однако вскоре мы поняли, что нам понадобится дополнительный уровень качества, который будет нацелен на устройства с небольшим, по нашим меркам, объемом RAM. Так родился еще один пресет качества — ULD (ultra low definition).

Так выглядит игра в качестве HD:

Так — в LD:

А так — в ULD:

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

Как мы видим, каждый пресет имеет настройку TargetFPSQualitySettings и внутри нее два уровня, отвечающие за максимально возможный FPS на устройстве. Затем «глобальные» пресеты качеств также разделяются: например, есть качество LD, а есть LD60. Это значит, что пользователи, устройства которых попадают в группу LD60 (по названию устройства — на iOS или по GPU — на Android), получают возможность в настройках включить 60 FPS:

Какое-то время мы рассуждали, нужно ли включать пользователям 60 FPS по умолчанию, но пришли к тому, что не стоит: это значительно повысит использование батареи мобильного устройства, а по нашим внутренним данным на разных проектах на такую частоту кадров переключаются 5-15% аудитории (у которой эта настройка вообще доступна).

Также внутри QM содержится важнейший параметр — с каким «тэгом» ресурсной системы работать:

Так, для ULD качества используется тег LD_ULD, который содержит набор ресурсов, упакованных нашей ресурсной системой для этих качеств. Объединение этих двух качеств дало нам большую экономию на дубликатах ресурсов, которые складываются в Asset Bundles — но это, я думаю, мы расскажем в наших следующих статьях.

Таким вот образом «собирается» каждое качество: это всего лишь набор уровней настроек.

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

Как пример, для ULD настройки рендера используется MasterTextureLimit = 1. Давайте посмотрим, как мы можем применить его к нашей игре.

Каждая настройка должна переопределять абстрактный класс WRQualitySetting, что и делает наш пользовательский класс RenderingQualitySetting:

public class RenderingQualitySettings : WRQualitySetting {         public RenderingPipelineAssetType RenderingPipelineAssetType { get; private set; }          public RenderingPipelineSetting RenderingPipelineSetting { get; private set; }          public int MasterTextureLimit { get; set; }          public MsaaQuality MSAA { get; set; }          // … some code … // }

Благодаря этому наш класс может перегружать метод Apply:

public override void Apply() {             base.Apply();  	// … some code … //                          // We don't want to switch MasterTextureQuality when it is set to 0 and the new quality is             // also using 0 (so i.e. HD -> LD or LD -> HD)             // So effectively we are only doing the switch when the MasterTextureQuality really changes.              // If MasterTextureLimit is > 0 then we switch in any way             if (MasterTextureLimit > 0)             {                 UnityEngine.QualitySettings.masterTextureLimit = MasterTextureLimit;             }                          // … some code … //   } 

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

Вызов метода Apply происходит в двух случаях:

  • инициализация игры — в этот момент мы поднимаем с диска пресет качества, который использует клиент, и применяем его;
  • переключение качества в настройках проекта — в этом случае после ряда проверок контроллер окна вызывает незамысловатый код:

private void OnConfirmPopupButtonClick() { 			var supportedPresetData = _supportedPresetsData.Find(x => x.IndexInUiPresetsLists == _selectedPresetIndex.Value); 			AnalyticsUtils.QualityPresetChanged(supportedPresetData.QmPreset.Name); 			ApplicationContext.QualityService.QualityManager.SetCurrentPreset(supportedPresetData.QmPreset); 			// ... reload game … // 		}

Вызов API QualityManager ApplicationContext.QualityService.QualityManager.SetCurrentPreset вызовет, в свою очередь, череду изменений внутри конфигураций и в конце переключит и применит все настройки, которые были зарегистрированы в QM.

Наверное, один из самых важных этапов, который был произведен перед переходом на новый QM — это чистка старых параметров качества.

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

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

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

На практике довольно сложно законфигурировать все устройства идеально, и бывают ситуации, когда на разных устройствах с одинаковым, казалось бы, SoC, игра ведет себя совершенно по-разному. Могут быть некачественные детали (например, дешевая память с низкими характеристиками) или некорректно написанные драйверы. В этом случае девайс всегда можно выделить отдельно и подобрать настройки под него. При создании QM мы учли подобные случаи, и мы можем производить конфигурацию пресетов не только per-GPU но и per-device (это активно используется, например, на Apple-устройствах).

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

Вместо заключения

Новый Quality Manager дал нам довольно большие возможности: это и мощность управления конфигурациями из старой системы, и простота тестирования, и возможность менять параметры буквально на лету через сервер, и упрощение разработки графического пайплайна. Также QM удобным образом позволил нам разделить настройки качества и выдать хорошую графику на телефонах, которые ее поддерживают, при этом сохранив приближенную к старой на слабеньких устройствах в таком же FPS, как в оригинальной игре.

Авторы материала: Дмитрий Самсонов, Senior Platform Developer, Павел Зинов, Head of Client Department

ссылка на оригинал статьи https://habr.com/ru/company/pixonic/blog/565400/

Нереализованные стартапы — проект АЭЛИТА

image
Введение

Я долго думал писать или не писать о подобных историях из жизни инноваторов, ведь раскрывая суть нереализованных проектов, ты можешь потерять глубинную основу проекта, его изюминку и даже возможное, его ноу-хау. Тем более, так уже случалось в моей практике, в рамках одного из проектов 10-ти летней давности. Не буду приводить пример по данному случаю, он уже был описан в статье. Эта история есть на сайте: intersofteurasia.ru/novosti/605/606.html.

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

Отмечу, что нереализованный проект под условным названием – АЭЛИТА находиться на стыке биомедицины, электроники, психологии и много чего ещё, поэтому он не так прост в реализации. Ведь для подобных проектов нужны люди-энтузиасты, работающие в разных, иногда диаметрально противоположных областях науки и техники, но междисциплинарный подход, как раз и способен дать удивительные результаты в случае реализации подобных проектов в жизни.

Основанная часть

Когда мои маленькие дети в 2014-2017 гг. пошли в 1-3 классы мы с женой отметили, что им тяжело даются непомерные современные нагрузки в школе на данном этапе — высокая утомляемость, быстрая потеря внимания, и многие другие симптомы, характерные современному динамичному образу жизни юных школьников. Мы задумались, что же делать. Снизить нагрузку – значит отстать от программы современного школьного обучения, или поискать другие методы и методики адаптации молодого организма к потокам обучающей информации. Тогда мне пришлось вспомнить свою специализацию в аспираторе МВТУ им. Н.Э. Баумана по кафедре «Биомедицинские технические системы и устройства», кандидатский минимум по медицине и биологии. Проанализировав эти события через призму собственных познаний, у нас с женой создалось первоначальное впечатление, что у наших маленьких школьников что-то не то с психосоматикой.

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

Мы начали искать специалистов области нейрофизиологии и психологии. И вот удача, нам попался прекрасный человек, врач нейропсихолог, кандидат медицинских наук, Борис Алексеевич Архипов. Сначала мы привели своего «старшего» первоклассника, и он прошёл несколько получасовых курсов у врача. Затем через 3 года мы привели в Центр АКМЭ своего младшего, тоже уже первоклассника, воспользовавшись прекрасными методиками доктора Архипова. Результаты сеансов, как в первом, так и во втором случае быстро исправили ситуацию с психофизической активностью и вниманием на уроках, и с успеваемостью по дисциплинам и активностью после школы. В итоге оба школьника учатся на 4 и 5, активны и адаптивны к большим нагрузкам.

Результаты методики нейропсихолога, доктора В.А. Архипова (https://ru.wikipedia.org/wiki/Архипов,_Борис_Алексеевич) меня тогда очень заинтересовали. Я решил сначала посмотреть со стороны на весь процесс врачевания пациента, а затем и сам прошёл один сеанс с одним из своих сыновей.

Позднее, с разрешения доктора Архипова, когда я стал досконально разбираться в технологии его методики, то сделал для Инвестиционных фондов презентацию нарождающегося проект АЭЛИТА со следующее резюме:

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

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

Тем не менее, уже более 40 лет существует практическая методика врача нейропсихолога Архипова Бориса Алексеевича, аттестованная при Минздраве РФ и успешно применяемая для нуждающихся пациентов различных возрастов начиная от 2-3 лет и до преклонного возраста. Стоит особо отметить, что данной методикой и процедурами коррекции психофизического состояния за вышеуказанный период воспользовалось более 20.000 пациентов различных возрастов.

Суть проблемы:

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

Цель проекта АЭЛИТА:

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

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

В дальнейшем, при реализации проекта АЭЛИТА, предполагалось использовать следующие целевые функции и способы оценки:

Способы оценки для проекта АПК АЭЛИТА:

 «Пространственно-временная характеристика двигательных паттернов произвольной деятельности в созданных условиях сенсибилизации и депривации».

 «Зрительно-моторные характеристики оценки оптомоторного и вестибуломоторного онтогенетического пространства человека».

 «Выявление актуального пространства человека путем исследования зрительно-моторных функций».

 «Построение индивидуальной карты-схемы пространственной организации психических функций на основе анализа ведущей сенсомоторной деятельности».

 «Ретроспективная оценка онтогенеза на основе анализа психомоторных пространственных функций».

 «Прогноз психических функций ребенка на основе анализа его актуального сенсомоторного пространства».

 «Реконструкция онтогенеза ребенка на основе выявления дизонтогенетических пространственных и временных сенсорных и моторных дисфункций».

 «Построение многоуровневой координатной пространственной схемы онтогенеза ребенка на основе анализа индивидуального зрительномоторного пространства».

 «Оценка психомоторных и сенсорных функций человека (ребенка) на основе моделирования зрительнокинестетического праксиса».

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

Причём, все более-менее масштабируемые функции врача нейропсихиатора теоретически возможно было бы смоделировать в рамках алгоритмов выложив их в облачный сервис, а затем переложив визуальный ряд и саму методику работы с пациентом благодаря VR интерфейсу, при этом используя систему распределённых детекторов на теле пациента. Но огромные массивы данных, которые возможно было бы поручить обработке Искусственному интеллекту в виде нейросетей с подключением технологии Bigdata, c сохранением и оперированием закономерностями, что хорошо уже реализовано в некоторых алгоритмах ИИ — совсем не тривиальная задача…

29 июня 2021г.

ссылка на оригинал статьи https://habr.com/ru/post/565240/