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

Анализ и проектирование систем

Перевод Выработка уникальных идей для Data Science-проектов за 5 шагов

22.09.2020 16:11:47 | Автор: admin
Вероятно, самое сложное в любом Data Science-проекте это придумать оригинальную, но реализуемую идею. Специалист, который ищет такую идею, легко может попасться в ловушку наборов данных. Он тратит многие часы, просматривая существующие наборы данных и пытаясь выйти на новые интересные идеи. Но у такого подхода есть одна проблема. Дело в том, что тот, кто смотрит лишь на существующие наборы данных (c Kaggle, Google Datasets, FiveThirtyEight), ограничивает свою креативность, видя лишь небольшой набор задач, на которые ориентированы изучаемые им наборы данных.

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



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

1. Почему я хочу начать работу над новым проектом?


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

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

2. К каким сферам относятся мои интересы и мой опыт?


Подумать над этим вопросом стоит по трём основным причинам.

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

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

3. Как найти вдохновение?


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

  • Новости, авторские статьи, публикации в блогах. Чтение о неких событиях или явлениях, которые наблюдали авторы публикаций, это отличный способ выработки идей. Например, портал WIRED опубликовал эту статью, посвящённую тому, что функция автодополнения ввода при поиске в Google демонстрирует политическую предвзятость. Вдохновившись этой идеей, можно исследовать систематические ошибки в языковых моделях. Или можно задаться вопросом о возможности предсказания географического положения человека на основе поисковых запросов, вводимых им в Google.
  • Научная литература. Научные публикации часто включают в себя рассказы о нерешённых вопросах, имеющих отношение к исследуемой теме. Например, в этой публикации рассказывается о языковой модели GPT-2 и упоминается о том, что эта модель, без её тонкой настройки, показывает себя на определённых задачах, вроде ответов на вопросы, не лучше, чем попытка решить эти задачи методом случайного угадывания. Почему бы не написать что-нибудь о нюансах тонкой настройки этой модели?
  • Материалы из сферы науки о данных. Чтение материалов, представляющих темы, связанные с Data Science, и содержащие обзоры соответствующих проектов, способно привести к новым идеям. Например, когда я прочитала об NLP-исследовании сериала Офис, я тут же пожалела о том, что мне эта идея не пришла раньше, чем автору материала. Но почему бы не исследовать какой-нибудь другой сериал? А может, изучить несколько фильмов и попытаться определить языковые паттерны? А для написания текстов к любимому сериалу можно попробовать воспользоваться моделью GPT-2.

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

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

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

4. Где найти подходящие данные?


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

  • Существующие хранилища данных: Kaggle, Google Datasets, FiveThirtyEight, BuzzFeed, AWS, UCI Machine Learning Repository, data.world, Data.gov и многие другие, которые можно найти с помощью Google.
  • Источники данных, используемые другими дата-сайентистами. Поищите сведения по интересующей вас теме в Google и в Google Scholar. Выясните, пытался ли уже кто-нибудь найти ответ на вопрос, похожий на ваш. Какие данные использовались в похожих исследованиях? Например, ресурс Our World in Data представляет академические и неакадемические источники данных, о которых вы можете не знать.
  • Данные, которые нужно собирать самостоятельно. Для сбора таких данных можно прибегнуть к веб-скрапингу, к анализу текстов, к различным API, к отслеживанию событий, к работе с лог-файлами.

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

5. Реализуема ли найденная идея?


Итак, у вас есть фантастическая идея! Но можно ли её реализовать? Снова пройдитесь по этапам процесса генерирования идей. Подумайте о том, чего вы хотите достичь (вопрос 1), интересна ли вам выбранная область, если ли у вас опыт работы в ней (вопрос 2), есть ли у вас данные, необходимые для реализации идеи (вопрос 4). Теперь вам нужно определить следующее: имеются ли у вас навыки, необходимые для реализации идеи и для достижения цели.

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

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

Итоги


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

Я, пользуясь описанной здесь методикой, постоянно нахожу оригинальные идеи для своих Data Science-проектов. Надеюсь, эта методика пригодится и вам.

Как вы ищите новые идеи для своих Data Science-проектов?



Подробнее..

Выбор архитектурного стиля (часть 2)

17.09.2020 12:16:31 | Автор: admin
Привет, хабр. Сегодня я продолжаю серию публикаций, которую написал специально к старту нового потока курса Software Architect.



Введение


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

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

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

Компонентно-ориентированная архитектура


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

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

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

Самая главная проблема такого монолита заключается в том, что разделение на модули является чисто логическим и может быть легко нарушено разработчиками. Может появиться модуль core, который постепенно превращается в помойку, может расти граф зависимостей между модулями и так далее. Для избежания таких проблем разработка должна вестить либо очень зрелой командой, либо под руководством архитектора, который на full time занимается code review и бьет нарушающих логическую структуру разработчиков по рукам.

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

Сервис-ориентированная архитектура


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

Сервис-ориентированная архитектура (SOA = service oriented architecture) решает все обозначенные проблемы монолита: при изменении затрагивается только одна служба, а четко определенный API поддерживает хорошую инкапсуляцию компонент.

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

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

Сервис-ориентированная архитектура неплохо поддерживается архитектурным коммьюнити и вендорами. Отсюда следует наличие множества курсов и сертификаций, хорошо проработанных паттернов. К последним относится, например, не безызвестная сервисная шина предприятия (ESB = enterprise service bus). При этом ESB это багаж от вендоров, она не обязательно должна использоваться в SOA.

Пик популярности сервис-ориентированной архитектуры приходился примерно на 2008 год, после чего она пошла на спад, который стал существенно более резким после появления микросервисов (~2015 год).

Заключение


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

Подробнее..

Паттерн сага как способ обеспечения консистентности данных

18.09.2020 14:13:41 | Автор: admin
Всем привет. Уже сейчас в OTUS открывает набор в новую группу курса Highload Architect. В связи с этим я продолжаю серию своих публикаций, написанных специально для этого курса, а также приглашаю вас на свой бесплатный демо урок по теме: Индексы в MySQL: best practices и подводные камни. Записаться на вебинар можно тут.



Введение


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

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

Паттерн Сага


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

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

Типов транзакций в саге несколько, целых четыре:

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

Организовывать сагу можно с помощью хореографии или оркестрации.

В случае с хореографической саги выделенный оркестратор отсутствует. На примере сервиса заказов и пользователей она может выглядеть так: сервис заказов получает запрос и создает заказ в состоянии PENDING, а затем публикует событие Заказ создан. Обработчик событий в сервисе пользователей обрабатывает данное событие, пытается зарезервировать товар и публикует результат в виде события. Сервис заказов обрабывает данное событие, подтверждая или отменяя заказ в зависимости от прочитанного результата.

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

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

Сага позволяет добиться ACD-модели (Atomicity + Consistency + Durability в терминах ACID), но одну букву мы потеряли. Недостаток буквы I приводит к известным проблемам недостатка изолированности. К ним относятся: потерянные обновления (lost updates) одна сага перезаписывает изменения, внесенные другой, не читая их при этом, грязное чтение (dirty reads) транзакция или сага читают незавершенные обновления другой саги, нечеткое/неповторяемое чтение (fuzzy/nonrepeatable reads) два разных этапа саги читают одни и те же данные, но получают разные результаты, потому что другая сага внесла изменения. Существует ряд паттернов, позволяющих пофиксить те или иные аномалии: семантическая блокировка, коммутативные обновления, пессимистическое представление, повторное чтение значения, файл изменений и по значению. Вопрос обеспечения изоляции остается открытым.

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

Заключение


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

Подробнее..

Как построить надежное приложение на базе 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, вы могли его встречать там. Так делать можно.

Подробнее..

2. Математическое описание систем автоматического управления ч. 2.4 2.8

31.08.2020 02:06:24 | Автор: admin

Лекции по курсу Управление Техническими Системами, читает Козлов Олег Степанович на кафедре Ядерные реакторы и энергетические установки, факультета Энергомашиностроения МГТУ им. Н.Э. Баумана. За что ему огромная благодарность.


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


В предыдущих сериях:
1. Введение в теорию автоматического управления
2. Математическое описание систем автоматического управления 2.1 2.3


В это части будут рассмотрены:
2.4 Основные виды входных воздействий
2.5. Основные положения и свойства интегральных преобразований Лапласа
2.6. Основные свойства преобразований Лапласа
2.7. Способы нахождения обратных преобразований Лапласа
2.8 Некоторые способы нахождения оригинала по известному изображению


Будет интересно познавательно и жестко.



На рисунке 3D график функции косеканс куба, к тебе лекции отношения не имеет, но чертовски красив.

2.4 Основные виды входных воздействий


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


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


2.4.1. Единичное ступенчатое воздействие


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


Реакция системы (звена) на такое воздействие называется переходной функцией.


Единичное ступенчатое воздействие обозначается 1(t) и бывает 3-х видов: два асимметричных и одно симметричное.


