...

суббота, 12 июня 2021 г.

Разгоняем REACTOR

Кому будет интересно?

Реактор сегодня - это стильно, модно, молодежно. Почему многие из нас практикуют реактивное программирование? Мало кто может ответить однозначно на этот вопрос. Хорошо - если Вы понимаете свой выигрыш, плохо - если реактор навязан организацией как данность. Большинство аргументов "ЗА" - это использование микросервисной архитектуры, которая в свою очередь обязывает микросервисы часто и много коммуницировать между собой. Для коммуникации в большинстве случаев выбирают HTTP взаимодействие. Для HTTP нужен легковесный веб-сервер, а что первое приходит на ум? Tomcat. Тут появляются проблемы с лимитом на максимальное количество сессий, при превышении которого веб-сервер начинает реджектить запросы (хотя лимита этого не так уж и легко достичь). Здесь на подмогу приходит реактор, который подобными лимитами не ограничен, и, например, Netty в качестве веб-сервера, который работает с реактивностью из коробки. Раз есть реактивный веб-сервер, нужен реактивный веб-клиент (Spring WebClient или Reactive Feign), а раз клиент реактивный, то вся эта жуть просачивается в бизнес логику, Mono и Flux становятся Вашими лучшими друзьями (хотя по началу есть только ненависть :))

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

Блокирующий и неблокирующий код

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

Лидер здесь - HTTP взаимодействие, вариантов масса, выбирай любой. Я предпочитаю Reactive Feign от Playtika, в комбинации со Spring Boot + WebFlux + Eureka мы получаем очень годную сборку для микросервисной архитектуры.

Давайте по-простому: НЕблокирующий код, это обычно всё, в названии чего есть reactive, а блокирующий - все оставшееся :) Hibernate + PostgreSQL - блокирующий, отправить почту через JavaMail - блокирующий, скинуть сообщение в очередь IBMMQ - блокирующий. Но есть, например, реактивный драйвер для MongoDB - неблокирующий. Отличительной особенностью блокирующего кода, является то, что глубоко внутри произойдет вызов метода, который заставит Ваш поток ждать (Thread.sleep() / Socket.read() и многие подобные), что для реактора - как нож в спину. Что же делать? Большинство бизнес логики завязано на базу данных, без нее никуда. На самом деле достаточно знать и уметь делать 2 вещи:

  • Необходимо понимать где блокирующий код. В этом может помочь проект BlockHound или его аналоги (тут тема для отдельной статьи)

  • Исполнение блокирующего кода необходимо переключать на пулы, готовые его выполнять, например: Schedulers.boundedElastic(). Делается это при помощи операторов publishOn & subscribeOn

Разгоняемся сами

Перед тем, как продолжить, необходимо немного размяться!

Уровень 1

    @Test
    fun testLevel1() {
        val result = Mono.just("")
            .map { "123" }
            .block()

        assertEquals("123", result)
    }

Начнем с простого, такой код обычно пишут начинающие reactor программисты. Как начать цепочку? Mono.just и ты на коне :) Оператор map трансформирует пустую строку в "123" и оператор block делает subscribe.

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

Уровень 2

    fun nonBlockingMethod1sec(data: String) 
    = data.toMono().delayElement(Duration.ofMillis(1000))

    @Test
    fun testLevel2() {
        val result = nonBlockingMethod1sec("Hello world")
            .flatMap { nonBlockingMethod1sec(it) }
            .block()

        assertEquals("Hello world", result)
    }

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

Уровень 3

    fun collectTasks() = (0..99)

    @Test
    fun testLevel3() {
        val result = nonBlockingMethod1sec("Hello world")
            .flatMap { businessContext ->
                collectTasks()
                    .toFlux()
                    .map {
                        businessContext + it
                    }
                    .collectList()
            }
            .block()!!

        assertEquals(collectTasks().toList().size, result.size)
    }

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

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

Уровень 4

    fun collectTasks() = (0..100)
    
    @Test
    fun testLevel4() {
        val result = nonBlockingMethod1sec("Hello world")
            .flatMap { businessContext ->
                collectTasks().toFlux()
                    .flatMap {
                        Mono.deferContextual { reactiveContext ->
                            val hash = businessContext + it + reactiveContext["requestId"]
                            hash.toMono()
                        }
                    }.collectList()
            }
            .contextWrite { it.put("requestId", UUID.randomUUID().toString()) }
            .block()!!

        assertEquals(collectTasks().toList().size, result.size)
    }

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

