Русский
Русский
English
Статистика
Реклама

Java

Строим безопасную разработку в ритейлере. Опыт одного большого проекта

17.09.2020 10:15:44 | Автор: admin
Некоторое время назад мы закончили строить процесс безопасной разработки на базе нашего анализатора кода приложений в одной из крупнейших российских ритейловых компаний. Не скроем, этот опыт был трудным, долгим и дал мощнейший рывок для развития как самого инструмента, так и компетенций нашей команды разработки по реализации таких проектов. Хотим поделиться с вами этим опытом в серии статей о том, как это происходило на практике, на какие грабли мы наступали, как выходили из положения, что это дало заказчику и нам на выходе. В общем, расскажем о самом мясе внедрения. Сегодня речь пойдет о безопасной разработке порталов и мобильных приложений ритейлера.


Для начала в целом про проект. Мы выстроили процесс безопасной разработки в крупной торговой компании, в которой ИТ-подразделение имеет огромный штат сотрудников и разделено на множество направлений, минимально коррелирующих между собой. Условно эти направления можно разделить на 3 основные группы. Первая, очень большая группа, это кассовое ПО, которое написано преимущественно на языке Java (90% проектов). Вторая, самая обширная с точки зрения объема кода группа систем это SAP-приложения. И наконец, третий блок представлял собой сборную солянку из порталов и мобильных приложений: разного рода внешние сайты для клиентов компании, мобильные приложения к этим сайтам, а также внутренние ресурсы мобильные приложения и веб-порталы для персонала ритейлера.

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

Этот подход мы сформулировали максимально просто: сканируется наиболее актуальный для всех разработчиков код. Если говорить в терминах Gitflow, а все группы проектов, за исключением SAP, вели ветки разработки в Gitflow, сканируется основная ветка разработки по расписанию.

Но, как всегда, из любого правила бывают исключения: общий подход не везде мог быть применен as is по ряду причин. Во-первых, наш инструмент (анализатор кода) имеет несколько ограничений, обусловленных тем, что мы хотим иметь возможность при необходимости делать наиболее глубокий анализ некоторых языков программирования. Так, в случае с Java анализ по байткоду гораздо более глубокий, чем по исходному коду. Соответственно, для сканирования Java-проектов требовалась предварительная сборка байткода и лишь затем его отправка на анализ. В случае с C++, Objective C и приложениями для iOS анализатор встраивался в процесс на этапе сборки. Также мы должны были учесть различные индивидуальные требования со стороны разработчиков всех проектов. Ниже расскажем, как мы выстроили процесс для порталов и мобильных приложений.

Порталы и мобильные приложения


Вроде бы все эти приложения объединены в одну логическую группу, но на самом деле в них была жуткая каша. Одних порталов было больше 120-ти (!). Компания очень большая, со множеством бизнес, административных и технических подразделений, и периодически каждое из них решает, что ему нужен свой портал и мобильное приложение. Этот портал и приложение создаются, ими какое-то время пользуются, а потом благополучно забрасывают. В результате на начальном этапе нам пришлось провести для заказчика инвентаризацию, поскольку даже разработчики этих приложений не имели единого списка кодовых баз. Например, для управления репозиториями в этой группе разработчики использовали два GitLab, администраторами которых являлись разные люди. Кроме того, среди порталов и мобильных приложений существенная часть проектов была реализована с помощью внешней разработки. Поэтому, когда подходило время релиза, зачастую подрядчики передавали компании исходные коды новой версии чуть ли не на флешке. В итоге компания имела зоопарк различных приложений и полный беспорядок в их коде. Нам пришлось составить список всех проектов, найти всех ответственных за них техвладельцев, тимлидов, а затем согласовать с основным заказчиком департаментом ИБ, какие из них мы будем анализировать.

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

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

Интеграция по стандартной схеме


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


Настройка интеграции с GitLab

Далеко не во всех приложениях применялась CI/CD, и там, где ее не было, нам приходилось настаивать на ее применении. Потому что если вы хотите по-настоящему автоматизировать процесс проверки кода на уязвимости (а не просто в ручном режиме загружать ссылку на анализ), чтобы система сама скачивала ее в репозиторий и сама выдавала результаты нужным специалистам, то без установки раннеров вам не обойтись. Раннеры в данном случае это агенты, которые в автоматическом режиме связываются с системами контроля версий, скачивают исходный код и отправляют в Solar appScreener на анализ.

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

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


Настройка интеграции с Jira

В редких случаях тим-лиды сами смотрели результаты сканирования и заводили задачи в Jira вручную.


Создание задачи в Jira

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

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


Результаты анализа и созданные в Jira задачи на исправление уязвимостей

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

Нестандартное в стандартном


В этом, на первый взгляд, не таком уж и сложном процессе имелось два серьезных ограничения. Во-первых, для анализа Android-приложений (то есть написанных на Java) нам нужна была сборка. А во-вторых, для iOS нужны были машины с MacOS, на которых устанавливался бы наш агент и имелась бы среда, которая позволяла бы собирать приложения. С Android-приложениями мы разобрались довольно просто: написали свои части в уже имеющиеся у разработчиков скрипты, которые запускались так же по расписанию. Наши части скриптов предварительно запускали сборку проекта в наиболее широкой конфигурации, которая направлялась в Solar appScreener на анализ. Для проверки же iOS-приложений мы устанавливали на Mac-машину наш MacOS-агент, который производил сборку кода и так же через GitLab CI отправлял код в анализатор на сканирование. Далее, как и в случае с другими видами ПО, офицер безопасности просматривал результаты анализа, верифицировал их и заводил задачи на исправления в Jira.

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

В тех проектах, где не было CI/CD, что было обязательным для нас условием, мы просто говорили: Ребята, хотите анализировать собирайте в ручном режиме и загружайте в сканер сами. Если у вас нет Java или JVM-подобных языков Scala, Kotlin и прочих, то можете просто загружать код в репозиторий по ссылке, и все будет хорошо.

Сложности проекта


Как видно из вышесказанного, в этом стеке приложений основной проблемой было отсутствие во многих проектах CI/CD. Разработчики часто делали сборки вручную. Мы начали интеграцию нашего анализатора с порталов Sharepoint на языке C#. Сейчас C# более-менее перешел на Linux-системы, хотя и не совсем полноценные. А когда проект был в самом разгаре, этот язык еще работал на Windows, и нам приходилось ставить агент на Windows для GitLab. Это было настоящим испытанием, поскольку наши специалисты привыкли использовать Linux-команды. Необходимы были особенные решения, например, в каких-то случаях нужно было указывать полный путь до exe-файла, в каких-то нет, что-то надо было заэкранировать и т.п. И уже после реализации интеграции c Sharepoint команда проекта мобильного приложения на PHP сказала, что у них тоже нет раннера и они хотят использовать C#-овский. Пришлось повторять операции и для них.

Резюме


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

  • внедряемое нами решение достаточно взрослое, чтобы проявлять нужную гибкость для построения процессов DevSecOps в кардинально разных средах внедрения. Гибкость достигается за счёт большого набора встроенных и кастомных интеграций, без которых трудозатраты на внедрение возросли бы в разы или сделали бы его невозможным;
  • настройка нужной автоматизации и последующий разбор результатов не требуют необъятного количества трудозатрат даже при огромном скоупе работ. Согласование и построение внедряемых процессов и полная их автоматизация возможны усилиями небольшой экспертной группы из 3-4 человек;
  • внедрение средств автоматической проверки кода и практик DevSecOps позволяет выявить недостатки текущих процессов DevOps и становится поводом для их настройки, улучшения, унификации и регламентирования. В конечном счёте получается win-win ситуация для всех участников процесса от рядовых разработчиков до топ-менеджеров отделов инженерии и ИБ.

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

А был ли у вас свой опыт реализации подобных проектов? Будем рады, если вы поделитесь с нами своими кейсами внедрения практик безопасной разработки в комментариях!

Автор: Иван Старосельский, руководитель отдела эксплуатации и автоматизации информационных систем
Подробнее..

Из песочницы Как НЕ надо начинать изучать программирование

12.09.2020 16:16:00 | Автор: admin
Приветствую, Хабровцы!

Решил поделиться своим опытом успешного изучения языка(ов) программирования.

Не сказать, что в IT-индустрии я полный профан, однако мой план обучения стать IT developer-ом с треском пошел по швам.

Немного предыстории.

Сразу скажу, что целенаправленного обучения по компьютерным наукам я не проходил. Да и специализация в образовании у меня далеко не техническая. Работал с 2005г. по 2012г. в различных компаниях, и мелких и крупных, непосредственно связанных с IT-индустрией. Научился всему понемногу: сис. администрированию Windows (даже MCP, MCSA успел получить), немного поюзал VMware (VCP тоже в копилке), дополнительно изучил разную кучу программ, которые сис. админы как правило используют в своей ежедневной работе.
Попробовал себя в корпоративных продажах, кстати, неплохо получалось. Успел поработать немного и у дистрибьютора ПО, а также в компаниях-интеграторах, неплохо разобрался в политиках лицензирования ПО. Планировал стать Project manager-ом, даже начал изучать PMBOK, тайм-менеджмент, различные международные стандарты, типа ISO, Tier, и даже замахнулся на PCI DSS.

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

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

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

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

Теперь сама история, поехали

Так вот, спустя 8 лет отдыха от IT в целом, принялся изучать заокеанский рынок труда и решил для начала специализироваться в мобильной разработке. Погуглив языки программирования для мобильных приложений и вдохновившись, что Google официально анонсировала язык Kotlin как приоритетный язык для android-приложении, твердо решил максимум за 1 год самостоятельно выучить Kotlin и строить планы по иммиграции на ПМЖ в США.

Пару недель просмотра тренингов и чтения мануалов мне хватило, чтобы убедиться, что без знаний Java в Kotlin делать нечего. Хотя на просторах интернета многие твердят что можно выучить с нуля. А после регистрации на GitHub-е, установки IntelliJ IDEA, JDK и попытки разобраться в коде я уже начал осознавать что придется учиться очень долго и упорно.
Было принято решение отложить Kotlin пока что в сторону, и углубиться в язык java. Так и сделал. Эх, помнится в мое время java был еще SUN-овским детищем.

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

Так вот скажу вам честно, она ни сколько не для новичков.

Пробовал не обращать на это внимания и читать дальше, усвояемость около 20-25%, понял что так дело не пойдет. Придется разбираться и в С языке, да еще и в книге регулярно черным по белому пишут, что материал рассчитан на читателей со знанием основ С языка.

Что-ж, выбора нет. Опять читаю кучу информации, сотни просмотров видео разной тематики о языке С. Качаю книгу Кернигана и Ричи Язык С, приступаю к изучению, усвояемость уже получше чем в Java, так сказать около 50-60%, что вовсе не радует меня.

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

Однако такие заголовки в книге как:
Настоящая книга не является вводным курсом в программирование; она предполагает определенное знакомство с основными понятиями программирования такими как переменные, операторы присваивания, циклы, функции
или:
предполагается рабочее владение основными элементами программирования; здесь не объясняется, что такое ЭВМ или компилятор, не поясняется смысл выражений типа N=N+1
а также такие фразы как:
Символические константы.
и т.д.
постепенно подводили меня к тому, что без изучения Computer Science мне не обойтись.
Параллельно начинаю вникать в Computer Sciense, качаю опять-таки тонны книг. Регистрируюсь на Гарвардский курс CS50, приступаю к изучению основ программирования, внимательно читаю книгу Владстона Феррейра Фило Теоретический минимум по Computer Science.

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

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

Я долго вникал почему 0 в степени 0 равен 1, и у меня ощущение что я до конца так и не понял всей сути.

А вот эта задача меня вообще заставила остановиться на чтении книги по CS, т.к. чем дальше читал, тем сложнее уже шли уравнения и задачи:



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

Решение то получил, однако понять, как преподаватель решила не смог:





На мой вопрос: как решаются такие уравнения?, ответ был очень прост:
учи исследование функции, начало анализа и задачи на оптимизацию. Алгебра 10-11 класс.
Ну думаю, ок, посмотрю пару видео-примеров для школьников в youtube, пойму как решать их, и дальше буду глокать изучение по CS.

И вот после просмотра подобных роликов по алгебре меня осенило

www.youtube.com/watch?v=RbX_QHxu7Lg
www.youtube.com/watch?v=FVSG7Neopuo

Я не то что не помню, как решаются такие задачи, элементарно, как выяснилось, попросту не знаю Алгебру за 10-11 класс!

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

Наверное, мои познания математики остались на уровне уроков математики 5-6 классов.

Начинаю осознавать, что для полной картины понимания Computer Science, мне необходимо будет заново учить алгебру, а затем и ВысшМат. Не исключаю, что походу скорее всего, появится необходимость и повторения уроков физики и еще чего-то из школьной программы. И до реального изучения Java и JS мне понадобится лет 5 изучения алгебры и высшей математики.
До Марса и обратно быстрее долететь, всего то 1,5 года, как утверждают ученные

И вот тут-то мне стало уже совсем как-то грустно.

Неужели чтобы стать программистом без технической базы, требуется так много времени?
Меня конечно вдохновляют статьи в интернете, где люди пишут, что за 1,5 года стали Java developer-ом и уехали в Германию, Канаду, США, однако оценивая свои печальный опыт я не уверен что такое возможно.

Или все-таки это не моё? И профессия разработчик это каста особенных людей?

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

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

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

Заранее благодарю!
Подробнее..

Бесплатные онлайн-мероприятия по разработке (15 сентября 23 сентября)

13.09.2020 16:10:25 | Автор: admin

Нажимайте на интересующую вас тему и откроется подробная информация о мероприятии.

Bussiness Intelligence Meetup #2
Business Intelligence Meetup #2Business Intelligence Meetup #2

Business Intelligence Meetup #2

15 сентября, начало в 19:00, Вторник

Практические проблемы оценки результатов тестирования производительности приложения - Сергей Миронов, Lead Software Engineer, EPAM

Во время доклада обсудим:

  • Описание выявленных проблем;

  • Особенности тестирования длительных операций (заданий);

  • Особенности тестирования API;

  • Предложенный вариант решения и использованные инструменты;

  • Применение и варианты развития рассмотренного решения.

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

Страница мероприятия

ESCAPE - Essential Skills, Competencies and People Engineering
Нетехническая конференция для всех, кто работает в IT. Нетехническая конференция для всех, кто работает в IT.

ESCAPE - Essential Skills, Competencies and People Engineering

1517 сентября

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

Страница мероприятия

Java Meetup

Java Meetup

16 сентября, начало в 19:00, Среда

  1. Как начинается рефакторинг - Ярослав Дмитриев, Java Developer, Andersen

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

  2. Из хэшей и веток - как работает Git - Синицын Артём, Half-Stack Developer, ScienceSoft

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

Страница мероприятия

Data Science Webinar

Data Science Webinar

16 сентября, 18:00-20:00, Среда

  1. Julia: язык для высокопроизводительных вычислений - Павел Шашкин, Senior Data Scientist, EPAM

    Julia - молодой язык программирования, созданный как высокопроизводительный инструмент для научных вычислений. Проект стремится объединить в себе сильные стороны Python, R, MATLAB и других, не уступая в быстродействии компилируемым языкам. Если вы решаете вычислительно тяжёлые задачи или работаете с большими объёмами данных, то Julia благодаря своей гибкости, JIT-компиляции, встроенной поддержке асинхронных, параллельных и распределённых вычислений может стать выгодной альтернативой привычным инструментам.

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

  2. Реконструкция позы человека в 3D: как мы делали виртуального тренера - Екатерина Деревянка, Data Scientist, EPAM

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

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

  3. Рекомендательные системы - от ритейла до хедж-фонда - Сергей Смирнов, Lead Data Scientist, EPAM

    Мы работаем с заказчиками в разных бизнес-доменах.

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

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

Страница мероприятия

Front-end Meetup

Front-end Meetup

17 сентября, начало в 18.30, Четверг

  1. Безопасность в web - Мария Сампир, Andersen

    Несколько известных хакерских атак последних лет. Три распространенных вида атак в сети интернет, их особенности и методы защиты. Как выбрать между безопасностью и user friendly.

  2. PWA - Владислав Фай, Andersen

    Что же такое PWA и как можно "прокачать" ваше приложение? Плюсы, минусы и подводные камни. В какой ситуации нужно задуматься о внедрении PWA. Реальные кейсы.

  3. Фоновые сервисы в браузерах есть ли жизнь после закрытия вкладки? - Максим Сальников, Web/Cloud евангелист, Full Stack разработчик в ForgeRock, Google Developer Expert и Microsoft MVP

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

Страница мероприятия

C++ MeetUp

С++ MeetUp

17 сентября, 18:00-20:00, Четверг

  1. Ещё чуть быстрее: делаем свой контейнер - Антон Полухин, эксперт-разработчик C++, Яндекс.Такси

    Поговорим о решении небольшой задачи на обработку потока данных, об алгоритмах, о нагрузке и об оптимизациях C++

  2. Rust vc C++ - Алексей Афанасьев, С++ разработчик, DataArt

    Кратчайший обзор Rust, сравнение производительности, стоит ли разработчику С++ переходить на Rust

  3. Обход проблем Pymalloc через модули Python на С++ - Александр Боргардт, С++ разработчик, IVA CV

    При обработке больших массивов данных на Python возникают проблемы потребления сотни гигабайт RAM из-за некомпактного хранения данных в памяти и низкой скорости загрузки и сохранения больших коллекций в память и на диск. Один из способов решения проблемы сделать allocator в Python через С++ API, Embedded VM и Module.

Страница мероприятия

Online Frontend Meetup: куда развиваться в 2020 году?

Online Frontend Meetup: куда развиваться в 2020 году?

19 сентября, 12:0017:00, Суббота

  1. React Hooks и производительность - Дмитрий Карпунин, Head of Frontend, Evrone

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

  2. Прогрессивно ли PWA? - Алексей Дорофеев, Senior Front-End developer, DataArt

    Идеал PWA и вероятность его реализации. Зачем нам PWA и чем оно лучше нативного. И лучше ли. Подводные камни и основные преимущества.

Страница мероприятия

Управление IT-персоналом: инструменты и технологии

Управление IT-персоналом: инструменты и технологии

23 сентября, начало в 19:00, Среда

Компания Andersen приглашает Вас на онлайн-встречу с бизнес-тренером, экспертом по управлению и мотивации персонала, Александром Фридманом.

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

Страница мероприятия

Подробнее..

Распознавание текста на картинке с помощью tesseract на Kotlin

11.09.2020 10:13:26 | Автор: admin

Ни для кого не секрет, что Python прочно занял первенство в ML и Data Science. А что если посмотреть на другие языки и платформы? Насколько в них удобно делать аналогичные решения?


К примеру, распознавание текста на картинке.


Среди текущих решений одним из наиболее распространённым инструментом является tesseract. В Python для него существует удобная библиотека, а для первоначальной обработки изображений, как правило, используется OpenCV. Для обоих этих инструментов есть исходные C++ библиотеки, поэтому их также возможно вызывать и из других экосистем. Попробуем это сделать в jvm и, в частности, на Kotlin.


Несколько слов о Kotlin. У него есть много удобных вещей для Data Science. В совокупности с экосистемой jvm получается статически типизированный Python на jvm. А не так давно ещё появилась возможность использовать Kotlin вместе с Apache Spark.

Первым делом установим tesseract. Его нужно установить отдельно на систему, согласно описанию из wiki. После установки можно проверить, что tesseract работает следующим образом:


tesseract input_file.jpg stdout -l eng --tessdata-dir /usr/local/share/tessdata/

Где --tessdata-dir путь до файлов tesseract (/usr/local/share/tessdata/ в macos). В случае успешной установки в stdout будет выведен распознанные текст.


После этого можно подключить tesseract в jvm и сравнить результат работы с нативным вызовом. Для этого подключим библиотеку:


implementation("net.sourceforge.tess4j:tess4j:4.5.3")

Для тех, кто не очень хорошо знаком с экосистемой jvm, есть лёгкий способ быстро себе всё настроить. Понадобится только установленная Java 13+. Её проще всего поставить через sdkman. Далее для удобства можно скачать Intellij IDEA, подойдёт и Community version. Основу проекта можно создать из IDE (new project -> Kotlin, gradle Kotlin) или можно клонировать репозиторий github, в котором перейти на ветку start.

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


 val api = Tesseract() api.setDatapath("/usr/local/share/tessdata/") api.setLanguage("eng") val image = ImageIO.read(File("input_file.jpg")) val result: String = api.doOCR(image)

Как видно, практически все команды совпадают с используемыми в вызове из командной строки. Но, как минимум, на macos нужно ещё дополнительно настроить системную переменную jna.library.path, в которую нужно добавить путь до dylib-библиотеки tesseract.


val libPath = "/usr/local/lib"val libTess = File(libPath, "libtesseract.dylib")if (libTess.exists()) {    val jnaLibPath = System.getProperty("jna.library.path")    if (jnaLibPath == null) {        System.setProperty("jna.library.path", libPath)    } else {        System.setProperty("jna.library.path", libPath + File.pathSeparator + jnaLibPath)    }}

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