$1(y) \Rightarrow \left[ \begin{gathered} 1_+(t)\\ 1(t)\\ 1_-(t) \end{gathered} \right. \ \ \ \mathbf{(2.4.1)}$


Рассмотрим каждый из этих видов воздействий:


$1(t)=\left \{ \begin{gathered} 1, \ если\ t>0\\ \frac{1}{2}, \ если\ t=0\\ 0, \ если\ t<0 \end{gathered} \right. \ \ \ \ 1_-(t) = \left \{ \begin{gathered} 1, \ если\ t\geq 0\\ 0, \ если\ t<0 \end{gathered} \right. \ \ \ \ 1_+(t) = \left \{ \begin{gathered} 1, \ если\ t> 0\\ 0, \ если\ t\leq0 \end{gathered} \right. $



Рисунок 2.4.1 Графики единичных ступенчатых воздействий

В теории управления наибольшее распространение имеет асимметричное воздействие 1+ (t), поскольку часто в анализе удобно рассматривать процесс, когда при t$\leq$0 САР находится в равновесии, и анализ переходных процессов ведется только при t > 0.


Для удобства представления будем в дальнейшем записывать воздействие 1+(t), опуская индекс. 1+ (t) 1(t).


Поскольку рассматриваемое входное воздействие имеет разрыв при t = 0 (что часто нежелательно в численных алгоритмах, использующих конечно-разностную схему), имеется формула, позволяющая приближенно описать единичное ступенчатое воздействие, в виде неразрывной функции:


$1(t) = \lim_{T\to0}(1 -e^{-\frac{t}{T}}) \ \ \ \mathbf{(2.4.2)}$


где Т постоянная времени, а текущее время $t \geq 0$


На рисунке 2.4.2 представлена графическая иллюстрация аппроксимации 1(t) по формуле (2.4.2).



Рисунок 2.4.1 Графики единичных ступенчатых воздействий

2.4.2. Единичное импульсное воздействие: функция Дирака


В математике различают три вида данного воздействия: одно симметричное и два асимметричных


$\delta (t) \Rightarrow \left[ \begin{gathered} \delta_+(t)\\ \delta(t)\\ \delta_-(t) \end{gathered} \right. $


Рассмотрим все эти воздействия:
Симметричное единичное импульсное воздействие (t) определено как:


$\delta (t) = \left\{ \begin{gathered} 0, если \ t >0\\ \infty, если \ t =0\\\ 0, если \ t <0 \end{gathered} \ \ \ \ \ \int_{-\infty}^{+\infty} \delta(t) dt = 0; \right.$


Графическая иллюстрация симметричного единичного импульсного воздействия представлена на рисунке 2.4.3. Фактически (t) импульс (с длительностью стремящейся к нулю и амплитудой, равной бесконечности), площадь которого равна 1.



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

Для симметричного единичного импульсного воздействия (t) существует аналитическая форма представления:


$\int_{-\infty}^{+\infty} \delta(t)dt = \int_{-\infty}^{+\infty} \lim_{h \to \infty}\frac{h}{\sqrt{\pi}}e^{-h^2t^2} = \lim_{h \to \infty}\frac{1}{\sqrt{\pi}}\int_{-\infty}^{+\infty}e^{-(ht)^2} d(ht)$


Введем новую переменную $u = h \cdot t$, тогда:


$\frac{1}{\sqrt{\pi}} \lim_{u \to \infty}\int_{-\infty}^{+\infty}e^{-(u)^2} d(u) = \frac{1}{\sqrt{\pi}} \cdot \sqrt{\pi} = 1, \ \ поскольку \lim_{u \to \infty}\int_{-\infty}^{+\infty}e^{-(u)^2} d(u) = \sqrt{\pi}$


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


$\delta_- (t) = \left\{ \begin{gathered} 0, если \ t \leq 0\\ \infty, если \ t =-\epsilon \\\ 0, если \ t < \epsilon \end{gathered} \right. \ \ \ \ \ \ \delta_+ (t) = \left\{ \begin{gathered} 0, если \ t > \epsilon \leq 0\\ \infty, если \ t =\epsilon \\\ 0, если \ t \leq 0\ \end{gathered} \right. \ \ \ \ \ \int_{-\infty}^{+\infty} \delta(t) dt = 0;$


где $\epsilon -$сколь угодно малое положительное число ( 0)


Графическая иллюстрация смещенных единичных импульсных воздействий представлена на рисунке 2.4.4



Рисунок 2.4.4 Смещенные единичные импульсные воздействия.

В дальнейшем в нашем курсе будет использоваться только + (t). ==> Индекс + опускается ==> + (t) (t).


Поскольку смещенное единичное импульсное воздействие фактически имеет разрыва при t = 0 (что иногда нежелательно в численных алгоритмах, использующих конечно-разностную схему), имеется формула, позволяющая приближенно описать смещенное единичное импульсное воздействие:


$\delta(t) = \lim_{T \to 0} \frac{1}{T} e^{\frac{-t}{T}} \ \ \ \mathbf{(2.4.3)}$


где Т постоянная времени, а текущее время t>0!!!
На рисунке 2.4.5 представлена графическая иллюстрация аппроксимации (t) по формуле (2.4.3).



Рисунок 2.4.5 Графики аппроксимаций единичного импульсного воздействия

Реакция САУ (звена) на воздействие (t) называется весовой функцией.


2.4.3. Единичное гармоническое воздействие


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


$x(t) = sin (\omega \cdot t) \ \ \ \ \ \mathbf{(2.4.4)} $


где круговая частота, [1/с]; $\ \ \omega = 2 \cdot \pi\cdot f$, где $f$ частота в Герцах.


На рисунке 2.4.6 представлен график единичного гармонического воздействия.


Рисунок 2.4.6 Гармоничное воздействие

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


$x(t) = e^{\omega \cdot i \cdot t} \ \ \ \ \ \mathbf{(2.4.5)}$


Необходимо отметить, что показательная форма комплексное воздействие, и оно выглядит так (действительная и мнимая части условно показаны на рисунке 2.4.7):


$x(t)=e^{i\cdot \omega \cdot t} = \underbrace{cos (\omega \cdot t)}_{Re}+ \underbrace{i \cdot sin(\omega \cdot t)}_{Im} \ \ \ \ \ \mathbf{(2.4.6)} $



Рисунок 2.4.7 Гармоничное воздействие действительна и менимая часть

Действительная часть комплексного воздействия (Re) на самом деле косинусоидальное воздействие. Но так как частотные характеристики САУ определяются в режиме установившихся гармонических колебаний (т.е. при очень-очень больших значениях t), то не важно, по какому закону вводилось единичное гармоническое воздействие по синусу или по косинусу.


2.4.4. Линейное воздействие


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


$x(t)= a \cdot t \ \ \ \ \ \ \mathbf{(2.4.7)}$


где t 0, а при t < 0 входное воздействие всегда равно нулю.


На рисунке 2.4.8 представлен график линейного входного воздействия



Рисунок 2.4.8 Линейное входное воздействие

2.5. Основные положения и свойства интегральных преобразований Лапласа


Решение однородного обыкновенного дифференциального уравнения (ОДУ) усоб(t) записывается в виде (если нет повторяющихся корней):


$y_{соб.}(t) = \sum_{j=0}^nc_je^{\lambda_i}t$


т.е. все члены уравнения имеют одну и ту же форму. Этот результат наводит на мысль: а нельзя ли ввести какое-то преобразование, в результате которого уравнение динамики (дифференциальное) можно привести к чисто алгебраическому, решение которого не представляет проблем. А если затем сделать соответствующее обратное преобразование, то получим усоб (t), то есть получим цепочку:


Обыкновенное дифференциальное уравнение (ОДУ) $\Rightarrow$ Алгебраическое уравнение $\Rightarrow$ Решение $\Rightarrow$ Обратное преобразование $\Rightarrow$ Результат.


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


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


$F(s) = \int_0^{\infty}f(t) \cdot e^{-st}dt \ \ \ \ \ \ \mathbf{(2.5.1)}$



Рисунок 2.5.1

где s = c+i: ] -; + [; с абсцисса абсолютной сходимости
(обычно в курсе УТС с = 0); f(t) прообраз (оригинал); F(s) изображение (образ);


Символическое обозначение преобразования Лапласа:


$f(t) \Longrightarrow F(s)\ \ \ \ \ \ \mathbf{(2.5.2)}$


Преобразование Лапласа существует, если при t<0 f(t ) = 0 и выполняется условия сходимости:


$\int_0^\infty \mid f(t) \mid \cdot e^{-ct} dt < \infty\ \ \ \ \ \ \mathbf{(2.5.3)}$



Рис. 2.5.2

Рис. 2.5.3

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


Обратное преобразование Лапласа определяется следующим соотношением:


$f(t) = \lim_{\omega \to \infty} \frac{1}{2\pi \cdot i} \int_{c-i\cdot \omega}^{c+i \cdot\omega} F(s) \cdot e^{st}ds\ \ \ \ \ \ \mathbf{(2.5.4)}$


Необходимо подчеркнуть, что если условие сходимости выполняется, то любому оригиналу соответствует изображение. Обратное преобразование Лапласа не всегда существует, т.е. если известно F(s), это не означает, что ему соответствует оригинал f(t)!


Прямое преобразование Лапласа символически обозначается:


$f(t) \longrightarrow F(s) \ \ \ или \ \ \ L[f(t)] \equiv F(s)\ \ \ \ \ \ \mathbf{(2.5.5)}$


Обратное преобразование Лапласа обозначается:


$F(s) \longrightarrow f(t) \ \ \ или \ \ \ L^{-1}[F(s)] \equiv f(t)\ \ \ \ \ \ \mathbf{(2.5.6)}$


Существует двухстороннее преобразование Лапласа $L_B [f(t)]$, частным случаем которого является обычное преобразование Лапласа


$L_B[f(t)] = \int_{-\infty}^{+\infty}f(t) \cdot e^{- s t} dt\ \ \ \ \ \ \mathbf{(2.5.7)}$


Если при t 0 функция f(t) = 0, то $L_B [f(t)] L[f(t)]$


Частным случаем двухстороннего преобразования Лапласа (при с = 0, т.е. s = i) является преобразование Фурье, определяемое соотношениями:


$\Psi(i \cdot \omega) \equiv L_B[f(t)]_{s=i \cdot \omega} = \int_{ - \infty}^{+\infty}f(t) \cdot e^{-i \cdot \omega \cdot t} dt \\ f(t) = \frac{1}{2 \pi}\int_{ - \infty}^{+\infty}\Psi(i \cdot \omega) \cdot e^{-i \cdot \omega \cdot t} d \omega\ \ \ \ \ \ \mathbf{(2.5.8)}$


2.5.1. Использование преобразования Лапласа для операции дифференцирования


Пусть известно $f(t)$ и его изображение по Лапласу: $f(t) \longrightarrow F(s), $ выведем выражение для $ L[f(t) ]$.


Воспользуемся соотношением (2.5.1): $F(s) = \int_0^{\infty}f(t) \cdot e^{-st}dt$, тогда получаем:


$L[f'(t)] = \int_0^{\infty}[f(t)'] \cdot e^{-st}dt\Rightarrow \left \{ \begin{array} \ по \ частям \\ u = e^{-st}; \Rightarrow du = -s \cdot e^{-st} ;\\ d \nu = f'(t)dt; \Rightarrow \nu = f(t). \end{array} \right. \Rightarrow u \cdot v \mid_0^{\infty}-\int_0^{\infty} v \cdot dv \Rightarrow$


$\Rightarrow f(t) \cdot e^{-st} \mid_0^{\infty}+ \int_0^{\infty}f(t)s \cdot e^{-st} dt=[f(\infty) \cdot \underbrace{e^{-s\cdot \infty}}_{0}-f(0) \cdot e^0]+s \underbrace{\int_0^{\infty}f(t) \cdot e^{-st}}_{F(s)}=$

$=s \cdot F(s) - f(0) \Rightarrow L[f'(t)] = s \cdot F(s) - f(0),\ \ \ \ \ \ \mathbf{(2.5.9)}$


где: $f(0) $ начальное условия.
Если начальные условия равны нулю, то $f(0)=0$;


$L[f'(t)] = s \cdot F(s);\ \ \ \ \ \ \mathbf{(2.5.9.a)}$


Аналогичным способом найдем изображение 2-ой производной:


$L[f''(t)] = \int_0^{\infty}[f''(t)] \cdot e^{-st}dt\Rightarrow \left \{ \begin{array} cu = e^{-st}; \Rightarrow du = -s \cdot e^{-st} ;\\ d \nu = f''(t)dt; \Rightarrow \nu = f'(t). \end{array} \right. \Rightarrow \\ \Rightarrow f'(t) \cdot e^{-st} \mid_0^{\infty}+s \cdot \underbrace{\int_0^{\infty} f'(t) \cdot e^{-st} dt}_{L[f'(t)]} = -f'(0)+s \cdot F[f'(t)] \Rightarrow$

$ L[f''(t)] = s^2 \cdot F(s) -s \cdot f(0) - f'(0),\ \ \ \ \ \ \mathbf{(2.5.10)}$


Если при $t=0, f(t) \ и \ f'(0)$ равны нулю (нулевые начальные условия), то:

$L[f''(t)] = s^2 \cdot F(s)\ \ \ \ \ \ \mathbf{(2.5.10.a)}$


Обобщая на производную n-го порядка при нулевых начальных условиях, имеем:


$L[f^{(n)}(t)] = s^n \cdot F(s)\ \ \ \ \ \ \mathbf{(2.5.11)}$


2.5.2. Использование преобразования Лапласа для операции интегрирования


Пусть известно $f(t)$ и его изображение по Лапласу: $f(t) \longrightarrow F(s), $ выведем выражение для $ L[\int f(t) ]$.

$L[\int f(t)dt] = \int_0^{\infty}[f(t)dt] \cdot e^{-st}dt \Rrightarrow \left \{ \begin{array} \ e^{-st}dt = dv; \Rightarrow v = - \frac{1}{s}e^{-st}; \\ \int f(t)dt = u; \Rightarrow du =f(t)dt;\\ \end{array} \right. \Rightarrow$


$[-1 \frac{1}{s}e^{-st} \cdot \int f(t) dt] \mid_0^{\infty} + \frac{1}{s} \underbrace{\int_0^{\infty}f(t) \cdot e^{-st} dt}_{F(s)} = \frac {1}{s}F(s)+\frac {1}{s}[\int f(t) dt ] \mid_0$


Окончательно:


$L[\int f(t)dt] =\frac {1}{s}F(s)+\underbrace{\frac {1}{s}[\int f(t) dt ] \mid_0}_{добавка \ \ н.у}\ \ \ \ \ \ \mathbf{(2.5.12)}$


Если начальные условия равные нулю, то:


$L[\underbrace{\ \int\int\int}_{n-раз} f(t)dt] =\frac {1}{s^n}F(s)\ \ \ \ \ \ \mathbf{(2.5.13)}$


Таким образом, операция интегрирования в оригинале функции приводит появлению в её изображении добавке, равной 1/s.


2.6. Основные свойства преобразований Лапласа


2.6.1. Свойство линейности


Пусть есть процессы описываемые функциями $f_1(t)$ и $f_2(t)$, каждый из которых имеет свое изображение по Лапласу: $f_1(t) \longrightarrow F_1(s); f_2(t) \longrightarrow F_2(s);$. Если $f(t) = f_1(t) \pm f_2(t) $ то:


$L[f(t)]=L[f_1(t) \pm f_2(t)] = F_1(s) \pm F_2(s) \ \ \ \ \ \ \ \ \mathbf{(2.6.1)}$


Если $f(t)= a \cdot f_1(t)$, то:


$L[f(t)] = L[a \cdot f_1(t)] = a \cdot L[f_1(t)]= a \cdot F_1(s) \ \ \ \ \ \ \ \ \mathbf{(2.6.2)}$


2.6.2. Свойство подобия (свойство изменения масштаба)


Пусть $f(t) \longrightarrow F(s)$ известно, необходимо найти $L[f(a \cdot t)]$


$L[f(a \cdot t)] = \frac{1}{a} \int_0^{\infty} f(a \cdot t) \cdot e^{-st \cdot \frac{a}{a}}d(at) = \frac{1}{a} \int_0^{\infty} f(a \cdot t) \cdot e^{-\frac{s}{a} \cdot at}d(at) \Rightarrow$


$L[f(at)]= \frac{1}{a} F(\frac{s}{a}) \ \ \ \ \ \ \ \ \mathbf{(2.6.3)}$


2.6.3. Свойство запаздывания (теорема запаздывания)


Пусть $f(t) \longrightarrow F(s)$ известно, необходимо найти $L[f(t - \tau )]$



Рисунок 2.6.1 Иллюстрация переходного процесса с запаздыванием

$L[f(t - \tau)] = \int_0^ \infty f(t -\tau)e^{-s[t- \tau+ \tau]}d(t- \tau) = e^{-s \cdot \tau} \underbrace{\int_0^\infty f(t -\tau)e^{-s[t- \tau]}d(t- \tau)}_{F(s)} $


$L[f(t \pm \tau)] = F(s) \cdot e^{\pm s\cdot \tau} \ \ \ \ \ \ \ \ \mathbf{(2.6.4)}$


2.6.4. Свойство смещения в комплексной плоскости


$L[f(t) \cdot e^{\pm s_0 \cdot t}]= F(s \pm s_0) \ \ \ \ \ \ \ \ \mathbf{(2.6.5)} $


2.6.5. Первая предельная теорема


Если $f(t) \longrightarrow F(s)$ известно, а так же существует $\lim_{s \to \infty}f(t)$, то:


$ \lim_{t \to \infty}f(t) = \lim_{s \to 0}s \cdot F(s) \ \ \ \ \ \ \ \ \mathbf{(2.6.6)} $



Рисунок 2.6.2 Иллюстрация первой предельной теоремы

Это означает, что оси t и s формально направлены в противоположные стороны, т.е. чем больше t, тем меньше s и наоборот.


2.6.6.Вторая предельная теорема


$\lim_{t \to 0}f(t)= \lim_{s \to \infty}s \cdot F(s) \ \ \ \ \ \ \ \ \mathbf{(2.6.7)}$


2.7. Способы нахождения обратных преобразований Лапласа по известному изображению


Вычисление оригиналов по известному (данному) изображению можно выполнить:
по соответствующим таблицам преобразований Лапласа;
по формулам Хэвисайда;
разложением на элементарные дроби;
другими способами.


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


Таблица основных преобразований Лапласа


Наименование функции Оригинал Изображение
1 Единичная импульсная ф-ция (t) 1
2 Единичное ступенчатое воздействие 1(t) $\frac{1}{s}$
3 Неединичные импульсное
и ступенчатое воздействия
a (t);
a 1(t)
$ a; \\ \frac{a}{s}$
4 Экспонента $e^{-at} \cdot 1(t)$ $ \frac{1}{s+a}$
5 Степенная функция $t^{n}$ $ \frac{n!}{s^{n+1}}$
6 Синусоида $sin(at)$ $ \frac{a}{s^{2}+a^2}$
7 Косинусоида $cos(at)$ $ \frac{s}{s^{2}+a^2}$
8 Смещенная экспонента $\frac{1}{a}(1-e^{-at})$ $ \frac{1}{s(s+a)}$
9 Затухающая синусоида $e^{-b \cdot t} \cdot sin (a \cdot t))$ $ \frac{a}{(s+b)^2+a^2}$
10 Затухающая косинусоида $e^{-b \cdot t} \cdot cos (a \cdot t))$ $ \frac{s+b}{(s+b)^2+a^2}$

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


Например, если изображение F(s) можно представить в виде отношения полиномов по степеням s, то наиболее общим и эффективным способом поиска оригинала является формула Хэвисайда.


Если $F(s) = \frac{D_1(s)}{D_0(s)}$, где $D_1(s)$ и $D_0(s)$ полиномы по степеням s, то:


$ f(t)=\sum_{j=1} \frac{1}{(k_j-1)!} \cdot \lim_{s \to s_j} \frac{d^{k_j-1}}{ds^{k_j-1}}[(s-s_j)^{k_j} \cdot F(s) \cdot e^{st}], \ \ \ \ \ \ \ \ \mathbf{(2.7.1)}$


где $s_j$ полюса изображения, т.е. те значения s при которых полином $D_0(s)$ обращается в ноль;
$k_j$ кратность j го полюса.


Если уравнение $D_0(s)=0$ имеет n различных корней, то это означает что полюса F(s) имеют кратность, равную единице, т.е. нет повторяющихся полюсов.


Необходимо отметить, что использование формулы (2.7.1) будет корректно только в том случае, когда степень полинома $D_0(s)$ выше степени полинома $D_1(s)$. Если степени равны, то необходимо выделить целую часть (разделив в столбик полиномы) и чисто дробную часть, после чего для чисто дробной части корректна формула (2.7.1).


2.8 Некоторые способы нахождения оригинала по известному изображению


В качестве иллюстрации возможностей формулы Хэвисайда рассмотрим следующий пример:


Пример 1. Предположим, что изображение F(s) некоторого неизвестного процесса f(t) равно:


$F(s) = \frac{A}{S^2(T \cdot S+1)}; \ \ \ \ \ \ F(s) \to f(t)=?$


$D_1(s) = A; \ \ \ \ \ D_0(s) = s^2(Ts+1)$


Найдем полюса:


$D_0(s) = s^2(Ts+1) \Rightarrow S_1 =-\frac{1}{T}; \ \ S_2=S_3=0; $


$ f(t) = S_1+S_2 \ \ для \ \ j=1; \ \ S_1=- \frac{1}{T} \ \ \Rightarrow \\ \lim_{s \to -\frac{1}{T}}[(s+\frac{1}{T})\cdot \frac{A}{S^2(T \cdot S+1)} \cdot e^{st}] \Rightarrow \frac{1}{T}\cdot \frac{A}{\frac{1}{T^2}} \cdot e^{-\frac{t}{T}}=A \cdot T \cdot e^{-\frac{t}{T}}; \\ для \ \ j =2,3 \\ \frac{1}{(2-1)!}\lim_{s \to 0} \frac{d}{ds}[(s-0)^2 \cdot \frac{A}{S^2(T \cdot S+1)} \cdot e^{st}] = \lim_{s \to 0}[-\frac{A}{(TS+1)^2} \cdot T \cdot e^{st}+t \cdot e^{st} \frac{A}{TS+1}] =\\ =[-AT +At] \Rightarrow \\ f(t) =A \cdot T \cdot e^{-\frac{t}{T}} - A \cdot T+A \cdot t = A[t - T(1 -e^{-\frac{t}{T}})]; $



Рисунок 2.8.1 График процесса построенный по изображению: $F(s) = \frac{A}{S^2(T \cdot S+1)}$.

Разложение на элементарные дроби.


$f(t) = ? \ \ \ \ \ F(s) = \frac{D_1(s)}{D_0(s)}$


Если корни уравнение уравнения $D_0(s) = 0$ различны, т.е. нет совпадающих, то:


$F(s) = \frac{D_1(s)}{D_0(s)}=\frac{a_1}{s -s_1}+\frac{a_2}{s -s_2}+ ..+\frac{a_k}{s -s_k} +R(s)$


где $S_i$ корни уравнения; $R(s)$ остаточный член (не разлагается на действительные дроби);


$s_1,s_2,...,s_k \in R \\ s_{k+1},s_{k+2},...,s_n \in C$


Используя свойства линейности преобразований Лапласа, мы можем представить $f(t) $ как сумму преобразований:


$f(t) = f_1(t)+f_2(t)+...+f_k(t)+f_{ост}(t)$



Пример 2


Имеем известное изображение:


$F(s)=\frac{s+3}{s^2+3s+2}$


$f(t)$ оригинал, при нулевых начальных условиях: $t =0, \ \ \ f(0)=0.$


Разложение на элементарные дроби:


$\left | \begin{gathered} s_1 = -1;\\ s_2 = -2; \end{gathered} \right. \ \ \Rightarrow F(s)=\frac{a_1}{s+1}+\frac{a_2}{s+2}.$


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


$a_1(s+2)+a_2(s+1) = s+3 \\ \left \{ \begin{gathered} a_1+a_2 =1\\ 2 \cdot a_1+a_2 = 3 \end{gathered} \right. \Rightarrow \left \{ \begin{gathered} a_1 =2\\ a_2 = -1 \end{gathered} \right. $


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


$F(s) = \frac{2}{s+1} - \frac{1}{s+2} \ \ \ и \\ L^{-1} \left[\frac{2}{s+1}\right] = 2 \cdot L^{-1} \left[\frac{1}{s+1}\right] = 2 \cdot e^{-t} \cdot 1(t) \\ L^{-1} \left[\frac{1}{s+2}\right] = e^{-2t} \cdot 1(t) \\ f(t) = f_1+f_2 =(2 \cdot e^{-t} -e^{-2t})\cdot 1(t) $



Рисунок 2.8.2 График процесса построенный по изображению:$F(s)=\frac{s+3}{s^2+3s+2}$.

В заключение несколько полезных ссылок теме описанной в этой лекции:


Подробнее..

Как программист читает Происхождение видов Дарвина

01.09.2020 08:13:07 | Автор: admin
Во время чтения Происхождения видов путем естественного отбора Чарльза Дарвина, меня не покидало стойкое дежавю. Позже я понял, что механизмы, описанные в книге сильно коррелируют с механизмами enterprise разработки в больших компаниях. Где в качестве условий окружающей среды выступают постоянно меняющиеся бизнес-требования и программисты, а в качестве организмов код.

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

Глава V. Законы вариации. Краткий обзор


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

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

Термины, которые я заменил в оригинальном параграфе



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


Еще заменил термины, которые в данном контексте будут выглядеть странно:
  • употребление -> использование (так как слово выглядит немного устаревшим)
  • организация -> структура приложения (чтобы смысл был верным)
  • образ жизни -> жизненный цикл


Так же предлагаю ознакомиться с оригинальным параграфом, его я спрятал под спойлер.

Читать оригинальный параграф из книги
Наше незнание законов вариации глубоко. Ни в одном из 100 случаев мы не можем определить причину, почему тот или другой орган изменился. Но во всех случаях, где мы обладаем средствами для сравнения, оказывается, что образование меньших различий между разновидностями одного вида вызывается действием тех же законов, что и больших различий между видами одного рода. Перемены в условиях обычно вызывают только колеблющуюся вариабельность, но иногда ведут к прямым и определенным результатам: и эти результаты с течением времени могут сделаться более сильно выраженными, хотя в пользу этого еще не имеется достаточного доказательства. Привычка в образовании конституциональных особенностей, употребление в усилении органа и неупотребление в их ослаблении и уменьшении во многих случаях кажутся мощными в своем действии. Части гомологичные склонны варьировать одинаковым образом, а равно и связываться друг с другом. Модификация твердых и наружных частей действует на части мягкие и внутренние. Когда одна какая-нибудь часть сильно развивается, она, возможно, отвлекает питательные вещества от с нею смежных частей, и всякая часть организации, которая может быть устранена без ущерба, будет устранена. Изменения в строении в раннем возрасте могут воздействовать на части, развивающиеся позднее; несомненно, встречаются многочисленные случаи коррелятивной вариации, природу которой мы не способны понять. Части, многократно повторяющиеся, изменчивы как в своем числе, так и в строении, и это, вероятно, проистекает из отсутствия строгой специализации таких частей для какой-либо особой функции, так что их модификациям не препятствовал естественный отбор. Последствием этой же причины, быть может, является факт, что органические существа, стоящие на низших ступенях органической лестницы, более изменчивы, чем вышестоящие, вся организация которых более специализирована. Органы рудиментарные, будучи бесполезными, не подпадают под действие естественного отбора и поэтому изменчивы. Признаки видовые, т. е. признаки, которыми виды одного рода стали различаться с того времени, как они ответвились от общего предка, более изменчивы, чем признаки родовые, т. е. такие, которые унаследованы издавна и у которых на протяжении указанного периода не возникали различия. В этих замечаниях мы касались специальных органов или частей, но тем не менее изменчивых, потому что они варьировали еще недавно и вследствие этого становились различными; но во II главе мы видели, что тот же принцип применим и к особи в целом. Мы убедились, что в области, заключающей много видов данного рода, т. е. где недавно происходило значительное изменение и дифференцировка или где активно шло производство новых видовых форм, в такой области и у таких видов мы и в настоящее время встречаем в среднем наибольшее число разновидностей. Вторичные половые признаки очень изменчивы; они же сильно различаются у видов одной группы. Изменчивость одних и тех же частей организации обычно была полезной как для образования вторичных половых различий между двумя полами одного вида, так и образования видовых различий между видами одного рода. Всякая часть или орган, чрезмерно или исключительным образом развитые по сравнению с той же частью или органом у родственных видов, должны были подвергнуться модификации в необычайных размерах со времени возникновения этого рода; отсюда нам понятно, почему они еще часто изменчивы в гораздо более значительной мере, чем другие части, так как вариация представляет медленный и долго длящийся процесс и естественный отбор в подобных случаях не располагал до сих пор достаточным временем, чтобы осилить тенденцию к дальнейшей изменчивости и реверсии к менее модифицированному состоянию. Но когда вид с необычайно развитым органом сделался родоначальником многочисленных модифицированных потомков, что, согласно моему взгляду, должно быть крайне медленным процессом, требующим огромного промежутка времени, то в подобном случае естественный отбор уже успел сообщить этому органу постоянные черты, несмотря на необычайность его развития. Виды, унаследовавшие от своего общего предка почти одинаковую конституцию и подвергшиеся воздействию сходных условий, естественно, имеют наклонность давать аналогичные вариации или иногда возвращаются к некоторым признакам своих далеких предков. Хотя, из-за реверсии и аналогичной вариации, новые и важные модификации не могут возникать, такие модификации будут добавляться к прекрасному и гармоничному многообразию природы.
Какова бы ни была причина, быть может, каждого слабого различия между потомством и их родителями и причина для каждого из них должна существовать, мы имеем основание полагать, что неуклонное кумулирование благоприятных различий вызвало все наиболее важные модификации строения в связи с образом жизни каждого вида.




Небольшое объяснение



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

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

Пример 1


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

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


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

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

Пример 2


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

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

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

Пример из жизни:
Если вы используете в проекте, например, кнопки из CSS Bootstrap фреймворка, то очевидно, что чаще будет меняться содержимое классов .btn или .btn-primary, чем переименовывание этих классов во что-нибудь в духе .g-button или g-button-first

Заключение


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

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

Мама, кажется я архитектор

02.09.2020 00:22:36 | Автор: admin


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


Кто такой архитектор?


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


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

или:


Архитектор человек, который лучше других знает когда нужно уходить из проекта.

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


  • Широкий кругозор с одновременно глубоким погружением в детали;
  • Способность комплексно видеть причинно-следственные связи и возможные результаты развития событий.

За этими пунктами стоит большой опыт. Можно ли обойтись без десятка(ков) лет в профессии, чтобы стать архитектором? Можно. Так же, как стать сеньором С++ через пару месяцев. Все зависит от человека, его способностей и самокритичности.


Если вы сами создавали хотя бы одну программку, вы уже архитектор. Архитектор-джун. Смешно звучит. Хотя.


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


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

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


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


Поэтому, к важным качествам архитектора относится и:


  • Высокий эмоциональный интеллект.

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


Что должен делать архитектор?


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


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


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


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


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


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


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


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


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


  • А давайте все сделаем на React! Мы так делали и у нас все получилось!

Сомневаться в этом нет причин. Но это не повод делать также. У проекта на котором он работал есть свои исторические корни и проблемы. Весьма вероятно, что React имеет минусы, о которых разработчик или умалчивает, или не знает.


Умалчивать разработчик может ненамеренно. Возможно он исключительно React использовал до этого. Ему не с чем сравнивать. Случается и обратное. Например, на рынке разработчик React выше оплачивается. На прошлом месте он завидовал коллегам пишущим на React. Но шанса изменить стек не было. Т.е. разработчик имеет исключительно шкурный интерес.


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


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


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


И конечно, архитектор обязан создавать системы с бизнесом, для бизнеса и ради бизнеса.


Чего не должен делать архитектор?


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


Т.е. количественный метод принятия решений. Где любой в равной степени может воздействовать на выбор.


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


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


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


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

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


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


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


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


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


Где искать архитектора?


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


  • Широкий, актуальный и релевантный опыт в ИТ;
  • Лидерские качества;
  • Развитые софтскилы.

Предлагаю эксперимент закройте рукой слово архитектор. Какая роль рисуется под этими скилами? Скорее ИТ-директор или CTO. И это вполне справедливо. Более того, вероятна ситуация, когда от архитектора требуются гораздо более развитые скилы из этого перечня. А разница заключается в обязанностях.


Для архитектора:


  • Создавать и развивать архитектуру систем;
  • Участвовать в разработке процессов компании;
  • Наставничество;
  • Разработка стандартов.
  • ...

Для директора:


  • Работа с подрядчиками;
  • Управление бюджетом;
  • Управление и развитие команды;
  • Обеспечение непрерывности процессов;
  • ...

Что мы видим? Очевидно, что архитектор и руководитель расходятся в цели существования.


Типичный юный ИТшник видит перед собой карьеру: джун->мидл->сеньор->тимлид->CTO. Но где-то на третьей ступени он начинает понимать, что, что-то идет не так. И уже на последнем теряет самореализацию в разработке. Он перегружен административкой. Забыл когда в последний раз код писал.


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


Путь архитектора это иной, весьма интересный путь. Я бы сказал, это правильный путь ИТшника. Не менеджера, а именно ИТшника. Который хочет иметь все более серьезные профессиональные вызовы. Он имеет возможность самореализации каждый день на всем карьерном пути.


Так где искать архитекторов? Мне кажется: среди счастливых профессионалов или внутри несчастных менеджеров.


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


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

Подробнее..

Micro Property минималистичный сериализатор двоичных данных для embedded систем

12.09.2020 20:07:24 | Автор: admin
Micro Property библиотека для сериализации данных с минимальными накладными расходами. Она разработана для использования в микроконтроллерах и различных встраиваемых устройствах с ограничениями по размеру памяти, которым приходится работать по низкоскоростным линиям связи.

Конечно, я знаю про такие форматы как xml, json, bson, yaml, protobuf, Thrift, ASN.1. Даже нашел экзотический Tree, который сам является убийцей JSON, XML, YAML и иже с ними.

Так почему же они все не подошли? Зачем я был вынужден написать еще один сериализатор?

image


Исходные требования


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

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

Также, часть этих устройств подключены к общей линии связи CAN, которая и обеспечивает передачу данных в рамках всей системы в целом. Скорость передачи данных по линии связи Modbus до 115200 Бод, а скорость по шине CAN ограничена скоростью до 50кБод из-за её протяженности и присутствия серьезных индустриальных помех.

Устройства в подавляющем большинстве разработаны на микроконтроллерах серий STM32F1x и STM32F2x. Хотя часть из них работает и на STM32F4x. Ну и конечно, Windows/Linux на базе систем с x86 микропроцессорами в качестве контроллеров верхнего уровня.

Для оценки объема данных, которые обрабатываются и передаются между устройствами или хранятся в качестве настроек/параметров работы: В одном случае 2 числа по 1 байт и 6 чисел по 4 байта, в другом 11 чисел по 1 байту и 1 число 4 байта и т.д. Для справки, размер данных в стандартном кадре CAN до 8 байт, а во фрейме Modbus, до 252 байт полезных данных.

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

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

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

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

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

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

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


Передача двоичных данных с идентификацией полей:
Минусы:
  • Неизбежные накладные расходы для передачи имени и типа данных для каждого поля.

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



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

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

image

А какие есть варианты?


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

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

Основные форматы xml, json, yaml и другие текстовые варианты с очень удобным и простым формальным синтаксисом, который хорошо подходит для обработки документов и одновременно удобен для чтения и редактирования человеком, пришлось сразу отбросить. И как раз из-за своего удобства и простоты они имеют очень большие накладные расходы при хранении бинарных данных, которые как раз и требовалось обрабатывать.

Поэтому, в виду ограниченности ресурсов и низкоскоростных линий связи, было решено использовать бинарный формат представления данных. Но и в случае форматов, умеющих преобразовывать данные в бинарное представление, таких как Protocol Buffers, Flat Buffers, ASN.1 или Apache Thrift, накладные расходы при сериализации данных, а так же общее удобство их применения не способствовало к немедленному внедрению любой из подобных библиотек.

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

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

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


Что получилось


В результате раздумий и нескольких экспериментов получился сериализатор со следующими особенностями и характеристиками:
  • Оверхед для данных фиксированного размера 1 байт (без учета длины имени поля данных).
  • Оверхед для данных переменного размера, таких как блоб, текстовая строка или массив 2 байта (так же без учета длины имени поля данных). Так как использовать данный формат предполагается в устройствах, работающих по протоколам CAN и Modbus, то для хранения размера можно ограничиться одним байтом.
  • Ограничения на размер имени поля 16 байт.
  • В качестве идентификатора поля используется текстовая строка с завершающим нулевым символом, которая обрабатывается как бинарные данные, т.е. без учета завершающего нуля. Вместо текстовой строки в качестве идентификаторов полей, можно использовать целые числа или произвольные бинарные данные размером до 16 байт.
  • Максимальный размер полей данных переменной длины (блоб, текстовая строка или массив) 252 байта (т.к. размеры полей хранятся в одном байте).
  • Общий размер сериализованных данных без ограничений.
  • При работе память не выделяется. Все действия происходят только с внешним буфером без внутреннего выделения и освобождения памяти.
  • Возможен режим работы только чтение, например для работы с настройками приложения, которые сохранены в программной памяти микроконтроллера. В том числе, корректно отрабатывается ситуация, когда данные размещены в очищенной флеш-памяти (заполненной 0xFF).
  • В режиме редактирования поддерживается только добавление новых данных до заполнения буфера. Возможность обновления полей штатным способом не реализована, потому что для изначальных задач подобный функционал не требовался. Хотя при необходимости есть возможность редактировать данные по указателю в буфере.
  • Ну а в случае крайней необходимости, можно будет попробовать добавить возможность обновления полей. Для этого даже оставлен в резерве один из типов.


Поддерживаемые типы данных:


  • Целые числа размером от 8 до 64 бит с преобразованием в сетевой порядок байт и обратно.
  • Логические значения и числа с плавающей запятой одинарной и двойной точности.
  • Двоичные данные переменной длины (блоб или массив байт).
  • Текстовые строки двоичные данные с завершающим нулевым символом в конце. При сериализации строк после данных записывается нулевой символ, чтобы потом было удобно с ними работать как с обычными строками, без необходимости копировать данные в промежуточный буфер или высчитывать количество символов в строке. Хотя есть возможность выстрелить себе в ногу и сохранить текстовую строку с нулевым символом в где нибудь в середине строки ;-)
  • Одномерные массивы для всех типов целых и вещественных чисел. При работе с массивами целых чисел, они автоматически преобразуются в сетевой порядок байт и обратно.


Хотелось бы отметить отдельно


Реализация сделана на С++ x11 в единственном заголовочном файле с использованием механизма шаблонов SFINAE (Substitution failure is not an error).

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

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

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

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

Реализация


Реализация находится тут: https://github.com/rsashka/microprop

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

Быстрое использование
#include "microprop.h"Microprop prop(buffer, sizeof (buffer));// Создать сериализатор и назначить ему буферprop.FieldExist(string || integer); // Проверить наличие поля с указанным IDprop.FieldType(string || integer); // Получить тип данных поляprop.Append(string || integer, value); // Добавить данныеprop.Read(string || integer, value); // Прочитать данные



Медленное и вдумчивое использование
#include "microprop.h"Microprop prop(buffer, sizeof (buffer)); // Создать сериализаторprop.AssignBuffer(buffer, sizeof (buffer)); // Назначить буферprop.AssignBuffer((const)buffer, sizeof (buffer)); // Назначить read only буферprop.AssignBuffer(buffer, sizeof (buffer), true); // Тоже read only буферprop.FieldNext(ptr); // Получить указатель на следующее полеprop.FieldName(string || integer, size_t *length = nullptr); // Указатель на ID поляprop.FieldDataSize(string || integer); // Размер сериализованных данных// Дальше все прозрачноprop.Append(string || blob || integer, value || array);prop.Read(string || blob || integer, value || array);prop.Append(string || blob || integer, uint8_t *, size_t);prop.Read(string || blob || integer, uint8_t *, size_t);prop.AppendAsString(string || blob || integer, string);const char * ReadAsString(string || blob || integer);



Пример реализации с использованием enum в качестве идентификатора данных
class Property : public Microprop {public:    enum ID {    ID1, ID2, ID3  };  template <typename ... Types>  inline const uint8_t * FieldExist(ID id, Types ... arg) {    return Microprop::FieldExist((uint8_t) id, arg...);  }  template <typename ... Types>  inline size_t Append(ID id, Types ... arg) {    return Microprop::Append((uint8_t) id, arg...);  }  template <typename T>  inline size_t Read(ID id, T & val) {    return Microprop::Read((uint8_t) id, val);  }  inline size_t Read(ID id, uint8_t *data, size_t size) {    return Microprop::Read((uint8_t) id, data, size);  }      template <typename ... Types>  inline size_t AppendAsString(ID id, Types ... arg) {    return Microprop::AppendAsString((uint8_t) id, arg...);  }  template <typename ... Types>  inline const char * ReadAsString(ID id, Types... arg) {    return Microprop::ReadAsString((uint8_t) id, arg...);  }};



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

Добавляем 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, который полностью готов к работе. Надеюсь статья оказалась полезной. С полным примером можно ознакомиться здесь.

Подробнее..

Перевод Как научиться заканчивать проекты правило двух недель

02.09.2020 10:06:10 | Автор: admin

Этот олень просто огромный!

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

Возвращайся!

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

Этого было слишком мало, мне нужно было увидеть Бэмби снова!

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

Хм можно ли как-то исправить ситуацию?

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

Пора приступать к мозговому штурму по разработке моего изобретения:

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

Всё это вполне реализуемо. Я уже представлял, как однажды получу на телефон уведомление: Скорей, на улице олень!

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

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

Я не хотел прислушиваться к этому голосу. Но он не лгал.

Что же делать?

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

Я обнаружил один признак.

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

Хм, это было неожиданно.

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

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

Мне нужно было бежать наперегонки со временем.


Как это сделать? Я ни за что не успею за две недели реализовать такое количество функций.

Настало время сделать то же, чем я занимался на работе: безжалостно урезать объём работы.

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

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

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

Нужно уничтожить все свистелки.

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

Трансляция видео в телефон? Забудем об этом.

Логгинг? Только в конце, если останется время.

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

Действительно ли мне нужно было создавать собственное приложение, сообщающее о животном? С этим справится Телеграм-бот. Отлично, не нужно учиться тому, как собирать приложения для iPhone.

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

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

Иногда урезание объёмов оказывается менее заметным. Некоторые этапы, которые на работе могут считаться best practices, дома будут ненужной обузой. Чтобы разобраться, что из них что, сначала задумайтесь, почему нечто вообще считается best practice.

Нужно ли писать тесты для кода распознавания движения? Я понятия не имею, как его тестировать. Чёрт возьми, да через две недели я даже не буду поддерживать этот код. Вырезаем. Как насчёт чёткого и обобщённого способа отправки уведомлений пользователям? Здесь я единственный получатель, поэтому просто пропишу себя в Телеграм-бот.

Но от стремления добавлять полезные функции не избавиться. У него даже есть название: scope creep (расползание границ).

Это настойчивое желание добавить ещё одну вещь. Нужно упростить способ кодирования этой функции. Что если я сделаю это при помощи другого инструмента?


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

Настало время марафона кодинга. Я нашёл подходящую камеру на Amazon. Покупаем. Написал код распознавания движений, связал его с камерой. Готово. Написал Телеграм-бота. Отлично! Соединил их вместе. Круто!

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

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

Нет! Я же почти его закончил! Проект был готов на 95%, для завершения оставался всего один толчок.

Но он просто больше не казался мне интересным.

Его никогда не двигало вперёд серьёзное видение, я начал его под влиянием момента. А мотивация, как и момент, прошли.

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

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

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

Пытаясь сделать меньше, мне удалось сделать больше.

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

И если я снова увижу Бэмби, то просто сделаю снимок.

Подробнее..

Ежегодная конференция Bentley Systems для профессионалов в области инфраструктуры запускается в цифровом формате

11.09.2020 20:19:02 | Автор: admin

В октябре 2020 Bentley Systems проведет ежегодную конференцию Year in Infrastructure в режиме онлайн. Спикеры события мировые эксперты проектирования, строительства и эксплуатации инфраструктуры, включая HS2, Tesla, NOV. Техническое руководство компаний проведет серию панельных дискуссий о реагировании на текущие вызовы, внедрение инноваций, поделится новаторскими практиками перехода на цифровое производство. Информационную поддержку события в Украине предоставляет Softprom by ERC, официальный дистрибьютор решений Bentley в регионе.

Компания Bentley Systems, Inc. глобальный вендор комплексных программных решений для проектирования, строительства и эксплуатации инфраструктурных объектов. В этом году конференция компании, которая проводится уже седьмой год, расширит свою аудиторию благодаря новому формату: Year in Infrastructure переходит в онлайн.

Softprom Value Added IT Distributor в странах СНГ и Европы, поставщик ИТ-сервисов и услуг с 20-ти летним опытом работы на рынке. Softprom предоставляет информационную поддержку события в Украине. С продуктами и сервисами Bentley, о которых пойдет речь на конференции, можно ознакомиться на сайте. На приобретение флагманских решений Plaxis, Microstation, ProStructures, STAAD действует акция: 6%-тная скидка до конца года.

Основное действо конференции Year in Infrastructure 2020 будет происходить 20-21 октября. В течение этих дней будут освещены темы, актуальные для инженеров, архитекторов, специалистов по геоинформационным технологиям, строителей и владельцев-операторов инфраструктуры. Ключевые события:

20-21 октября. Руководители проектов о будущем и возможностях


Грег Бентли (Greg Bentley), генеральный директор Bentley Systems, вместе с руководителями передовых технологических компаний будет дискутировать о проблемах устойчивости к внешним воздействиям и способах решения проблем с помощью цифрового прогресса.

Кит Бентли (Keith Bentley), основатель компании и главный технический директор, обсудит стратегию цифровых двойников (digital twins) Bentley с другим визионерами отрасли, уже внедривших этот подход в проектах или управлении активами.

20 октября. Обсуждение цифровых двойников


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

21 октября. Церемония вручения наград Year in Infrastructure 2020


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

Дополнительная информация о Year in Infrastructure 2020 и регистрация на мероприятие доступны на сайте: https://yii.bentley.com/en.
Подробнее..

Выбор архитектурного стиля (часть 1)

28.08.2020 14:05:29 | Автор: admin
Привет, хабр. Прямо сейчас в OTUS открыт набор на новый поток курса Software Architect. В преддверии старта курса хочу поделиться с вами своей авторской статьёй.



Введение


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

Немного истории


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

Если говорить вкратце, то микросервисы в нашем текущем понимании возникли следующим образом: в 2011 Джеймс Льюис, анализируя работы различных компаний, обратил внимание на появление нового паттерна micro-app, который оптимизировал SOA с точки зрения ускорения развертывания сервисов. Несколько позже, в 2012 году, на архитектурном саммите паттерн был переименован в микросервис. Таким образом, первоначальной целью внедрения микросервисов было улучшение пресловутого time to market.

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

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

Монолит


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

Размер


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

Связанность


Монолит представляет из себя большой комок грязи (big ball of mud), изменения в котором могут привести к непредсказуемым последствиям. Внося изменения в одном месте, можно повредить монолит в другом (то самое ухо почесал, *@ отвалилась). Связано это с тем, что компоненты в монолите имеют очень сложные и, главное, неочевидные взаимосвязи.

Развертывание


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

Масштабируемость


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

Еще один пример (более классический): сервис A намного более популярен, чем сервис B, поэтому вы хотите, чтобы сервисов A было 100, а сервисов B было 10. Опять-таки два варианта: либо разворачиваем 100 полноценных монолитов, либо на каких-то из них руками сервисы B придется отключать.

Надежность


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

Косность


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

Заключение


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



Читать ещё:


Подробнее..

Создание автоматической системы борьбы с злоумышленниками на сайте (фродом)

29.08.2020 12:21:18 | Автор: admin
Последние примерно полгода я занимался созданием системы борьбы с фродом (fraudulent activity, fraud, etc.) без какой-либо начальной инфраструктуры для этого. Сегодняшние идеи, которые мы нашли и реализовали в нашей системе, помогают нам обнаруживать множество мошеннических действий и анализировать их. В этой статье я хотел бы рассказать о принципах, которым мы следовали, и о том, что мы сделали для достижения текущего состояния нашей системы, не углубляясь в техническую часть.


Принципы нашей системы


Когда вы слышите такие термины, как automatic и fraud, вы, скорее всего, начинаете думать о машинном обучении, Apache Spark, Hadoop, Python, Airflow и других технологиях экосистемы Apache Foundation и области Data Science. Я думаю, что есть один аспект использования этих инструментов, который, обычно, не упоминается: они требуют наличия определенных предварительных условий в вашей корпоративной системе, прежде чем начать их использовать. Короче говоря, вам нужна корпоративная платформа данных, которая включает озеро данных и хранилище. Но что, если у вас нет такой платформы, и вам все еще нужно развивать эту практику? Следующие принципы, о которых я рассказываю ниже, помогли нам достичь момента, когда мы можем сосредоточиться на улучшении наших идей, а не на поиске работающей. Тем не менее, это не плато проекта. В плане еще много вещей с технологической и продуктовой точек зрения.

Принцип 1: ценность для бизнеса в первую очередь


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

Принцип 2: Расширенный интеллект человека (augmented intelligence)


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

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

Принцип 3: платформа обширных аналитических данных


Самая сложная часть нашей системы это сквозная проверка рабочего процесса системы. Аналитики и разработчики должны легко получать наборы данных за прошлые периоды со всеми метриками, которые использовались для анализа. Кроме того, платформа данных должна предоставлять простой способ дополнить существующий набор показателей новым. Процессы, которые мы создаем, а это не только программные процессы, должны позволять легко пересчитывать предыдущие периоды, добавлять новые метрики и изменять прогноз данных. Мы могли бы добиться этого, накапливая все данные, которые генерирует наша производственная система. В таком случае данные постепенно стали бы помехой. Нам нужно было бы хранить растущий объем данных, которые мы не используем, и защищать их. В таком сценарии со временем данные будут становиться все более и более нерелевантными, но по-прежнему требуют наших усилий для управления ими. Для нас накопление данных (data hoarding) не имело смысла, и мы решили использовать другой подход. Мы решили организовать хранилища данных в реальном времени вокруг целевых сущностей, которые мы хотим классифицировать, и хранить только те данные, которые позволяют проверять самые последние и актуальные периоды. Сложность этих усилий заключается в том, что наша система является неоднородной с несколькими хранилищами данных и программными модулями, которые требуют тщательного планирования для согласованной работы.

Конструктивные концепции нашей системы


У нас есть четыре основных компонента в нашей системе: система приема (ingestion system), вычисления (computational), анализ (BI analysis) и система отслеживания (tracking system). Они служат для конкретных изолированных целей, и мы держим их изолированными, следуя определенным подходам в разработке.

image

Дизайн на основе контрактов


Прежде всего, мы договорились, что компоненты должны полагаться только на определенные структуры данных (контракты), которые передаются между ними. Это позволяет легко интегрировать между ними и не навязывать конкретный состав (и порядок) компонентов. Например, в некоторых случаях это позволяет нам напрямую интегрировать систему приема с системой отслеживания предупреждений. В таком случае это будет сделано в соответствии с согласованным контрактом на оповещения. Это означает, что оба компонента будут интегрированы с использованием контракта, который может использовать любой другой компонент. Мы не будем добавлять дополнительный контракт на добавление предупреждений в систему отслеживания из системы ввода. Такой подход требует использования заранее определенного минимального количества контрактов и упрощает систему и коммуникации. По сути, мы используем подход, который называется Contract First Design, и применяем его к контрактам потоковой передачи данных. [2]

Стриминг везде


Сохранение и управление состоянием в системе неизбежно приведет к усложнениям в ее реализации. В общем случае, состояние должно быть доступным из любого компонента, оно должно быть согласованным и предоставлять наиболее актуальное значение для всех компонентов, и оно должно быть надежным с правильными значениями. Кроме того, наличие вызовов к постоянному хранилищу для получения последнего состояния увеличит количество операций ввода-вывода и сложность алгоритмов, использующихся в наших конвейерах реального времени. Из-за этого мы решили убраться хранение состояния, по возможности, полностью из нашей системы. Этот подход требует включения всех необходимых данных в передаваемый блок данных (сообщение). Например, если нам нужно вычислить общее количество некоторых наблюдений (количество операций или случаев с определенными характеристиками), мы вычисляем его в памяти и генерируем поток таких значений. Зависимые модули будут использовать разбиение (partition) и пакетирование (batch) для дробления потока по сущностям и оперирования последними значениями. Этот подход устранил необходимость иметь постоянное дисковое хранилище для таких данных. Наша система использует Kafka в качестве брокера сообщений, и его можно использовать как базу данных с KSQL. [3] Но его использование сильно связало бы наше решение с Kafka, и мы решили не использовать его. Выбранный нами подход позволяет заменить Kafka другим брокером сообщений без серьезных внутренних изменений системы.

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

Проблемы нашей системы


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

  • Нам все еще необходимо определить процессы и политики, которые способствуют накоплению значимых и актуальных данных для нашего автоматического анализа, обнаружения и исследования данных.
  • Внедрение результатов анализа человеком в процесс автоматической настройки системы для её обновления на последних данных. Это не только обновление нашей модели, но также обновление процессов и улучшение понимания наших данных.
  • Нахождение баланса между детерминированным подходом IF-ELSE и ML. Кто-то сказал: ML это инструмент для отчаявшихся. Это означает, что вы захотите использовать ML, когда больше не понимаете, как оптимизировать и улучшить свои алгоритмы. С другой стороны, детерминированный подход не позволяет обнаружение аномалий, которые не были предвидены.
  • Нам нужен простой способ проверить наши гипотезы или коореляции между метриками в данных.
  • Система должна иметь несколько уровней истинно положительных (true positive) результатов. Случаи мошенничества это лишь часть всех случаев, которые можно считать положительными для системы. Например, аналитики хотят получать все подозрительные случаи для проверки, и лишь небольшая часть из них мошенничество. Система должна эффективно предоставлять аналитикам все случаи, независимо от того, является ли это реальным мошенничеством или просто подозрительным поведением.
  • Платформа данных должна позволять получать наборы данных за прошлые периоды с вычислениями, созданными и рассчитанными на лету.
  • Простое и автоматическое развертывание любого из компонентов системы как минимум в трех различных средах: производственной, экспериментальной (бета) и для разработчиков.
  • И последнее, но не менее важное. Нам необходимо создать обширную платформу проверки производительности, на которой мы сможем анализировать наши модели. [4]


Ссылки


  1. What is Augmented Intelligence?
  2. Implementing an API-First Design Methodology
  3. Kafka Transforming Into Event Streaming Database
  4. Understanding AUC ROC Curve
Подробнее..

Из песочницы Фасетные фильтры как готовить и с чем подавать

30.08.2020 00:09:30 | Автор: admin

О чем речь


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

  1. как реагирует UI, когда пользователь использует фильтры;
  2. алгоритм формирования значений фильтров;
  3. шаблоны запросов и структуры индекса ElasticSearch с пояснениями.

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



Понятия, чтобы было понятно


Полнотекстовый поиск поиск товаров по слову или фразе. Для пользователя это поле для ввода текста с кнопкой Найти, которое доступно на любой странице сайта.

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

Поведение фасетных фильтров


Коротко звучит так: фильтр фильтрует товары и фильтрует варианты выбора в других фильтрах.

Фильтрует товары


С этим просто. Пользователь выбрал:

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

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

Фильтрует варианты выбора в других фильтрах


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

  1. Заходим в раздел Телефоны, видим фильтры по характеристикам: Бренд, Диагональ, Память. Каждый фильтр содержит значения.
  2. Выбираем бренд. Из фильтров Диагональ и Память пропадает часть значений. В фильтре Бренд все значения остаются как на шаге 1.
  3. Выбираем диагональ. Еще часть значений пропадает из фильтра Память, и пропадает часть значений из фильтра Бренд. Значения в фильтре Диагональ остаются, как на шаге 2.
  4. Выбираем память. Из фильтров Бред и Диагональ пропадает еще часть значений. Значения для фильтра Память остаются, как на шаге 3.
  5. Сбрасываем выбранные значения в фильтре Память. Фильтры восстанавливают состояние шага 3 и т.д.

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

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

Каждый активный фильтр имеет свою выборку товаров.

Если у нас N фильтров и:

  • нет активных, то выборка общая. Она одинакова для всех фильтров, и совпадает с поисковой выдачей;
  • активно M, и M < N, то количество выборок M + 1, где 1 выборка на которую наложены все активные фильтры. Она одинакова для всех неактивных фильтров и совпадает с поисковой выдачей;
  • активно M, и N = M, то количество выборок N. Каждый фильтр имеет свою выборку.

В итоге, когда пользователь выбирает значение фасетного фильтра, происходит следующее:

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

Возникает вопрос как это реализовать на практике?

Реализация на Elasticsearch (ES)


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

Правильные типы текстовых полей


В ES нас интересует 2 типа данных:

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

ES анализирует значения в поле с типом text и формирует словарь для полнотекстового поиска. Значения в поле с типом keyword индексируются в том виде, в котором получены. Агрегация и сортировка доступна только для полей с типом keyword.

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

fields
PUT my_index{ mappings: {  properties: {   some_property: {     type: text, // 1    fields: { // 2     raw: {       type: keyword     }    }   }  } }}

  1. характеристику товара объявляем как поле типа text.
  2. через параметр fields создаем дочернее виртуальное поле типа keyword. Виртуальное, потому что присутствует в индексе и нет в описании товара. ES автоматически сохраняет данные в дочернее поле в том виде, как получил.

Так для каждой характеристики.

В запросах для операций точного сравнения, сортировки и агрегации нужно использовать дочернее виртуальное поле типа keyword. В примере это some_property.raw. Для поиска по тексту родительское.

copy_to.
PUT my_index{ mappings: {  properties: {   all_properties: { // 1    type: text   },some_property_1: {    type: keyword,    copy_to: all_properties // 2    },   some_property_2: {    type: keyword,    copy_to: all_properties   }  } }

  1. Создать в индексе виртуальное поле с типом text.
  2. Каждую характеристику объявить как keyword с параметром copy_to. Значением параметра указать виртуальное поле. ES копирует значение всех характеристик в виртуальное поле при сохранении документа.

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

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

Я предпочитаю вариант с copy_to. Тогда для построения запроса полнотекстового поиска достаточно знать одно поле с копией значений всех характеристик.

Запросы


Для поиска товаров


Будем считать, что структура индекса как в варианте с copy_to. Для полнотекстового поиска в ES используется конструкция match, для сравнения со значениями фасетных фильтров terms query. boolean query объединяет конструкции в один запрос. Он будет примерно таким:

{ query : {   bool: {   must: {    match: {      virtual_field_for_fulltext_searching: {      query: some text     }    }   },   filter: {     must: [          {property_1: [ value_1_1, , value_1_n]},  {property_n: [ value_n_1, , value_n_m]}]   }  } }}

query.bool.must.match основной запрос на полнотекстовый поиск
query.bool.filter фильтры для уточнения основного запроса. must внутри означает логическое и между фильтрами. Массив значений в каждом фильтре логическое или.

Для значений фильтров


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

Структура запроса будет такой:
{ size: 0, query : {   bool: {   must: {    match: {     field_for_fulltext_searching: {      fuzziness: 2,      query: some text     }    }   },   filter: {      }  } }, aggs : {  inavtive_filter_agg : {   filter : {    },   aggs: {    some_inavtive_filter_subagg: {      terms : {      field : some_property     }    },    ...     some_other_inavtive_filter_subagg: {     terms : {      field : some_other_property     }    }   }     },   active_filter_1_agg : {    filter: {     },    aggs: {     active_filter_1_subagg: {       terms : {        field: property_1       }     }    }  },  ,  active_filter_N_agg : {    filter: {           },    aggs: {     active_filter_N_subagg: {       terms : {        field: property_N       }     }    }  } }}

query.bool основной запрос, операции фильтрации выполняются в его контексте. Он состоит из:
  • match запрос на полнотекстовый поиск;
  • filters фильтры по характеристикам, которые не связаны с фасетными фильтрами и должны присутствовать в любом подмножестве. Это может быть фильтр по in_stock, is_visible, если всегда нужно показывать только товары в наличии или только видимые.

aggs.inavtive_filter_agg агрегация для неактивных фасетных фильтров состоит из:
  • filter - условия по характеристикам, которые сформированы активными фасетными фильтрами. Вместе с основным запросом формируют выборку товаров, на котором выполняются дочерние агрегации этого раздела;
  • aggs это объект из именованных агрегаций для каждого не активного фильтра.

aggs.active_filter_1_agg агрегация получения значений первого из активных фасетных фильтров. Каждая конструкция связана с одним фасетным фильтром. Состоит из:
  • filter условия по характеристикам, которые сформированы активными фасетными фильтрами, кроме текущего. Вместе с основным запросом формирует выборку товаров, на котором выполняется дочерняя агрегация этого раздела;
  • aggs объект из одной агрегации по характеристике текущего активного фасетного фильтра.

Важно указать size: 0, иначе получите список товаров соответствующих основному запросу без агрегаций.

В итоге


Получили два запроса:

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

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

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

System Analysis Online Meetup 0809

02.09.2020 14:13:27 | Автор: admin
Сообщество System Analysis в Райффайзенбанке приглашает на онлайн-митап. Подключайтесь к нам 8 сентября, приготовили много интересного: проведем баттл Digital vs Legal, узнаем об эффективном тандеме аналитика и разработка и раскроем 5 секретных символов BPMN.




О чем будем говорить


Digital vs Legal

Анастасия Кривицкая, Райффайзенбанк

О докладе: Расскажу личный опыт работы с нормативными правовыми актами (НПА), и как справляться, если на пути к диджитальным процессам встает НПА.

Об аналитике и разработке

Маргарита Маликова, Райффайзенбанк

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

5 секретных символов BPMN, которые сэкономят вам кучу времени

Денис Котов, Тинькофф

О докладе: В BPMN 100+ символов и логических конструкций. Некоторые из них вы не видели, а зря они экономят кучу времени. О пяти таких, самых экономных, расскажу на выступлении.
>> Активничаем с сообществом в Telegram, присоединяйтесь: @Open_SA_Community_Raif

Начнем митап в 18:30.
Регистрируйтесь, чтобы получить ссылку на трансляцию: письмо со ссылкой придет вам на почту. Мы вас ждем, до встречи online!
Подробнее..

Перевод K8s в проде и в разработке четыре мифа

11.09.2020 12:04:11 | Автор: admin

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

Не будет.

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

В этом утверждении есть важный исходный постулат: это проблема не Kubernetes, а всего разнообразия контейнеров и микросервисов. Развернуть контейнер относительно просто. А использовать и масштабировать контейнеры (и контейнеризированные микросервисы) в боевой среде уже значительно сложнее.

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

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

Миф первый: запуск Kubernetes в среде разработки или тестирования гарантирует, что ваши эксплуатационные потребности будут удовлетворены

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

Разницу между запуском в проде и запуском в одной из сред можно сравнить с разницей между гибкостью (agility) и гибкостью в сочетании с надёжностью и производительностью. И в последнем случае придётся поработать.

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

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

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

Миф второй: вы обеспечили надёжность и безопасность

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

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

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

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

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

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

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

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

Рекомендуется относиться к безопасности контейнеров как к десятиуровневой системе, охватывающей стек контейнеров (хост и реестры), а также вопросы, связанные с жизненным циклом контейнеров (например, управление API). Подробно об этих десяти уровнях и их связи с инструментами оркестрации вроде Kubernetes рассказывается в подкасте со специалистом по безопасности из Red Hat, а также в статье Ten Layers of Container Security.

Миф третий: оркестрация превращает масштабирование в пустяк

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

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

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

Продуктивные кластеры масштабировать сложнее, чем кластеры для разработки или наладки. Хотя в Kubernetes довольно просто масштабировать приложения горизонтально, но DevOps нужно помнить о некоторых нюансах, особенно когда речь идёт о поддержании работы сервисов при масштабировании инфраструктуры. Важно убедиться, что основные сервисы, а также системы оповещения об уязвимостях и нарушении безопасности, были распределены по узлам кластера и работали со stateful-томами, чтобы данные не терялись при масштабировании вниз.

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

Миф четвёртый: Kubernetes везде работает одинаково

В реальности: различия в работе в разных средах могут быть подобно тому, как различается запуск Kubernetes на ноутбуке разработчика и на боевом сервере. Многие считают, что если K8s работает локально, то он будет работать и в любой эксплуатационной среде. Хотя он обеспечивает согласованные среды, однако в зависимости от вендора могут быть серьёзные отличия.

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

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

Публично-приватные внедрения Kubernetes сложнее, чем кажется на бумаге, потому что многие необходимые сервисы являются проприетарными, например, балансировщики нагрузки и файрволы. Контейнер, который прекрасно работает в локальной среде, в облаке с другим набором инструментов может вообще не запуститься, или работать в незащищённом режиме. Поэтому технологии service mesh вроде Istio привлекают столько внимания. Они обеспечивают доступность сервисов приложений везде, где работает ваш контейнер, так что вам не нужно думать об инфраструктуре а это главная фишка контейнеров.

Подробнее..

Из песочницы Многоканальные массовые рассылки на Redis

18.09.2020 00:06:41 | Автор: admin

Вводная


Привет, Хабр! Меня зовут Борис и в этом труде я поделюсь с тобой опытом проектирования и реализации сервиса массовых рассылок, как части объемлющей системы оповещения студентов преподавателями (далее также Ада), которую тоже я осуществляю.



Ада


Нужна затем, чтобы свести на нет число прерываний учебного процесса по следующим причинам:

  1. Преподаватели не хотят делиться личными контактными данными;
  2. Студенты на самом деле тоже у них просто выбора особо нет;
  3. В силу специфики моей альма-матер, многие преподаватели вынуждены или предпочитают использовать мобильные устройства без доступа к сети Интернет;
  4. Если передавать сообщения через старост групп, то в игру вступает эффект испорченного телефона, а также фактор ой, я забыл:(.

Работает примерно так:

  1. Преподаватель через один из доступных ему каналов связи: СМС, Telegram, SPA-приложение передает Аде текст сообщения и список адресатов;
  2. Ада транслирует полученное сообщение всем заинтересованным* студентам по всевозможным каналам связи.

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

Предполагается, что


  1. Общее число пользователей не превысит десяти тысяч;
  2. Соотношение студент преподаватель / член УВП (деканаты, здравпункт, военно-учетный стол и т.д.) будет держаться на уровне 10:1;
  3. Оповещения текстовые по содержанию и носят преимущественно экстренный характер: Моей пары сегодня не будет, Тебя отчисляют))0 и т.д.

Ключевые требования к сервису рассылок


  1. Простота интеграции с другими информационными системами ВУЗа;
  2. Возможность отложенной доставки, принудительная перепланировка времени отправки сообщений, поставленных в очередь в неподобающее для приличных студентов время;
  3. Разделяемая между каналами связи история и ограничения на отправку;
  4. Достоверность и полнота обратной связи: если кому-то чего-то не дойдет, а понять это будет нельзя, то всем будет обидно.

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

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

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

Подготовительная


Очень грубо и сильно абстрактно ~ 5 минут
Redis это открытое [BSD 3-clause] ПО, реализующее хранение данных типа ключ-значение в ОЗУ (преимущественно).

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

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

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

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

Подробно и из первых уст ~ 15 минут
  1. Pub/Sub Redis. Пробегитесь глазами по первому параграфу, осознайте fire&forget момент, посмотрите, как работают команды PUBLISH, SUBSCRIBE и их паттерн-вариации;
  2. Redis Keyspace Notifications. Первые три параграфа;
  3. EXPIRE Redis. Параграф How Redis expires keys;
  4. Redis 6.0 Default Configuration File. В дополнение к предыдущей ссылке. Строки 939:948 (The default effort of the expire cycle);
  5. EVAL Redis. Отличие EVAL от EVALSHA, а также параграфы Atomicity of scripts, Global variables protection и Available libraries, в последнем нас интересует только cjson;
  6. Redis Lua Scripts Debugger. Не обязательно, но может прилично сэкономить вам слез в будущем. У меня вот кончились пользуюсь каплями;
  7. Исторические аспекты появления микросервисной архитектуры. Тоже не обязательно, но весьма доходчиво и интересно.

Концептуальная


Наивный подход


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

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


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

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


Сломался один из методов = сломался весь сервис.

Прикладная проблема


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

API ВКонтакте работает только через HTTP; у Telegram есть HTTP-шлюз, но он менее стабилен, нежели MTProto и хуже документирован.

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

Как с этим быть?


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

Непонятно? Закажите покушать!


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



  1. Вы нажимаете на большую желтую кнопку Заказать;
  2. Яндекс.Еда находит курьера, сообщает выбранные позиции ресторану и возвращает вам номер заказа, дабы разбавить неопределенность ожидания;
  3. Ресторан по завершении готовки обновляет статус заказа и отдает еду курьеру;
  4. Курьер, в свою очередь, отдает еду вам, после чего помечает заказ как выполненный.

Приятного аппетита!

Вернемся к проектированию


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

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

  1. Кто отправил;
  2. Что отправил;
  3. Откуда;
  4. Кому;
  5. Кто и как получил.

История создается вместе с заказом как два отдельных Redis ключа, связанных через суффикс:

suffix={Идентификатор пользователя}:{UNIX-время в наносекундах}История=history:{suffix}Заказ=delivery:{suffix}

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

Зрение курьеров работает через подписку на событие DEL ключей по форме delivery:*.

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

Так как курьеров несколько велика вероятность возникновения конкуренции на стадии изменения истории.



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

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



Отслеживание статуса

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

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



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

Зачем мудрить с событиями в Redis, если есть RabbitMQ и Celery


На то есть как минимум пять объективных причин:

  1. Redis уже используется другими сервисами Ады, RabbitMQ/Celery новая зависимость;
  2. Redis нужен нам, в первую очередь, как СУБД, а не средство IPC;
  3. Использования Redisa как хранилища истории защищает нас от SQL-инъекций в текстах сообщений;
  4. Проблема масштабируемости не стоит и в обозримой перспективе не встанет. Кроме того, эта самая масштабируемость в контексте данной задачи достигается скорее за счет увеличения API-лимитов, нежели горизонтального наращивания вычислительных мощностей;
  5. Celery пока что не дружит с asyncio, а программный костяк проекта составляет уже реализованная с основой на asyncio библиотека.

Предметная


Система оповещения (объемлющая) исполнена в виде множества микросервисов. Удобства ради, интерфейсы, методы инициализации слоев данных, текста ошибок, а также некоторые блоки повторяющейся логики были вынесены в библиотеку core, которая, в свою очередь, опирается на: gino (asyncio обертка SQLAlchemy), aioredis и aiohttp.

В коде можно увидеть разные сущности, например, User, Contact или Allegiance. Связи между ними представлены на диаграмме ниже, краткое описание под спойлером.


О сущностях ~ 3 минуты
Пользователь человек.

У пользователя есть роль: студент, преподаватель, деканат и т. д., а также почта и имя.

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

Пользователи могут состоять в группах [allegiance].

Из групп можно формировать потоки [supergroup].

Группы и потоки могут принадлежать [ownership] пользователям.

Генерация ключа истории


delivery/handlers/history_key/get GitHub

Очередь


delivery/handlers/queue/put GitHub

Обратите внимание на:

  1. Комментарий 171:174;
  2. То, что все манипуляции с Redisом [164:179] завернуты в транзакцию.

Зрение курьеров [94:117]


core/delivery GitHub

Обновление истории курьерами


core/redis_lua GitHub

Инструкции [48:60] отменяют преобразование пустых списков в словари ([] -> {}), так как большинство языков программирования, и CPython в том числе, интерпретируют их иначе, нежели LUA.

ISS: Allow differentiation of arrays and objects for proper empty-object serialization GitHub

Трекер


delivery/handlers/track/post GitHub имплементация.
connect/telegram/handlers/select GitHub [101:134] пример использования в пользовательском интерфейсе.

Курьеры


Всякая доставка из task_stream (@Зрение курьеров) обрабатывается в отдельной asyncio-сопрограмме.

Общая стратегия работы с временными ограничениями прикладных интерфейсов такова: мы не считаем RPS (requests per second), но корректно /реагируем/ на ответы по типу http.TooManyRequests.

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

Telegram


courier/telegram GitHub
Как было замечено ранее, MTProto интерфейс Telegramа выигрывает у HTTP-аналога в стабильности и размере документации. Для взаимодействия с оным мы воспользуемся готовым решением, а именно LonamiWebs/Telethon.

ВКонтакте


courier/vk GitHub
ВКонтакте API поддерживает массовые рассылки через передачу списка идентификаторов в метод messages.send (не более сотни), а также позволяет склеить до двадцати пяти messages.send в одном execute, что дает нам 2500 сообщений за вызов.

Любопытный факт
Многие методы ВКонтакте API, и execute в том числе, наиболее полно описаны в русской версии документации.

Заключительная


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

Основной недостаток заключается в fire&forget эффекте Pub/Sub, т.е. если удаление ключа заказа придется на момент болезни одного из курьеров, то в соответствующем домене никто ничего не получит, что впрочем будет отражено в истории.
Подробнее..

Вначале был монолит как мы меняем нашу архитектуру, не мешая бизнесу

18.09.2020 16:04:09 | Автор: admin


Всем привет! Меня зовут Игорь Наразин, я тим-лид команды в направлении логистики Delivery Club. Хочу рассказать, как мы строим и трансформируем нашу архитектуру и как это влияет на наши процессы в разработке.

Сейчас Delivery Club (как и весь рынок фудтеха) растёт очень быстро, что порождает огромное количество вызовов для технической команды, которые можно обобщить двумя самыми важными критериями:

  • Нужно обеспечивать высокую стабильность и доступность всех частей платформы.
  • Одновременно с этим держать высокий темп разработки новых фич.

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

Но нам удаётся (пока) и то, и другое. О том, как мы это делаем, и пойдет речь далее.

Во-первых, я расскажу про нашу платформу: как мы её трансформируем с учетом постоянно растущих объемов данных, какие критерии предъявляем к нашим сервисами и с какими проблемами сталкиваемся на этом пути.

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

Начнём с платформы.

Вначале был монолит


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

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

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

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

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

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

Экосистема


Как рассказывал Андрей Евсюков в статье про наши команды, у нас выделены главные направления по доменным областям: R&D, Logistics, Consumer, Vendor, Internal, Platform. В рамках этих направлений уже сосредоточены основные доменные области, с которыми работают сервисы: например, для Logistics это курьеры и заказы, а для Vendor рестораны и позиции.

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

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

Низкие нагрузки, синхронные запросы, всё работает круто.

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


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

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

Шина данных


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

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

  • mobile-gateway, который является backend for frontend для мобильного приложения;
  • courier-tracker, который хранит логику получения и отдачи координат;
  • logistics-couriers, который хранит эти координаты. Они присылаются из мобильных приложений курьеров.



В первоначальной схеме это всё работало синхронно: запросы из мобильного приложения раз в минуту шли через mobile-gateway к сервису courier-tracker, который обращался к logistics-couriers и получал координаты. Конечно, в этой схеме было не всё так просто, но в итоге всё сводилось к простому выводу: чем больше у нас активных заказов, тем больше запросов на получение координат приходило в logistics-couriers.

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

Транспорт


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

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

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

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

Для решения этой проблемы мы написали обёртку микросервис на Go, который скрыл Kafka за своим API. Это добавило два преимущества:

  • валидация данных в момент отправки и приёма. По сути, это одни и те же DTO, поэтому мы всегда уверены в формате ожидаемых данных.
  • быстрая интеграция наших сервисов с этим транспортом.

Таким образом, работа с Kafka стала максимально абстрагированной для наших сервисов: они лишь работают с верхнеуровневым API этой обёртки.

Вернёмся к примеру


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

Сервису courier-tracker остаётся аккумулировать координаты в нужном объёме и на нужный срок. В итоге наш эндпоинт становится максимально простым: взять данные из базы сервиса и отдать их мобильному приложению. Рост нагрузки на неё теперь для нас безопасен.



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

Eventually consistency


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

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



Таким образом, мы четко разделяем наши сервисы на те, что являются мастерами данных и те, кто использует эти данные. По сути, это headless commerce из evolutionary archicture у нас четко отделены все витрины (сайт, мобильные приложения) от производителей этих данных.

Денормализация


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

За эти уведомления отвечает сервис logistics-courier-notifications. После того, как он получил запрос на отправку, его задача сгенерировать сообщения для тех курьеров, которые попали в таргетинг. Для этого ему необходимо знать нужную информацию по всем курьерам Delivery Club. И у нас есть два варианта для решения этой задачи:

  • сделать эндпоинт на стороне сервиса мастера данных по курьерам (logistics-couriers), который по переданным полям сможет отфильтровать и вернуть нужных курьеров;
  • хранить всю нужную информацию прямо в сервисе, потребляя её из соответствующего топика и сохраняя те данные, по которым нам в дальнейшем нужно будет фильтровать.

Часть логики генерации сообщений и фильтрования курьеров не является нагруженной, она выполняется в фоне, поэтому вопроса о нагрузках на сервис logistics-couriers не стоит. Но если выбрать первый вариант, мы столкнёмся с набором проблем:

  • придётся поддерживать узкоспециализированный эндпоинт в стороннем сервисе, который, скорее всего, понадобится только нам;
  • если выбрать слишком широкий фильтр, то в выборку попадут вообще все курьеры, которые просто не поместятся в HTTP-ответ, и придётся реализовывать пагинацию (и итерировать по ней при опросе сервиса).

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

В итоге у нас сформулированы несколько важных принципов к проектированию сервисов:

  • У сервиса должна быть конкретная ответственность. Если для его полноценного функционирования нужен ещё сервис, то это ошибка проектирования, их нужно либо объединять, либо пересматривать архитектуру.
  • Критично смотрим на любые синхронные обращения. Для сервисов в одном направлении это допустимо, но для общения между сервисами разных направлений нет
  • Share nothing. Мы не ходим в БД сервисов в обход них самих. Все запросы только через API.
  • Specification First. Сначала описываем и утверждаем протоколы.

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



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

Как мы планируем развивать нашу архитектуру


Delivery club, как я говорил вначале, быстро растёт, мы релизим в прод огромное количество новых фич. А ещё больше экспериментируем (подробно об этом рассказал Николай Архипов) и тестируем гипотезы. Это всё порождает огромное количество источников данных и ещё больше вариантов их использования. А правильное управление потоками данных, которые очень важно грамотно выстроить это и есть наша задача.

Дальше мы будем продолжать внедрять выработанные подходы во все сервисы Delivery Club: строить экосистемы сервисов вокруг платформы с транспортом в виде шины данных.

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

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

Поэтому для старых сервисов мы решили использовать Debezium, который позволяет стримить информацию напрямую из таблиц на основе bin-log: в итоге получается готовый топик с сырыми данными из таблицы. Но они непригодны для использования в исходном виде, поэтому через трансформеры на уровне Kafka они будут преобразованы в понятный для потребителей формат и запушены в новый топик. Таким образом, у нас будет набор приватных топиков с сырыми данными из таблиц, который будет трансформироваться в удобный формат и транслироваться в публичный топик для использования потребителями.



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

Дальше из шины данных сервисы будут потреблять данные из нужных топиков. И мы сами будем использовать эти данные для своих систем: например, стримить через Kafka Connect в ElasticSearch или в DWH. С последним процесс будет сложнее: чтобы информация в нём была доступна для всех, её необходимо очистить от любых персональных данных.

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

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


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

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



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

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

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

Архитектурный комитет


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

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

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

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

В итоге, проблему с контролем крупных изменений мы закрыли, остаётся вопрос общего подхода к качеству кода в Delivery Club: конкретные проблемы кода или фреймворка в разных командах могут решаться по-разному. Мы пришли к гильдиям по модели Spotify: это объединения неравнодушных к какой-то технологии людей. Например, есть гильдии Go, PHP и Frontend.

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

Код на прод


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

В чек-листе обычно указывается:

  • ответственный за сервис (это обычно тех-лид сервиса);
  • ссылки на дашборд с настроенными алертами;
  • описание сервиса и ссылка на Swagger;
  • описание сервисов, с которым будет взаимодействовать;
  • предполагаемая нагрузка на сервис;
  • ссылка на health-check. Это URL, по которому служба эксплуатации настраивает свои мониторинги. Health-check раз в какой-то период дёргается: если вдруг он не ответил с кодом 200, значит, с сервисом что-то не так и к нам прилетает алерт. В свою очередь, health check может дёргать такие же URLы критичных для него сервисов, а также обязательно включать проверку всех компонентов сервиса, например, PostgreSQL или Redis.

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

Для примера можно взять уже рассмотренный выше сервис courier-tracker, отслеживающий курьеров на карте. Одна из основных метрик в нём это количество курьеров, у которых обновляются координаты. Если вдруг какие-то маршруты долго не обновляются, приходит алерт что-то пошло не так. Может быть, где-то за данными не сходили, или неправильно зашли в базу, или отвалился какой-нибудь другой сервис. Это не техническая метрика и не продуктовая, но она показывает жизнеспособность сервиса.

Для метрик мы используем Graylog и Prometheus, строим дашборды и настраиваем алерты в Grafana.

Несмотря на объём подготовки, доставка сервисов в прод достаточно быстрая: все сервисы изначально упакованы в Docker, в stage выкатываются автоматически после формирования типизированного чарта для Kubernetes, а дальше всё решается кнопкой в Jenkins.

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

Под капотом


Сейчас у нас 162 микросервиса, написанные на PHP и Go. Они распределились между сервисами примерно 50% на 50%. Изначально мы переписали на Go некоторые высоконагруженные сервисы. Дальше стало ясно, что Go проще в поддержке и мониторинге в продакшене, у него низкий порог входа, поэтому в последнее время мы пишем сервисы только на нём. Цели переписать на Go оставшиеся PHP-сервисы нет: он вполне успешно справляется со своими функциями.

В PHP-сервисах у нас Symfony, поверх которого мы используем свой небольшой фреймворк. Он навязывает сервисам общую архитектуру, благодаря которой мы снижаем порог входа в исходный код сервисов: какой бы сервис вы ни открыли, всегда будет понятно, что и где в нём лежит. А также фреймворк инкапсулирует слой транспорта общения между сервисами, для разработчика запрос в сторонний сервис выглядит на высоком уровне абстракции:
$courierResponse = $this->courierProtocol->get($courierRequest);
Здесь мы формируем DTO запроса ($courierRequest), вызываем метод объекта протокола конкретного сервиса, который является обёрткой над конкретным эндпоинтом. Под капотом наш объект $courierRequest преобразуется в объект запроса, который заполняется полями из DTO. Это всё гибко настраивается: поля могут подставляться как в заголовки, так и в сам URL запроса. Далее запрос посылается через cURL, получаем объект Response и обратно его трансформируем в ожидаемый нами объект $courierResponse.

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

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

В итоге, изменения в протоколе сервиса могут превратиться в несколько пулл-реквестов: в сам сервис, в его SDK, и в сервис, которому нужен этот протокол. В последнем нам нужно поднять версию импортированного SDK, чтобы туда попали изменения. Это часто вызывает вопросы у новых разработчиков: Я ведь только изменил параметр, почему мне нужно делать три реквеста в три разных репозитория?!

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

Технический радар




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

  • Python, на котором пишет команда Data Science.
  • Kotlin и Swift для разработки мобильных приложений.
  • PostgreSQL в качестве базы данных, но на некоторых старых сервисах всё ещё крутится MySQL. В микросервисах используем несколько подходов: для каждого сервиса своя БД и share nothing мы не ходим в базы данных в обход сервисов, только через их API.
  • ClickHouse для узкоспециализированных сервисов, связанных с аналитикой.
  • Redis и Memcached в качестве in-memory хранилищ.


При выборе технологии мы руководствуемся специальными принципами. Одним из основных требований является Ease of use: используем максимально простую и понятную технологию для разработчика, по возможности придерживаясь принятого стека. Для тех, кто хочет узнать весь стек конкретных технологий, у нас составлен очень подробный техрадар.

Long story short


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

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

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

На этом у меня всё, спасибо, что дочитали!
Подробнее..

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

18.09.2020 16:04:09 | Автор: admin


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


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



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





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





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


Хочешь, чтобы никто не нашел, назови не думая


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


Название инструкции не должно вызывать эмоции

Я использую два вида названий:


  • название-проблема;
  • название-процесс.

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


Что такое название-процесс


Цель инструкции с таким названием объяснить какой-то процесс.


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


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


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


Пример названия-процесса: Работа с программой Рутокен Диск.


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

Его поисковый запрос: как сохранить файл в защищённом разделе или как работать с Рутокен Диском.


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





Ещё один пример названия-процесса: Работа с Рутокен через КриптоПро CSP в macOS.


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


Его поисковый запрос: Рутокен КриптоПро macOS.


Он открывает инструкцию и выполняет последовательно все шаги из неё.





Пример плохого названия инструкции: Инструкция по форматированию Рутокена и установке нового PIN-кода для клиентов, переходящих с системы ДБО.


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


Мне это название не нравится по трём причинам:


  • Оно слишком длинное.
  • В нём фигурирует две процедуры: форматирование токена и смена PIN-кода.
  • Название сформулировано не так, как его будет искать пользователь.

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


Плохое название может привести к финансовым потерям

Сейчас по запросу как изменить PIN-код Рутокена пользователь найдёт нашу инструкцию.


Что такое название-проблема


Цель инструкции с таким названием помочь решить проблему.


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


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


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


Пример такого названия: Недоступна смарт-карта Рутокен.


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


Его поисковый запрос: токены или смарт-карты недоступны. Он описывает проблему так, как она сформулирована в окне с ошибкой.


Он находит эту инструкцию и выполняет необходимые действия в системе.


Второй пример названия, но теперь уже раздела: Что делать, если PIN-код Пользователя заблокирован.


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


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


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


Он откроет эту инструкцию, найдёт нужный раздел и разблокирует PIN-код.


Пример плохого названия инструкции: Рутокен вставлен, но красный диод не горит. Что делать?


Что мне не нравится в этом названии:


  • Оно слишком длинное, в нём указаны лишние подробности, а именно то, что Рутокен вставлен. За счёт этого можно было сократить название.
  • Я бы заменила слово диод, не каждый пользователь его знает.


Давайте сделаем общие выводы:


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


Хочешь, чтобы никто не нашёл, забудь про навигацию


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


Содержание как карта


Главное требования к содержанию инструкции оно должно быть логичным.
Цель содержания, как и у любой карты ускорить процесс поиска. Помочь пользователю быстро найти нужный раздел. Да, кто-то скажет, что в инструкции всегда можно воспользоваться внутренним поиском, нажав Ctrl+F, но это не всегда удобно. Если страниц в инструкции много, то внутренний поиск может только запутать пользователя.


Содержание должно быть логичным




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


Самый простой случай, когда описана программа, которая работает в одной операционной системе. В нашем случае это программа Рутокен Логон. Посмотрим на то, как устроена навигация в инструкции для неё.





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


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



Установка -> Запуск -> Настройка -> Процесс работы -> Удаление

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


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


Ранее мы рассматривали инструкцию про Рутокен Диск, она как раз была с таким содержанием. В ней пользователь находит раздел со своей операционной системой и подраздел с необходимой процедурой.


Такая последовательность изложения выглядит так:



Выбор операционной системы -> Запуск -> Настройка -> Процесс работы -> Удаление

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


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


У пользователя полминуты на то, чтобы найти нужный раздел

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


Вот пример такой инструкции. Она написана для пользователей Рутокен U2F.





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


Содержание может всё испортить


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





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





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



Давайте сделаем общие выводы:


  1. При составлении содержания учитывайте логику взаимодействия пользователя с тем, что вы описываете.
  2. Если пользователь пропустил содержание, то ему может помочь навигация в разделе.
  3. Если пропускать шаги в инструкции нельзя, то навигация не нужна.


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

Подробнее..

Перевод Контрольный список для ревью кода в распределенных системах

25.09.2020 18:15:34 | Автор: admin
points of view by sanja

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

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

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

Удаленная система выходит из строя


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

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

  1. Определите путь обработки ошибок. В коде должны быть явно определены средства для обработки ошибок. Например, правильно разработанная страница ошибок, журнал исключений с метриками ошибок или автоматическое выключение с механизмом резервирования. Главное ошибки должны обрабатываться явно. Нельзя допускать, чтобы пользователи видели ошибки работы вашего кода.
  2. Составьте план восстановления. Рассмотрите каждое удаленное взаимодействие в вашем коде и выясните, что нужно сделать для восстановления прерванной работы. Запускается ли рабочий процесс с точки сбоя? Публикуются ли все полезные данные во время сбоя в таблице очередей или таблице повторов? И повторяются ли каждый раз, когда удаленная система восстанавливается? Есть ли скрипт для сравнения баз данных двух систем и их синхронизации? Системный план восстановления нужно разработать и реализовать до развертывания фактического кода.

Удаленная система медленно отвечает


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

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

  1. Установите тайм-ауты для удаленных системных вызовов. Это касается и тайм-аутов для удаленного вызова API и базы данных, публикации событий. Проверьте, установлены ли конечные и разумные тайм-ауты для всех удаленных систем в вызовах. Это позволит не тратить ресурсы на ожидание, если удаленная система перестала отвечать на запросы.
  2. Используйте повторные попытки после тайм-аута. Сеть и системы ненадежны, повторные попытки обязательное условие устойчивости системы. Как правило, повторные попытки устраняют множество пробелов в межсистемном взаимодействии.

    Если возможно, используйте какой-то алгоритм в ваших попытках (фиксированный, экспоненциальный). Добавление небольшого джиттера в механизм повтора даст передышку вызываемой системе, если она под нагрузкой, и приведет к увеличению скорости ответа. Обратная сторона повторных попыток идемпотентность, о которой расскажу позже в этой статье.
  3. Применяйте автоматический размыкатель (Circuit Breaker). Существует не так много реализаций, которые поставляются с этим функционалом, например Hystrix. В некоторых компаниях пишут собственных внутренних обработчиков. Если есть возможность, обязательно используйте Circuit Breaker или инвестируйте в его создание. Четкая структура для определения запасных вариантов в случае ошибки хороший вариант.
  4. Не обрабатывайте тайм-ауты как сбои. Тайм-ауты не сбои, а неопределенные сценарии. Их следует обрабатывать способом, который поддерживает разрешение неопределенности. Стоит создавать явные механизмы разрешения, которые позволят системам синхронизироваться в случае тайм-аута. Механизмы могут варьироваться от простых сценариев согласования до рабочих процессов с отслеживанием состояния, очередей недоставленных писем и многого другого.
  5. Не вызывайте удаленные системы внутри транзакций. Когда удаленная система замедляется, вы дольше удерживаете соединение с базой данных. Это может быстро привести к исчерпанию соединений с базой данных и, как следствие, к сбою в работе вашей собственной системы.
  6. Используйте интеллектуальное пакетирование. Если работаете с большим количеством данных, выполняйте пакетные удаленные вызовы (вызовы API, чтение БД), а не последовательные это устранит нагрузку на сеть. Но помните: чем больше размер пакета, тем выше общая задержка и больше единица работы, которая может дать сбой. Так что оптимизируйте размеры пакета для производительности и отказоустойчивости.

Построение системы, которую вызывают другие системы


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

    SLA с высокой пропускной способностью сложно реализовать: ограничение распределенной скорости сама по себе сложная задача. Но стоит помнить об этом и иметь возможность обрывать вызовы, если выходите за пределы SLA. Другой важный аспект знание времени отклика нижестоящих систем, чтобы определить, какую скорость ожидать от вашей системы.
  3. Определите и ограничьте пакетирование для API-интерфейсов. Максимальные размеры пакетов явно определяют и ограничивают обещанным SLA это следствие соблюдения SLA.
  4. Подумайте заранее о наблюдаемости системы. Наблюдаемость способность анализировать поведение системы, не обращая внимания на ее внутреннее устройство. Подумайте заранее, какие метрики и данные о системе нужны, чтобы ответить на вопросы, на которые ранее невозможно было получить ответы. И продумайте инструменты для получения этих данных.

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

Общие рекомендации


  1. Используйте агрессивное кэширование. Сеть нестабильна, поэтому кэшируйте столько данных, сколько возможно. Ваш механизм кэширования может быть удаленным, например сервер Redis, работающий на отдельной машине. По крайней мере, вы перенесете данные в свою область управления и уменьшите нагрузку на другие системы.
  2. Определите единицу сбоя. Если API или сообщение представляет несколько единиц работы (пакет), то какова единица сбоя? В случае сбоя вся полезная нагрузка выйдет из строя или, напротив, отдельные блоки будут успешны или потерпят неудачу независимо? Отвечает ли API кодом успеха или неудачей в случае частичного успеха?
  3. Изолируйте внешние доменные объекты на границе системы. Еще один пункт, который, по моему опыту, вызывает проблемы в долгосрочной перспективе. Не стоит разрешать использование доменных объектов других систем во всей вашей системе для повторного использования. Это связывает вашу систему с работоспособностью другой системы. И каждый раз, когда меняется другая система, нужно рефакторить код. Поэтому стоит создавать собственную внутреннюю схему данных и преобразовывать внешние полезные данные в эту схему. И затем использовать внутри собственной системы.

Безопасность


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

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

Удачи!

Что еще почитать:

  1. Как спокойно спать, когда у вас облачный сервис: основные архитектурные советы.
  2. Как технический долг и лже-Agile убивают ваши проекты.
  3. Наш канале в Телеграме о цифровой трансформации.
Подробнее..

Категории

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

© 2006-2020, personeltest.ru