Уровень 5

    fun collectTasks() = (0..1000)
    
    fun doSomethingNonBlocking(data: String)
        = data.toMono().delayElement(Duration.ofMillis(1000))
    
    fun doSomethingBlocking(data: String): String {
        Thread.sleep(1000); return data
    }

    val pool = Schedulers.newBoundedElastic(10, Int.MAX_VALUE, "test-pool")
    private val logger = getLogger()

    @Test
    fun testLevel5() {
        val counter = AtomicInteger(0)
        val result = nonBlockingMethod1sec("Hello world")
            .flatMap { _ ->
                collectTasks().toFlux()
                    .parallel()
                    .runOn(pool)
                    .flatMap {
                        Mono.deferContextual { _ ->
                            doSomethingNonBlocking(it.toString())
                                .doOnRequest { logger.info("Added task in pool ${counter.incrementAndGet()}") }
                                .doOnNext { logger.info("Non blocking code finished ${counter.get()}") }
                                .map { doSomethingBlocking(it) }
                                .doOnNext { logger.info("Removed task from pool ${counter.decrementAndGet()}") }
                        }
                    }.sequential()
                    .collectList()
            }
            .block()!!

        assertEquals(collectTasks().toList().size, result.size)
    }

Вот мы и добрались до итогового варианта! Часть с реактивным контекстом была опущена для более наглядной демонстрации того, зачем мы здесь собрались. У нас появились два новых метода: doSomethingNonBlocking (3) & doSomethingBlocking (6) - один с неблокирующим ожиданием в секунду, второй с блокирующим. Мы создали пул потоков для обработки задач (10), добавили счетчик активных задач в реакторе (15). У нас появился оператор parallel (19) и обратный ему sequential (29). Задачи мы назначили на свежесозданный пул (20). Для понимания, что же происходит внутри, добавили логирование внутри операторов doOnRequest (вызывается перед исполнением метода), doOnNext (вызывается после исполнения метода). Основная задумка - на примере, определить сколько задач одновременно выполняется в реакторе и за какое время цепочка завершит свою работу.

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

И вот здесь начинается самое интересное. Попробуйте ответить на несколько вопросов. Как Вы считаете, сколько времени будет выполнятся данная цепочка? В ней 100 задач, в каждой задаче неблокирующее ожидание в 1 секунду, блокирующее ожидание в 1 секунду, и у нас в наличии пул из 10 потоков? (Вполне годная задачка на собеседование senior reactor developer :))

Правильный ответ

Около 12 секунд. Рассуждаем от блокирующего :) Блокирующее ожидание никуда не деть, и тут имеем 100 блокирующих секунд на 10 потоков, итого 10 секунд. Неблокирующее ожидание заметно нам лишь в первый раз, далее оно незаметно запускается в передышках между блокирующим. Не забываем про одну секунду сбора "бизнес контекста" перед запуском задач.

А теперь уберем строку (26) .map { doSomethingBlocking(it) } . Освободим наш реактор от блокирующего кода, интересно, сколько теперь времени займет выполнение цепочки?

Правильный ответ

2 секунды! 1 на сбор "бизнес контекста" и 1 на выполнение всех задач. Реактор запустит 100 задач одновременно. Но ведь у нас пул из 10 потоков? Как так? Первый разрыв шаблона.

Мы идем до конца и увеличиваем количество задач в методе collectTasks() до ... 1000? а может быть сразу до 15000? Как долго реактор будет выполнять столько задач?

Правильный ответ

2 секунды! 1 на сбор "бизнес контекста" и 1 на выполнение всех задач. Реактор запустит ВСЕ задачи одновременно. Второй разрыв шаблона. Где предел?

А это вообще легально?

Как же так и как это контролировать? Почему это опасно? Что если внутри параллельной обработки Вы решите вызвать другой микросервис? Если у вас 30000 задач, и по завершению каждой, Вам нужно отправлять запрос соседнему микросервису, Вы с удивлением можете обнаружить, что реактор непременно постарается выполнить все вызовы одновременно (Вы ведь используете реактивный web-client или реактивный feign, верно?) Открытие такого большого количества сокетов повлечет за собой превышение лимита открытых файловых дескрипторов в системе, что как минимум создаст проблемы с невозможностью создания новых сокетов в системе и помешает другим сервисам, а как максимум повалит Вам на сервере SSH и Вы потеряете доступ к серверу. Сомневаюсь, что в этот момент, программист будет кричать "зато смотри как быстро работает".