Перейдём теперь к обработке изображений с OpenCV. В Python для работы с ней не требуется ставить каких-либо дополнительных инструментов, кроме пакета в pip. В описании OpenCV под java указан порядок установки, когда всё ставится отдельно. Для самой jvm-экосистемы подход, когда требуются установки каких-либо нативных библиотек, не совсем привычен. Чаще всего если зависимости требуется какие-либо дополнительные библиотеки, то либо она сама их скачивает (как, например, djl-pytorch), либо при подключении через систему сборки внутри себя уже содержит библиотеки под различные операционные системы. К счастью, для OpenCV есть такая сборка, которой и воспользуемся:


implementation("org.openpnp:opencv:4.3.0-2")

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


nu.pattern.OpenCV.loadLocally()

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


 Imgproc.cvtColor(mat, mat, Imgproc.COLOR_BGR2GRAY)

Как вы уже обратили внимание, аргументом для OpenCV выступает Mat, который представляет из себя основной класс-обёртку вокруг изображения в OpenCV в jvm, похожий на привычный BufferedImage.


Сам экземпляр Mat можно получить привычным для Python кода вызовом imread:


val mat = Imgcodecs.imread("input.jpg")

В таком виде экземпляр можно дальше передавать в OpenCV и проделывать с ним различные манипуляции. Но для Java общепринятым является BufferedImage, вокруг которого, как правило, уже может быть выстроен pipeline загрузки и обработки изображения. В связи с чем возникает необходимость конвертации BufferedImage в Mat:


val image: BufferedImage = ...val pixels = (image.raster.dataBuffer as DataBufferByte).dataval mat = Mat(image.height, image.width, CvType.CV_8UC3)            .apply { put(0, 0, pixels) }

И обратной конвертации Mat в BufferedImage:


val mat = ...var type = BufferedImage.TYPE_BYTE_GRAYif (mat.channels() > 1) {    type = BufferedImage.TYPE_3BYTE_BGR}val bufferSize = mat.channels() * mat.cols() * mat.rows()val b = ByteArray(bufferSize)mat[0, 0, b] // get all the pixelsval image = BufferedImage(mat.cols(), mat.rows(), type)val targetPixels = (image.raster.dataBuffer as DataBufferByte).dataSystem.arraycopy(b, 0, targetPixels, 0, b.size)

В частности, тот же tesseract в методе doOCR поддерживает как файл, так и BufferedImage. Используя вышеописанные преобразования, можно вначале обработать изображения с помощью OpenCV, преобразовать Mat в Bufferedimage и передать подготовленное изображение на вход tesseract.


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



Для начала проверим результат нахождения текста на изображении без обработки. И вместо метода doOCR будем использовать getWords, чтобы получить ещё confidence (score в Python-библиотеке) для каждого найденного слова:


val image = ImageIO.read(URL("http://img.ifcdn.com/images/b313c1f095336b6d681f75888f8932fc8a531eacd4bc436e4d4aeff7b599b600_1.jpg"))val result = api.getWords(preparedImage, ITessAPI.TessPageIteratorLevel.RIL_WORD)

В результате будет найден только разный мусор:


[ie, [Confidence: 2.014679 Bounding box: 100 0 13 14], bad [Confidence: 61.585358 Bounding box: 202 0 11 14], oy [Confidence: 24.619446 Bounding box: 21 68 18 22], ' [Confidence: 4.998787 Bounding box: 185 40 11 18], | [Confidence: 60.889648 Bounding box: 315 62 4 14], ae. [Confidence: 27.592728 Bounding box: 0 129 320 126], c [Confidence: 0.000000 Bounding box: 74 301 3 2], ai [Confidence: 24.988930 Bounding box: 133 283 41 11], ee [Confidence: 27.483231 Bounding box: 186 283 126 41]]

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


Пробуем следующие преобразования:


// convert to grayImgproc.cvtColor(mat, mat, Imgproc.COLOR_BGR2GRAY)// text -> white, other -> blackImgproc.threshold(mat, mat, 244.0, 255.0, Imgproc.THRESH_BINARY)// inverse Core.bitwise_not(mat, mat)

После них посмотрим на картинку в результате (которую можно сохранить в файл через Imgcodecs.imwrite("output.jpg", mat) )



Теперь если посмотреть на результаты вызова getWords, то получим следующее:


[WHEN [Confidence: 94.933418 Bounding box: 48 251 52 14], SHE [Confidence: 95.249252 Bounding box: 109 251 34 15], CATCHES [Confidence: 95.973259 Bounding box: 151 251 80 15], YOU [Confidence: 96.446579 Bounding box: 238 251 33 15], CHEATING [Confidence: 96.458656 Bounding box: 117 278 86 15]]

Как видно, весь текст успешно распознался.


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


import net.sourceforge.tess4j.ITessAPIimport net.sourceforge.tess4j.Tesseractimport nu.pattern.OpenCVimport org.opencv.core.Coreimport org.opencv.core.CvTypeimport org.opencv.core.Matimport org.opencv.imgproc.Imgprocimport java.awt.image.BufferedImageimport java.awt.image.DataBufferByteimport java.io.Fileimport java.net.URLimport javax.imageio.ImageIOfun main() {    setupOpenCV()    setupTesseract()    val image = ImageIO.read(URL("http://img.ifcdn.com/images/b313c1f095336b6d681f75888f8932fc8a531eacd4bc436e4d4aeff7b599b600_1.jpg"))    val mat = image.toMat()    Imgproc.cvtColor(mat, mat, Imgproc.COLOR_BGR2GRAY)    Imgproc.threshold(mat, mat, 244.0, 255.0, Imgproc.THRESH_BINARY)    Core.bitwise_not(mat, mat)    val preparedImage = mat.toBufferedImage()    val api = Tesseract()    api.setDatapath("/usr/local/share/tessdata/")    api.setLanguage("eng")    val result = api.getWords(preparedImage, ITessAPI.TessPageIteratorLevel.RIL_WORD)    println(result)}private fun setupTesseract() {    val libPath = "/usr/local/lib"    val libTess = File(libPath, "libtesseract.dylib")    if (libTess.exists()) {        val jnaLibPath = System.getProperty("jna.library.path")        if (jnaLibPath == null) {            System.setProperty("jna.library.path", libPath)        } else {            System.setProperty("jna.library.path", libPath + File.pathSeparator + jnaLibPath)        }    }}private fun setupOpenCV() {    OpenCV.loadLocally()}private fun BufferedImage.toMat(): Mat {    val pixels = (raster.dataBuffer as DataBufferByte).data    return Mat(height, width, CvType.CV_8UC3)        .apply { put(0, 0, pixels) }}private fun Mat.toBufferedImage(): BufferedImage {    var type = BufferedImage.TYPE_BYTE_GRAY    if (channels() > 1) {        type = BufferedImage.TYPE_3BYTE_BGR    }    val bufferSize = channels() * cols() * rows()    val b = ByteArray(bufferSize)    this[0, 0, b] // get all the pixels    val image = BufferedImage(cols(), rows(), type)    val targetPixels = (image.raster.dataBuffer as DataBufferByte).data    System.arraycopy(b, 0, targetPixels, 0, b.size)    return image}

Если сравнить полученный код с Python-версией, то разница будет минимальная. Производительность тоже должна быть практически сравнимой (за исключением, быть может, чуть больших преобразований изображения между Mat и BufferedImage).


Преимущество Python в рамках текущего примера будет только в бесшовной передаче изображений между OpenCV и tesseract. Экосистема Python сама по себе удобна тем, что все библиотеки общаются одними и теми же типами.


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


Python на текущий момент, беcспорно, лидер в ML. И в первую очередь все инструменты и библиотеки появляются на нём. Тем не менее, в других экосистемах можно использовать те же инструменты. Особенно учитывая, что если что-то есть под Python, то должна быть и нативная библиотека, которую можно легко подключить.


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


https://djl.ai/ Deep Learning на jvm, где можно подключать модели из pytorch и tensorflow
https://deeplearning4j.org/ аналогичное решение с возможностью обучать модели и импортировать существующие на tensorflow и keras
https://kotlinlang.org/docs/reference/data-science-overview.html разные полезные вещи по Data Science на Kotlin (и Java)


Весь код доступен в репозитории https://github.com/evgzakharov/kotlin_tesseract.

Подробнее..

Как построить надежное приложение на базе Event sourcing?

15.09.2020 14:04:30 | Автор: admin

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



The Project


Проект JoomAds предлагает продавцам инструменты продвижения товаров в Joom. Для продавца процесс продвижения начинается с создания рекламной кампании, которая состоит из:


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

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



Рис. 1


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


JoomAds API может изменять часть состояния при регистрации покупок успешно прорекламированных товаров, корректируя остаток бюджета рекламных кампаний (Рис. 1). Настройками кампаний управляет сервис кампаний JoomAds Campaign, метаданными продукта сервис Inventory, данные ранжирования расположены в хранилище аналитики (Рис. 2).


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



Рис. 2
JoomAds API выступает в роли медиатора данной микросервисной системы.


Pure Microservices equals Problems


Вы можете задать справедливый вопрос: Есть ли проблема в таком количестве внешних коммуникаций? Всего три вызова....


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


Быстродействие


Любые внешние коммуникации (например, поход за метаданными товара в Inventory) это дополнительные накладные расходы, увеличивающие время ответа медиатора. Такие расходы не проблема на ранних этапах развития проекта: последовательные походы в JoomAds Campaign, Inventory и хранилище аналитики вносили небольшой вклад во время ответа JoomAds API, т.к. количество рекламируемых товаров было небольшим, а рекламная выдача присутствовала только в разделе Лучшее.


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


Например, поиск товаров Inventory не был рассчитан на высокие частоты запросов, но он нам нужен именно таким.


Отказоустойчивость


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


Отказ любой зависимости JoomAds API ведет к некорректной или неповторяемой рекламной выдаче, либо к ее полному отсутствию.


Сложность поддержки


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


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


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


Эти наблюдения привели нас к осознанию необходимости изменений. Новый JoomAds должен генерировать экономически эффективную и согласованную рекламную выдачу при отказе JoomAds Campaign, Inventory или хранилища аналитики, а также иметь предсказуемое быстродействие и отвечать на входящие запросы быстрее 100 мс в 95% случаев.


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


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


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


Monolith over microservices (kind of)


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


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


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


Materialization


Совместить лучшие качества микросервисов и монолитной архитектуры нам позволил подход, именуемый Materialized View. Материализованные представления часто встречаются в реализациях СУБД. Основной целью их внедрения является оптимизация доступа к данным на чтение при выполнении конкретных запросов.


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


Например, для запросов состояния продукта по его идентификатору (см. Рис. 3) или запросов состояния множества продуктов по идентификатору рекламной кампании.



Рис. 3


Материализованное представление данных расположено во внутреннем хранилище JoomAds API, поэтому замыкание входящих коммуникаций на него положительно сказывается на производительности и отказоустойчивости системы, т.к. доступ на чтение теперь зависит только от доступности / производительности хранилища данных JoomAds, а не от аналогичных характеристик внешних ресурсов. JoomAds API является надежным монолитным приложением!


Но как обновлять данные Materialized View?


Data Sourcing


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


  • Изолировать клиентскую сторону от проблем доступа к внешним ресурсам.
  • Учитывать возможность высокого времени ответа компонентов инфраструктуры JoomAds.
  • Предоставлять механизм восстановления на случай утраты текущего состояния Materialized View.

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


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


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


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


Event Sourcing


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


В результате адаптации Event Sourcing подхода в инфраструктуре JoomAds появились три новых компонента: хранилище материализованного представления (MAT View Storage), конвейер материализации (Materialization Pipeline), а так же конвейер ранжирования (Ranking Pipeline), реализующий поточное вычисление потоварных score'ов ранжирования (см. Рис. 4).



Рис. 4


Discussion, Technologies


Materialized View и Event Sourcing позволили нам решить основные проблемы ранней архитектуры проекта JoomAds.


Специализированные Materialized View значительно повысили надежность и быстродействие клиентских запросов. Обновление данных с использованием Event Sourcing подхода повысило надежность коммуникации с внешними сервисами, предоставило инструменты контроля консистентности данных и позволило избавиться от неэффективных запросов к внешним ресурсам.


Однако у всех решений есть цена. Чем больше несовместимых классов запросов реализует ваше приложение, тем больше материализованных представлений вам требуется собрать. Такой подход увеличивает потребление ресурсов по памяти, системе хранения данных и CPU. Материализованные представления JoomAds располагаются в хранилище Apache Cassandra, поэтому процесс порождения новых представлений, удаления старых или модификации существующих можно назвать безболезненным.


В нашем случае MAT View целиком хранится в одной таблице Cassandra: добавление колонок в таблицы Cassandra безболезненная операция, удаление MAT View осуществляется удалением таблицы. Таким образом, крайне важно выбрать удачное хранилище для реализации Materialized View в вашем проекте.


Event Sourcing предъявляет серьезные требования к своим пользователям. Генерация событий изменения данных во всех интересующих подсистемах в заданном формате с возможностью установления хронологического порядка следования это сложная организационно-техническая задача, которую крайне трудно реализовать в середине жизненного цикла ПО. Можно назвать удачей, что хранилище данных Inventory уже имело функцию генерации событий на обновление метаданных продуктов.


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


Вместо этого мы воспользовались популярными open-source решениями, развивающимися при участии Apache Software Foundation: Apache Kafka и Apache Flink.


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


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


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


Takeaway


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


P.S. Этот пост был впервые опубликован в блоге Joom на vc, вы могли его встречать там. Так делать можно.

Подробнее..

Scala мертва?

22.09.2020 10:23:41 | Автор: admin

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

Предыстория:
мой основной бэкграунд - Java бэкенд. В какой-то момент стала интересна Scala. Я поработал около года в маленьком стартапе, где мы переписывали бэкенд с Python на Scala. Затем через некоторое время я начал искать варианты с переездом в цивилизованные страны и получил 4-5 офферов на Scala в нескольких Европейских странах и 1 оффер на Java Так я оказался в Австралии. И без Scala.

Сложно сказать, почему я сделал такой выбор, но естественно через некоторое время я задумался, а не стоит ли попытаться воплотить свою мечту и все же найти компанию, которая бы использовала Scala не только для Data-Science, но и для разработки бэкенда.

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

Здесь нужно сделать ремарку о том, что же из себя представляет рынок разработки в Австралии: здесь есть офисы нескольких крупных компаний типа Google, Amazon, Microsoft, есть Atlassian, который пытается быть на них похож, есть несколько заскорузлых монструозных банков (как везде) и огромная куча мелких (и не очень) стартапов. Как вы можете себе представить, почти никто из них не использует Scala вне контекста Data Science.

Компании можно разделить на 2 большие категории: устоявшиеся компании (в основном Java и, что меня очень сильно удивляет, dotNet, видимо это следствие как раз наличия офиса Microsoft и работы его маркетологов) и хипстерские стартапы с GoLang или NodeJS на бэкенде. Надо сказать, что здесь довольно сильно развито движение FullStack разработки, поэтому NodeJS очень популярен, так как позволяет фронтендерам писать бэкенд :)

На удивление, есть даже несколько (больше 1) компаний, пишущих на Clojure и Elixir. Kotlin тоже немного присутствует, но только совместно с Java.

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

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

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

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

Навороченный рантайм вытеснен Docker и Kubernetes, которые благодаря легкому автоскейлингу позволяют теоретически писать приложения даже без сборки мусора, просто перезапуская приложение каждые N минут. Вместо этого важными становятся: малый размер бинарника (здравствуй, GoLang), простота и предсказуемость (даже примитивность рантайма), интероперабельность с другими частями системы (здравствуй TypeScript и Swift на бэкенде).

В общем, к моему большому сожалению, похоже из всех JVM-based языков причины использовать на бэкенде остались только у Java, благодаря необходимости поддерживать огромное легаси и наличию большого количества разработчиков на рынке, ну и возможно Kotlin, благодаря Android (хотя как это связано с бэкендом я не придумал).

А теперь давайте сыграем в небольшую игру.

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

Раунд 1: Go vs Java

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

Раунд 2: Go vs Scala

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

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

Раунд 3: Java vs Scala

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

Раунд 4: Java vs Kotlin

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

Раунд 5: Kotlin vs Scala

См. Раунд 3.

Раунд 6: Scala/Java/Kotlin vs TypeScript

Если бы у меня был бэкграунд фронтендера, я бы, возможно, выбрал TypeScript. Ну и если бы я верил в то, что система, написанная FullStack-разработчиками может в принципе работать :)

Раунд 7: Go vs TypeScript

Ну тут просто супер очевидно, даже вообще без вариантов.

А что выбрали бы вы?

В качестве заключения.

В общем, резюмируя сказанное выше, повторю, что у меня складывается стойкое мнение, что микросервисы и Kubernetes постепенно убивают Java и другие языки на базе JVM. Понятно, что Java будет жить вечно, как Cobol, Kotlin будет жить в Android, а вот Scala, похоже, в Data Science. Благодаря современным технологиям становится совсем не важно, на чем написан твой бэкенд, если его можно быстро запускать/перезапускать/автоскейлить.

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

Подробнее..

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

08.09.2020 20:05:54 | Автор: admin

image


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


В языках вечно не хватает чего-то простого лямбда-функций, именованных объединений, кастомных примитивных типов. Я лезу в обсуждения на Stack Overflow, в Github и вижу, как разрабы жалуются им не хватает того же, чего и мне. Но обсуждения почти всегда заканчиваются одинаково: нужная фича не появится, потому что главный дизайнер языка и члены его команды нужной ее не считают.


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


Но сейчас я понимаю это полная чушь.


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


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


Как программист, я не могу сказать: "да, пользователи хотят эту фичу, но лично мне она не нравится поэтому ее не будет". Я не могу как Роб Пайк сказать своим пользователям-программистам, что дженерики, которые все очень хотят, будут в Go 2, а Go 2 будет когда будет. Роб Пайк задумал Go как очень простой язык. Дженерики по его мнению добавляют сложность, которая испортит код.


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


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


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


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


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


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


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




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


Когда я пишу на языке программирования я чувствую, что я делаю это по чужим правилам, к которым я непричастен и на которые не могу повлиять. Когда я программирую, я отдаю свою профессиональную жизнь на милость решениям Гослинга, Хейлсберга, Одерского, Бреслава, Сайма.


Если вы знакомы с работами ныне популярного Гуриева о современных диктатурах, то вы слышали, что "автократы нового формата стремятся выглядеть демократами". Попытки изобразить, что дизайн языка программирования находится в руках "сообщества" не проходят простейшую проверку логикой. Комитеты и форумы посвященные дизайну языка, которые "открыты" для предложений, в действительности служат ширмой для диктатуры основной команды разработки языка. Я для примера посмотрел на список основных контрибьюторов в репозитории KEEP (процесс эволюции и улучшения Kotlin), топ 10 человек там это члены основной команды Kotlin. Сами предложили сами реализовали.


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


Культ языка программирования, чаще всего, агрессивный. Я помню как в конце нулевых я пытался объяснить джавистам, что их язык отстал от C#. Вместо вдумчивых обсуждений нужны ли в языке лямбда-функции (C# 3.5 2007) я встречал агрессию, усмешки и снисходительные комментарии в духе: "мелкомягкие все скопировали у богов".


В Stack Overflow в вопросах вида как сделать в Java X именитые участники сообщества отвечают, что X вреден и делать так ни в коем случае не надо, а то что язык такого не позволяет это величайшее благо, ибо защищает программистов. Такие комментарии люто заплюсованы. И наоборот: заминусованы те, что находятся не в канве основной линии партии.


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


Видели ли вы когда-либо в книге уровня "Философия Java" (Брюса Эккеля) раздел хотя бы на пару страниц, в котором обсуждались бы возможные улучшения в языке? Хотя бы одним глазком посмотреть: как может быть или (не дай Бог) как должно быть. Были ли лямбда-функции такой уж инновационной концепцией, что сложно было предположить, как это могло бы выглядеть в Java? Или, к примеру, как может выглядеть null-safety в Java, хотя бы гипотетически?


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




Когда-то давно случай распорядился так, что Oak вытащили из мусорного ведра, назвали Java и влили в него беспрецедентные маркетинговые бюджеты со статьями в Wall Street Journal. Затем Java сопротивлялась добавлению "синтаксического сахара" около 10-ти лет (условно 2002-2012). Наконец-то в нее добавляют все то, что должно быть в хорошем языке программирования. Это не было каким-то вынужденным 10-ти летним простоем в дизайне языка, во время которого переписывали компилятор или решали другие объективные проблемы развития. Просто Sun и Oracle предпочитали нанимать юристов, а не разработчиков.


В C# только что добавили тип данных запись (record type), это заняло всего лишь 9 лет отсчитывая от релиза другого языка F# 2.0 (2010), в котором уже была очевидна полезность этих типов. Оба языка разрабатывались под одной крышей Microsoft. Трудно предположить, что в команде C# не заметили возможностей F#. Просто они решили не торопиться. Возможно года через 3-4 мы получим именованные объединения (tagged union или discriminated union) в C#. Почему так долго? А нет никаких причин просто дизайнерам языка так захотелось.