Разрыв шаблона. Thread Pool & Reactor

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

Классический thread pool - двери. Больше дверей - больше пропускная способность, все работает быстрее.

Теперь встречайте reactor! Вы видите двери? Нет никаких дверей

Реактор это большой мешок с подарками, или воздушная труба, задачи в которую валятся и летают там пока не выполнятся. А кто эти люди в желтом? Это наши epoll реактивные потоки, которые ни в коем случае нельзя нагружать блокирующими задачами. Можно провести аналогию с прорабами или инженерами. Они здесь, чтобы управлять процессом, а не чтобы выполнять тяжелую работу. Займите одного инженера тяжелой задачей, и когда к нему придет следующий рабочий с вопросом "что делать дальше?", он не сможет ответить, потому что был занят. Вот так и появляются таймауты в реактивном коде. Казалось бы микросервис стоит без нагрузки, выполняет какие-то задачки, а один из 500 запросов к нему падает с тайм-аутом, и непонятно почему. Велика вероятность что инженер был занят блокирующей задачей! Заботьтесь о своих инженерах и поручайте тяжелую работу специально обученным рабочим, например, Schedulers.boundedElastic().

Как контролировать эту "трубу", в которую валится всё без контроля? Вот мы и подошли к кульминации

Конфигурируем реактор!

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

Парад настроек открывает parallel с его аргументом parallelism

Меняя parallelism, мы можем регулировать количество запускаемых rails (это местное понятие реактора, которое похоже на корутины, но по сути является количеством одновременно выполняемых неблокирующих задач). Prefetch мы рассмотрим более подробно в следующем разделе.

Но одного parallelism недостаточно, реактор все еще будет нагребать задач как не в себя.

Мало кто обращал внимание что у оператора flatMap (только того что запускается на Flux) есть перегрузки с интересными аргументами, а именно maxConcurrency