В Scala 3 (aka Dotty) добавляют новое ключевое слово enum. 14 лет выходили новые релизы этого языка начиная с 2006 года (релиз 2.0), но за это время создатели Scala не подумали сделать нормальные перечисляемые типы. Есть enumeratum, и различные другие "варианты", но сам факт добавления нового ключевого слова enum прямо говорит о том, что эти костыли не работают как надо. Если бы только мы не должны были ждать Dotty более 3 лет.




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


Спасибо пацанам из Мы обречены. Побывал у них гостем. Смотрите, там весело.
Подробнее..

Перевод Что нового в Java 15?

12.09.2020 14:11:18 | Автор: admin


Скрытые классы, изолированные классы, текстовые блоки, записи и EdDSA: в JDK 15 имеется много ценного.

Как гласит одно из моих любимых выражений, в Java 15 присутствует много богатого шоколадного добра. В новую версию (релиз 15 сентября 2020г.) включены 14 важных изменений (JEP), направленных на совершенствование JDK. В этой статье дается краткий обзор новинок на основе информации, содержащейся в JEP.

14 JEP можно разделить на пять групп. Для более подробного изучения смотрите документацию по каждому из них.

Новый функционал, захватывающий дух:
  • JEP 339: алгоритм цифровой подписи на основе кривой Эдвардса (EdDSA)
  • JEP 371: скрытые классы

Дополнения к существующему стандартному функционалу Java SE:
  • JEP 378: текстовые блоки
  • JEP 377: сборщик мусора типа Z (ZGC)
  • JEP 379: Shenandoah сборщик мусора с малым временем паузы

Улучшение устаревшего функционала Java SE:
  • JEP 373: переработка устаревшего DatagramSocket API

С нетерпением ждем новинок:
  • JEP 360: изолированные классы (предварительная версия)
  • JEP 375: сопоставление с образцом для instanceof (вторая предварительная версия)
  • JEP 384: записи (вторая предварительная версия)
  • JEP 383: API доступа к внешней памяти (вторая инкубаторная функция)

Удаление и прекращение поддержки:
  • JEP 372: удаление Nashorn JavaScript Engine
  • JEP 374: отключение и исключение тенденциозной блокировки
  • JEP 381: удаление портов Solaris и SPARC
  • JEP 385: исключение RMI активации при удалении


Новый функционал, захватывающий дух


Алгоритм цифровой подписи на основе кривой Эдвардса (JEP 339). Я буду первым, кто признает, что алгоритм цифровой подписи Edwards-Curve (EdDSA), немного выходит за рамки моих знаний о шифровании. Ладно, я вообще ничего про это не знаю. Однако, этот JEP разработан как платформо-независимая реализация EdDSA с лучшей производительностью, чем существующая реализация на языке C ECDSA.

В соответствии с документацией JDK, EdDSA современная схема подписи с эллиптической кривой, которая имеет несколько преимуществ по сравнению с существующими в JDK.

Цели реализации:
  1. Основная цель этого JEP реализация схемы подписи так, как это стандартизировано в RFC 8032. При этом новая схема не заменяет ECDSA.
  2. Разработка платформо-независимой реализации EdDSA с более высокой производительностью, чем существующая реализация ECDSA при той же степени безопасности. Например, EdDSA, использующий Curve25519 при ~126 битах защиты, должен быть таким же быстрым, как ECDSA, использующий кривую secp256r1 при ~128 битах защиты.
  3. Помимо этого, реализация не будет содержать секретных областей. Эти свойства важны для предотвращения сторонних атак.

Теперь вы знаете больше, чем я. В скором времени ожидайте статью в журнале Java Magazine, объясняющую EdDSA.

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

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

Релиз скрытых классов закладывает основу отказа от использования разработчиками нестандартного API sun.misc.Unsafe::defineAnonymousClass. Oracle намеревается исключить и удалить этот класс в будущем.

Дополнения к существующим стандартам Java SE


Текстовые блоки (JEP 378), пришедшие из Project Amber, после двух предварительных версий в JDK 13 и 14 окончательно зарелизились. Целью их создания стало желание разработчиков уменьшить трудности объявления и использования многострочных строковых литералов в Java.

Текстовые блоки автоматически форматируют строки предсказуемым образом, но если этого недостаточно, разработчик может взять форматирование на себя. Во второй версии предварительного функционала представлены две новые escape-последовательности для управления новыми строками и пробелами. Например, \<line-terminator> явно подавляет вставку символа новой строки.

Раньше, чтобы вывести одну длинную строку текста, вам пришлось бы написать так:



Использование \<line-terminator> упрощает чтение кода:



Управляющая последовательность \s может предотвратить удаление конечных пробелов. Таким образом, следующий текст представляет три строки, каждая из которых состоит ровно из пяти символов. (В средней строке, соответствующей зеленому цвету, \s не используется, потому что в ней и так пять символов.)



Сборщик мусора типа Z (JEP 377) был представлен в JDK 11 в качестве экспериментального функционала. Теперь он получил официальный статус. ZGC это параллельный, поддерживающий NUMA, масштабируемый сборщик мусора с малой задержкой, предназначенный для предоставления пауз при сборке мусора менее 10 миллисекунд даже в многотерабайтных кучах. Среднее время паузы, согласно тестам Oracle, составляет менее 1 миллисекунды, а максимальное менее 2 миллисекунд. На рисунке 1 показано сравнение параллельного сборщика мусора Java, G1 и ZGC, при этом время паузы ZGC увеличено в 10 раз.

image

Рисунок 1. Сравнение времени паузы сборщиков мусора

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

Важно: поскольку ZGC больше не является экспериментальным, вам не нужно использовать
-XX:+UnlockExperimentalVMOptions для его использования.

И хотя я говорю о сборщиках мусора со сверхнизким временем паузы, Shenandoah (JEP 379) теперь является стандартной функцией JDK 15. Он был экспериментальным, начиная с JDK 12. Теперь, как и в случае с ZGC, вам не нужно использовать -XX:+UnlockExperimentalVMOptions.

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

Модернизация устаревшего функционала Java SE


Переработка устаревшего DatagramSocket API (JEP 373). Считайте, что это в основном рефакторинг некоторого кода юрского периода, поскольку этот JEP заменяет старые, трудно поддерживаемые реализации API-интерфейсов java.net.DatagramSocket и java.net.MulticastSocket более простыми и современными реализациями, которые легко поддерживать и отлаживать, и которые будут работать с виртуальными потоками Project Loom.

Поскольку существует очень много кода, использующего старый API (c JDK 1.0), устаревшая реализация не будет удалена. Фактически, новое специфичное для JDK системное свойство, jdk.net.usePlainDatagramSocketImpl, настраивает JDK для использования устаревшей реализации, если рефакторинг API вызывает проблемы при регрессионных тестах или в некоторых случаях.

С нетерпением ждем новинок


Изолированные классы (JEP 360). Первая предварительная версия, созданную в Project Amber. Изолированные классы и интерфейсы ограничивают их расширение или реализацию другими классами. Почему это важно? Разработчик может захотеть управлять кодом, который отвечает за реализацию определенного класса или интерфейса. Изолированные классы предоставляют более декларативный способ, чем модификаторы доступа для ограничения использования суперкласса. Например:



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

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

Сопоставление с образцом для instanceof (JEP 375). Вторая предварительная версия еще одна разработка Project Amber. Первая предварительная версия функционала была в Java 14 и по сравнению с ней нет никаких изменений.

Цель улучшить Java за счет сопоставления с образцом для оператора instanceof. Это позволяет более кратко и безопасно выразить общую логику в программе, а именно условное извлечение компонентов из объектов. Позвольте мне отослать вас к отличной статье Мала Гупта Сопоставление с образцом для instanceof в Java 14 в качестве примера.

Записи (JEP 384), вторая предварительная версия. Записи это классы, которые действуют как прозрачные носители неизменяемых данных. Новый JEP включает уточнения, основанные на отзывах сообщества, и поддерживает несколько новых дополнительных форм локальных классов и интерфейсов. Записи также поступают от Project Amber.

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

Доступ к внешней памяти (JEP 383) это вторая инкубаторная версия API, которая позволяет программам Java безопасно и эффективно обращаться к внешней памяти вне кучи Java. Цель состоит в том, чтобы начать замену java.nio.ByteBuffer и sun.misc.Unsafe. Это часть проекта Panama, который улучшает связь между Java и другими API.

В JEP метко описывается необходимость этого нововведения следующим образом:

Когда дело доходит до доступа к внешней памяти, разработчики сталкиваются с дилеммой должны ли они выбрать безопасный, но ограниченный (и, возможно, менее эффективный) путь, такой как ByteBuffer API, или они должны отказаться от гарантий безопасности и принять опасный и неподдерживаемый Unsafe API?

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

Удаление и прекращение поддержки


Ни одно из этих решений не должно вызывать споров.

Удаления движка Nashorn JavaScript (JEP 372). Данный движок, его API и инструмент jjs были объявлены устаревшими еще в Java 11. Пришло время попрощаться.

Отключение и исключение тенденциозной блокировки (JEP 374) позволяет избавиться от старого метода оптимизации, используемого в JVM HotSpot для уменьшения накладных расходов, связанных с неконтролируемой блокировкой. Данная блокировка исторически приводила к значительному повышению производительности по сравнению с обычными методами блокировки, но прирост производительности, наблюдаемый в прошлом, сегодня гораздо менее очевиден. Стоимость выполнения атомарных инструкций на современных процессорах снизилась.

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

Удаление портов Solaris и SPARC (JEP 381). В данном случае удалению подлежит весь исходный код, специфичный для операционной системы Solaris и архитектуры SPARC. Больше нечего сказать.

Исключение RMI активации при удалении (JEP 385). Упрощает Java, удаляя устаревшую часть вызова удаленных методов, которая стала опциональной, начиная с Java 8.

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

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

Заключение


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

Добавляем ORM в проект за четыре шага

17.09.2020 00:18:21 | Автор: admin

Представим, что вашему проекту срочно понадобился ORM, и вы хотите внедрить его как можно быстрее. В этой статье я хочу рассказать, как это можно сделать всего за четыре шага на примере использования open source проекта Apache Cayenne.


Для начала вкратце опишу механизм работы с данной библиотекой. Схема базы данных и модели описывается в xml файле, который может быть сгенерирован через GUI приложение или через консоль. Затем на основе xml файла генерируются java объекты, которые являются соответствующим отображением таблиц в базе. Последним шагом создается ServerRuntime объект, который инкапсулирует в себе весь стек Apache Cayenne.
Итак, перейдем к примеру. Что необходимо сделать:


  • Создать схему базы данных
  • Импортировать схему в проект, то есть получить xml файлы с описанием схемы
  • Создать объектную модель, то есть сгенерировать java классы
  • Проинициализировать ServerRuntime для доступа к базе данных из приложения

Что потребуется для начала? Уже существующий maven или gradle проект, Java 1.8+ и база данных. Мой тестовый проект использует maven, java 14 и самую свежую версию Apache Cayenne 4.2.M1. В качестве базы я использую mysql. Вы для своих проектов можете использовать стабильную версию 4.1 и любую из известных реляционных баз на ваш выбор.
Для наглядности я прикреплю ссылку на пример.


Создание схемы


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


CREATE SCHEMA IF NOT EXISTS cars_demo; USE cars_demo;CREATE TABLE car_brand (ID INT NOT NULL AUTO_INCREMENT, NAME VARCHAR(200) NULL, COUNTRY VARCHAR(200) NULL, PRIMARY KEY (ID)) ENGINE=InnoDB;CREATE TABLE car_model (ID INT NOT NULL AUTO_INCREMENT, NAME VARCHAR(200) NULL, CAR_BRAND_ID INT NULL, PRIMARY KEY (ID)) ENGINE=InnoDB;CREATE TABLE feedback (CAR_MODEL_ID INT NULL, ID INT NOT NULL AUTO_INCREMENT, FEEDBACK VARCHAR(200) NULL, PRIMARY KEY (ID)) ENGINE=InnoDB;ALTER TABLE car_model ADD FOREIGN KEY (CAR_BRAND_ID) REFERENCES car_brand (ID) ON DELETE CASCADE;ALTER TABLE feedback ADD FOREIGN KEY (CAR_MODEL_ID) REFERENCES car_model (ID) ON DELETE CASCADE;

Первый шаг пройден, двигаемся ко второму.


Импорт схемы


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


            <plugin>                <groupId>org.apache.cayenne.plugins</groupId>                <artifactId>cayenne-maven-plugin</artifactId>                <version>${cayenne.version}</version>                <configuration>                    <dataSource> <!--1-->                        <driver>com.mysql.jdbc.Driver</driver>                         <url>jdbc:mysql://127.0.0.1:3306/cars_demo</url>                         <username>root</username>                         <password>root</password>                    </dataSource>                    <cayenneProject>${project.basedir}/src/main/resources/cayenne/cayenne-project.xml</cayenneProject> <!--2-->                    <map>${project.basedir}/src/main/resources/cayenne/datamap.map.xml</map> <!--3-->                    <dbImport> <!--4-->                        <defaultPackage>cayenne.note.project.model</defaultPackage>                        <catalog>cars_demo</catalog>                    </dbImport>                </configuration>                <dependencies>                    <dependency> <!--5-->                        <groupId>mysql</groupId>                        <artifactId>mysql-connector-java</artifactId>                        <version>${mysql.version}</version>                    </dependency>                </dependencies>            </plugin>

  • (1) DataSource, для подключения к базе
  • (2) Путь, где будет лежать сгенерированный xml, который необходим для запуска Cayenne
  • (3) Путь, где будет лежать xml с описанием модели и базы
  • (4) Базовый пакет, где позже будут находиться сгенерированные классы
  • (5) Зависимость от mysql-connector для работы с mysql

Далее в консоли запускаем импорт модели:


mvn cayenne:cdbimport

После выполнения этой команды должны появится два файла, указанные в (2) и (3). Как я уже говорил, файл cayenne-project.xml является служебным файлом, необходимым для работы библиотеки. Файл datamap.map.xml это описание модели базы данных и ее объектного отображения, а также всех связей.


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


Генерация классов


В предыдущем пункте мы сгенерировали описание модели, теперь же нам необходимо сгенерировать java классы, которые можно будет использовать в проекте. Сделать это очень просто, достаточно просто запустить в консоли команду:


mvn cayenne:cgen

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


Пример использования


Мы на финишной прямой, осталось только привести пример использования Apache Cayenne.
Создадим ServerRuntime это основной стэк Cayenne, который создается один раз для всего проекта.
Из рантайма всегда можно получить ObjectContext объект, который используется для работы с базой данных.


ServerRuntime cayenneRuntime = ServerRuntime.builder()                .dataSource(DataSourceBuilder                        .url("jdbc:mysql://127.0.0.1:3306/cars_demo")                        .driver("com.mysql.cj.jdbc.Driver")                        .userName("root") // Need to change to your username                        .password("root") // Need to change to your password                        .build())                .addConfig("cayenne/cayenne-project.xml")                .build();        ObjectContext context = cayenneRuntime.newContext();

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


CarBrand carBrand = context.newObject(CarBrand.class);carBrand.setName("BMW");carBrand.setCountry("Germany");CarModel carModel = context.newObject(CarModel.class);carModel.setName("i3");carModel.setCarBrand(carBrand);Feedback feedback = context.newObject(Feedback.class);feedback.setFeedback("Like");feedback.setCarModel(carModel);context.commitChanges();

Как видно, мы создаем объекты при помощи ObjectContext, затем модифицируем их и фиксируем изменения при помощи context.commitChanges().


Для выборки сущностей можно использовать API на любой вкус от чистого sql и ejbql до хорошо читаемого API. Полное описание можно найти в документации.
Небольшой пример обычного селекта из базы с использованием Apache Cayenne:


List<CarBrand> carBrans = ObjectSelect.query(CarBrand.class).select(context);

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

Подробнее..

Из песочницы Ищем утечки памяти с помощью Eclipse MAT

19.09.2020 16:20:36 | Автор: admin
Пожалуй, все java-разработчики, участвующие в коммерческих проектах рано или поздно сталкиваются с проблемой утечки памяти, влекущей за собой медленную работу приложения и почти неизбежно приводящую в итоге к известной OutOfMemoryError. В данной статье будет рассмотрен реальный пример такой ситуации и способ поиска ее причины с помощью Eclipce Memory Analizer.

Введение


Утечкой памяти принято называть ситуацию, когда количество занятой памяти в куче растет при длительной работе приложения и не уменьшается после завершения работы Garbage Collector-а. Как известно, память jvm делится на кучу и стек. В стеке кэшируются значения переменных простых типов и ссылок на объекты в разрезе потока, а в куче хранятся сами объекты. Также в куче есть пространство, именуемое Metaspace, где хранятся данные о загруженных классах и данные привязанные к самим классам, а не их экземплярам, в частности, значения статических переменных. Периодически запускаемый java-машиной Garbage Collector (далее GC) находит в куче объекты, на которые больше нет ссылок и освобождает память, занимаемую этими объектами. Алгоритмы работы GC разные и сложные, в частности, при очередном запуске GC не каждый раз исследует всю кучу на предмет поиска неиспользуемых объектов, поэтому рассчитывать на то, что какой-либо больше неиспользуемый объект, будет удален из памяти после одного запуска GC не стоит, но если объем памяти, используемой приложением неуклонно растет без видимых причин длительное время, то пора задуматься о том, что могло привести к такой ситуации.

В состав jvm входит многофункциональная утилита Visual VM (далее VM). VM позволяет визуально на графиках наблюдать в динамике ключевые показатели jvm, в частности, объем свободной и занятой памяти в куче, количество загруженных классов, потоков и.т.д. Кроме этого, с помощью VM можно снимать и исследовать дампы памяти. Разумеется, VM позволяет также снимать дампы потоков, и осуществлять профилирование приложения, однако обзор этих функций выходит за рамки данной статьи. Все что нам нужно от VM в данном примере, это подключиться к виртуальной машине и посмотреть для начала на общую картинку использования памяти. Хотелось бы отметить, что для подключения VM к удаленному серверу требуется, чтобы на нем были настроены параметры jmxremote, так как подключение осуществляется через jmx. За описанием данных параметров можно обратиться к официальной документации Oracle или многочисленным статьям на Хабре.

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



На вкладке Heap можно увидеть общий объем и объем занятой памяти jvm. Нужно отметить, что на данной вкладке учитывается и память типа Metaspace (ну, а как иначе, ведь это тоже куча). На вкладке Metaspace отображается информация только по памяти занимаемой метаданными (самими классами и объектами к ним привязанными).

Взглянув на график мы видим, что общий объем памяти heap составляет ~10Гб, текущий занятый объем ~5.8Гб. Гребни на графике соответствуют вызовам GC, почти прямая линия (без гребней), начиная примерно с 10:18 может (но не обязательно!) говорить о том, что с этого времени сервер приложений почти не работал, так как не было активного выделения и освобождения памяти. Вообще, данный график соответствует нормальной работе сервера приложений (если, конечно, судить о работе только по памяти). Проблемным графиком был бы такой, где прямая горизонтальная синяя линия без гребней, проходила бы примерно на уровне оранжевой, обозначающей максимальный объем памяти в куче.

Теперь взглянем на другой график.



Вот тут мы подходим непосредственно к разбору примера, являющегося основной темой настоящей статьи. На графике Classes отображается количество загруженных в Metaspace классов, и оно составляет ~ 73 тысячи объектов. Хотелось бы обратить внимание, что речь не об экземплярах классов, а о самих классах, то есть объектах типа Class<?>. Из графика не видно, по сколько же экземпляров каждого отдельного типа ClassА или ClassB загружено в память. Возможно, количество одинаковых классов типа ClassA по какой-то причине кратно увеличивается? Должен сказать, что в примере о котором будет рассказано ниже 73000 уникальных классов являлось абсолютно нормальной ситуацией.

Дело в том, что в одном из проектов, в которых принимал участие автор данной статьи, был разработан механизм универсального описания сущностей предметной области (как например в 1С) названный словарной системой, и аналитики, настраивающие систему под конкретного заказчика или под конкретную бизнес область, имели возможность через специальный редактор моделировать бизнес модель создавая новые и изменяя существующие сущности, оперируя не на уровне таблиц, а такими понятиями как Документ, Счет, Сотрудник и.т.д. Ядро системы создавало для данных сущностей таблицы в реляционной СУБД, причем для каждой сущности могло создаваться несколько таблиц, так как универсальная система позволяла исторично хранить значения атрибутов и еще много чего требующего создания дополнительных служебных таблиц в БД.

Полагаю, что те, кому приходилось работать с ORM-фреймворками уже догадались к чему автор, отвлекся от основной темы статьи на разговор о таблицах. В проекте использовался Hibernate и для каждой таблицы должен был существовать класс Entity-бина. При этом, поскольку, новые таблицы создавалась динамически в процессе работы системы аналитиками, то классы бинов Hibernate генерировались, а не писались вручную разработчиками. И при каждой очередной генерации создавалось около 50-60 тысяч новых классов. Таблиц в системе было значительно меньше (примерно 5-6 тыс), но для каждой таблицы генерировался не только класс Entity-бина, а еще много вспомогательных классов, что в итоге приводило к общей цифре.

Механизм работы был следующий. При старте системы на основании метаданных в БД генерировались классы Entity-бинов и вспомогательные классы (далее просто классы бинов). При работе системы фабрика сессий Hibernate создавала сессии, сессии создавали экземпляры объектов классов бинов. При изменении структуры (добавлении, изменении таблиц) происходила перегенерация классов бинов и создание новой фабрики сессий. После перегенерации новая фабрика создавала новые сессии, которые использовали уже новые классы бинов, старая фабрика и сессии закрывались, а старые классы бинов выгружались GC, так как на них больше не было ссылок из объектов инфраструктуры Hibernate.





В какой-то момент появилась проблема, что количество классов бинов стало увеличиваться после каждой очередной перегенерации. Очевидно, что это происходило из-за того, что старый набор классов, который уже не должен был использоваться, по какой-то причине не выгружался из памяти. Для того чтобы разобраться в причинах такого поведения системы нам пришел на помощь Eclipse Memory Analizer (MAT).

Поиск утечки памяти


MAT умеет работать с дампами памяти находя в них потенциальные проблемы, но для начала этот дамп памяти нужно получить, а в реальных средах с получением дампа есть определенные нюансы.

Снятие дампа памяти


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



Но, из-за большого размера дампа VM может просто не справиться с этой задачей, зависнув через некоторое время после нажатия кнопки Heap Dump. Кроме того, совсем не факт, что будет возможно подключение по jmx к продуктовому серверу приложений, необходимое для VM. В этом случае на помощь нам приходит другая утилита из состава jvm, которая называется jMap. Она запускается в командной строке, непосредственно на сервере где работает jvm, и позволяет задать дополнительные параметры снятия дампа:

jmap -dump:live,format=b,file=/tmp/heapdump.bin 14616

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

Другая распространенная ситуация, это когда снятие дампа вручную не представляется возможным из-за того, что падает сама jvm с OutOfMemoryError. В этой ситуации на помощь приходит опция -XX:+HeapDumpOnOutOfMemoryError и в дополнение к ней -XX:HeapDumpPath, позволяющая указать путь к снятому дампу.

Далее, снятый дамп открываем с помощью Eclipse Memory Analizer. Файл может быть большим по объему (несколько гигабайтов), поэтому надо предусмотреть достаточное количество памяти в файле MemoryAnalyzer.ini:

-Xmx4096m

Локализация проблемы с использованием MAT


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

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

  • Закрываются все Hibernate-сессии (класс SessionImpl)
  • Закрывается старая фабрика сессий (SessionFactoryImpl) и обнуляется ссылка на нее из LocalSessionFactoryBean
  • Пересоздается ClassLoader
  • Обнуляются ссылки на старые классы бинов в классе-генераторе
  • Происходит перегенерация классов бинов

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

Запускаем MAT и открываем полученный ранее файл дампа памяти. После открытия дампа MAT показывает самые большие по объему цепочки объектов в памяти.



После нажатия Leak Suspects видим подробности:

2 сегмента круга по 265 M это 2 экземпляра SessionFactoryImpl. Непонятно почему их 2 экземпляра и, скорее всего, каждый из экземпляров держит ссылки на полный набор классов Entity-бинов. О потенциальных проблемах MAT сообщает нам следующим образом.



Сразу отмечу, что Problem Suspect 3 на самом деле проблемой не является. В проекте был реализован парсер собственного языка, являющегося мультиплатформенной надстройкой над SQL и позволяющего оперировать не таблицами, а сущностями системы, а 121M занимает кэш его запросов.

Вернемся к двум экземплярам SessionFactoryImpl. Нажимаем Duplicate Classes и видим, что действительно каждого класса Entity-бина по 2 экземпляра. То есть ссылки на старые классы Entity-бинов остались и, скорее всего, это ссылки из SesssionFactoryImpl. Судя по исходному коду этого класса ссылки на классы бинов должны храниться в поле classMetaData.

Щелкаем на Problem Suspect 1, далее на класс SessionFactoryImpl и по контекстному меню выбираем List Objects-> With outgouing references. Таким образом мы можем увидеть все объекты на которые ссылается SessionFactoryImpl.



Раскрываем объект classMetaData и убеждаемся, что действительно в нем хранится массив классов Entity-бинов.



Теперь нам нужно понять, что не позволяет сборщику мусора удалить один экземпляр SessionFactoryImpl. Если вернуться к пункту Leak Suspects-> Leaks-> Problem Suspect 1, то мы увидим стек ссылок, который приводит к ссылке на SessionFactoryImpl.



Видим, что в классе EntityManager есть массив dbTransactionListeners, который использует объекты сессий SessionImpl в качестве ключей, а сессии ссылаются на SessionFactoryImpl.

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

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

Возможности Eclipse Memory Analizer



Итак, Eclipse Memory Analiser позволяет:

  • Узнать о том какие цепочки объектов занимают максимальный объем памяти и определить точки входа в эти цепочки (Leak Suspects)
  • Посмотреть дерево всех входящих ссылок на объект (Shortest Paths To the accumulation point)
  • Посмотреть дерево всех исходящих ссылок объекта (Объект-> List Objects->With outgouing references)
  • Увидеть дубликаты классов, загруженные разными ClassLoader-ами (Duplicate Classes)
Подробнее..

Перевод Знакомимся с Event Sourcing. Часть 2

24.09.2020 16:22:36 | Автор: admin
Перевод статьи подготовлен в преддверии старта курса Java Developer. Professional.
Читать первую часть.



Особенности реализации Event Sourcing


С технической точки зрения для Event Sourcing требуется только реализация записи событий в журнал и чтения из журнала.

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

Журнал событий (event log) очень распространенный паттерн, используемый совместно с системами обмена сообщениями (Message broker, Message-oriented middleware) и системами обработки потоков событий. Брокер сообщений, используемый как журнал событий, при необходимости может хранить всю историю сообщений.

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

В более сложных Event Sourcing-системах должны присутствовать производные хранилища состояния для эффективных запросов на чтение, так как получение текущего состояния через обработку всего журнала событий со временем может перестать масштабироваться. И реляционные, и документные БД могут использоваться и как журнал событий и как хранилище производных сущностей, через которые можно быстро получить текущее состояние. Фактически такое разделение задач представляет собой CQRS (Command Query Responsibility Segregation, разделение ответственности на команды и запросы). Все запросы направляются в производное хранилище, что позволяет оптимизировать его независимо от операций записи.

Помимо технической части есть и другие моменты, на которые стоит обратить внимание.

Потенциальные проблемы Event Sourcing


Несмотря на преимущества Event Sourcing, у него есть и недостатки.

Самые большие сложности обычно связаны с мышлением разработчиков. Разработчики должны выйти за рамки обычных CRUD-приложений и хранилищ сущностей. Теперь основной концепцией должны стать события.

При Event Sourcing много сил тратится на моделирование событий. После записи событий в журнал они должны считаться неизменными, иначе, и история и состояние могут быть повреждены или искажены. Журнал событий это исходные данные, а это значит, что необходимо очень внимательно следить за тем, чтобы они содержали всю информацию, необходимую для получения полного состояния системы на определенный момент времени. Также необходимо учитывать, что события могут интерпретироваться повторно, поскольку система (и бизнес, который она представляет) со временем изменяются. И не надо забывать про ошибочные и подозрительные события с корректной обработкой валидации данных.

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

Event Sourcing может хорошо работать в больших системах, так как паттерн журнал событий естественным образом масштабируется горизонтально. Например, журнал событий одной сущности необязательно должен физически находиться вместе с журналом событий другой сущности. Однако, такая легкость масштабирования приводит к дополнительным проблемам в виде асинхронной обработки и согласования данных в конечном счете (eventually consistent). Команды на изменение состояния могут приходить на любой узел, после чего системе необходимо определить, какие узлы отвечают за соответствующие сущности и направить команду на эти узлы, после чего обработать команду, а затем реплицировать сгенерированные события на другие узлы, где хранятся журналы событий. И только после завершения этого процесса новое событие становится доступным как часть состояния системы. Таким образом, Event Sourcing фактически требует, чтобы обработка команд была отделена от запроса состояния, то есть CQRS.

Поэтому Event Sourcing-системам необходимо учитывать промежуток времени между выдачей команды и получением уведомления об успешной записи события в журнал. Состояние системы, которое пользователи видят в это время, может быть неправильным. Или, точнее, немного устаревшим. Для уменьшения влияния этого фактора необходимо его учитывать при проектировании интерфейса пользователя и в других компонентах. Также необходима правильная обработка ситуаций, когда команда завершается ошибкой, отменяется в процессе выполнения или одно событие заменяется более новым при корректировке данных.

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

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

Выводы


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

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

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

Читать первую часть

Подробнее..

10 Kubernetes-инструментов из разряда важно, шпаргалка по созданию Kubernetes-операторов на Java и многое другое

10.09.2020 12:19:55 | Автор: admin


Прокачивайте скилы, читайте, смотрите, думайте, применяйте на практике! Станьте частью DevNation!

Начни новое:



Качай (это легально):



Почитать на досуге:



Мероприятия:



По-русски:


Мы продолжаем серию пятничных вебинаров про нативный опыт использования Red Hat OpenShift Container Platform и Kubernetes. Регистрируйтесь и приходите:

Император Оператор: Операторы в OpenShift и Kubernetes

Упс, вебинар прошел, но есть запись.

Подробнее..

Перевод 3 года с Kubernetes в production вот что мы поняли

21.09.2020 12:22:00 | Автор: admin
Прим. перев.: в очередной статье из категории lessons learned DevOps-инженер австралийской компании делится главными выводами по итогам продолжительного использования Kubernetes в production для нагруженных сервисов. Автор затрагивает вопросы Java, CI/CD, сетей, а также сложности K8s в целом.

Свой первый кластер Kubernetes мы начали создавать в 2017 году (с версии K8s 1.9.4). У нас было два кластера. Один работал на bare metal, на виртуальных машинах RHEL, другой в облаке AWS EC2.

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

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

Вот ключевые уроки, которые мы вынесли из опыта использования Kubernetes в production на протяжении трех лет.

1. Занимательная история с Java-приложениями


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

В 20172018 годах некоторые наши приложения работали на Java восьмой версии. Частенько они отказывались функционировать в контейнерных средах вроде Docker и падали из-за проблем с heap-памятью и неадекватной работы сборщиков мусора. Как оказалось, эти проблемы были вызваны неспособностью JVM работать с механизмами контейнеризации Linux (cgroups и namespaces).

С тех пор Oracle приложила значительные усилия, чтобы повысить совместимость Java с миром контейнеров. Уже в 8-ой версии Java появились экспериментальные флаги JVM для решения этих проблем: XX:+UnlockExperimentalVMOptions и XX:+UseCGroupMemoryLimitForHeap.

Но, несмотря на все улучшения, никто не будет спорить, что у Java по-прежнему плохая репутация из-за чрезмерного потребления памяти и медленного запуска по сравнению с Python или Go. В первую очередь это связано со спецификой управления памятью в JVM и ClassLoader'ом.

Сегодня, если нам приходится работать с Java, мы по крайней мере стараемся использовать версию 11 или выше. И наши лимиты на память в Kubernetes на 1 Гб выше, чем ограничение на максимальный объем heap-памяти в JVM (-Xmx) (на всякий случай). То есть, если JVM использует 8 Гб под heap-память, лимит в Kubernetes на память для приложения будет установлен на 9 Гб. Благодаря этим мерам и улучшениям жизнь стала чуточку легче.

2. Обновления, связанные с жизненным циклом Kubernetes


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

Дело в том, что в Kubernetes слишком много движущихся частей, которые необходимо учитывать при проведении обновлений. Для того, чтобы кластер мог работать, приходится собирать все эти компоненты вместе начиная с Docker и заканчивая CNI-плагинами вроде Calico или Flannel. Такие проекты, как Kubespray, KubeOne, kops и kube-aws, несколько упрощают процесс, однако все они не лишены недостатков.

Свои кластеры мы разворачивали в виртуальных машинах RHEL с помощью Kubespray. Он отлично себя зарекомендовал. В Kubespray были сценарии для создания, добавления или удаления узлов, обновления версии и почти все, что необходимо для работы с Kubernetes в production. При этом сценарий обновления сопровождался предостережением о том, что нельзя пропускать даже второстепенные (minor) версии. Другими словами, чтобы добраться до нужной версии, пользователю приходилось устанавливать все промежуточные.

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

3. Сборка и деплой


Будьте готовы к тому, что придется пересмотреть пайплайны сборки и деплоя. При переходе на Kubernetes у нас прошла радикальная трансформация этих процессов. Мы не только реструктурировали пайплайны Jenkins, но с помощью инструментов, таких как Helm, разработали новые стратегии сборки и работы с Git'ом, тегирования Docker-образов и версионирования Helm-чартов.

Вам понадобится единая стратегия для поддержки кода, файлов с deploymentами Kubernetes, Dockerfiles, образов Docker'а, Helm-чартов, а также способ связать все это вместе.

После нескольких итераций мы остановились на следующей схеме:

  • Код приложения и его Helm-чарты находятся в разных репозиториях. Это позволяет нам версионировать их независимо друг от друга (семантическое версионирование).
  • Затем мы сохраняем карту с данными о том, какая версия чарта к какой версии приложения привязана, и используем ее для отслеживания релиза. Так, например, app-1.2.0 развертывается с charts-1.1.0. Если меняется только файл с параметрами (values) для Helm, то меняется только patch-составляющая версии (например, с 1.1.0 на 1.1.1). Все требования к версиям описываются в примечаниях к релизу (RELEASE.txt) в каждом репозитории.
  • К системным приложениям, таким как Apache Kafka или Redis (чей код мы не собирали и не модифицировали), у нас иной подход. Нам не были нужны два репозитория, поскольку Docker-тег был просто частью версионирования Helm-чартов. Меняя Docker-тег для обновления, мы просто увеличиваем основную версию в теге чарта.

(Прим. перев.: сложно пройти мимо такой трансформации и не указать на нашу Open Source-утилиту для сборки и доставки приложений в Kubernetes werf как один из способов упростить решение тех проблем, с которыми столкнулись авторы статьи.)

4. Тесты Liveliness и Readiness (обоюдоострый меч)


Проверки работоспособности (liveliness) и готовности (readiness) Kubernetes отлично подходят для автономной борьбы с системными проблемами. Они могут перезапускать контейнеры при сбоях и перенаправлять трафик с нездоровых экземпляров. Но в некоторых условиях эти проверки могут превратиться в обоюдоострый меч и повлиять на запуск и восстановление приложения (это особенно актуально для stateful-приложений, таких как платформы обмена сообщениями или базы данных).

Наш Kafka стал их жертвой. У нас был stateful set из 3 Broker'ов и 3 Zookeeper'ов с replicationFactor = 3 и minInSyncReplica = 2. Проблема возникала при перезапуске Kafka после случайных сбоев или падений. Во время старта Kafka запускал дополнительные скрипты для исправления поврежденных индексов, что занимало от 10 до 30 минут в зависимости от серьезности проблемы. Такая задержка приводила к тому, что liveliness-тесты постоянно завершались неудачей, из-за чего Kubernetes убивал и перезапускал Kafka. В результате Kafka не мог не только исправить индексы, но даже стартовать.

Единственным решением на тот момент виделась настройка параметра initialDelaySeconds в настройках liveliness-тестов, чтобы проверки проводились только после запуска контейнера. Главная сложность, конечно, в том, чтобы решить, какую именно задержку установить. Отдельные запуски после сбоя могут занимать до часа времени, и это необходимо учитывать. С другой стороны, чем больше initialDelaySeconds, тем медленнее Kubernetes будет реагировать на сбои во время запуска контейнеров.

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

Обновление: в свежих версиях Kubernetes появился третий тип тестов под названием startup probe. Он доступен как альфа-версия, начиная с релиза 1.16, и как бета-версия с 1.18.

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

5. Работа с внешними IP


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

В своем кластере мы используем Calico как CNI и BGP в качестве протокола маршрутизации, а также для взаимодействия с пограничными маршрутизаторами. В Kube-proxy задействован режим iptables. Доступ к нашему очень загруженному сервису в Kubernetes (ежедневно он обрабатывает миллионы подключений) открываем через внешний IP. Из-за SNAT и маскировки, проистекающих от программно-определяемых сетей, Kubernetes нуждается в механизме отслеживания всех этих логических потоков. Для этого K8s задействует такие инструменты ядра, как сonntrack и netfilter. С их помощью он управляет внешними подключениями к статическому IP, который затем преобразуется во внутренний IP сервиса и, наконец, в IP-адрес pod'а. И все это делается с помощью таблицы conntrack и iptables.

Однако возможности таблицы conntrack небезграничны. При достижении лимита кластер Kubernetes (точнее, ядро ОС в его основе) больше не сможет принимать новые соединения. В RHEL этот предел можно проверить следующим образом:

$  sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_maxnet.netfilter.nf_conntrack_count = 167012net.netfilter.nf_conntrack_max = 262144

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

Это полностью сбило нас с толку, когда мы только начинали в 2017 году. Однако сравнительно недавно (в апреле 2019-го) проект Calico опубликовал подробное исследование под метким названием Why conntrack is no longer your friend (есть такой её перевод на русский язык прим. перев.).

Действительно ли вам нужен Kubernetes?


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

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

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

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

Помните, что технология исключительно ради технологии бессмысленна.

P.S. от переводчика


Читайте также в нашем блоге:

Подробнее..

Шпаргалка по Ansible k8s, практичный учебник по awk, а также 4 причины использовать Jamstack при веб-разработке

24.09.2020 14:19:46 | Автор: admin


Традиционно короткий дайджест полезных материалов, найденных нами в сети за последние две недели.

Начни новое:



Качай:


  • Шпаргалка по Ansible k8s
    Ansible k8s это специальный модуль для управления объектами Kubernetes из плейбуков Ansible. Как объединить Ansible и Kubernetes при автоматизации облака? Ответ: использовать модуль Ansible k8s, чтобы управлять объектами Kubernetes прямо из плейбуков. И поможет в этом наша шпаргалка, которая содержит полезные советы и сведения по ключевым командам этого модуля.
  • Шпаргалка по тестированию приложений Quarkus


  • Книжка-раскраска Контейнерные супергерои
    Децентрализованная команда опенсорсных контейнерных супергероев в лице Podman, CRI-O, Buildah, Skopeo и OpenShift спасает Землю от атаки астероидов, развертывая над планетой защитный экран.



Почитать на досуге:



Мероприятия:


  • 30 сентября, jconf.dev
    Бесплатная виртуальная Java-конференция прямо у вас на экране. Четыре технотрека с экспертами по Java и облаку, 28 углубленных сессий и два потрясающих основных доклада.
  • 13-14 октября, AnsibleFest
    Выступления, демонстрации, практические занятия и все это в онлайне. Отличная возможность виртуально пообщаться с девелоперами, админами и ЛПР-ами, которые успешно справляются с вызовами перемен с помощью опенсорсных технологий ИТ-автоматизации.

По-русски:


Мы продолжаем серию пятничных вебинаров про нативный опыт использования Red Hat OpenShift Container Platform и Kubernetes. Регистрируйтесь и приходите:

Император Оператор: Операторы в OpenShift и Kubernetes
Упс, вебинар прошел, но есть запись.

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

Подробнее..

Из песочницы Что такое Vertx, и почему он подходит для РСХБ

22.09.2020 20:11:13 | Автор: admin

Как известно, кто убьет дракона, тот сам становится драконом. Spring, как фреймворк общего назначения, был очень хорош на фоне java EE 10 лет назад. Но сейчас стал очень монструозным и тяжелым на подьем. Сегодня рассмотрим Vertx как фреймворк-основу для создания микросервисов.


Что такое Vertx?



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


Минимально возможной единицей деплоя является verticle(вертикл). Это некий аналог микросервиса. Вертиклы могут быть запущены в разных java машинах на разных физических хостах и общаться между собой через встроенную распределенную шину данных (event bus).


Vertx является фреймворком-полиглотом это значит, что каждый вертикл может быть написан на своем языке программирования. Список поддерживаемых языков: Java,Kotlin,JavaScript,Groovy,Ruby, Scala.


Установка вертиклов возможна в уже запущенный Vertx с разных источников, таких как локальная папка, git или maven репозиторий без остановки или рестарта уже запущенных вертиклов.


Больше технической информации можно найти на http://vertx.io. Также в разделе http://vertx.io/docs помимо самой документации доступны несколько книг и ссылки на примеры кода на всех поддерживаемых языках.


Почему Vertx?


Итак, мы выяснили, что же ты такое. Теперь давайте попробуем понять, почему именно Vertx?


Инфраструктура внутренних приложений банка очень разнородна. Для нашей организации требуется фреймворк с большим количеством готовых плагинов, компонентов, интеграций с другими системами, а также готовыми системами мониторинга и разворачивания в гетерогенной инфраструктуре. Vertx можно легко использовать как консольное приложение, так и в виде кластера внутри Kubernetes с функционалом высокой доступности для каждого verticle. Есть готовые образы docker и поддержка разных систем авторизации и хранения конфигураций.


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


Тестовое приложение


Для примера давайте напишем небольшой микросервис с минимальным rest api и метриками для Prometheus также доступными через web socket.


Функционал максимально простой (2 rest endpoint). Мы принимаем rest запрос, далее переадресовываем на сервер Московской биржи и формируем ответ клиенту. Также подключим micrometer метрики и будем публиковать их по встроенной шине данных в web socket.
Итак, начнем.
Идем на http://start.vertx.io, создаем проект с 4 модулями:


  • Vert.x Web
  • Vert.x Web Client
  • Metrics using Micrometer
  • SockJS Service Proxies

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


./mvnw clean compile exec:java

то на http://localhost:8888 сможем увидеть приветствие: Hello from Vert.x!


Из коробки мы имеем уже готовый веб-сервер, шину данных и возможность поднять данный проект в кластере. И это при размере финального толстого jar в 7,5 мегабайт!


Ладно, хватит восхвалений, перейдем к написанию нашего микросервиса.


Шаг 1. Обработка HTTP запросов


В Vertx маршрутизация http запросов осуществляется с помощью Router. Назначить обработчики запросов очень просто:


Router router = Router.router(vertx); //создаем роутерrouter.get("/hello").handler(rc -> { //назначаем обработчик для GET запросов для пути /hello  rc.response()    .putHeader(HttpHeaders.CONTENT_TYPE, "text/plain")    .end("Hello from Vert.x!");});vertx.createHttpServer()  .requestHandler(router) //создаем http сервер и назначаем роутер обработчиком запросов  .listen(8888, http -> {....

Теперь то же приветствие доступно по пути http://localhost:8888/hello.


Шаг 2. Пишем REST API


API будет состоять из 2-х точек с вызовом другого удаленного api московской биржи:


  1. получение списка облигаций РСХБ
  2. получение описания по любой облигации

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


private Router createRestRouter(WebClient webClient) {  Router restApi = Router.router(vertx);  restApi.get("/rshb_bonds").handler(rc -> {    webClient      .get(80, "iss.moex.com", "/iss/securities.json")      .addQueryParam("q", "РСХБ")      .send(response -> {        rc.response()          .putHeader(HttpHeaders.CONTENT_TYPE, "application/json")          .end(processMoexBondsRequest(response.result().bodyAsJsonObject()).encodePrettily());      });  });  restApi.get("/rshb_bonds/:bondId").handler(rc -> { //часть url используется как параметр    String bondId = rc.request().getParam("bondId");      webClient        .get("/iss/securities/"+ bondId +".json")        .send(response -> {          rc.response()            .putHeader(HttpHeaders.CONTENT_TYPE, "application/json")            .end(                processMoexBondDescriptionRequest(response.result().bodyAsJsonObject())            );        });    });  return restApi;}

Функции processMoexBondsRequest, processMoexBondDescriptionRequest можно посмотреть на github. Их реализация опущена для читаемости листинга. Так же пока опустим обработку ошибок.


А так же прикрепим роут c нашим api в главный роут с префиксом /rest/api/v1.


router.mountSubRouter("/rest/api/v1/", createRestRouter(webClient));

Самые внимательные читатели уже заметили, что для вызовов удаленного http rest сервиса используется встроенный http клиент. Это тоже модуль Vertx, скрывающий за собой целый пласт функционала, такого как: ssl, пул соединений, таймауты соединений и т.д. Создается обьект в едином стиле всего фреймворка:


WebClientOptions webClientOptions = new WebClientOptions();webClientOptions //значения по умолчанию, это позволит не проставлять их при каждом вызове    .setDefaultPort(80)    .setDefaultHost("iss.moex.com");WebClient webClient = WebClient.create(vertx, webClientOptions);

ВСЕ! мы создали api, который можно проверить по следующим url:
http://localhost:8888/rest/api/v1/rshb_bonds
http://localhost:8888/rest/api/v1/rshb_bonds/RU000A101WQ2


Шаг 3. Получение метрик приложения


Для получения метрик нашего приложения, мы используем уже включенный в состав приложения модуль Micrometer metrics, а для того, чтобы он заработал, нужно указать эту настройку при запуске Vertx. Но в данный момент мы задействуем для запуска стандартный класс io.vertx.core.Launcher, который по умолчанию использует пустые параметры запуска. Ничего страшного, расширим его и укажем наш класс, в качестве стартового в pom.xml


public class LauncherWithMetrics extends Launcher {  public static void main(String[] args) {    new LauncherWithMetrics().dispatch(args); // необходимо для корректной работы лаунчера  }  @Override  public void beforeStartingVertx(VertxOptions options) {    options.setMetricsOptions(      new MicrometerMetricsOptions()        .setPrometheusOptions(new VertxPrometheusOptions().setEnabled(true))        .setEnabled(true));  }}

Осталось добавить в роутер точку /metrics


router.route("/metrics").handler(PrometheusScrapingHandler.create());

Готово. Теперь мы можем подключить Prometheus сервер к нашему сервису и собирать метрики. И все, что для этого понадобилось пара десятков строк кода.


Шаг 4. Демонстрация метрик в браузере


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


public class MetricsBusPublisher extends AbstractVerticle {  @Override  public void start(Promise<Void> startPromise) {    MetricsService metricsService = MetricsService.create(vertx);    vertx.setPeriodic(      1000,      h ->        vertx.eventBus().publish("metrics", metricsService.getMetricsSnapshot().encode())    );    startPromise.complete();  }}

и обработчик в роутер для трансляции содержимого шины в веб сокет:


SockJSBridgeOptions opts = new SockJSBridgeOptions()  .addOutboundPermitted(new PermittedOptions()    .setAddress("metrics")); // В целях безопасности можно указать, какие очереди доступны для трансляции, а какие могут принимать сообщения из вебсокета. Фильтры возможны на основе регулярных выражений.router.mountSubRouter("/eventbus", SockJSHandler.create(vertx).bridge(opts));

Теперь можно запустить webSocket.html из корня проекта и посмотреть метрики в реальном времени.


Где найти проект


Проект доступен на github:
https://github.com/RshbExample/VertxFirstSteps.git


В заключение


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

Подробнее..

Из песочницы Пример создания утилиты для Unigraphics NX с помощью библиотеки NXOpen на языке Java

08.09.2020 22:18:39 | Автор: admin
Решил рассказать, кому интересно, как можно создавать любые утилиты для Unigraphics NX с помощью библиотеки NXOpen и языка программирования Java.В качестве примера моя утилита будет строить 2d сетку на все свободных телах и гранях(это может быть полезно для задачи оптимизации).

Необходимую информацию по библиотеке NXOpen можно найти на официальном сайте.

В корневой папки NX лежат необходимые нам библиотеки по умолчанию, а так же примеры:

  • C:\Program Files\Siemens\NX 12.0\NXBIN с расширением jar
  • C:\Program Files\Siemens\NX 12.0\UGOPEN с расширением jar
  • C:\Program Files\Siemens\NX 12.0\UGOPEN\SampleNXOpenApplications\Java.

Для упрощения написания кода можно за основу использовать журнал записи своих действий в текстовый файл.По умолчанию Unigraphics NX записывает на языке Visual Basic, но в настройках можно поменять на Java или на любой другой из списка доступных:



Вот пример записи журнала в текстовый файл.

Листинг кода
// NX 12.0.1.7// Journal created by Aleksandr on Sun Nov  3 19:49:32 2019 RTZ 2 (зима)//import nxopen.*;public class journalTT{  public static void main(String [] args) throws NXException, java.rmi.RemoteException  {    nxopen.Session theSession = (nxopen.Session)nxopen.SessionFactory.get("Session");    nxopen.cae.FemPart workFemPart = ((nxopen.cae.FemPart)theSession.parts().baseWork());    nxopen.cae.FemPart displayFemPart = ((nxopen.cae.FemPart)theSession.parts().baseDisplay());    // ----------------------------------------------    //   Меню: Вставить->Сетка->2D сетка...    // ----------------------------------------------    int markId1;    markId1 = theSession.setUndoMark(nxopen.Session.MarkVisibility.VISIBLE, "Начало");        nxopen.cae.FEModel fEModel1 = ((nxopen.cae.FEModel)workFemPart.findObject("FEModel"));    nxopen.cae.MeshManager meshManager1 = ((nxopen.cae.MeshManager)fEModel1.find("MeshManager"));    nxopen.cae.Mesh2d nullNXOpen_CAE_Mesh2d = null;    nxopen.cae.Mesh2dBuilder mesh2dBuilder1;    mesh2dBuilder1 = meshManager1.createMesh2dBuilder(nullNXOpen_CAE_Mesh2d);        nxopen.cae.MeshCollector nullNXOpen_CAE_MeshCollector = null;    mesh2dBuilder1.elementType().destinationCollector().setElementContainer(nullNXOpen_CAE_MeshCollector);        mesh2dBuilder1.elementType().setElementTypeName("CQUAD4");        nxopen.Unit unit1 = ((nxopen.Unit)workFemPart.unitCollection().findObject("MilliMeter"));    mesh2dBuilder1.propertyTable().setBaseScalarWithDataPropertyValue("quad mesh overall edge size", "10", unit1);        mesh2dBuilder1.propertyTable().setBooleanPropertyValue("target minimum element edge length", false);        mesh2dBuilder1.propertyTable().setBaseScalarWithDataPropertyValue("surface curvature threshold", "5.05", unit1);        nxopen.Unit nullNXOpen_Unit = null;    mesh2dBuilder1.propertyTable().setBaseScalarWithDataPropertyValue("small feature value", "1", nullNXOpen_Unit);        theSession.setUndoMarkName(markId1, "Диалоговое окно 2D сетка");        nxopen.DisplayableObject [] objects1  = new nxopen.DisplayableObject[1];    nxopen.cae.CAEBody cAEBody1 = ((nxopen.cae.CAEBody)workFemPart.findObject("CAE_Body(14)"));    nxopen.cae.CAEFace cAEFace1 = ((nxopen.cae.CAEFace)cAEBody1.findObject("CAE_Face(14)"));    objects1[0] = cAEFace1;    boolean added1;    added1 = mesh2dBuilder1.selectionList().add(objects1);        nxopen.DisplayableObject [] objects2  = new nxopen.DisplayableObject[1];    nxopen.cae.CAEBody cAEBody2 = ((nxopen.cae.CAEBody)workFemPart.findObject("CAE_Body(3)"));    nxopen.cae.CAEFace cAEFace2 = ((nxopen.cae.CAEFace)cAEBody2.findObject("CAE_Face(3)"));    objects2[0] = cAEFace2;    boolean added2;    added2 = mesh2dBuilder1.selectionList().add(objects2);        nxopen.DisplayableObject [] objects3  = new nxopen.DisplayableObject[1];    nxopen.cae.CAEBody cAEBody3 = ((nxopen.cae.CAEBody)workFemPart.findObject("CAE_Body(2)"));    nxopen.cae.CAEFace cAEFace3 = ((nxopen.cae.CAEFace)cAEBody3.findObject("CAE_Face(2)"));    objects3[0] = cAEFace3;    boolean added3;    added3 = mesh2dBuilder1.selectionList().add(objects3);        nxopen.DisplayableObject [] objects4  = new nxopen.DisplayableObject[1];    nxopen.cae.CAEBody cAEBody4 = ((nxopen.cae.CAEBody)workFemPart.findObject("CAE_Body(1)"));    nxopen.cae.CAEFace cAEFace4 = ((nxopen.cae.CAEFace)cAEBody4.findObject("CAE_Face(1)"));    objects4[0] = cAEFace4;    boolean added4;    added4 = mesh2dBuilder1.selectionList().add(objects4);        int markId2;    markId2 = theSession.setUndoMark(nxopen.Session.MarkVisibility.INVISIBLE, "2D сетка");        theSession.deleteUndoMark(markId2, null);        int markId3;    markId3 = theSession.setUndoMark(nxopen.Session.MarkVisibility.INVISIBLE, "2D сетка");        mesh2dBuilder1.setAutoResetOption(false);        mesh2dBuilder1.elementType().setElementDimension(nxopen.cae.ElementTypeBuilder.ElementType.SHELL);        mesh2dBuilder1.elementType().setElementTypeName("CQUAD4");        nxopen.cae.DestinationCollectorBuilder destinationCollectorBuilder1;    destinationCollectorBuilder1 = mesh2dBuilder1.elementType().destinationCollector();        destinationCollectorBuilder1.setElementContainer(nullNXOpen_CAE_MeshCollector);        destinationCollectorBuilder1.setAutomaticMode(true);        mesh2dBuilder1.propertyTable().setIntegerPropertyValue("meshing method", 0);        mesh2dBuilder1.propertyTable().setBaseScalarWithDataPropertyValue("quad mesh overall edge size", "10", unit1);        mesh2dBuilder1.propertyTable().setBooleanPropertyValue("block decomposition option bool", false);        mesh2dBuilder1.propertyTable().setBooleanPropertyValue("mapped mesh option bool", false);        mesh2dBuilder1.propertyTable().setIntegerPropertyValue("fillet num elements", 3);        mesh2dBuilder1.propertyTable().setIntegerPropertyValue("num elements on cylinder circumference", 6);        mesh2dBuilder1.propertyTable().setBaseScalarWithDataPropertyValue("element size on cylinder height", "1", unit1);        mesh2dBuilder1.propertyTable().setIntegerPropertyValue("quad only option", 0);        mesh2dBuilder1.propertyTable().setBooleanPropertyValue("mesh individual faces option bool", false);        mesh2dBuilder1.propertyTable().setIntegerPropertyValue("midnodes", 0);        mesh2dBuilder1.propertyTable().setBooleanPropertyValue("geometry tolerance option bool", false);        mesh2dBuilder1.propertyTable().setBaseScalarWithDataPropertyValue("geometry tolerance", "0", unit1);        mesh2dBuilder1.propertyTable().setBooleanPropertyValue("target maximum element edge length bool", false);        mesh2dBuilder1.propertyTable().setBaseScalarWithDataPropertyValue("maximum element edge length", "1", unit1);        mesh2dBuilder1.propertyTable().setBooleanPropertyValue("target minimum element edge length", false);        mesh2dBuilder1.propertyTable().setBooleanPropertyValue("split poor quads bool", false);        mesh2dBuilder1.propertyTable().setBooleanPropertyValue("move nodes off geometry bool", false);        mesh2dBuilder1.propertyTable().setBooleanPropertyValue("max quad warp option bool", false);        mesh2dBuilder1.propertyTable().setBaseScalarWithDataPropertyValue("max quad warp", "5", nullNXOpen_Unit);        mesh2dBuilder1.propertyTable().setBaseScalarWithDataPropertyValue("max jacobian", "5", nullNXOpen_Unit);        mesh2dBuilder1.propertyTable().setBooleanPropertyValue("target element skew bool", false);        mesh2dBuilder1.propertyTable().setBaseScalarWithDataPropertyValue("element skew", "30", nullNXOpen_Unit);        mesh2dBuilder1.propertyTable().setBooleanPropertyValue("max included angle quad option bool", false);        nxopen.Unit unit2 = ((nxopen.Unit)workFemPart.unitCollection().findObject("Degrees"));    mesh2dBuilder1.propertyTable().setBaseScalarWithDataPropertyValue("max included angle quad", "150", unit2);        mesh2dBuilder1.propertyTable().setBooleanPropertyValue("min included angle quad option bool", false);        mesh2dBuilder1.propertyTable().setBaseScalarWithDataPropertyValue("min included angle quad", "30", unit2);        mesh2dBuilder1.propertyTable().setBooleanPropertyValue("max included angle tria option bool", false);        mesh2dBuilder1.propertyTable().setBaseScalarWithDataPropertyValue("max included angle tria", "150", unit2);        mesh2dBuilder1.propertyTable().setBooleanPropertyValue("min included angle tria option bool", false);        mesh2dBuilder1.propertyTable().setBaseScalarWithDataPropertyValue("min included angle tria", "30", unit2);        mesh2dBuilder1.propertyTable().setBooleanPropertyValue("mesh transition bool", false);        mesh2dBuilder1.propertyTable().setBaseScalarWithDataPropertyValue("mesh size variation", "50", nullNXOpen_Unit);        mesh2dBuilder1.propertyTable().setBaseScalarWithDataPropertyValue("surface curvature threshold", "5.05", unit1);        mesh2dBuilder1.propertyTable().setBooleanPropertyValue("alternate feature abstraction bool", false);        mesh2dBuilder1.propertyTable().setBaseScalarWithDataPropertyValue("minimum feature element size", "0.001", unit1);        mesh2dBuilder1.propertyTable().setBaseScalarWithDataPropertyValue("small feature tolerance", "10", nullNXOpen_Unit);        mesh2dBuilder1.propertyTable().setBaseScalarWithDataPropertyValue("small feature value", "1", nullNXOpen_Unit);        mesh2dBuilder1.propertyTable().setBooleanPropertyValue("suppress hole option bool", false);        mesh2dBuilder1.propertyTable().setBaseScalarWithDataPropertyValue("hole diameter tolerance", "0", unit1);        mesh2dBuilder1.propertyTable().setIntegerPropertyValue("hole suppresion point type", 0);        mesh2dBuilder1.propertyTable().setBooleanPropertyValue("merge edge toggle bool", false);        mesh2dBuilder1.propertyTable().setBaseScalarWithDataPropertyValue("edge angle", "15", unit2);        mesh2dBuilder1.propertyTable().setBooleanPropertyValue("quad mesh edge match toggle", false);        mesh2dBuilder1.propertyTable().setBaseScalarWithDataPropertyValue("quad mesh edge match tolerance", "0.02", unit1);        mesh2dBuilder1.propertyTable().setBaseScalarWithDataPropertyValue("quad mesh smoothness tolerance", "0.01", unit1);        mesh2dBuilder1.propertyTable().setBaseScalarWithDataPropertyValue("quad mesh surface match tolerance", "0.001", unit1);        mesh2dBuilder1.propertyTable().setIntegerPropertyValue("quad mesh transitional rows", 3);        mesh2dBuilder1.propertyTable().setBaseScalarWithDataPropertyValue("min face angle", "20", unit2);        mesh2dBuilder1.propertyTable().setIntegerPropertyValue("mesh time stamp", 0);        mesh2dBuilder1.propertyTable().setBaseScalarWithDataPropertyValue("quad mesh node coincidence tolerance", "0.0001", unit1);        mesh2dBuilder1.propertyTable().setIntegerPropertyValue("mesh edit allowed", 0);        mesh2dBuilder1.propertyTable().setIntegerPropertyValue("transition edge seeding", 0);        mesh2dBuilder1.propertyTable().setIntegerPropertyValue("cylinder curved end num elements", 6);        int id1;    id1 = theSession.newestVisibleUndoMark();        int nErrs1;    nErrs1 = theSession.updateManager().doUpdate(id1);        nxopen.cae.Mesh [] meshes1 ;    meshes1 = mesh2dBuilder1.commitMesh();        theSession.deleteUndoMark(markId3, null);        theSession.setUndoMarkName(id1, "2D сетка");        mesh2dBuilder1.destroy();        // ----------------------------------------------    //   Меню: Инструменты->Повторить команду->3 Остановка записи журнала    // ----------------------------------------------    // ----------------------------------------------    //   Меню: Инструменты->Журнал->Остановка записи    // ----------------------------------------------    }    public static final int getUnloadOption() { return nxopen.BaseSession.LibraryUnloadOption.IMMEDIATELY; }  }  }


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

Я сделал это так:

        try {            for (int i = 1; i < 20; i++) {                 obj="object"+i;                cAEB="cAEBody"+i;                cAEF="cAEFace"+i;                adde="added"+i;                //Object objects = new Object();                nxopen.DisplayableObject [] obj  = new nxopen.DisplayableObject[1];                nxopen.cae.CAEBody cAEB = ((nxopen.cae.CAEBody)workFemPart.findObject(("CAE_Body"+"("+i+")")));                nxopen.cae.CAEFace cAEF = ((nxopen.cae.CAEFace)cAEB.findObject("CAE_Face"+"("+i+")"));                obj[0] = cAEF;                boolean adde;                adde = mesh2dBuilder1.selectionList().add(obj);           }        } catch (Exception ex) {

Вот конечный результат исполненный в программе:

Подробнее..

Из песочницы Написание десктопного приложения с помощью JavaFX

09.09.2020 16:16:51 | Автор: admin
Решил я написать исполняемый jar файл с удобным интерфейсом для расчета своих повседневных задач по работе. Начал с самых простых.

image

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

Начнем с настройка интерфейса, а в помощь возьмем Scene Builder


Создадим рабочую область с размером 900х600.

image

Создадим несколько контейнеров c размерами и отступами(я сначала разметку делал просто на листочке, но листочек уже потерялся).

image

Далее пройдемся по внутренним элементам контейнеров


  1. Кнопка Button
    Присвоим уникальное имя нашей кнопки и название метода в котором потом будем реализовывать наш код

    image
  2. Поле TextField
    Присвоим уникальное имя нашему текстовому полю. Поле понадобиться для ввода/вывода числовых значения. Так же добавим дополнительное имя полю, которое при вводе символом будет пропадать

    image

    image
  3. Поле Label
    Поле Label я использовал для обозначения ячеек.
  4. Поле Image
    Тут указываем имя существующей картинки находящийся в проекте

    image
  5. Поле CheckBox
    Тут указываем уникальное имя. Это необходимо для определения типа закрепления

    image

Конечный код файла sample.fxml

Листинг sample.fxml
<?xml version="1.0" encoding="UTF-8"?><?import javafx.scene.control.Button?><?import javafx.scene.control.CheckBox?><?import javafx.scene.control.Label?><?import javafx.scene.control.TextField?><?import javafx.scene.image.Image?><?import javafx.scene.image.ImageView?><?import javafx.scene.layout.GridPane?><?import javafx.scene.layout.Pane?><?import javafx.scene.layout.VBox?><Pane prefHeight="600.0" prefWidth="900.0" xmlns="http://personeltest.ru/away/javafx.com/javafx/8.0.112" xmlns:fx="http://personeltest.ru/away/javafx.com/fxml/1" fx:controller="sample.Controller">   <children>      <Button fx:id="button" layoutX="40.0" layoutY="200.0" mnemonicParsing="false" onAction="#showDateTime" prefHeight="71.0" prefWidth="528.0" text="=" />      <VBox layoutX="312.0" layoutY="20.0" minHeight="-Infinity" minWidth="-Infinity" prefHeight="200.0" prefWidth="280.0" GridPane.columnIndex="1">         <children>            <TextField fx:id="myTextField1" promptText="Значение а" />            <TextField fx:id="myTextField2" promptText="значение b (наименьшая сторона)" />            <TextField fx:id="myTextField3" promptText="Толщина" />            <TextField fx:id="myTextField4" prefHeight="25.0" prefWidth="280.0" promptText="Модуль упругости Е" />            <TextField fx:id="myTextField6" prefHeight="25.0" prefWidth="280.0" promptText="Действующие напряжения" />         </children>      </VBox>      <VBox layoutX="611.0" prefHeight="562.0" prefWidth="271.0" GridPane.columnIndex="21">         <children>            <ImageView fx:id="image" fitHeight="116.0" fitWidth="174.0" pickOnBounds="true" preserveRatio="true">               <image>                  <Image url="@Zadelka1.PNG" />               </image></ImageView>            <CheckBox fx:id="checkbox1" mnemonicParsing="false" text="Все края защемлены" />            <ImageView fitHeight="122.0" fitWidth="237.0" layoutX="30.0" pickOnBounds="true" preserveRatio="true">               <image>                  <Image url="@Zadelka2.PNG" />               </image></ImageView>            <CheckBox fx:id="checkbox2" mnemonicParsing="false" text="Шарнир по верхним  и нижним кромкам" />            <ImageView fitHeight="127.0" fitWidth="272.0" pickOnBounds="true" preserveRatio="true">               <image>                  <Image url="@Zadelka3.PNG" />               </image></ImageView>            <CheckBox fx:id="checkbox3" mnemonicParsing="false" text="Шарнир по бокам" />            <ImageView fitHeight="129.0" fitWidth="392.0" pickOnBounds="true" preserveRatio="true">               <image>                  <Image url="@Zadelka4.PNG" />               </image></ImageView>            <CheckBox fx:id="checkbox4" mnemonicParsing="false" text="Шарнир кругом" />         </children>      </VBox>            <VBox layoutX="20.0" layoutY="20.0" minHeight="-Infinity" minWidth="-Infinity" prefHeight="200.0" prefWidth="262.0">         <children>            <Label prefHeight="25.0" prefWidth="185.0" text="Значение a" />            <Label prefHeight="25.0" prefWidth="262.0" text="Значение b (наименьшая сторона)" />            <Label prefHeight="25.0" prefWidth="188.0" text="Толщина" />            <Label prefHeight="25.0" prefWidth="186.0" text="Модуль упругости E" />            <Label prefHeight="25.0" prefWidth="188.0" text="Действующие напряжения" />         </children>      </VBox>      <VBox layoutX="312.0" layoutY="311.0" prefHeight="200.0" prefWidth="280.0">         <children>            <TextField fx:id="myTextField5" prefHeight="25.0" prefWidth="280.0" promptText="Касательное напряжения(кр)" />            <TextField fx:id="myTextField7" prefHeight="25.0" prefWidth="280.0" promptText="Запас прочности" />         </children>      </VBox>      <VBox layoutX="20.0" layoutY="310.0" prefHeight="200.0" prefWidth="262.0">         <children>            <Label prefHeight="25.0" prefWidth="260.0" text="Критическое касательное напряжения" />            <Label prefHeight="25.0" prefWidth="187.0" text="Запас прочности" />         </children>      </VBox>   </children>  </Pane>


Создание главного метода класса Main


Класс Main
package sample;import javafx.application.Application;import javafx.fxml.FXMLLoader;import javafx.scene.Parent;import javafx.scene.Scene;import javafx.stage.Stage;public class Main extends Application  {    @Override    public void start(Stage primaryStage) {        try {//Ссылка на sample.fxml(наш интерфейс).Название программы и создание нашей главной сцен            Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));            primaryStage.setTitle("Чистый сдвиг");            primaryStage.setScene(new Scene(root));            primaryStage.show();        } catch(Exception e) {            e.printStackTrace();        }    }    public static void main(String[] args)  {        launch(args);    }}


Создания класса Controller.Самое важное помечено комментариями


Класс Controller
ackage sample;import javafx.fxml.FXML;import javafx.fxml.Initializable;import javafx.scene.control.Button;import javafx.scene.control.CheckBox;import javafx.scene.control.TextField;import javafx.stage.Stage;import java.net.URL;import java.util.ResourceBundle;import static java.lang.Double.NaN;import static java.lang.Double.parseDouble;import static java.lang.String.valueOf;public class Controller extends TextField implements Initializable  {//Создание переменных разлчиных типов связанных с интерфейсом    @FXML    private TextField myTextField4;    @FXML    private TextField myTextField1;    @FXML    private TextField myTextField2;    @FXML    private TextField myTextField3;    @FXML    private TextField myTextField5;    @FXML    private TextField myTextField6;    @FXML    private TextField myTextField7;    @FXML    private CheckBox checkbox1;    @FXML    private CheckBox checkbox2;    @FXML    private CheckBox checkbox3;    @FXML    private CheckBox checkbox4;    private double k;    private double krit;    private double zapas;    @Override    public void initialize(URL location, ResourceBundle resources) {                //Применения фильтра для ввода параметров        myTextField1.setTextFormatter(new AlphaNumericTextFormatter());        myTextField2.setTextFormatter(new AlphaNumericTextFormatter());        myTextField3.setTextFormatter(new AlphaNumericTextFormatter());        myTextField4.setTextFormatter(new AlphaNumericTextFormatter());        //myTextField6.setTextFormatter(new AlphaNumericTextFormatter());    }    public void showDateTime() throws Exception {//Перевод строки в число        Double a = parseDouble(myTextField1.getText());        Double b = parseDouble(myTextField2.getText());        Double t = parseDouble(myTextField3.getText());        Double e = parseDouble(myTextField4.getText());        Double c = b / a;//Условия выбора CheckBox и отношения стороны        if (checkbox1.isSelected()) {            if (c <= 0.1) {                k = 8;            } else if ((c > 0.1) && (c <= 0.15)) {                k = 8.5;            } else if ((c > 0.15) && (c <= 0.2)) {                k = 8.8;            } else if ((c > 0.2) && (c <= 0.25)) {                k = 9;            } else if ((c > 0.25) && (c <= 0.30)) {                k = 9.2;            } else if ((c > 0.3) && (c <= 0.35)) {                k = 9.5;            } else if ((c > 0.35) && (c <= 0.40)) {                k = 9.8;            } else if ((c > 0.40) && (c <= 0.45)) {                k = 10.3;            } else if ((c > 0.45) && (c <= 0.50)) {                k = 10.5;            } else if ((c > 0.5) && (c <= 0.55)) {                k = 10.8;            } else if ((c > 0.55) && (c <= 0.6)) {                k = 11;            } else if ((c > 0.6) && (c <= 0.65)) {                k = 11.5;            } else if ((c > 0.65) && (c <= 0.7)) {                k = 11.8;            } else if ((c > 0.7) && (c <= 0.75)) {                k = 12;            } else if ((c > 0.75) && (c <= 0.8)) {                k = 12.5;            } else if ((c > 0.8) && (c <= 0.85)) {                k = 12.8;            } else if ((c > 0.85) && (c <= 0.9)) {                k = 13;            } else if ((c > 0.9) && (c <= 0.95)) {                k = 13.5;            } else if ((c > 0.95) && (c <= 1)) {                k = 14;            }            krit = (k * e) / (Math.pow((b / t), 2));            String d = valueOf(krit);            //перевод числа в строку            myTextField5.setText(d);            }            else if (checkbox2.isSelected()) {                if (c <= 0.1) {                    k = 8;                } else if ((c > 0.1) && (c <= 0.15)) {                    k = 8.2;                } else if ((c > 0.15) && (c <= 0.2)) {                    k = 8.4;                } else if ((c > 0.2) && (c <= 0.25)) {                    k = 8.5;                } else if ((c > 0.25) && (c <= 0.30)) {                    k = 8.6;                } else if ((c > 0.3) && (c <= 0.35)) {                    k = 8.7;                } else if ((c > 0.35) && (c <= 0.40)) {                    k = 8.9;                } else if ((c > 0.40) && (c <= 0.45)) {                    k = 9;                } else if ((c > 0.45) && (c <= 0.50)) {                    k = 9.2;                } else if ((c > 0.5) && (c <= 0.55)) {                    k = 9.4;                } else if ((c > 0.55) && (c <= 0.6)) {                    k = 9.5;                } else if ((c > 0.6) && (c <= 0.65)) {                    k = 9.7;                } else if ((c > 0.65) && (c <= 0.7)) {                    k = 10;                } else if ((c > 0.7) && (c <= 0.75)) {                    k = 10;                } else if ((c > 0.75) && (c <= 0.8)) {                    k = 10.2;                } else if ((c > 0.8) && (c <= 0.85)) {                    k = 10.5;                } else if ((c > 0.85) && (c <= 0.9)) {                    k = 10.6;                } else if ((c > 0.9) && (c <= 0.95)) {                    k = 11;                } else if ((c > 0.95) && (c <= 1)) {                    k = 11;                }            krit = (k * e) / (Math.pow((b / t), 2));            String d = valueOf(krit);            myTextField5.setText(d);            }        else if (checkbox3.isSelected()) {            if (c <= 0.1) {                k = 4.9;            } else if ((c > 0.1) && (c <= 0.15)) {                k = 5.1;            } else if ((c > 0.15) && (c <= 0.2)) {                k = 5.2;            } else if ((c > 0.2) && (c <= 0.25)) {                k = 5.5;            } else if ((c > 0.25) && (c <= 0.30)) {                k = 5.8;            } else if ((c > 0.3) && (c <= 0.35)) {                k = 6;            } else if ((c > 0.35) && (c <= 0.40)) {                k = 6.2;            } else if ((c > 0.40) && (c <= 0.45)) {                k = 6.5;            } else if ((c > 0.45) && (c <= 0.50)) {                k = 6.9;            } else if ((c > 0.5) && (c <= 0.55)) {                k = 7;            } else if ((c > 0.55) && (c <= 0.6)) {                k = 7.5;            } else if ((c > 0.6) && (c <= 0.65)) {                k = 8;            } else if ((c > 0.65) && (c <= 0.7)) {                k = 8.2;            } else if ((c > 0.7) && (c <= 0.75)) {                k = 8.5;            } else if ((c > 0.75) && (c <= 0.8)) {                k = 9.1;            } else if ((c > 0.8) && (c <= 0.85)) {                k = 9.5;            } else if ((c > 0.85) && (c <= 0.9)) {                k = 10;            } else if ((c > 0.9) && (c <= 0.95)) {                k = 10.5;            } else if ((c > 0.95) && (c <= 1)) {                k = 11;            }            krit = (k * e) / (Math.pow((b / t), 2));            String d = valueOf(krit);            myTextField5.setText(d);        }        else if (checkbox4.isSelected()) {            if (c <= 0.1) {                k = 4.8;            } else if ((c > 0.1) && (c <= 0.15)) {                k = 5;            } else if ((c > 0.15) && (c <= 0.2)) {                k = 5;            } else if ((c > 0.2) && (c <= 0.25)) {                k = 5.1;            } else if ((c > 0.25) && (c <= 0.30)) {                k = 5.2;            } else if ((c > 0.3) && (c <= 0.35)) {                k = 5.4;            } else if ((c > 0.35) && (c <= 0.40)) {                k = 5.5;            } else if ((c > 0.40) && (c <= 0.45)) {                k = 5.6;            } else if ((c > 0.45) && (c <= 0.50)) {                k = 5.9;            } else if ((c > 0.5) && (c <= 0.55)) {                k = 6;            } else if ((c > 0.55) && (c <= 0.6)) {                k = 6.2;            } else if ((c > 0.6) && (c <= 0.65)) {                k = 6.5;            } else if ((c > 0.65) && (c <= 0.7)) {                k = 6.7;            } else if ((c > 0.7) && (c <= 0.75)) {                k = 7;            } else if ((c > 0.75) && (c <= 0.8)) {                k = 7.1;            } else if ((c > 0.8) && (c <= 0.85)) {                k = 7.4;            } else if ((c > 0.85) && (c <= 0.9)) {                k = 7.8;            } else if ((c > 0.9) && (c <= 0.95)) {                k = 8;            } else if ((c > 0.95) && (c <= 1)) {                k = 8.3;            }            krit = (k * e) / (Math.pow((b / t), 2));            String d = valueOf(krit);            myTextField5.setText(d);        }        Double f = parseDouble(myTextField6.getText());            zapas = krit/f;            String g = valueOf(zapas);            myTextField7.setText(g);        }        }


Создание двух классов для применения фильтра ввода данных (Исключение букв, а так же возможность ограничения количества вводы/выводы). Решения не мое, а нашел я его на stackoverflow.Из всех предложенных оно мне показалось самым простым и понятным.

Класс AlphaNumericTextFormatter

Класс AlphaNumericTextFormatter
public class AlphaNumericTextFormatter extends TextFormatter<String> {    /** The Constant ALPHA_NUMERIC_ONLY. */    //private static final String ALPHA_NUMERIC_ONLY = "^[a-zA-Z0-9]*$";    /** MAKE NUMERIC ONLY **/    private static final String DIGITS_ONLY = "^[0-9.]*$";    /**     * Instantiates a new alpha numeric text formatter.     */    public AlphaNumericTextFormatter() {        super(applyFilter(null));    }    /**     * Instantiates a new alpha numeric text formatter.     *     * @param maxLength     *            the max length     */    public AlphaNumericTextFormatter(int maxLength) {        super(applyFilter(new MaxLengthTextFormatter(maxLength).getFilter()));    }    /**     * Apply filter.     *     * @param filter     *            the filter     * @return the unary operator     */    private static UnaryOperator<Change> applyFilter(UnaryOperator<Change> filter) {        return change -> {            if (change.getControlNewText() != null && change.getControlNewText().matches(DIGITS_ONLY)) {                if (filter != null) {                    filter.apply(change);                }                return change;            }            return null;        };    }}


Класс MaxLengthTextFormatter

Листинг кода
package sample;import java.util.function.UnaryOperator;import javafx.scene.control.TextFormatter;import javafx.scene.control.TextFormatter.Change;public class MaxLengthTextFormatter extends TextFormatter<String> {    private int maxLength;    public MaxLengthTextFormatter(final int maxLength) {        super(new UnaryOperator<Change>() {            public Change apply(Change change) {                if (change.isDeleted()) {                    if (change.getControlNewText().length() > maxLength) {                        change.setText(change.getText().substring(0, maxLength));                    }                } else if (change.getControlText().length() + change.getText().length() >= maxLength) {                    int maxPos = maxLength - change.getControlText().length();                    change.setText(change.getText().substring(0, maxPos));                }                return change;            }        });        this.maxLength = maxLength;    }    public int getMaxLength() {        return this.maxLength;    }}


Ну и в конце создания jar файла


image

image

На этом все. Теперь готов файл который будет запускаться на любом компьютере с установленной java.
Подробнее..
Категории: Java , Javafx

Репликация Oracle и UCP Fast Connection Failover

12.09.2020 18:14:51 | Автор: admin

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

  • Контролируемая смена ролей баз данных. "Primary" становится "Standby" инаоборот, "Standby" становится "Primary". Такая процедура обычно называется "Switchover".
  • Аварийная смена роли "Standby" на"Primary". Это обычно называется "Failover".

Вобоих случаях приложение должно нетолько "знать" про IP-адрес нового "Primary" сервера, ноиуметь кнему обратиться тогда, когда это нужно. Далее следует краткая инструкция того, как это можно сделать спомощью Oracle Universal Connection Pool (UCP), атакже демонстрация "Switchover".


Для эксперимента будем использовать:

  • MacBook cобъемом RAM 16Гб (для эксперимента нужно более 8Гб)
  • Virtual Box версии 6.1.12
  • 2xвиртуальные машины (далее VM) cCentOS 7Minimal, каждая изкоторых имеет
    • 2x vCPU
    • 2048 Гб RAM (с временным увеличением до 8Гб, по очереди)
    • 40 Гб HDD
    • отключенное аудио, чтобы избежать загрузки CPU 100%
  • Oracle Database 19c


Выполним следующие шаги:

  1. Настройка виртуальных машин
  2. Установка Oracle
  3. Репликация Oracle
  4. Установка и настройка Oracle Grid
  5. Демонстрация "Switchover" на тестовом Java приложении


Настройка виртуальных машин



Создаем виртуальные машины (далее VMs) стипом Linux Red Hat, стартуем. При запуске Virtual Box предлагает выбрать iso, скоторого запуститьVM (вэксперименте используется CentOS-7-x86_64-Minimal-1908.iso). Оставляем все поумолчанию, перезагружаем, обновляем, устанавливаем "Virtual Box Guest Additions", отключаем firewalld, чтобы немешался. "Как очищается политура это всякий знает", поэтому отметим только, что после обновления VMs переключаем ихсетевые интерфейсы садаптера NAT на"виртуальный адаптер хоста" vboxnet0. Этому адаптеру, который можно создать винструментах Virtual Box, вручную задаем адрес 192.168.56.1/24 иотключаем DHCP. Посути, это IP-адрес шлюза поумолчанию для VMs иадрес Java-приложения. Просто для наглядности. Аесли наCentOS все еще нужен интернет, томожно включить NAT наMacOS:

  1. Переключаемся впользователя root спомощью команды sudo su -.
  2. Разрешаем перенаправление трафика спомощью команды sysctl -w net.inet.ip.forwarding=1.
  3. Вфайл /var/root/pfnat.conf добавляем содержательную часть файла /etc/pf.conf, вкоторую наместо строки 3вставляем правило для NAT:
    nat on enX from vboxnet0:network to any -> (enX)

    где enX имя сетевого интерфейса с маршрутом по умолчанию.
  4. Выполняем команду pfctl -f pfnat.conf -e, чтобы обновить правила пакетного фильтра.


Шаги 2-4 можно выполнить в одну команду.
sysctl -w net.inet.ip.forwarding=1 \ && grep -v -E -e '^#.*' -e '^$' /etc/pf.conf | head -n2 > pfnat.conf \ && INET_PORT=$(netstat -nrf inet | grep default | tr -s ' ' | cut -d ' ' -f 4) \ && echo "nat on ${INET_PORT} from vboxnet0:network to any -> (${INET_PORT})" >> pfnat.conf \ && grep -v -E -e '^#.*' -e '^$' /etc/pf.conf | tail -n+3 >> pfnat.conf \ && pfctl -f pfnat.conf -e



В/etc/hosts всех VMs добавляем однообразные записи, чтобы они могли "пинговать" друг друга подоменным именам.

127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6

192.168.56.78 oracle1.localdomain oracle1
192.168.56.79 oracle2.localdomain oracle2


Установка Oracle



Выполняем "Software only" установку наoracle1 иoracle2 спомощью rpm-пакетов, директорию скоторыми можно расшарить сMacOS через Virtual Box (Machine->Settings->Shared Folder).

yum -y localinstall oracle-database-preinstall-19c-1.0-1.el7.x86_64.rpm

yum -y localinstall oracle-database-ee-19c-1.0-1.x86_64.rpm


Далее, выполняем создание экземпляра СУБД наVM"oracle1". Для этого под пользователем oralce запускаем соответствующий скрипт.

/etc/init.d/oracledb_ORCLCDB-19c configure


Эта процедура занимает некоторое время. После создания экземпляра на обоих хостах добавляем вфайл /home/oracle/.bash_profile вот эти строчки:

export ORACLE_HOME="/opt/oracle/product/19c/dbhome_1"
export ORACLE_BASE="/opt/oracle"
export ORACLE_SID="ORCLCDB"
export PATH=$PATH:$ORACLE_HOME/bin


Нуинастраиваем ssh для удобства, чтобы можно было сразу подключиться подпользователем oracle. Подключаемся кхосту поssh ипод пользователем oracle подключаемся кБД.

user@macbook:~$ ssh oracle@oracle1
Last login: Wed Aug 12 16:17:05 2020
[oracle@oracle1 ~]$ sqlplus / as sysdba

SQL*Plus: Release 19.0.0.0.0 Production on Wed Aug 12 16:19:44 2020
Version 19.3.0.0.0

Copyright 1982, 2019, Oracle. All rights reserved.

Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 Production
Version 19.3.0.0.0

SQL>


Собственно, Oracle. Для эксперимента нам также нужно настроить репликацию.

Репликация Oracle.




Для настройки репликации воспользуемся немного дополненной инструкцией:

  1. ПереводимБД насервере oracle1в "Archive Mode". Для этого вsqlplus выполняем команды:

    SHUTDOWN IMMEDIATE;
    

    STARTUP MOUNT;
    

    ALTER DATABASE ARCHIVELOG;
    

    ALTER DATABASE OPEN;
    
  2. Не выходя из sqlplus, включаем "Force logging" насервере oracle1.

    ALTER DATABASE FORCE LOGGING;
    

    ALTER SYSTEM SWITCH LOGFILE;
    
  3. Создаем "redo log" файлы насервере oracle1.
    ALTER DATABASE      ADD STANDBY LOGFILE      THREAD 1 GROUP 10 ('/opt/oracle/oradata/ORCLCDB/standby_redo01.log')      SIZE 209715200;
    

    ALTER DATABASE      ADD STANDBY LOGFILE      THREAD 1 GROUP 11 ('/opt/oracle/oradata/ORCLCDB/standby_redo02.log')      SIZE 209715200;
    

    ALTER DATABASE      ADD STANDBY LOGFILE      THREAD 1 GROUP 12 ('/opt/oracle/oradata/ORCLCDB/standby_redo03.log')      SIZE 209715200;
    

    ALTER DATABASE      ADD STANDBY LOGFILE      THREAD 1 GROUP 13 ('/opt/oracle/oradata/ORCLCDB/standby_redo04.log')      SIZE 209715200;
    
  4. Включаем "FLASHBACK" на сервере oracle1, без него работать не будет.
    SQL> host
    [oracle@oracle1 ~]$ mkdir /opt/oracle/recovery_area
    [oracle@oracle1 ~]$ exit
    SQL> alter system set db_recovery_file_dest_size=2g scope=both;
    SQL> alter system set db_recovery_file_dest='/opt/oracle/recovery_area' scope=both;
    SQL> ALTER DATABASE FLASHBACK ON;
  5. Включаем нечто автоматическое насервере oracle1.
    SQL> ALTER SYSTEM SET STANDBY_FILE_MANAGEMENT=AUTO;
  6. Модифицируем tnsnames.ora и listener.ora на сервере oracle1 и oracle2 до следующих содержаний:
    oracle1, файл $ORACLE_HOME/network/admin/tnsnames.ora
    LISTENER_ORCLCDB =  (ADDRESS = (PROTOCOL = TCP)(HOST = oracle1.localdomain)(PORT = 1521))ORCLCDB =  (DESCRIPTION =    (ADDRESS_LIST =      (ADDRESS = (PROTOCOL = TCP)(HOST = oracle1.localdomain)(PORT = 1521))    )    (CONNECT_DATA =      (SID = ORCLCDB)    )  )ORCLCDB_STBY =  (DESCRIPTION =    (ADDRESS_LIST =      (ADDRESS = (PROTOCOL = TCP)(HOST = oracle2.localdomain)(PORT = 1521))    )    (CONNECT_DATA =      (SID = ORCLCDB)    )  )
    


    oracle1, файл $ORACLE_HOME/network/admin/listener.ora
    LISTENER =  (DESCRIPTION_LIST =    (DESCRIPTION =      (ADDRESS = (PROTOCOL = TCP)(HOST = oracle1.localdomain)(PORT = 1521))      (ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC1521))    )  )SID_LIST_LISTENER =  (SID_LIST =    (SID_DESC =      (GLOBAL_DBNAME = ORCLCDB_DGMGRL)      (ORACLE_HOME = /opt/oracle/product/19c/dbhome_1)      (SID_NAME = ORCLCDB)    )  )ADR_BASE_LISTENER = /opt/oracle
    


    oracle2, файл $ORACLE_HOME/network/admin/tnsnames.ora
    LISTENER_ORCLCDB =  (ADDRESS = (PROTOCOL = TCP)(HOST = oracle2.localdomain)(PORT = 1521))ORCLCDB =  (DESCRIPTION =    (ADDRESS_LIST =      (ADDRESS = (PROTOCOL = TCP)(HOST = oracle1.localdomain)(PORT = 1521))    )    (CONNECT_DATA =      (SID = ORCLCDB)    )  )ORCLCDB_STBY =  (DESCRIPTION =    (ADDRESS_LIST =      (ADDRESS = (PROTOCOL = TCP)(HOST = oracle2.localdomain)(PORT = 1521))    )    (CONNECT_DATA =      (SID = ORCLCDB)    )  )
    


    oracle2, файл $ORACLE_HOME/network/admin/listener.ora
    LISTENER =  (DESCRIPTION_LIST =    (DESCRIPTION =      (ADDRESS = (PROTOCOL = TCP)(HOST = oracle2.localdomain)(PORT = 1521))      (ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC1521))    )  )SID_LIST_LISTENER =  (SID_LIST =    (SID_DESC =      (GLOBAL_DBNAME = ORCLCDB_STBY_DGMGRL)      (ORACLE_HOME = /opt/oracle/product/19c/dbhome_1)      (SID_NAME = ORCLCDB)    )  )ADR_BASE_LISTENER = /opt/oracle
    

  7. На сервере oracle1 перезагружаем конфигурацию слушателя listener-а.
    [oracle@oracle1 ~]$ lsnrctl reload

    Было
    [oracle@oracle1 ~]$ lsnrctl status

    LSNRCTL for Linux: Version 19.0.0.0.0 Production on 15-AUG-2020 08:17:24

    Copyright 1991, 2019, Oracle. All rights reserved.

    Connecting to (DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=oracle1.localdomain)(PORT=1521)))
    STATUS of the LISTENER
    Alias LISTENER
    Version TNSLSNR for Linux: Version 19.0.0.0.0 Production
    Start Date 15-AUG-2020 08:09:57
    Uptime 0 days 0 hr. 7 min. 26 sec
    Trace Level off
    Security ON: Local OS Authentication
    SNMP OFF
    Listener Parameter File /opt/oracle/product/19c/dbhome_1/network/admin/listener.ora
    Listener Log File /opt/oracle/diag/tnslsnr/oracle1/listener/alert/log.xml
    Listening Endpoints Summary
    (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=oracle1.localdomain)(PORT=1521)))
    (DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(KEY=EXTPROC1521)))
    (DESCRIPTION=(ADDRESS=(PROTOCOL=tcps)(HOST=oracle1.localdomain)(PORT=5500))(Security=(my_wallet_directory=/opt/oracle/admin/ORCLCDB/xdb_wallet))(Presentation=HTTP)(Session=RAW))
    Services Summary
    Service ORCLCDB has 1 instance(s).
    Instance ORCLCDB, status READY, has 1 handler(s) for this service
    Service ORCLCDBXDB has 1 instance(s).
    Instance ORCLCDB, status READY, has 1 handler(s) for this service
    Service ac8d8d741e3e2a52e0534e38a8c0602d has 1 instance(s).
    Instance ORCLCDB, status READY, has 1 handler(s) for this service
    Service orclpdb1 has 1 instance(s).
    Instance ORCLCDB, status READY, has 1 handler(s) for this service
    The command completed successfully

    Стало (появилась строчка для Data Guard: ORCLCDB_DGMGRL)
    [oracle@oracle1 ~]$ lsnrctl status

    LSNRCTL for Linux: Version 19.0.0.0.0 Production on 15-AUG-2020 08:17:32

    Copyright 1991, 2019, Oracle. All rights reserved.

    Connecting to (DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=oracle1.localdomain)(PORT=1521)))
    STATUS of the LISTENER
    Alias LISTENER
    Version TNSLSNR for Linux: Version 19.0.0.0.0 Production
    Start Date 15-AUG-2020 08:09:57
    Uptime 0 days 0 hr. 7 min. 34 sec
    Trace Level off
    Security ON: Local OS Authentication
    SNMP OFF
    Listener Parameter File /opt/oracle/product/19c/dbhome_1/network/admin/listener.ora
    Listener Log File /opt/oracle/diag/tnslsnr/oracle1/listener/alert/log.xml
    Listening Endpoints Summary
    (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=oracle1.localdomain)(PORT=1521)))
    (DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(KEY=EXTPROC1521)))
    (DESCRIPTION=(ADDRESS=(PROTOCOL=tcps)(HOST=oracle1.localdomain)(PORT=5500))(Security=(my_wallet_directory=/opt/oracle/admin/ORCLCDB/xdb_wallet))(Presentation=HTTP)(Session=RAW))
    Services Summary
    Service ORCLCDB has 1 instance(s).
    Instance ORCLCDB, status READY, has 1 handler(s) for this service
    Service ORCLCDBXDB has 1 instance(s).
    Instance ORCLCDB, status READY, has 1 handler(s) for this service
    Service ORCLCDB_DGMGRL has 1 instance(s).
    Instance ORCLCDB, status UNKNOWN, has 1 handler(s) for this service
    Service ac8d8d741e3e2a52e0534e38a8c0602d has 1 instance(s).
    Instance ORCLCDB, status READY, has 1 handler(s) for this service
    Service orclpdb1 has 1 instance(s).
    Instance ORCLCDB, status READY, has 1 handler(s) for this service
    The command completed successfully
  8. Меняем пароль пользователю SYS на сервере oracle1.
    SQL> alter user sys identified by "pa_SSw0rd";
  9. На сервере oracle2 запускаем listener и создаем реплику.
    [oracle@oracle2 ~]$ lsnrctl start
    [oracle@oracle2 ~]$ orapwd file=$ORACLE_BASE/product/19c/dbhome_1/dbs/orapwORCLCDB entries=10 password=pa_SSw0rd
    [oracle@oracle2 ~]$ echo "*.db_name='ORCLCDB'" > /tmp/initORCLCDB_STBY.ora
    [oracle@oracle2 ~]$ mkdir -p $ORACLE_BASE/oradata/ORCLCDB/pdbseed
    [oracle@oracle2 ~]$ mkdir -p $ORACLE_BASE/oradata/ORCLCDB/ORCLPDB1
    [oracle@oracle2 ~]$ mkdir -p $ORACLE_BASE/admin/ORCLCDB/adump
    [oracle@oracle2 ~]$ mkdir /opt/oracle/recovery_area
    [oracle@oracle2 ~]$ sqlplus / as sysdba
    SQL> STARTUP NOMOUNT PFILE='/tmp/initORCLCDB_STBY.ora'
    [oracle@oracle2 ~]$ rman TARGET sys/pa_SSw0rd@ORCLCDB AUXILIARY sys/pa_SSw0rd@ORCLCDB_STBY
    RMAN> DUPLICATE TARGET DATABASE FOR STANDBY FROM ACTIVE DATABASE DORECOVER SPFILE SET db_unique_name='ORCLCDB_STBY' COMMENT 'Is standby' NOFILENAMECHECK;
  10. На обоих серверах (oracle1 и oracle2) запускаем Data Guard.
    SQL> ALTER SYSTEM SET dg_broker_start=true;
  11. На сервере oracle1 подключаемся к консоли управления Data Guard и создаем конфигурацию.
    [oracle@oracle1 ~]$ dgmgrl sys/pa_SSw0rd@ORCLCDB
    DGMGRL> CREATE CONFIGURATION my_dg_config AS PRIMARY DATABASE IS ORCLCDB CONNECT IDENTIFIER IS ORCLCDB;
    DGMGRL> ADD DATABASE ORCLCDB_STBY AS CONNECT IDENTIFIER IS ORCLCDB_STBY MAINTAINED AS PHYSICAL;
    DGMGRL> enable configuration;


Итак, врезультате создания реплики ивключения Data Guard мыимеем следующее состояние:

DGMGRL> show configurationConfiguration - my_dg_config  Protection Mode: MaxPerformance  Members:  orclcdb      - Primary database    orclcdb_stby - Physical standby database       Warning: ORA-16854: apply lag could not be determinedFast-Start Failover:  DisabledConfiguration Status:WARNING   (status updated 40 seconds ago)


Это плохое состояние. Давайте подождем немного

DGMGRL> show configurationConfiguration - my_dg_config  Protection Mode: MaxPerformance  Members:  orclcdb      - Primary database    orclcdb_stby - Physical standby database Fast-Start Failover:  DisabledConfiguration Status:SUCCESS   (status updated 55 seconds ago)


Вот теперь состояние хорошее! Правда, реплика весьма пассивна, она непринимает запросы начтение (OPEN MODE: MOUNTED).

SQL> show pdbs    CON_ID CON_NAME  OPEN MODE  RESTRICTED---------- ------------------------------ ---------- ----------  2 PDB$SEED  MOUNTED 3 ORCLPDB1  MOUNTED


Давайте сделаем реплику активной, чтобы она принимала запросы на чтение. Тогда мы, в перспективе, сможем разгрузить сервер с "Primary" базой. Это то, что называется "Active Data Guard", либо active standby. Насервере oracle2 выполняем следующую последовательность команд вsqlplus:
alter database       recover managed standby database cancel;

alter database open;

alter database       recover managed standby database       using current logfile       disconnect from session;

alter pluggable database all open;

Вот теперь можно и почитать с реплики (OPEN MODE: READ ONLY):
SQL> show pdbs;    CON_ID CON_NAME  OPEN MODE  RESTRICTED---------- ------------------------------ ---------- ----------  2 PDB$SEED  READ ONLY  NO 3 ORCLPDB1  READ ONLY  NO

Ивконсоли DataGuard насервере oracle1 все выглядит неплохо:
DGMGRL> show configuration;Configuration - my_dg_config  Protection Mode: MaxPerformance  Members:  orclcdb      - Primary database    orclcdb_stby - Physical standby database Fast-Start Failover:  DisabledConfiguration Status:SUCCESS   (status updated 19 seconds ago)


Несмотря нато, что Data Guard унас теперь Active, кластер все еще вомногом пассивен. Нам инашему восхитительному Java-приложению онпо-прежнему нескажет нислова отом, что Primary теперь неPrimary. Для этого насерверах кластера должна быть запущена еще одна служба, ONS (Oracle Notification Services). Ипохоже, что для запуска этой службы установки Oracle Database недостаточно, нам потребуется установить Oracle Grid.



Установка и настройка Oracle Grid.




Тут все достаточно просто: как и в процессе установки Oracle Database мы просто будем следовать официальной инструкции.

  1. Насервере oracle1 иoracle2 под пользователем root загружаем ираспаковываем архив сOracle Grid, ставим gcc-c++, добавляем пользователя oracle вгруппу asm. Вслучае Virtual Box, как при установке Oracle, просто монтируем распакованную директорию Oracle Grid кобеим виртуальным машинам икопируем.
    mkdir -p /opt/oracle/product/19c/grid \        && cp -r /mnt/oracle_grid_19300/LINUX/* /opt/oracle/product/19c/grid/ \        && chown -R oracle:oinstall /opt/oracle/product/19c/grid
    

    yum install -y gcc-c++
    

    groupadd asm && usermod -aG asm oracle
    

  2. Далее, переключаемся впользователя oracle, выполняем проверку соответствия машины системным требованиям Oracle Grid.
    cd /opt/oracle/product/19c/grid/ && ./runcluvfy.sh stage -pre hacfg
    

    Verifying Physical Memory ...FAILED
    Required physical memory = 8GB
    Verifying Swap Size ...FAILED
    Required = 2.6924GB (2823138.0KB); Found = 2GB (2097148.0KB)]

    Может это ограничение иможно как-то изящно обойти, номыбудем поочередно добавлять оперативной памяти иswap, только чтобы Oracle Grid встал, апотом возвращать прежние значения.
  3. Выключаем сперва oracle2, затем oracle1.
    SQL> shutdown immediate

    # poweroff
  4. Добавляем оперативной памяти oracle1 до8192Мб, включаемVM игенерируем swap под пользователем root.
    dd if=/dev/zero of=/swap_file bs=1G count=7 \            && chmod 600 /swap_file && mkswap /swap_file \            && echo '/swap_file  swap  swap  defaults  0 0' >> /etc/fstab \            && swapoff -a && swapon -a
    
  5. Вновь проверяем соответствие машины системным требованиям, чтобы убедиться, что все хорошо.
    [oracle@oracle1 grid]$ cd /opt/oracle/product/19c/grid/ && ./runcluvfy.sh stage -pre hacfg
    Pre-check for Oracle Restart configuration was successful.

    Отлично! Теперь можно выполнить конфигурацию Oracle Grid.
  6. Под пользователем oracle насервере oracle1в директории /opt/oracle/product/19c/grid/ создаем файл grid_configwizard.rsp.
    cd /opt/oracle/product/19c/grid/ && touch grid_configwizard.rsp
    

    Содержимое файла grid_configwizard.rsp будет весьма лаконично, потому что нас интересует только oracle restart, без всяких там ASM ипрочих восхитительных технологий.
    oracle.install.responseFileVersion=/oracle/install/rspfmt_crsinstall_response_schema_v19.0.0
    INVENTORY_LOCATION=/opt/oracle/oraInventory
    oracle.install.option=CRS_SWONLY
    ORACLE_BASE=/opt/oracle
    oracle.install.asm.OSDBA=oinstall
    oracle.install.asm.OSASM=asm
    oracle.install.asm.SYSASMPassword=oracle
    oracle.install.asm.diskGroup.name=data
    oracle.install.asm.diskGroup.redundancy=NORMAL
    oracle.install.asm.diskGroup.AUSize=4
    oracle.install.asm.diskGroup.disksWithFailureGroupNames=/dev/sdb
  7. Выполняем скрипт понастройке oracle grid вконсольном режиме.
    ./gridSetup.sh -silent \               -responseFile /opt/oracle/product/19c/grid/grid_configwizard.rsp
    
  8. Порезультатам настройки, oracle grid просит выполнить скрипт под пользователем root, что мыиделаем.
    /opt/oracle/product/19c/grid/root.sh
    
  9. Далее, выполняем настройку oracle restart посредством запуска под пользователем root скрипта roothas.sh.
    cd /opt/oracle/product/19c/grid/crs/install/ && ./roothas.sh
    

  10. Переключаемся впользователя oracle ивыполняем скрипт runInstaller.
    cd /opt/oracle/product/19c/grid/oui/bin/ \                            && ./runInstaller -updateNodeList \                                ORACLE_HOME=/opt/oracle/product/19c/grid \                                -defaultHomeName CLUSTER_NODES= CRS=TRUE
    
  11. На машине oracle1 комментируем в /etc/fstab строчку, которая начинается на /swap_file, выключаем машину и возвращаем ей 2048 Мб RAM.
  12. Повторяем все предыдущие шаги раздела "Установка и настройка Oracle Grid" для VM oracle2.
  13. После того, как мыснова выдали виртуальным машинам вменяемое количество оперативной памяти, добавляем нужные сервисы вконфигурацию Oracle Restart. Для этого включаем обе виртуальные машины инасервере oracle1 под пользователем oracle добавляем экземплярБД вконфигурацию Oracle Restart.
    srvctl add database -db ORCLCDB \            -oraclehome /opt/oracle/product/19c/dbhome_1 \            -role PRIMARY
    

    /opt/oracle/product/19c/grid/bin/crsctl modify \            res ora.cssd -attr "AUTO_START=always" -unsupported
    

    /opt/oracle/product/19c/grid/bin/crsctl stop has
    

    /opt/oracle/product/19c/grid/bin/crsctl start has
    
  14. Насервере oracle2 под пользователем oracle добавляем экземплярБД вконфигурацию Oracle Restart.
    /opt/oracle/product/19c/grid/bin/crsctl modify \            res ora.cssd -attr "AUTO_START=always" -unsupported
    

    /opt/oracle/product/19c/grid/bin/crsctl stop has
    

    /opt/oracle/product/19c/grid/bin/crsctl start has
    

    srvctl add database -db ORCLCDB_STBY \           -oraclehome /opt/oracle/product/19c/dbhome_1 \           -role PHYSICAL_STANDBY \           -spfile /opt/oracle/product/19c/dbhome_1/dbs/spfileORCLCDB.ora \           -dbname ORCLCDB -instance ORCLCDB
    
  15. Наобоих серверах, oracle1 иoracle2, под пользователем oracle добавляем listener вконфигурацию Oracle Restart.
    srvctl add listener
    
  16. Насервере oracle1 под пользователем oracle добавляем вконфигурацию изапускаем сервис БД, ORCLCDB.
    srvctl add service -db ORCLCDB -service orclpdb -l PRIMARY -pdb ORCLPDB1
    

    srvctl start service -db ORCLCDB -service orclpdb
    
  17. Насервере oracle2 под пользователем oracle добавляем вконфигурацию сервис БД, ORCLCDB_STBY, истартуем экземплярБД
    srvctl add service -db ORCLCDB_STBY -service orclpdb -l PRIMARY -pdb ORCLPDB1
    

    srvctl start database -db ORCLCDB_STBY
    
  18. Насервере oracle1 иoracle2 запустим службу ons.
    srvctl enable ons
    

    srvctl start ons
    


Демонстрация "Switchover" на тестовом Java приложении




Витоге мыимеем 2сервера oracle срепликацией врежиме active-standby ислужбой ons, вкоторую будут отправляться события опереключениях, иJava-приложение сможет обрабатывать эти события помере ихпоступления.

Создаем тестового пользователя и таблицу в Oracle.
Подключаемся ксерверу oracle1 под пользователем oracle ивsqlplus выполняем команду посозданию пользователя и таблицы
  • sqlplus / as sysdba
    
  • alter session set container=ORCLPDB1;
    
  • CREATE USER testus        IDENTIFIED BY test        DEFAULT TABLESPACE USERS        QUOTA UNLIMITED ON USERS;
    
  • GRANT CONNECT,       CREATE SESSION,       CREATE TABLE       TO testus;
    
  • CREATE TABLE TESTUS.MESSAGES (TIMEDATE TIMESTAMP, MESSAGE VARCHAR2(100));
    



После создания тестового пользователя и таблицы, подключаемся к серверу oracle2 и "открываем" экземпляр на чтение, чтобы убедиться, что репликация работает.
  [oracle@oracle2 ~]$ sqlplus / as sysdba SQL> alter pluggable database all open; SQL> alter session set container=ORCLPDB1; SQL> column table_name format A10; SQL> column owner format A10; SQL> select table_name, owner from dba_tables where owner like '%TEST%';TABLE_NAME OWNER---------- ----------MESSAGES   TESTUS


Тестовое приложение состоит из одного только класса Main, вцикле пытается добавить запись втестовую таблицу, азатем этуже запись прочитать (см. методы "putNewMessage()" и"getLastMessage()"). Еще имеется метод "getConnectionHost()", который изобъекта "connection" спомощью "Reflection API" получает IP-адрес подключения кБД. Как далее видно вконсоли, после "switchover" этот адрес меняется.

Запускаем тестовое приложение ивыполняем вконсоли Data Guard "switchover".

DGMGRL> switchover to orclcdb_stby;


Видим, как влоге приложения меняется IP-адрес подключения, простой составляет 1минуту:

[Wed Aug 26 23:56:55 MSK 2020]: Host 192.168.56.78: - 2020-08-26 23:56:55|Ты кто такой?[Wed Aug 26 23:56:56 MSK 2020]: Host 192.168.56.78: - 2020-08-26 23:56:56|Ты кто такой?[Wed Aug 26 23:56:57 MSK 2020]: Host 192.168.56.78: - 2020-08-26 23:56:57|Ты кто такой?[Wed Aug 26 23:56:58 MSK 2020]: SQLException: - Давай досвидания![Wed Aug 26 23:57:02 MSK 2020]: SQLException: - Давай досвидания![Wed Aug 26 23:57:06 MSK 2020]: SQLException: - Давай досвидания![Wed Aug 26 23:57:10 MSK 2020]: SQLException: - Давай досвидания![Wed Aug 26 23:57:14 MSK 2020]: SQLException: - Давай досвидания![Wed Aug 26 23:57:18 MSK 2020]: SQLException: - Давай досвидания![Wed Aug 26 23:57:23 MSK 2020]: SQLException: - Давай досвидания![Wed Aug 26 23:57:27 MSK 2020]: SQLException: - Давай досвидания![Wed Aug 26 23:57:31 MSK 2020]: SQLException: - Давай досвидания![Wed Aug 26 23:57:35 MSK 2020]: SQLException: - Давай досвидания![Wed Aug 26 23:57:39 MSK 2020]: SQLException: - Давай досвидания![Wed Aug 26 23:57:43 MSK 2020]: SQLException: - Давай досвидания![Wed Aug 26 23:57:47 MSK 2020]: SQLException: - Давай досвидания![Wed Aug 26 23:57:51 MSK 2020]: Host 192.168.56.79: - 2020-08-26 23:57:52|Ты кто такой?[Wed Aug 26 23:57:53 MSK 2020]: Host 192.168.56.79: - 2020-08-26 23:57:53|Ты кто такой?[Wed Aug 26 23:57:54 MSK 2020]: Host 192.168.56.79: - 2020-08-26 23:57:54|Ты кто такой?


Переключаем обратно, для верности.

DGMGRL> switchover to orclcdb;


Ситуация симметричная.

[Wed Aug 26 23:58:54 MSK 2020]: Host 192.168.56.79: - 2020-08-26 23:58:54|Ты кто такой?[Wed Aug 26 23:58:55 MSK 2020]: Host 192.168.56.79: - 2020-08-26 23:58:55|Ты кто такой?[Wed Aug 26 23:58:56 MSK 2020]: SQLException: - Давай досвидания![Wed Aug 26 23:59:00 MSK 2020]: SQLException: - Давай досвидания![Wed Aug 26 23:59:04 MSK 2020]: SQLException: - Давай досвидания![Wed Aug 26 23:59:08 MSK 2020]: SQLException: - Давай досвидания![Wed Aug 26 23:59:12 MSK 2020]: SQLException: - Давай досвидания![Wed Aug 26 23:59:16 MSK 2020]: SQLException: - Давай досвидания![Wed Aug 26 23:59:20 MSK 2020]: SQLException: - Давай досвидания![Wed Aug 26 23:59:24 MSK 2020]: SQLException: - Давай досвидания![Wed Aug 26 23:59:28 MSK 2020]: SQLException: - Давай досвидания![Wed Aug 26 23:59:32 MSK 2020]: SQLException: - Давай досвидания![Wed Aug 26 23:59:36 MSK 2020]: SQLException: - Давай досвидания![Wed Aug 26 23:59:40 MSK 2020]: SQLException: - Давай досвидания![Wed Aug 26 23:59:44 MSK 2020]: Host 192.168.56.78: - 2020-08-26 23:59:45|Ты кто такой?[Wed Aug 26 23:59:46 MSK 2020]: Host 192.168.56.78: - 2020-08-26 23:59:46|Ты кто такой?


Также, обратим внимание наTCP-соединения нахостах oralce1 иoracle2. Data-порт занят подключениями только на primary, ONS-порт занят и на primary, и на replica.

До switchover.
oracle1Every 1.0s: ss -tapn | grep 192.168.56.1 | grep ESTAB | grep -v ":22"ESTAB 0 0 192.168.56.78:1521 192.168.56.1:49819 users:(("oracle_21115_or"...))ESTAB 0 0 192.168.56.78:1521 192.168.56.1:49822 users:(("oracle_21117_or"...))ESTAB 0 0 [::ffff:192.168.56.78]:6200 [::ffff:192.168.56.1]:49820 users:(("ons"...))

oracle2Every 1.0s: ss -tapn | grep 192.168.56.1 | grep ESTAB | grep -v ":22"ESTAB 0 0 [::ffff:192.168.56.79]:6200 [::ffff:192.168.56.1]:49821 users:(("ons"...))

После switchover.
oracle1Every 1.0s: ss -tapn | grep 192.168.56.1 | grep ESTAB | grep -v 22  Wed Aug 26 16:57:57 2020ESTAB 0 0 [::ffff:192.168.56.78]:6200 [::ffff:192.168.56.1]:51457 users:(("ons"...))

oracle2Every 1.0s: ss -tapn | grep 192.168.56.1 | grep ESTAB | grep -v ":22" Wed Aug 26 16:58:35 2020ESTAB 0 0 192.168.56.79:1521 192.168.56.1:52259 users:(("oracle_28212_or"...))ESTAB 0 0 192.168.56.79:1521 192.168.56.1:52257 users:(("oracle_28204_or"...))ESTAB 0 0 [::ffff:192.168.56.79]:6200 [::ffff:192.168.56.1]:51458 users:(("ons"...))

Заключение


Таким образом, мысмоделировали влабораторных условиях кластер Data Guard сминимальным количеством настроек иреализовали отказоустойчивое подключение тестового Java-приложения. Теперь мызнаем что разработчик хочет отDBA, аDBA отразработчика. Осталось еще запустить ипротестировать Fast Start Failover, но,пожалуй, врамках отдельной статьи.
Подробнее..

Вышла Java 15

15.09.2020 18:11:52 | Автор: admin

Сегодня в свет вышла новая, 15-я версия платформы Java.


Скачать JDK 15 можно по следующим ссылкам:


  • Oracle JDK (проприетарная версия, обратите внимание на ограничения в использовании).
  • OpenJDK (бесплатная версия)

В новый релиз попало 14 JEP'ов и сотни более мелких улучшений. Если хочется ознакомиться с полным списком изменений с точностью до всех JIRA-тикетов, то их можно посмотреть на сайте Алексея Шипилёва. Также если интересны все изменения API, то их можно посмотреть здесь.



Перечислим JEP'ы, которые попали в Java 15:


Язык


Блоки текста (JEP 378)


Блоки текста, которые появились в Java 13 и прошли два preview, теперь стали стабильной синтаксической конструкцией. Это значит, что в Java теперь две постоянные конструкции, которые появились с выхода Java 11: выражения switch и блоки текста.


Паттерн-матчинг для оператора instanceof (второе preview) (JEP 375)


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


Записи (второе preview) (JEP 384)


Записи, которые также появились в Java 14, тоже остались в режиме preview. Изменений по сравнению с прошлой версией немного: убрано ограничение, что канонический конструктор должен быть public, а также разрешены локальные перечисления и интерфейсы.


Sealed классы (preview) (JEP 360)


В Java появилось языковое нововведение: запечатанные классы. Помечаются такие классы модификатором sealed, после чего круг классов, которые могут наследоваться от данного класса, становится ограниченным. sealed классы мы подробно рассматривали в этой статье.


JVM


ZGC (JEP 377)


ZGC, который появился в Java 11 в экспериментальном статусе, теперь официально готов к продуктовой разработке. Напомним, что ZGC это сборщик мусора, который нацелен на маленькие паузы (< 10мс) и готовность работать в условиях огромных куч (> 1TB).


Shenandoah (JEP 379)


Shenandoah, ещё один низкопаузный сборщик мусора и конкурент ZGC, теперь также имеет статус готового к продуктовой разработке. Shenandoah впервые появился в Java 12. Также недавно стало известно, что Shenandoah был бэкпортирован в JDK 11, который является текущим LTS-релизом Java. Это значит, что чтобы его использовать, необязательно обновляться до JDK 15, а достаточно обновиться до JDK 11.0.9, которая выйдет 20 октября 2020 года.


Disable and Deprecate Biased Locking (JEP 374)


Biased Locking, который много лет существовал в JDK, было решено убрать из-за сложности поддержки и неочевидных преимуществ этой оптимизации. Начиная с этого релиза, опция -XX:+UseBiasedLocking отключена по умолчанию, а при её использовании и всех её связанных опций будет выдаваться предупреждение. Про мотивы отключения Biased Locking рассказал Сергей Куксенко в подкасте Hydra.


Удаление портов Solaris и SPARC (JEP 381)


Порты JDK на Solaris/SPARC, Solaris/x64 и Linux/SPARC, которые стали deprecated for removal в Java 14, теперь удалены окончательно. Удаление этих портов упростит и ускорит разработку JDK.


API


Скрытые классы (JEP 371)


Появился новый тип классов, называемых скрытыми. На скрытые классы не могут прямо ссылаться другие классы, и всё их использование может осуществляться только через рефлексию. Также их нельзя обнаружить по имени, и их методы не появляются в стек-трейсах. Создаются такие классы с помощью нового метода Lookup.defineHiddenClass().


Удаление движка JavaScript Nashorn (JEP 372)


Движок Nashorn, который стал deprecated for removal в Java 11, теперь удалён окончательно. В качестве замены Nashorn теперь придётся искать другой движок JavaScript, например, GraalVM JavaScript или Rhino.


Reimplement the Legacy DatagramSocket API (JEP 373)


Реализации старых сокетов из JDK 1.0 java.net.DatagramSocket and java.net.MulticastSocket были полностью заменены на более простые, современные и легкоадаптируемые к виртуальным нитям, которые планируется ввести в язык в рамках проекта Loom. Ранее в Java 13 были переписаны java.net.Socket и java.net.ServerSocket.


Foreign-Memory Access API (Second Incubator) (JEP 383)


API для доступа вне кучи Java, которое появилось в Java 14 в статусе модуля-инкубатора, остаётся в этом статусе.


Deprecate RMI Activation for Removal (JEP 385)


Устаревшая и малоиспользуемая часть RMI, которая называется RMI Activation, стала deprecated for removal.


Edwards-Curve Digital Signature Algorithm (EdDSA) (JEP 339)


Современный алгоритм с открытым ключом для создания цифровой подписи EdDSA реализован в Java.


Java 15, как и 12, 13, 14, является STS-релизом, и у неё выйдет только два обновления.

Подробнее..
Категории: Java , Graalvm , Java15 , Java14 , Java13 , Java12 , Sealed , Nashorn , Zgc , Shenandoah

Из песочницы Multi connection IBM MQ с использованием Spring

17.09.2020 00:18:21 | Автор: admin
Приведу пример как сконфигурировать несколько endpoints для подключения к IBM MQ.

Цель:

  • читать из нескольких очередей, именованных одинаково, но находящихся на разных хостах/администраторах очередей
  • писать ответ в рандомно определенную ноду

0. Будем считать, что вы на данный момент уже развернули MQ или пользуетесь чьей-то.

1. Подгружаем зависимости в проект:

maven
<dependency>    <groupId>com.ibm.mq</groupId>    <artifactId>mq-jms-spring-boot-starter</artifactId>    <version>2.3.3</version></dependency>


gradle
compile group: 'com.ibm.mq', name: 'mq-jms-spring-boot-starter', version: '2.3.3'


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

mq:  servers:    - queueManager: QM1      channel: DEV.ADMIN.SVRCONN      connName: ibmmq.ru(1414)      user: admin      password: passw0rd    - queueManager: QM2      channel: DEV.ADMIN.SVRCONN      connName: ibmmq.ru(1415)      user: admin      password: passw0rd  queue1: QUEUE1  queue2: QUEUE2

3. Создаем классы для считывания этих пропертей:

import lombok.Data;import lombok.EqualsAndHashCode;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.context.annotation.Configuration;@Configuration@ConfigurationProperties(prefix = "mq")@EqualsAndHashCode(callSuper = false)@Datapublic class MqConfig {    private List<ConnectionConfiguration> servers;    private String queue1;    private String queue2;}

import lombok.Data;import lombok.EqualsAndHashCode;@Data@EqualsAndHashCode(callSuper = false)public class ConnectionConfiguration {    String queueManager;    String channel;    String connName;    String user;    String password;}

4. Создаем слушателя:

import javax.jms.MessageListener;import lombok.SneakyThrows;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.messaging.handler.annotation.Payload;import org.springframework.stereotype.Component;@Component@Slf4jpublic class MqListener implements MessageListener {    @SneakyThrows    @Override    public void onMessage(@Payload javax.jms.Message message) {        log.info("Получено сообщение <" + message + ">");        //TODO: сюда добавим отправку ответа чуть позже    }

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

import com.fasterxml.jackson.databind.ObjectMapper;import com.ibm.mq.jms.MQConnectionFactory;import com.ibm.msg.client.wmq.WMQConstants;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.jms.annotation.EnableJms;import org.springframework.jms.config.*;import org.springframework.jms.connection.CachingConnectionFactory;import org.springframework.jms.core.JmsTemplate;import org.springframework.jms.support.QosSettings;import org.springframework.jms.support.converter.MappingJackson2MessageConverter;import org.springframework.jms.support.converter.MessageConverter;import org.springframework.jms.support.converter.MessageType;import org.springframework.jms.support.converter.SimpleMessageConverter;import javax.jms.*;import java.util.*;import static javax.jms.DeliveryMode.NON_PERSISTENT;import static javax.jms.Session.CLIENT_ACKNOWLEDGE;@Configuration@EnableJms@Slf4jpublic class MqConfiguration {    @Autowired    MqConfig mqConfig;    @Autowired    private JmsListenerEndpointRegistry registry;//Создаем фабрики слушателей, на вход которых также используем созданные connectionFactories    @Bean    public List<JmsListenerContainerFactory> myFactories(            @Qualifier("myConnFactories")             List<CachingConnectionFactory> connectionFactories,            MqListener mqListener) {        List<JmsListenerContainerFactory> factories = new ArrayList<>();        connectionFactories.forEach(connectionFactory -> {            DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();            factory.setConnectionFactory(connectionFactory);            factory.setSessionAcknowledgeMode(CLIENT_ACKNOWLEDGE);            QosSettings qosSettings = new QosSettings();            qosSettings.setDeliveryMode(NON_PERSISTENT);            factory.setReplyQosSettings(qosSettings);            SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();            endpoint.setId("myJmsEndpoint-"+ UUID.randomUUID());            endpoint.setDestination(mqConfig.getQueue1());            endpoint.setMessageListener(mqListener);            registry.registerListenerContainer(endpoint, factory);            factories.add(factory);        });        return factories;    }//Создаем лист темплейтов для отправки сообщений, на вход которому скармливаем созданные коннекты    @Bean    @Qualifier("myJmsTemplates")    public List<JmsTemplate> jmsTemplates(            @Qualifier("myConnFactories")             List<CachingConnectionFactory> connectionFactories) {        return getJmsTemplates(new ArrayList<ConnectionFactory>(connectionFactories));    }    public List<JmsTemplate> getJmsTemplates(List<ConnectionFactory> connectionFactories) {        List<JmsTemplate> jmsTemplates = new ArrayList<>();        for (ConnectionFactory connectionFactory : connectionFactories) {            JmsTemplate jmsTemplate = new JmsTemplate();            jmsTemplate.setConnectionFactory(connectionFactory);            jmsTemplate.setMessageConverter(new SimpleMessageConverter());            jmsTemplate.setDefaultDestinationName(mqConfig.getQueue2());            jmsTemplate.setDeliveryMode(NON_PERSISTENT);            jmsTemplate.setDeliveryPersistent(false);            jmsTemplate.setExplicitQosEnabled(true);            jmsTemplates.add(jmsTemplate);        }        return jmsTemplates;    }//Определяем коннекшионФактори для каждого элемента массива из yml-пропертей    @Bean    @Qualifier("myConnFactories")    public List<CachingConnectionFactory> connectionFactories() throws JMSException {        List<CachingConnectionFactory> factories = new ArrayList<>();        for (ConnectionConfiguration server : mqConfig.getServers()) {            CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory();            MQConnectionFactory cf = new MQConnectionFactory();            cachingConnectionFactory.setTargetConnectionFactory(cf);            cf.setQueueManager(server.getQueueManager());            cf.setChannel(server.getChannel());            cf.setConnectionNameList(server.getConnName());            cf.setStringProperty(WMQConstants.USERID, server.getUser());            cf.setStringProperty(WMQConstants.PASSWORD, server.getPassword());            cf.setStringProperty("XMSC_WMQ_CONNECTION_MODE", "1");            factories.add(cachingConnectionFactory);        }        return factories;    }}

endpoint.setMessageListener(mqListener);
Здесь указываем слушателя (которого создали в п.4), чтобы определить действия при приеме сообщения.

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

import javax.jms.TextMessage;public interface MqService {    void sendToMq(TextMessage msg);}

import javax.jms.TextMessage;import org.springframework.jms.JmsException;import org.springframework.jms.core.JmsTemplate;import org.springframework.stereotype.Service;@Service@Slf4jpublic class MqServiceImpl implements MqService {    @Autowired    private MqConfig mqConfig;    @Autowired    @Qualifier("myJmsTemplates")    List<JmsTemplate> jmsTemplates;    @Override    public void sendToMq(TextMessage msg ) {        //какая-то логика        //рандомным образом определяем в какую ноду/темплейт отправлять сообщение.        int maxIndex = jmsTemplates.size()-1; // Конечное значение диапазона - "до"        int randomNumber = (int) Math.round(Math.random() * maxIndex);        jmsTemplates.get(randomNumber).convertAndSend(mqConfig.getQueue2(), msg);    }}

7. Добавляем отправку ответа в слушатель:

    @Autowired    MqService mqService;    @SneakyThrows    @Override    public void onMessage(@Payload javax.jms.Message message) {        log.info("Получено сообщение <" + message + ">");        mqService.sentToMq((TextMessage) message);    }

Вуаля, готово, можно проверять.
Подробнее..
Категории: Java , Spring , Jms , Ibm mq

Категории

Последние комментарии

© 2006-2020, personeltest.ru