maxConcurrency очень важен, по дефолту значение стоит Integer.MAX_VALUE (определяет сколько неблокирующих задач может выполняться одновременно на одной рельсе. Понимаете теперь откуда аппетит у реактора?

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

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

Подведем небольшой итог:

  • parallel (parallelism)

  • flatMap (maxConcurrency)

  • Количество запусков цепочки

Эти три параметра являются множителями, для расчета количества одновременных задач.

По дефолту это Кол-во ядер * Integer.MAX_VALUE * Количество запусков цепочки

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

        val result = nonBlockingMethod1sec("Hello world")
            .flatMap { _ ->
                collectTasks().toFlux()
                    .parallel(1)
                    .runOn(pool, 1)
                    .flatMap({
                        Mono.deferContextual { _ ->
                            doSomethingNonBlocking(it.toString())
                        }
                    }, false, 1, 1)
                    .sequential()
                    .collectList()
            }
            .block()!!

Стоп, или не всё?

Thread Pool

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

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

Распределение задач по рельсам

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

Зеленые прямоугольники это наши задачи, которые распределяются в реакторе по алгоритму round-robin, что в случае с синтетическими данными дает красивую картинку.

Хорошо загруженный реактор (задачи равномерно распределены). 54 блокирующих задачи (каждая по 1сек),
 round-robin распределение по 6 рельсам
Хорошо загруженный реактор (задачи равномерно распределены). 54 блокирующих задачи (каждая по 1сек),
 round-robin распределение по 6 рельсам

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

Плохо загруженный пул (задачи распределены не равномерно) 54 блокирующих задачи (каждая по 1сек кроме 2ух),
 round-robin распределение по 6 рельсам
Плохо загруженный пул (задачи распределены не равномерно) 54 блокирующих задачи (каждая по 1сек кроме 2ух),
 round-robin распределение по 6 рельсам

Оператор collectList() вернет нам результат только после завершения последней задачи, и как мы видим, наш пул будет простаивать пока 1 поток трудится разгребая очередь накопившихся задач. Это создает неприятные задержки, когда Вы знаете что можно быстрее, но быстрее не происходит.

Бороться с этим можно несколькими способами

  • concatMap вместо flatMap (посмотрите в профилировщик на ваш пул, передумаете)

  • правильно планировать задачи, чтобы исключить аномалии (почти невозможно)

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

  • prefetch (наш выбор!)

Параметр prefetch у flatMap & runOn позволяет определить, сколько задач будет взято на одну рельсу на старте, а затем при достижении некоторого порога выполнения задач, реквесты будут повторяться с этим количеством. Значение по умолчанию - 256. Сменив значение на 1, можно заставить реактор использовать механизм "work stealing", при котором, рельсы и потоки, которые освободились, будут забирать задачи себе на выполнение и картина получится гораздо более приятная.

Хорошо загруженный пул (задачи равномерно распределены) 54 блокирующих задачи (каждая по 1сек кроме 2ух),
 round-robin распределение по 6 рельсам Prefetch !
Хорошо загруженный пул (задачи равномерно распределены) 54 блокирующих задачи (каждая по 1сек кроме 2ух),
 round-robin распределение по 6 рельсам Prefetch !

На этом у меня всё. Будет интересно прочесть Ваши замечания и комментарии, на 100% истину не претендую, но все результаты подкреплены практическими примерами, на Spring Boot + Project Reactor 3.4. Всем спасибо!

Adblock test (Why?)

Что можно найти на испанской барахолке: новые находки за сегодня


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

Но, конечно, я продолжаю обращать внимание лишь на то, что интересно гику. Антиквариат, посуда, одежда и все такое — проходят мимо внимания. Эта статья — такой же «прямой репортаж» с барахолки, как и прошлый, с фотографиями, сделанными непосредственно на месте и описанием интересных вещей, которые удалось найти. Что же, приступим.
Встретила меня барахолка сразу сюрпризом — видеокартой ASUS ENGTX275. Это древняя карта без HDMI, с GDDR3, 896 МБ ОЗУ, видеочипом GeForce GTX 275 и частотой видеопамяти 1134 МГц. Карта эта поступила в продажу в далеком 2009 году. О том, насколько она древняя, можно судить по наличию 2 выходов DVI-I и S-Video.



Продавец меня заверил, что карта рабочая, ее вынули из «супер-игрового ПК». Насколько этот ПК — супер, было понятно сразу. Но карту решил купить, хотя бы просто на детали. Сторговались за 10 евро, при текущей цене на eBay от 35 до 150 евро.

Затем на глаза попался ноутбук Toshiba nb500 — здесь такие раньше раздавали учебным заведениям, и по прошествии пары лет стал встречать их на барахолках и онлайн-сервисах, аналогах «Авито». Ноутбук неплохой для своего времени — небольшой, относительно легкий, с хорошей автономией батареи. Тянул без проблем офисные приложения и интернет.

Сейчас учебным заведениям раздают уже девайсы поновее, и не от Toshiba, а от Lenovo. Кстати, к этому ноутбуку у меня есть еще рабочая материнка — попалась ранее, я купил за 3 евро. Ноутбук продавец оценил в 10 евро, но покупать я не стал — уже и так разных девайсов море, ставить скоро будет некуда. А это — обычный ноутбук, причем очень сильно устаревший.

Следующей увидел целую россыпь древней оперативной памяти и беспроводный модуль связи EVO-108PCI. Это PCI-модуль, который работает на скорости в 108 Мбит/с. Рядом со всем этим счастьем — жесткие диски емкостью в 40-80 ГБ.

Еще один девайс — новые видеоочки Samsung Gear VR. В упаковке, со всеми документами и прочим. Стоимость новых таких очков — около 30 евро. Продавец хотел 15. Девайс этот, как и подобные ему видеоочки других производителей, быстро вошел в моду и еще быстрее — вышел из нее. Все потому, что в пластиковый корпус нужно вставлять смартфон, который и служит проводником в виртуальную реальность. Толку с такого девайса не очень много.


Еще попался какой-то десктопный ПК от ASUS. Если не учитывать пыль и грязь, внешне он в отличном состоянии. Продавец заявил, что девайс работает. Хотел за него 70 евро, но, думаю, если бы я реально хотел купить системник, спокойно сбросил бы цену до 20 евро.

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



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

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

А вот гораздо более интересная, чем ресиверы и древняя память, штука. Это нечто вроде Segway, только более надежный в плане устойчивости транспорт. Предназначен он для детей весом до 30 кг — больше просто не потянет.


Внутри — батарея, которую нужно заряжать 12В адаптером. Управление крайне простое — есть педаль «газ» и «тормоз». Чем сильнее жмешь на «газ», тем быстрее едешь.

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

На мой взгляд, штука не самая нужная в хозяйстве. Скутер — тяжелый, места занимает много. Ребенок долго кататься не будет — ему быстро надоест, что, вероятно, произошло с экземпляром с барахолки. Стоит это транспортное средство 165 евро на Амазоне. Раньше его цена, уверен, была гораздо выше. Ах, да, продавец хотел за него 30 евро, уверял, что все работает. Сел — и поехал.

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

Какая-то самоделка (наверное). Выглядит полуразобранным, рядом — пульт. Впереди машинки — веб-камера от Logitech. Если это не самоделка, то не знаю, кому подобное творение могло бы пригодиться, выглядит не очень современно, машинки на радиоуправлении гораздо портативнее.

Мне все же кажется, это кто-то самостоятельно собирал машинку-робота.

Потом пошли бесконечные россыпи старых и очень старых телефонов, более-менее современных планшетов и прочей электроники. Что-то работает, что-то — нет. Стоимость подобных девайсов на барахолке просто копеечная.





Встречаются и диски для разных игровых консолей. Вот, например, игры для Sony PS2, причем в очень хорошем состоянии.


А вот — старые ноутбуки, некоторые — в рабочем состоянии, как уверял продавец. Кто покупает подобное? Чаще всего — марокканцы, которые везут затем все это добро в свою страну.

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

Вот ноутбуки и чуть поновее.

А вот совсем раритет, уже не ноутбуки, а сотовые.

Ну а теперь — о покупках


PSP 1000

Как и в прошлый раз, мне повезло купить PSP. На этот раз не 3000 модель, а чуть более старую — 1000. Купил за 10 евро, поскольку видел, что состояние близко к идеальному. У консоли было три проблемы:
  • Заедала кнопка включения.
  • Отсутствовала кнопка открытия лотка для дисков.
  • При подключении к питанию загорался оранжевый светодиод и ничего не происходило.


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

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

Внутри консоли оказался диск с игрой Dexter, на удивление, увлекательной, а также картой памяти формата Sony в 2 ГБ. Раньше подобные карты такой емкости стоили очень много.

Больше проблем с консолью не было никаких экран в идеальном состоянии, диски читаются, все идеально.

Lenovo A7600-F


Не знаю, чем мне приглянулся этот планшет с разбитым дисплеем, но я решил его приобрести. Включать даже не пробовал, у продавца ничего не спрашивал — купил и все. Мне его отдали за 13 евро. Посмотрел на eBay, с целым экраном просят от 60 до 150 евро.

Как оказалось, планшет полностью рабочий. Сам экран (разбит только сенсор, который, впрочем, работает) стоит что-то около 15 евро. Думаю, поменяю экран, а потом подарю какой-то знакомой семье, у которой есть дети. Планшет не самый новый, но YouTube, интернет, игры потянет без особых проблем. Можно использовать его и как читалку.

Беспроводная зарядка


Купил за 3 евро, ломаться там обычно нечему, так что надеялся, что работает. Так и оказалось — принес домой, вытер от пыли, подключил к micro-USB — загорелся красный светодиод. Положил на него свой iPhone 8 Plus — пошла зарядка. Чудесно, теперь кабель для телефона мне особо и не нужен, только для редких случаев передачи данных.

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

Adblock test (Why?)

[Перевод] Стриминг видео с помощью Akka Streams

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

Правда, не следует думать, что то, о чём мы будем тут говорить, подобно простому примеру, вроде println(«Hello world»), в котором используется система акторов Akka. Сегодня вы узнаете о том, как создать свой первый сервис для потоковой передачи видео (прошу прощения, если моё предположение неверно, и у вас это уже не первый такой проект). В частности, тут будут использованы пакеты Akka HTTP и Akka Streams, с помощью которых мы создадим REST API, который обладает способностями стриминга видеофайлов в формате MP4. При этом устроен этот API будет так, чтобы то, что он выдаёт, соответствовало бы ожиданиям HTML5-тега <video>. Кроме того, тут я скажу несколько слов о наборе инструментов Akka в целом, и о некоторых его компонентах, вроде Akka Streams. Это даст вам определённый объём теории, которая пригодится вам в работе. Но, прежде чем мы приступим к делу, хочу задать один вопрос.

Почему я решил рассказать о стриминге видео?


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

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

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

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

А теперь мы можем переходить к нашей основной теме.

Что такое набор инструментов Akka? Удобно ли им пользоваться?


Akka — это опенсорсный набор инструментов, который нацелен на упрощение разработки многопоточных и распределённых приложений, и, кроме того, даёт программисту среду выполнения для подобных приложений. Системы, которые базируются на Akka, обычно очень и очень хорошо масштабируются. Проект Akka основан на модели акторов, в нём используется система конкурентного выполнения кода, основанная на акторах. Создатели этого проекта многое почерпнули из Erlang. Инструменты Akka написаны на Scala, но в проекте имеются DSL и для Scala, и для Java.

Если говорить об удобстве работы с Akka, то, полагаю, писать код с использованием этого набора инструментов весьма удобно. Если вы хотите получше его изучить — рекомендую начать с этого материала.

Основной объём кода, который мы будем тут рассматривать, будет использовать пакеты HTTP и Streams. Мы практически не будем пользоваться стандартным пакетом Akka Actors.

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

Что такое Akka Streams?


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

Для нас важен тот факт, что в Akka Streams есть встроенный механизм back-pressure («обратное давление»). Благодаря этому решается одна из самых сложных проблем мира стриминговой передачи данных. Это — настройка правильной реакции поставщика данных на работу в условиях, когда потребитель данных не может справиться с нагрузкой. И эту проблему решает инструмент, которым мы будем пользоваться, а нам остаётся лишь научиться работать с этим инструментом, не вдаваясь в какие-то чрезмерно сложные и запутанные темы.

Пакет Akka Streams, кроме того, даёт в наше распоряжение API, который совместим с интерфейсами, необходимыми для работы с Reactive Streams SPI. И, между прочим, стоит отметить, что сам проект Akka входит в число основателей инициативы Reactive Streams.

Про Akka Streams мы поговорили. Поэтому можем переходить к нашей следующей теоретической теме.

Что такое Akka HTTP?


Akka HTTP — это, как и Akka Streams, пакет, входящий в набор инструментов Akka. Этот пакет основан на пакетах Akka Streams и Akka Actors. Он направлен на то, чтобы упростить работу приложений, в которых используются инструменты Akka, с внешним миром по протоколу HTTP. Этот пакет поддерживает и серверные, и клиентские возможности. Поэтому с его помощью можно создавать и REST API, и HTTP-клиентов, которые отправляют запросы к неким внешним сервисам.

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

HTML5-тег <video>


Тег <video> — это новый элемент, который появился в HTML5. Он создавался как замена Adobe Video Player. Несложно понять, что главная задача этого HTML-элемента заключается в предоставлении разработчикам возможностей встраивания в HTML-документы медиаплееров, способных воспроизводить видеофайлы. Собственно, этот тег очень похож на <img>.

В теге <video> размещают тег <source>, имеющий два важных атрибута. Первый — это src, который используется для хранения ссылки на видео, которое нужно воспроизвести. Второй — это type, который содержит сведения о формате видео.

Между открывающим и закрывающим тегом <video> </video> можно ввести какой-то текст, который будет использован в роли текста, выводимого вместо элемента <video> в тех случаях, когда этот элемент не поддерживается браузером. Но в наши дни тег <video> поддерживает даже Internet Explorer, поэтому вероятность возникновения ситуации, в которой может понадобиться подобный текст, стремится к нулю.

Как будет работать стриминг с использованием тега

Тут мне хотелось бы обратить ваше внимание на 5 важных вещей:

  1. Я использовал возможности тега <source> вместо использования соответствующих атрибутов тега <video>.
  2. В атрибуте src тега <source> я указал путь, при обращении к которому бэкенд начнёт потоковую передачу видеофайла.
  3. В атрибуте type тега <source> я указал тип файла.
  4. Я добавил к тегу <video> атрибуты autoplay и muted для того чтобы видео начинало бы воспроизводиться автоматически.
  5. К тегу <video> я добавил и атрибут controls, благодаря чему будут выводиться элементы управления видеоплеера, встроенного в страницу.

На элемент <div> можете особого внимания не обращать. Он тут нужен лишь для стилизации плеера.

Для проверки правильности работы системы достаточно запустить бэкенд и открыть вышеописанный HTML-документ в любом современном браузере.


Правильная работа стримингового сервиса

Обратите внимание на то, что автоматическое воспроизведение видео не начнётся до тех пор, пока к тегу <video> не будут добавлены атрибуты muted и autoplay. Если не оснастить тег <video> этими атрибутами — воспроизведение придётся включать вручную, нажимая на соответствующую кнопку.

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

Что можно улучшить?


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

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

Итоги


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

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

Вот GitHub-репозиторий с кодом проекта.

Какие инструменты вы выбрали бы для создания собственного стримингового сервиса?


Adblock test (Why?)

Искусственный фотосинтез. Перспективы и проблемы

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

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

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

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

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

Естественный фотосинтез, будучи продуктом биологической эволюции, не отличается эффективностью. Его КПД составляет всего 1-2%, чего вполне хватает для поддержки медленного жизненного цикла растений. Атом магния, хорошо заметный в вышеприведенной формуле, играет роль катализатора. Но растения используют в таком качестве магний, так как биологическая эволюция использует в основном легкие металлы, один из них – магний (12-й элемент). Оптимизируя фотосинтез, нам следовало бы изобрести искусственные листья, а также повысить эффективность самого процесса. Для этого нужно было бы заменить магний другими катализаторами – металлами, способными его заменить, а значит, схожими с магнием в соответствии с периодическим законом.

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

Давайте об этом поговорим.

Биохимия фотосинтеза

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

Фотосинтез протекает в четыре этапа:

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

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

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

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

Немного простой химии.

Расщепление воды на кислород и водород:

Образовавшиеся протоны идут на синтез углеводов.

Реакция фотосинтеза в общем виде

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

История

Процесс искусственного фотосинтеза in vitro, без участия листьев, был впервые осуществлен в 1972 году в Токийском университете. Кеничи Хонда и его аспирант Акира Фудзисима сообщили о том, что смогли смоделировать фотосинтез, подавая свет на электрод из диоксида титана, погруженный в воду. Электроны под действием света покидали металл, оставляя на своем месте положительно заряженные дырки, куда затем захватывались электроны из окружающей воды. Хонда и Фудзисима продемонстрировали, что таким образом получение кислорода катализировалось на фотоаноде, а свободный водород скапливался на платиновом катоде. Так впервые удалось разложить воду на составляющие при помощи светочувствительного элемента.

В 1998 году Джон Тёрнер и Оскар Хаселев из Национальной лаборатории возобновляемой энергетики из штата Колорадо разработали первый «искусственный лист»: интегрированное фотоэлектрическое устройство, позволяющее расщеплять воду, получая на вход в качестве энергии свет и ничего более. В результате КПД при производстве водорода достиг целых 12,4%, но материалы для поддержки реакции оказались очень дорогими: в состав устройства входил полупроводник на основе галлий-индиевого фосфида, а также платина в качестве катализатора.  

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

Впрочем, неорганические фотосинтезирующие устройства вряд ли способны конкурировать с традиционными солнечными батареями в качестве источника энергии, а сами быстро выходят из строя по причине коррозии, связанной с резким увеличением уровня pH, возникающем при их работе. Неорганические фотосинтезирующие элементы в целом близки к пределу производительности. Устройство, разработанное в 2018 году специалистами из технического университета Ильменау и Калифорнийского технологического института, работает на основе диоксида титана. В нем предусмотрена дополнительная защита от коррозии, оно работает на протяжении 20 часов и достигает КПД 19%.   

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

Рубиско или как ускорить фотосинтез

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

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

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

На этой иллюстрации, взятой с сайта «Naked Science», показано, как с повышением температуры меняется темп фиксации углерода (слева) и выделения углекислого газа (справа).

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

Упоминавшийся выше диоксид титана также поглощает фотоны именно в «зеленой» части спектра. Но фотосинтезирующие свойства фотоэлектрического элемента можно улучшить, задействовав в нем другие материалы, в частности, кремний, улавливающий свет в области спектра примерно до 1100 нм. Для максимально полного использования спектра ведутся эксперименты по включению в фотоэлектрические элементы других металлосодержащих соединений: оксида цинка ZnO, оксида железа Fe2O3, висмут-ванадиевого соединения с кислородом BiVO4, нитрида тантала Ta3N5 и некоторых других.

Фотосинтез и солнечная энергетика

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

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

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

Заключение

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

Adblock test (Why?)