1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/yu120-lemon-guide

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
Architecture.md 310 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
gitlife-traslator Отправлено 30.11.2024 18:34 ec8a065

Архитектура

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

Архитектура развивается

Архитектурные шаблоны

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

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

Многоуровневая архитектура

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

В общих информационных системах наиболее распространёнными являются следующие четыре уровня:

  • представление (также называемое UI-уровнем);
  • приложение (также называемое уровнем обслуживания);
  • бизнес-логика (также называемая уровнем домена);
  • доступ к данным (также называемый уровнем постоянства).

Использование:

  • общие настольные приложения;
  • веб-приложения для электронной коммерции.

Клиент-серверная архитектура

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

Использование:

  • электронная почта, файловый обмен и банковские онлайн-приложения.

Архитектура главный-подчиненный

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

Использование:

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

Шаблон «труба-фильтр»

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

Использование:

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

Брокерская архитектура

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

Сервер публикует свои функции (услуги и характеристики) посреднику. Клиент запрашивает услуги у посредника, который затем перенаправляет клиента на соответствующий сервис в своём центре регистрации.

Использование:

  • программное обеспечение для обмена сообщениями, такое как Apache ActiveMQ, Apache Kafka, RabbitMQ и JBoss Messaging.

Одноранговая архитектура

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

Использование:

  • такие сети обмена файлами, как Gnutella и G2;
  • проприетарные мультимедийные протоколы, такие как P2PTV и PDTP;
  • специализированные мультимедийные приложения, такие как Spotify.

Архитектурный шаблон «шина событий»

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

Использование:

  • разработка под Android;
  • службы уведомлений.

Модель-представление-контроллер (MVC)

Этот шаблон, также известный как MVC, разделяет интерактивное приложение на три части:

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

Это сделано для разделения представления информации и способа её представления, а также для принятия пользовательского ввода. Он разделяет компоненты и позволяет эффективно повторно использовать код.

Использование:

  • архитектура веб-приложений для основных языков программирования;
  • такие веб-фреймворки, как Django и Rails.

Чёрная доска

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

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

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

Использование:

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

Интерпретатор

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

Использование:

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

Монолитная архитектура приложений

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

Преимущества:

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

Недостатки:

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

Вертикальная архитектура приложений

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

  • систему электронной коммерции (управление пользователями, управление товарами, управление заказами);
  • бэкенд-систему (управление пользователями, управление заказами, управление клиентами);
  • CMS-систему (рекламное управление, маркетинговое управление).

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

Преимущества:

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

Недостатки:

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

Распределённая архитектура

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

Так возникает новый тип распределённой системы. Она разделяет проект на два аспекта: представление и сервисный слой, сервисный слой содержит бизнес-логику. Уровень представления отвечает только за обработку взаимодействия с интерфейсом и вызывает различные сервисы сервисного уровня. 缺点

  • 系统间耦合度变高,调用关系错综复杂,难以维护.

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

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

Преимущества:

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

Недостатки:

  • Между сервисами могут быть зависимости, и если один из компонентов выйдет из строя, это может оказать значительное влияние (эффект «снежного кома»).
  • Управление сервисами сложное, а их тестирование и развёртывание затруднены.

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

https://juejin.cn/user/4177799914474653/posts

https://www.cnblogs.com/jiujuan/p/13280473.html

Микросервисная архитектура — это дальнейшее развитие сервис-ориентированной архитектуры (SOA), она подчёркивает необходимость «полного разделения» сервисов.

Преимущества:

  • Улучшенное управление разработкой.
  • Более быстрая разработка.
  • Поддержка итеративной разработки или современной инкрементальной разработки.
  • Полное использование преимуществ современных экосистем разработки программного обеспечения (облачные технологии, контейнеры, DevOps, Serverless).
  • Поддержка горизонтального и вертикального масштабирования.
  • Малый объём, что снижает сложность понимания для разработчиков.

Недостатки:

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

Определение микросервиса

  • Группа небольших сервисов.

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

  • Независимые процессы.

    Микросервисы работают в независимых процессах, таких как Java-программы, развёрнутые в Tomcat, или контейнеры Docker.

  • Лёгкое взаимодействие.

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

  • На основе бизнес-возможностей.

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

  • Раздельное развёртывание.

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

  • Отсутствие централизованного управления.

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

Закон Конвея

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

Эпоха монолитных приложений

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

Решение этой проблемы — микросервисы:

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

Преимущества и недостатки микросервисов

Преимущества:

  • Сильная модульность границ.

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

  • Возможность независимого развёртывания.

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

  • Разнообразие технологий.

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

Недостатки:

  • Сложность распределённых систем.

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

  • Конечная согласованность.

    Данные в микросервисах распределены, и каждая команда имеет свою собственную копию данных. Например, команда A имеет данные о заказах, а команда B также имеет данные о заказах. Возникает вопрос: следует ли синхронизировать изменения данных команды A с данными команды B? Если нет эффективного решения проблемы согласованности данных, это может привести к несогласованности данных.

  • Сложное управление инфраструктурой.

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

  • Трудности тестирования.

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

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

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

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

  • Регистрация и обнаружение сервисов. В микросервисной архитектуре существует множество сервисов, от нескольких десятков до сотен. Они имеют сложные зависимости между собой. Возникает вопрос, как потребители сервисов находят их производителей. Регистрация и обнаружение сервисов решают эту проблему.
  • Балансировка нагрузки. Чтобы справиться с большим трафиком, наши поставщики услуг обычно развёртывают свои сервисы в больших масштабах. В этом случае возникает проблема балансировки нагрузки. Кроме того, нашим сервисам требуется маршрутизация, которая является важным инструментом. Если нам нужно реализовать механизмы серого или зелёного развёртывания, то нам потребуется рассмотреть мягкую маршрутизацию.
  • Мониторинг и логирование. Логирование важно для устранения проблем и определения их причин. Нам нужна хорошая система мониторинга и управления журналами.
  • Метрики. Когда нам необходимо отслеживать количество вызовов сервисов и время задержки, возникают ошибки, мониторинг метрик становится ключевым инструментом.
  • Отслеживание вызовов. Микросервисы имеют сложную структуру вызовов. Без хорошего отслеживания вызовов разработчикам легко потеряться в этой структуре. Отслеживание вызовов помогает быстро определить проблему и лучше понять всю систему микросервисов.
  • Ограничение потока и разрыв цепи. Микросервисы представляют собой распределённую систему. Если нет хороших мер по ограничению потока и разрыву цепи, когда один сервис выходит из строя или возникают задержки, вся система может стать неработоспособной.
  • Безопасность и контроль доступа. Некоторые сервисы не хотят, чтобы все имели доступ к ним. Это связано с конфиденциальной информацией или финансовыми данными. Здесь необходимы меры безопасности и контроля доступа.
  • RPC и REST. RPC и REST имеют свои преимущества и недостатки. Если микросервисный фреймворк поддерживает оба типа вызовов, это обеспечивает большую гибкость.
  • Сериализация. Существует высокопроизводительная двоичная сериализация, которая не оптимизирована для разработчиков, и более дружественная текстовая сериализация с хорошей производительностью. Выбор зависит от сценария использования.
  • Генерация кода. При масштабной разработке рекомендуется использовать контрактно-ориентированный подход. Разработчики сначала определяют контракты, а затем автоматически генерируют соответствующий код. Это обеспечивает согласованность и стандартизацию кода.
  • Унификация обработки исключений. Мы хотим, чтобы система управления сервисами включала унифицированную обработку исключений. Это позволяет стандартизировать обработку ошибок и упрощает их идентификацию.
  • Документация. Микросервисы предназначены для конечных пользователей. Отсутствие хорошей документации делает использование API сложным и затратным. Хорошая документация важна для обеспечения эффективности и согласованности.
  • Централизованный конфигурационный центр. Микросервисные фреймворки должны включать централизованное управление конфигурацией, чтобы избежать разрозненной конфигурации между сервисами.
  • Интеграция с MQ, Cache и DB. Основная идея управления микросервисами заключается в объединении всех этих аспектов в единую платформу или фреймворк. Разработчикам не нужно беспокоиться о внешних аспектах, что повышает эффективность разработки. Управление этими аспектами осуществляется специализированными командами.

Многоуровневый мониторинг микросервисов

Мониторинг микросервисов включает несколько уровней:

  1. Базовый мониторинг. Обычно за него отвечают администраторы. Он охватывает такие аспекты, как сеть, коммутаторы, маршрутизаторы и другие низкоуровневые устройства. Их надёжность и стабильность напрямую влияют на стабильность верхних уровней. Необходимо отслеживать сетевой трафик, потери пакетов, ошибки пакетов, соединения и т. д.
  2. Системный мониторинг. Включает физические машины, виртуальные машины, операционные системы и другие системные компоненты. Мониторинг ключевых показателей, таких как загрузка процессора, использование памяти, дисковый ввод-вывод и пропускная способность сети, важен для понимания состояния системы.
  3. Мониторинг приложений. Связан с конкретными сервисами. Важны показатели, такие как URL-адреса, количество обращений, задержки, производительность сервисов, ошибки и взаимодействие с кешем, включая его попадание и производительность.
  4. Бизнес-мониторинг. Для типичного веб-сайта, например, важны такие бизнес-показатели, как регистрация, вход в систему, оформление заказа и оплата. Эти данные могут влиять на стратегию компании.
  5. Мониторинг пользовательского опыта. Приложения могут быть доступны через приложения, H5 и ПК. Пользовательский опыт включает производительность, наличие ошибок и другие аспекты. Этот мониторинг важен для улучшения качества обслуживания клиентов.
  6. Точки мониторинга:
    • Логирование.
    • Метрики.
    • Отслеживание вызовов.
    • Системы оповещения.
    • Проверка работоспособности.

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

Для сбора журналов популярным решением является ELK (Elasticsearch, Logstash, Kibana). Elasticsearch — распределённая поисковая система, Logstash — агент сбора журналов, а Kibana — интерфейс запросов к журналам.

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

Агенты микросервисов, такие как Springboot, предоставляют функции проверки работоспособности для мониторинга использования процессора, памяти, JVM и других параметров. Nagios и Zabbix — популярные платформы для мониторинга работоспособности, которые регулярно проверяют состояние микросервисов и могут отправлять уведомления соответствующим специалистам. Вызов поведения восстановления

В настоящее время на рынке существует несколько основных вариантов выбора цепочки вызовов: zipkin, pinpoint, cat, skywalking. Среди них стоит отметить skywalking — новый инструмент цепочки вызовов, созданный китайскими разработчиками. Он использует открытый исходный код и основан на анализе цепочки вызовов с помощью внедрения байт-кода. Подключение к системе не требует изменения кода, а также поддерживается множеством плагинов. Пользовательский интерфейс в нескольких инструментах является более функциональным и привлекательным. В настоящее время skywalking включён в инкубационный проект Apache.

Преимущества skywalking:

  • Во-первых, с точки зрения реализации skywalking практически не вмешивается в код, используя java-зонды и усиление байт-кода, в то время как cat использует внедрение кода, zipkin — перехват запросов, а pinpoint — java-зонды и усиление байт-кода.
  • Во-вторых, с точки зрения детализации анализа skywalking работает на уровне метода, тогда как zipkin работает на уровне интерфейса, а остальные два инструмента также работают на уровне метода.
  • С точки зрения хранения данных skywalking может использовать ES, который является известным решением для хранения данных в системах журналов. Другие инструменты также могут использовать ES. Pinpoint использует Hbase, cat — mysql или HDFS. Это может быть сложным выбором, но если компания имеет опыт работы с ES, это может стать важным фактором при выборе решения.
  • Также стоит учитывать влияние на производительность. Согласно некоторым отчётам о производительности, представленным в интернете, хотя они могут не быть абсолютно точными, skywalking демонстрирует наиболее эффективное воздействие на пропускную способность среди четырёх инструментов. Некоторые тесты также подтверждают это.

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

Категория Zipkin Pinpoint SkyWalking CAT
Основной принцип Перехват запроса, отправка (HTTP, MQ) данных в службу zipkin Java-зонд, усиление байт-кода Java-зонд, усиление байт-кода

Подключение

Категория Zipkin Pinpoint SkyWalking CAT
Способ подключения На основе linkerd или sleuth, требуется только конфигурация Javaagent, байт-код Javaagent, байт-код Код внедряется
Протокол между агентом и сборщиком HTTP, MQ Thrift gRPC HTTP/TCP
OpenTracing × ×

Анализ

Категория Zipkin Pinpoint SkyWalking CAT
Уровень детализации Интерфейс Метод Метод Код
Глобальный статистический анализ вызовов ×
Поиск traceid × ×
Оповещение ×
Мониторинг JVM × ×

Отображение пользовательского интерфейса

Категория Zipkin Pinpoint SkyWalking CAT
Надёжность ** ***** **** *****

Хранение данных

Категория Zipkin Pinpoint SkyWalking CAT
Хранение данных ES, mysql, Cassandra, память Hbase ES, H2 mysql, hdfs

Управление потоком

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

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

Изоляция

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

Ограничение потока

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

Снижение нагрузки

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

Hystrix

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

Процесс вызова Hystrix

Общий рабочий процесс выглядит следующим образом:

  1. Создание объекта HystrixCommand для инкапсуляции запроса и настройки необходимых параметров в конструкторе.
  2. Выполнение команды. Hystrix предоставляет несколько методов выполнения команд, наиболее часто используемыми являются синхронный и асинхронный методы.
  3. Проверка, открыта ли цепь. Если она открыта, сразу перейти к методу fallback.
  4. Проверить, заполнены ли пул потоков, очередь или счётчик сигналов. Если да, немедленно перейти к методу fallback.
  5. Выполнить метод run, обычно это HystrixCommand.run(). Если во время выполнения происходит тайм-аут или ошибка, немедленно перейти к методу fallback.
  6. Независимо от того, на каком этапе находится выполнение, будет проводиться отчёт о метриках, чтобы собрать данные о мониторинге плавких предохранителей.
  7. Метод fallback также делится на реализацию и резервные части.
  8. Наконец, вернуть ответ на запрос.

Шаблоны проектирования

Отдельная база данных для каждого микросервиса (Database per Microservice)

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

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

Преимущества:

  • Данные полностью принадлежат сервису.
  • Степень связи между командами разработчиков снижается. Недостатки:
  • Обмен данными между сервисами становится более сложным.
  • Обеспечение гарантий ACID для транзакций в масштабе приложения становится сложной задачей.
  • Тщательное проектирование разделения монолитной базы данных является сложной задачей. Когда использовать отдельную базу данных:
  • В крупных корпоративных приложениях.
  • Когда команде разработчиков необходимо полностью контролировать микросервисы для достижения масштабируемости разработки и повышения скорости. Когда не следует использовать отдельную базу данных:
  • В небольших приложениях.
  • Если разработкой всех микросервисов занимается одна команда. Примеры технологий:
  • Все SQL и NoSQL базы данных предоставляют логические разделения данных (например, отдельные таблицы, коллекции, структуры или базы данных).

Источник событий (Event Sourcing)

В микросервисной архитектуре, особенно при использовании отдельной базы данных для каждого сервиса, микросервисы должны обмениваться данными. Для эластичных, высокодоступных и отказоустойчивых систем они должны взаимодействовать через асинхронную передачу событий. В этом случае может потребоваться выполнить операцию, аналогичную обновлению базы данных и отправке сообщения, но использование двухфазной блокировки (2PL) в распределённых сценариях с большими объёмами данных может быть невозможным, поскольку оно не масштабируется. А большинство NoSQL баз данных даже не поддерживают двухфазную блокировку или не могут реализовать распределённые транзакции. В этих сценариях можно использовать событийный паттерн на основе событий. В традиционных базах данных непосредственно хранятся текущие «состояния» бизнес-сущностей, а в источнике событий сохраняются все обновления состояний или другие важные события, а не сами сущности. Это означает, что все изменения бизнес-сущности будут сохранены в виде серии неизменяемых событий. Поскольку данные хранятся в виде последовательности событий, а не прямых обновлений, различные сервисы могут вычислять требуемое состояние данных, воспроизводя события из хранилища.

Преимущества:

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

Недостатки:

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

Когда следует использовать источник событий:

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

Когда не следует использовать источник событий:

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

Разделение команд и запросов (CQRS)

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

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

В своей простой форме используются разные сущности или ORM-модели для операций чтения и записи:

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

Продвинутая форма включает использование разных хранилищ данных для операций чтения и записи. Продвинутый CQRS обычно сочетается с паттерном источника событий. Для различных сценариев используются разные типы хранилищ для записи и чтения данных. Хранилище для записи является «системой записей», то есть основным источником всей системы.

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

Оптимизированные для поиска (например, Apache Solr, Elasticsearch) или операции чтения (KV-хранилища, документно-ориентированные базы данных) нереляционные базы данных часто используются в качестве хранилищ для чтения. Многие случаи используют читаемые масштабируемые реляционные базы данных при необходимости SQL-запросов. Нестандартизованные и специально оптимизированные данные сохраняются в хранилищах для чтения.

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

Преимущества:

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

Недостатки:

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

Когда использовать CQRS:

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

Когда не использовать CQRS:

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

Saga

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

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

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

Существует два основных способа координации управления транзакциями Saga:

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

Преимущества:

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

Недостатки:

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

Когда использовать Saga:

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

Когда не использовать Saga:

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

Backends for Frontends (BFF)

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

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

Преимущества:

  • разделение фокуса между BFF, позволяя оптимизировать их для конкретных UI;
  • повышенная безопасность;
  • уменьшение частоты связи между UI и последующими микрослужбами.

Недостатки:

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

Когда использовать BFF:

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

Когда не использовать BFF:

  • если у приложения есть несколько пользовательских интерфейсов, но одинаковые потребности в API;
  • если основные микросервисы не развёрнуты в DMZ.

Примеры подходящих технологий:

  • любой серверный фреймворк (Node.js, Spring, Django, Laravel, Flask, Play и т. д.).

API Gateway

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

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

Преимущества:

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

Недостатки:

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

Когда использовать API Gateway:

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

Когда не использовать API Gateway:

  • безопасность и централизованное управление не являются приоритетами в частных проектах или небольших компаниях;
  • небольшое количество микросервисов. 就会拦截客户端请求并路由到新的微服务.

一旦迁移了所有的功能,遗留单体应用程序就会被“扼杀(Strangler)”,即退役。

Преимущества:

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

Недостатки:

  • сложность совместного использования данных между существующими монолитными приложениями и новыми микросервисами;
  • увеличение задержки из-за добавления Facade (API-шлюза);
  • усложнение процесса тестирования.

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

断路器

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

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

Режим обрыва имеет три состояния:

  • закрыто: запросы направляются к микросервису, ведётся подсчёт сбоев за определённый период времени. При превышении порогового значения, система переходит в открытое состояние;
  • открыто: запросы к микросервису завершаются с ошибкой. После тайм-аута система переходит в полуоткрытое состояние;
  • полуоткрыто: ограниченное количество запросов к микросервису допускается. Успешные запросы переводят систему в закрытое состояние, а неудачные — в открытое.

Преимущества:

  • повышение отказоустойчивости и эластичности микросервисной архитектуры;
  • предотвращение каскадных сбоев.

Недостатки:

  • необходимость сложной обработки исключений;
  • ведение журналов и мониторинг;
  • поддержка ручного сброса.

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

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

Внешняя конфигурация

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

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

Преимущества:

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

Недостатки:

  • необходимо выбрать фреймворк, поддерживающий внешнюю конфигурацию.

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

Контрактное тестирование на основе потребления

В микросервисной архитектуре обычно существует множество микросервисов, разработанных разными командами. Эти микросервисы совместно работают для удовлетворения бизнес-требований (например, клиентских запросов) и взаимодействуют синхронно или асинхронно. Интеграционное тестирование микросервисов-потребителей может быть сложной задачей. Обычно используются TestDouble для ускорения и снижения стоимости тестирования. Однако TestDouble не всегда могут точно представлять реальных поставщиков услуг, и если поставщик услуг изменяет свой API или сообщения, TestDouble может не обнаружить эти изменения. Альтернативой является сквозное тестирование, которое обязательно перед выпуском в производство, но оно может быть хрупким, медленным, дорогостоящим и не заменяет интеграционное тестирование (Test Pyramid).

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

Преимущества:

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

Недостатки:

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

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

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

Мониторинг и обнаружение признаков сбоев

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

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

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

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

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

Анализ проблемы: логирование и анализ

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

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

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

Шлюз выполняет функции управления доступом к сервисам и обеспечения безопасности. Он контролирует доступ к сервисам на основе определённых правил и политик.

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

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

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

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

Отключение, понижение уровня обслуживания и ограничение трафика

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

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

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

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

Модульное тестирование проверяет отдельные компоненты системы, интеграционное — взаимодействие между компонентами, а системное — работу всей системы в целом. Тестирование помогает выявить и исправить ошибки до выпуска системы в эксплуатацию. FaaS (Function as a Service) — сервис, предоставляющий платформу для создания, запуска и управления функциями. Поддерживает множество языков программирования, таких как Java, Node.js, Dart и другие, что облегчает участие разработчиков в тестировании на различных этапах. FaaS основан на идее событийно-управляемого подхода: функция выполняется только при срабатывании определённого события, в противном случае ресурсы сервера не используются.

BaaS (Backend as a Service) — набор сторонних базовых услуг, предоставляемых для вызова функций, таких как аутентификация, логирование, база данных и т. д. Эти услуги предоставляются непосредственно поставщиками услуг и могут быть легко вызваны разработчиками без необходимости самостоятельной реализации.

Преимущества

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

Недостатки

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

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

  • Отправка уведомлений.
  • WebHook.
  • Статистический анализ данных.
  • Триггеры и задачи по расписанию.
  • Чат-боты.

Сервисная сетка (Service Mesh)

Существует три основных модели обнаружения сервисов:

  1. Традиционная централизованная модель прокси. Прокси-сервер выступает в качестве посредника между потребителями и производителями услуг. Он выполняет функции обнаружения сервисов и балансировки нагрузки. Эта модель часто используется в крупных компаниях, таких как eBay и Слетать.ру.

  2. Модель с клиентским встроенным прокси. В этой модели прокси-серверы интегрированы в клиентские приложения. Они выполняют обнаружение сервисов и балансировку нагрузки. Примеры включают Netflix Eureka и Ribbon.

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

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

Особенности сервисной сетки

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

Открытые реализации сервисной сетки

  • Первое поколение: Linkerd (на Scala) и Envoy (на C++ 11).
  • Второе поколение: Istio (совместный проект Google, IBM и Lyft) и Conduit (похожий на Istio, но с использованием Rust для sidecar и Go для управления).

Архитектура

Показатели

  • QPS (Queries Per Second) — количество запросов, обрабатываемых сервером за секунду. Используется для оценки максимальной пропускной способности сервера.
  • TPS (Transactions Per Second) — количество транзакций, выполняемых сервером за секунду. Включает в себя запросы, обработку на сервере и ответ клиенту.
  • Конкуренция (Concurrency) — максимальное количество одновременных запросов, которые может обработать сервер.
  • Пропускная способность (Throughput) — общее количество запросов или транзакций, обработанных сервером за определённый период времени.
  • PV (Page View) — количество просмотров страниц за определённый период.
  • UV (Unique Visitor) — количество уникальных посетителей за определённый период.
  • DAU (Daily Active User) — количество активных пользователей за день.
  • MAU (Monthly Active User) — количество активных пользователей за месяц.

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

  • CPU-интенсивность каждого запроса.
  • Влияние внешних интерфейсов и ввода/вывода на скорость обработки.
  • Количество одновременных запросов.

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

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

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

Нагрузочное тестирование

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

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

Цель нагрузочного тестирования — выявить изменения в производительности системы под различными углами нагрузки и получить данные о производительности для оптимизации.

Определение модели сетевой инфраструктуры. Разработка сценариев нагрузки для тестирования пропускной способности системы. Создание инструментов для внедрения нагрузки. Разработка инструментов сбора данных о производительности.

Параллельное тестирование

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

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

Конфигурационное тестирование

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

Установление стандартов для настройки ресурсов. Подготовка сценариев тестирования конфигураций.

Тестирование на прочность

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

Стресс-тестирование

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

Стабильность системы

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

Разделение интерфейса и сервера

Принципы разделения интерфейса и сервера

  • Данные, отображаемые на интерфейсе, должны быть получены непосредственно из ответа сервера.
  • Логика рендеринга должна быть ограничена интерфейсом.
  • Интерфейс должен избегать выполнения бизнес-логики.
  • Формат передачи данных между интерфейсом и сервером должен быть стандартизирован.
  • Ответ сервера должен содержать информацию о разбиении на страницы.
  • Выбор элементов (выпадающие списки, флажки, радиокнопки) должен контролироваться сервером.
  • Для логических значений в ответе сервера следует использовать 1/0 вместо true/false.
  • Даты в ответе сервера должны передаваться в формате строки.

Разработка API

Безопасность API

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

  • Использование токена для аутентификации. Токен содержит информацию об идентификаторе приложения, времени создания, случайном числе и подписи. Подпись формируется на основе секретного ключа приложения.
  • Применение POST-запросов для взаимодействия с API. GET-запросы могут раскрывать конфиденциальную информацию, поэтому рекомендуется использовать POST.
  • Ограничение доступа к API через белый список IP-адресов. Это предотвращает несанкционированный доступ и атаки на API.
  • Ведение журнала запросов для отслеживания активности и выявления аномалий.
  • Шифрование чувствительных данных, таких как номера заказов, с использованием асимметричного шифрования.

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

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

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

  • Версионирование API для предотвращения изменений в существующих запросах. Каждый запрос должен включать версию API. ``` public class R implements Serializable {

    private static final long serialVersionUID = 793034041048451317L;

    private int code; private String message; private Object data = null;

    public int getCode() { return code; } public void setCode(int code) { this.code = code; }

    public String getMessage() { return message; } public void setMessage(String message) { this.message = message; }

    public Object getData() { return data; }

    /**

    • 放入响应枚举 */ public R fillCode(CodeEnum codeEnum){ this.setCode(codeEnum.getCode()); this.setMessage(codeEnum.getMessage()); return this; }

    /**

    • 放入响应码及信息 */ public R fillCode(int code, String message){ this.setCode(code); this.setMessage(message); return this; }

    /**

    • 处理成功,放入自定义业务数据集合 */ public R fillData(Object data) { this.setCode(CodeEnum.SUCCESS.getCode()); this.setMessage(CodeEnum.SUCCESS.getMessage()); this.data = data; return this; } }

В тексте запроса представлен фрагмент кода на языке Java, который описывает класс `R`, реализующий интерфейс `Serializable`. Класс содержит три поля: `code`, `message` и `data`, а также методы для установки и получения значений этих полей.

Класс предоставляет методы `fillCode()` и `fillData()` для заполнения полей `code` и `data` соответственно. Метод `fillCode()` принимает объект типа `CodeEnum` или два параметра — код и сообщение, которые используются для установки соответствующих значений полей. Метод `fillData()` используется для успешного завершения операции и заполнения поля `data`. **response_type=code&**  
**client_id=CLIENT_ID&**  
**redirect_uri=http://juejin.im/callback&**  
**scope=read&**  
**state=10001** — это параметры запроса, которые используются для авторизации пользователя и получения доступа к ресурсам приложения.  

В запросе описывается процесс авторизации с использованием OAuth 2.0, который включает в себя следующие шаги:  
1. Пользователь посещает страницу приложения и соглашается предоставить доступ к своим данным.  
2. Приложение перенаправляет пользователя на сервер аутентификации (OAuth server), где пользователь подтверждает своё согласие на предоставление доступа.  
3. Сервер аутентификации возвращает приложению код авторизации (authorization code).  
4. Приложение отправляет запрос на сервер аутентификации с кодом авторизации, секретным ключом (client secret) и другими параметрами для получения токена доступа (access token).  
5. Сервер аутентификации проверяет запрос и, если он действителен, возвращает токен доступа.  
6. Приложение использует токен доступа для доступа к защищённым ресурсам.

Также в запросе описываются различные режимы работы OAuth 2.0:  
* **Упрощённый режим (Implicit Grant)** — используется для приложений без серверной части. В этом режиме пользователь сразу получает токен доступа после предоставления согласия на доступ.  
* **Режим пароля (Resource Owner Password Credentials Grant)** — позволяет приложению получить токен доступа, используя пароль пользователя. Этот режим требует высокого уровня доверия между приложением и сервером аутентификации.  
* **Клиентский режим (Client Credentials Grant)** — приложение получает токен доступа без участия пользователя. Используется для серверных приложений, которым требуется доступ к другим сервисам.  
* **Скрытый режим** — используется для приложений без серверной части, где токен доступа получается напрямую от сервера аутентификации без использования кода авторизации.  
* **Паролизированный режим** — похож на режим пароля, но используется для командных строк или других приложений без пользовательского интерфейса.  
* **Доверительный режим** — аналогичен клиентскому режиму, но не требует участия пользователя.

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

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

**Принцип работы HTTPS**

HTTPS — это протокол, который обеспечивает безопасность передачи данных через интернет. Он основан на протоколе HTTP и использует шифрование для защиты информации. HTTPS состоит из двух частей: HTTP и SSL/TLS. SSL/TLS — это криптографический протокол, обеспечивающий конфиденциальность, целостность и аутентификацию данных.

**Всегда ли безопасен HTTPS?**

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

**Симметричное шифрование**

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

**Асимметричное шифрование**

Асимметричное шифрование требует двух ключей: открытого и закрытого. Открытый ключ может использоваться для шифрования данных, а закрытый ключ — для их расшифровки. Асимметричные алгоритмы, такие как RSA, широко используются в протоколах безопасности, включая HTTPS. Однако и здесь есть свои риски. Злоумышленники могут попытаться подделать открытый ключ или создать свой собственный и использовать его для отправки зашифрованных данных.

**Хранение паролей**

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

*MD5*

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

*Соль*

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

*Bcrypt*

Bcrypt — это алгоритм, специально разработанный для безопасного хранения паролей. Он медленнее, чем MD5, что делает его более устойчивым к атакам методом перебора. Spring Security рекомендует использовать Bcrypt для хеширования паролей.

**Авторизация**

Авторизация — это процесс определения прав доступа пользователя к определённым ресурсам. Существует несколько моделей авторизации:

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

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

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

#### Иерархия уровней доступа к данным

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

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

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

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

#### Наследование функций

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

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

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

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

**RBAC**

RBAC — это зрелая модель управления правами. В традиционной модели прав права предоставляются непосредственно пользователю. В RBAC вводится понятие «роль», и сначала права назначаются роли, а затем роли назначаются пользователям. Таким образом, благодаря добавлению роли процесс предоставления прав становится более гибким и удобным. В RBAC в зависимости от сложности прав существует четыре уровня: RBAC0, RBAC1, RBAC2 и RBAC3. Среди них RBAC0 является базовым, а RBAC1, RBAC2 и RBAC3 основаны на RBAC0. Мы можем выбрать подходящую модель прав в зависимости от степени сложности прав нашего продукта.

#### Базовая модель RBAC0

RBAC0 — это основа, и многие продукты могут создавать модель прав на основе RBAC0. В этой модели права присваиваются роли, а роли присваиваются пользователям. Пользователь и роль, роль и право — всё это отношения «многие ко многим». Права пользователя равны сумме прав всех его ролей.

#### Модель иерархических ролей RBAC1

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

#### Модель ролей с ограничениями RBAC2

RBAC2 также основан на RBAC0 и добавляет ограничения к отношениям между пользователями, ролями и правами. Эти ограничения можно разделить на две категории: статическое разделение обязанностей (Static Separation of Duty, SSD) и динамическое разделение обязанностей (Dynamic Separation of Duty, DSD). Конкретные ограничения показаны на рисунке ниже.

#### Унифицированная модель RBAC3

RBAC3 объединяет RBAC1 и RBAC2, поэтому RBAC3 имеет как иерархические роли, так и различные ограничения.

#### Расширение модели групп пользователей

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

#### Система заказов и платежей

##### Системный дизайн

* **Отношения с внешними системами**: все системы, используемые внешними пользователями предприятия, находятся на этом уровне, включая официальный веб-сайт, обычные пользовательские терминалы C, а также системы для продавцов и дистрибьюторов, такие как сотрудничество с банками и сотрудничество с WeChat на платформах партнёров. Такие системы являются аванпостами предприятий для реализации бизнес-моделей.

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

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

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

##### Отношения вверх и вниз по течению

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

##### Бизнес-архитектура

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

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

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

##### Процесс проектирования

Процесс заказа в основном относится к процессу создания заказа до завершения транзакции. Согласно текущим электронным торговым центрам (E-mall), системам управления складами (WMS) и системам управления логистикой (TMS), процесс потока в основном выглядит следующим образом:

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

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

**Повторная оплата**

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

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

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

* Добавить промежуточный статус «Оплата» для платёжных заказов. При оплате одного и того же заказа сначала проверяется наличие статуса «Оплата». При оплате необходимо добавить блокировку. После завершения оплаты статус платёжного потока обновляется до «Оплата успешна».
* Платёжному центру необходимо установить тайм-аут (например, 30 секунд). Если платёжный результат не получен в течение этого времени, следует вызвать интерфейс для активного запроса платёжного результата. Например, проверять каждые 10, 20 и 30 секунд. Если максимальный запрос не даёт результата, необходимо выполнить обработку исключений.
* После получения платёжного результата платёжный центр должен синхронизировать его с бизнес-системой. Можно отправить сообщение MQ или напрямую вызвать его. В последнем случае необходимо усилить повторную попытку (например, SpringBoot Retry).
* Независимо от того, является ли это платёжный центр или бизнес-приложением, при получении уведомления о платеже необходимо учитывать возможность эквивалентности интерфейса, обрабатывать сообщение только один раз и игнорировать остальные.
* Бизнес-приложение также должно активно запрашивать платёжные результаты по истечении времени ожидания.

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

Чтобы предотвратить повторную отправку заказа, можно сделать следующее:

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

Вот пример наилучшей практики использования WeChat Pay:

**Оплата и сверка**

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

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

* Ctrip создаёт заказ и отправляет платёжный запрос третьей стороне.
* Третья сторона создаёт заказ и направляет платёжный запрос в банк.
* Банк завершает списание средств и возвращает платёж третьей стороне.
* Третья сторона завершает обновление заказа и возвращает его Ctrip.
* Ctrip изменяет статус заказа.

Вышеупомянутый процесс можно упростить следующим образом:

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

**Внешняя потеря заказа**

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

Меры противодействия:

* Увеличьте время ожидания сети.
* Обратите внимание: при подключении к внешнему каналу обязательно установите время ожидания соединения с сетью и время ожидания чтения.

* Получайте асинхронные уведомления.

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

Примечание:

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

* Запрос потери заказа.

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

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

* Сверка.

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

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

**Внутренняя потеря заказа**

Отношения между внутренними заказами платёжной компании обычно представляют собой соотношение 1:N между платёжными заказами и заказами канала:

Заказ канала представляет отношения между третьей платёжной компанией и внешним каналом. Фактически, с точки зрения внешнего канала, третья платёжная компания является внешним продавцом. Если используется отношение 1:1 между заказами, когда первая оплата не удаётся, внешний продавец может повторно использовать тот же номер заказа для инициирования оплаты в третью платёжную компанию.

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

Причины внутренней потери заказа:

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

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

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

Решение внутренней потери заказа:

* Распределённая транзакция.

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

* Асинхронная компенсация обновления.

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

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

Решения:

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

* Задача по расписанию для компенсации.
* Компенсация с задержкой сообщения.

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

Рассмотрим подробнее следующие шаги.

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

*Повторить:*

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

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

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

**Седьмой шаг:** вызов платёжного запроса интерфейса оплаты канала.

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

— результат оплаты успешен;
— результат оплаты определённо неудачен;
— количество записей о пополнении достигло максимального числа,

то **на восьмом шаге запись о пополнении будет удалена**.

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

***Вопрос 2:***

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

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

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

Это объясняет необходимость удаления записей из таблицы непогашенных заказов на восьмом этапе.

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

***Вопрос 3:***

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

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

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

***План Б:*** Отложенное сообщение для пополнения

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

***Общий процесс:***

Общий процесс этого плана показан на схеме архитектуры.

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

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

На пятом шаге приложение для пополнения получает сообщение о пополнении и инициирует запрос на оплату пополнения.

На восьмом шаге, если результат платёжного запроса равен одному из следующих условий:

— платёж успешен;
— платёж определённо неудачен;
— количество запросов достигло максимума,

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

***Очередь отложенных сообщений:***

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

1. На основе отсортированного набора Redis (SortedSet) реализовать очередь отложенных сообщений. Можно обратиться к реализации от Zynga.

2. На основе алгоритма временного колеса (TimingWheel) реализовать очередь отложенных сообщений, например, как в Kafka.

3. На основе RocketMQ реализовать очередь отложенных сообщений.

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

```properties
1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h

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

Преимущества и недостатки плана с отложенным сообщением:

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

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

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

В WeChat API версии 3 помимо настройки API-ключа требуется настроить API v3 ключ и сертификат, выданный центром сертификации (CA), который был запрошен.

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

Если вы используете открытый исходный код WeChat разработки, убедитесь, что он поддерживает версию 3.

Конфигурирование сервера

На заднем плане публичного аккаунта — разработка — базовая конфигурация — конфигурация сервера включите и заполните информацию о сервере.

Настройка белого списка

На заднем фоне публичного аккаунта — разработка — основная конфигурация — информация о разработке публичного аккаунта настройте ключ разработчика и одновременно заполните белый список IP-адресов.

Безопасное доменное имя интерфейса JS

На заднем фоне публичного аккаунта — публичный аккаунт — настройка — функциональная настройка установите безопасное доменное имя для интерфейса JS.

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

JSAPI Мини-программа
Протокол оплаты HTTP/HTTPS HTTPS
Каталог оплаты Есть Нет
Доменное имя авторизации Есть Нет

Процесс оплаты

Из-за обновления API WeChat в интерфейсе API версии 3 необходимо загрузить запрошенный сертификат API. WeChat уже упаковал соответствующие пакеты jar и предоставил примеры загрузки. Вы можете обратиться к «https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_3.shtml» для получения дополнительной информации. Здесь мы не будем вдаваться в подробности. В качестве примера мы подробно изучим основной процесс подключения WeChat (поскольку некоторые интерфейсы API версии 3 все ещё находятся в стадии обновления, интерфейс версии 2 относительно завершён).

Интерфейс WeChat для оформления заказа

Пользователь инициирует оплату через приложение WeChat, и на заднем плане магазина создаётся заказ. Затем вызывается интерфейс оформления заказа WeChat для создания предоплаченного заказа и возврата номера заказа! Основные параметры, связанные с интерфейсом оформления заказа, включают только несколько важных параметров:

Параметр запроса Обязательный Тип Описание
appid Да String Идентификатор публичного аккаунта
mch_id Да String Номер магазина
nonce_str Да String Случайная строка длиной не более 32 символов
sign Да Строка Подпись, по умолчанию используется MD5 для шифрования
out_trade_no Да Строка Внутренний номер системы заказа
total_fee Да Int Общая сумма заказа в единицах
notify_url Да Строка Интерфейс уведомления об оплате

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

  1. Все отправленные непустые параметры сортируются в пары ключ-значение (key1=value1&key2=value2).
  2. Ключ String добавляется в конец строки вместе с ключом key=секретный ключ магазина.
  3. Полученная строка шифруется с помощью MD5.

Оплата

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

Имя параметра Обязательный Тип Описание
AppId Да String Идентификатор публичного аккаунта
TimeStamp Да String Текущее время в виде метки времени
NonceStr Да String Случайная строка
Package Да String Формат предварительного заказа: prepay_id=***
SignType Да String Тип подписи, по умолчанию MD5
PaySign Да String Подпись

Способ подписи такой же, как и у интерфейса оформления заказа.

Пример псевдокода JavaScript:

function onBridgeReady(){
    WeixinJSBridge.invoke(
        'getBrandWCPayRequest', {
            // Идентификатор публичного аккаунта, предоставленный магазином
            "appId":"wx2421b1c4370ec43b",
            // Метка времени с 1970 года в секундах
            "timeStamp":"1395712654",  
            // Случайная строка
            "nonceStr":"e61463f8efa94090b1f366cccfbbb444",
            "package":"prepay_id=u802345jgfjsdfgsdg888", 
            // Способ подписи WeChat
            "signType":"MD5",
            // Подпись WeChat
            "paySign":"70EA570631E4BB79628FBCA90534C63FF7FADD89"
        },
        function(res){
            if(res.err_msg == "get_brand_wcpay_request:ok" ){
                // Используйте вышеуказанный метод для определения ответа переднего плана, WeChat Team серьёзно напоминает:
                // res.err_msg будет возвращать ok после успешной оплаты пользователем, но это не гарантирует его надёжность.
            } 
        }); 
}
if (typeof WeixinJSBridge == "undefined"){
    if( document.addEventListener ){
        document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
    }else if (document.attachEvent){
        document.attachEvent('WeixinJSBridgeReady', onBridgeReady); 
        document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
    }
}else{
    onBridgeReady();
}

Обратите внимание на предложение в примере псевдокода: // res.err_msg вернёт ok после успешной оплаты пользователя, но это не обязательно надёжно. Почему это так? Я приведу пример, который должен прояснить ситуацию. Если вы идёте в супермаркет за покупками, разве вы говорите, что оплата прошла успешно, и вы можете забрать товар? Конечно, нет, это означает, что магазин получил деньги после того, как вы заплатили, и только тогда вы можете забрать свой товар. То есть успешное сообщение здесь не обязательно означает, что платёж прошёл успешно, конкретная ситуация успеха или неудачи будет сообщена вам асинхронно платформой WeChat. GEOADD

Использование:

GEOADD key longitude latitude member [longitude latitude member ...]

Здесь:

key — ключ, который будет использоваться для хранения данных;

longitude и latitude — координаты местоположения (долгота и широта);

member — объект, который будет добавлен в список.

Команда GEOADD добавляет указанные объекты в заданный ключ. Если ключ не существует, он создаётся автоматически. Перевод текста запроса на русский язык:

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

Алгоритм работы с GEOADD:

  1. Извлечение и проверка параметров.
  2. Преобразование широты, долготы и имени объекта в 52-битное значение geohash.
  3. Сохранение объекта и его значения geohash в коллекции key.

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

После успешной вставки возвращается значение: (integer) N, где N — количество успешно вставленных объектов.

Анализ исходного кода показывает, что Redis использует упорядоченное множество (zset) для хранения объектов местоположения. Каждый элемент в упорядоченном множестве представляет собой объект с местоположением, а значение score элемента соответствует 52-битному значению geohash местоположения.

Примечание:

  • Тип double имеет точность 52 бита.
  • Geohash кодируется способом base32, 52 бита позволяют хранить до 10 знаков geohash, что соответствует географической области размером примерно 0,6 * 0,6 метра. Другими словами, теоретически после преобразования через Redis geo местоположение может иметь ошибку примерно 0,3 * 1,414 = 0,424 метра.*

Краткое описание алгоритма GEOADD:

  • Извлекает и проверяет параметры.
  • Преобразует широту и долготу в 52-битный geohash (score).
  • Вызывает команду ZADD для сохранения объекта и его geohash-значения в ключе коллекции.

Команда GEORADIUS:

Способ использования:

GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [ASC|DESC] [COUNT count] [STORE key] [STORedisT key].

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

Единицы измерения радиуса: m | km | ft | mi — метры, километры, футы или мили.

Дополнительные параметры:

  • WITHDIST — возвращает расстояние между центром и объектом местоположения вместе с объектами местоположения. Единицы измерения расстояния совпадают с единицами измерения радиуса.
  • WITHCOORD — возвращает широту и долготу объекта местоположения вместе с объектом местоположения.
  • WITHHASH — возвращает 52-разрядное целое число со знаком, представляющее geohash объекта местоположения после кодирования. Этот параметр используется в основном для приложений нижнего уровня или отладки и не имеет большого практического применения.
  • ASC|DESC — возвращает объекты местоположения в порядке от ближайшего к дальнему или от дальнего к ближайшему.
  • COUNT count — выбирает первые N совпадающих объектов местоположения (по умолчанию возвращаются все объекты).
  • STORE key — сохраняет информацию о местоположении в указанной коллекции.
  • STORedisT key — сохраняет расстояние от центра в указанной коллекции.

Из-за наличия параметров STORE и STORedisT команды GEORADIUS и GEORADIUSBYMEMBER считаются командами записи и могут выполняться только на главном экземпляре. При высокой частоте запросов (QPS) они могут вызвать перегрузку главного экземпляра при чтении и записи. Чтобы решить эту проблему, в Redis 3.2.10 и Redis 4.0.0 были добавлены команды GEORADIUS_RO и GEORADIUSBYMEMBER_RO, доступные только для чтения. Однако в реальной разработке было обнаружено, что в Java-пакете Redis.clients.jedis.params.geo класс GeoRadiusParam не содержит параметров STORE и STORedisT, поэтому при вызове georadius выполняется только запрос к главному экземпляру без сохранения данных.

Успешный запрос возвращает список членов без дополнительных параметров, например: ["member1", "member2", "member3"].

С дополнительными параметрами возвращается список членов, каждый из которых также является списком, например: [ ["member1", distance1, [longitude1, latitude1]] ["member2", distance2, [longitude2, latitude2]] ].

Краткое изложение алгоритма GEORADIUS:

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

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

Это описание не очень понятно, поэтому мы проиллюстрируем алгоритм с помощью двух диаграмм:

Рисунок 1: Центр слева — это центр поиска, зелёная окружность — целевая область, все точки — объекты поиска, красные точки — удовлетворяющие условиям объекты. При реальном поиске сначала вычисляется уровень geohash на основе радиуса поиска (рисунок 2), затем определяется положение сетки geohash (красная сетка на рисунке 2); затем в каждой сетке geohash выбираются точки (синие и красные точки), и, наконец, отфильтровываются точки, находящиеся в пределах радиуса поиска (красные точки).

Статистика больших объёмов данных

Типичные сценарии включают:

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

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

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

  • двоичная статистика состояния;
  • агрегатная статистика;
  • сортировочная статистика;
  • базовая статистика.

Двоичная статистика

Что такое двоичная статистика?

Значения элементов в наборе данных имеют только два возможных значения: 0 и 1. В сценариях, где требуется определить статус входа или выхода пользователя, достаточно записать «вход» (1) или «отсутствие» (0), «вошёл в систему» (1) или «не вошёл в систему» (0). Если мы используем тип String в Redis для хранения статуса входа 100 миллионов пользователей (ключ — идентификатор пользователя, значение — 0 для отсутствия и 1 для входа), нам потребуется хранить 100 миллионов строк, что приведёт к значительному использованию памяти.

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

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

len — занимает 4 байта и представляет длину используемого буфера; alloc — занимает 4 байта и представляет фактическое выделенное пространство буфера, обычно больше len; buf — массив байтов, содержащий фактические данные. Redis автоматически добавляет символ «\0» в конец массива, занимая дополнительный байт.

Таким образом, помимо buf для хранения фактических данных, len и alloc являются дополнительными накладными расходами. Кроме того, существует структура RedisObject, которая также вносит свой вклад в использование памяти, поскольку Redis поддерживает множество типов данных, и каждый тип данных имеет некоторые общие метаданные, такие как время последнего доступа и количество ссылок. Поэтому Redis использует структуру RedisObject для унифицированного хранения этих метаданных и указывает на фактические данные.

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

Формула приблизительного расчёта использования пространства: ($offset/8/1024/1024) MB.

Что такое Bitmap?

Bitmap использует тип String SDS для хранения массива битов. Redis использует каждый байт массива для представления 8 битов, причём каждый бит представляет двоичное состояние (0 или 1) элемента. Мы можем рассматривать Bitmap как массив битов, где каждый элемент массива представляет состояние двоичного элемента. В Bitmap смещение называется смещением. Для наглядности мы можем представить каждый байт массива как строку, каждая строка состоит из 8 ячеек, представляющих 8 битов в байте, как показано ниже:

8 битов образуют байт, поэтому Bitmap значительно экономит память. Это преимущество Bitmap.

Когда мы сталкиваемся со статистическими сценариями, требующими только двоичных состояний данных, такими как наличие пользователя, принадлежность IP-адреса к чёрному списку и статистика входа и выхода, мы можем рассмотреть возможность использования Bitmap. Нам нужен только один бит, чтобы представить 0 или 1. Это значительно уменьшает использование памяти при анализе больших объёмов данных. Бит-массивы в Redis: операции с данными и примеры использования

При работе с данными, хранящимися в виде бит-массивов, необходимо учитывать, что смещение начинается с 0.

Для хранения данных о состоянии входа пользователя достаточно использовать ключ login_status и смещение, равное идентификатору пользователя. Если пользователь находится в сети, то значение смещения устанавливается равным 1, а если пользователь не в сети — 0. Для проверки состояния входа конкретного пользователя используется команда GETBIT.

Пример использования команд SETBIT и GETBIT

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

  1. Выполняем команду SETBIT login_status 10086 1, чтобы указать, что пользователь вошёл в систему.
  2. Проверяем состояние входа этого пользователя, используя команду GETBIT login_status 10086. Если возвращается значение 1, это означает, что пользователь вошёл в систему.
  3. Выходим из системы, устанавливая значение смещения для данного пользователя равным 0 с помощью команды SETBIT login_status 10086 0.

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

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

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

  1. Записываем факт посещения сайта пользователем с помощью команды SETBIT uid:sign:89757:202105 15 1. Здесь uid:sign: — ключ, 89757: — идентификатор пользователя, 202105 — год и месяц, 15 — дата.
  2. Определяем, был ли пользователь онлайн 16 мая 2021 года, с помощью команды GETBIT uid:sign:89757:202105 15.
  3. Подсчитываем общее количество дней, когда пользователь был онлайн в этом месяце, используя команду BITCOUNT uid:sign:89757:202105. Эта команда возвращает количество установленных бит в массиве.

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

Определение даты первого посещения сайта

Redis предоставляет команду BITPOS, которая позволяет определить позицию первого установленного бита в массиве. По умолчанию команда проверяет весь массив, но можно ограничить диапазон поиска с помощью параметров start и end.

Чтобы найти дату первого посещения сайта пользователем с идентификатором 89757 в мае 2021 года, нужно выполнить команду BITPOS uid:sign:89757:202105 1. Однако следует учесть, что возвращаемое значение нужно увеличить на 1, так как смещение начинается с нуля.

Количество пользователей, которые были онлайн непрерывно в течение 7 дней

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

Мы создаём отдельный бит-массив для каждого дня и используем идентификатор пользователя в качестве смещения. Если пользователь был онлайн, соответствующий бит устанавливается в 1. Затем мы объединяем семь таких массивов с помощью команды BITOP AND, создавая новый массив. Если все семь соответствующих битов установлены в 1 для одного и того же пользователя, это указывает на то, что он был онлайн семь дней подряд.

Команда BITOP также позволяет выполнять другие логические операции над массивами, такие как OR, NOT и XOR.

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

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

Использование HyperLogLog для подсчёта уникальных посетителей

HyperLogLog — это структура данных, которая используется для приблизительного подсчёта уникальных элементов в наборе данных. Она основана на вероятности и обеспечивает точность около 0,81%. Этого достаточно для большинства задач, связанных с подсчётом уникальных посетителей.

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

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

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

  1. PFADD Redis данные user1 user2 user3.
  2. PFADD MySQL данные user1 user2 user4.
  3. PFMERGE базы данных Redis и MySQL.
  4. PFCOUNT базы данных (возвращаемое значение = 4).

Объединяются несколько HyperLogLog в один HyperLogLog, объединённый HyperLogLog имеет мощность, близкую к мощности объединения всех входных HyperLogLog.

Пользователь user1 и user2 посетили страницы Redis и MySQL, они учитываются только один раз.

Сортировка и статистика

В Redis из четырёх типов коллекций (List, Set, Hash, Sorted Set) List и Sorted Set упорядочены.

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

  • Sorted Set: элементы сортируются по весу, который определяется пользователем. Используется для рейтинга, например, по количеству просмотров или лайков.

Список последних комментариев

Можно использовать List для сортировки комментариев по времени добавления. Например, список ответов в личном кабинете WeChat: каждый публичный аккаунт имеет свой List, в котором сохраняются все комментарии пользователей. Каждый раз, когда пользователь комментирует, используется LPUSH key value [value ...] для добавления комментария в начало списка.

LPUSH 码哥字节 1 2 3 4 5 6

Затем используйте LRANGE key star stop для получения элементов из указанного диапазона.

> LRANGE 码哥字бец 0 4
1) «6»
2) «5»
3) «4»
4) «3»
5) «2»

Обратите внимание, что не все последние списки могут быть реализованы с помощью List. Для часто обновляемых списков использование List может привести к дублированию или пропуску элементов при разбиении на страницы. Например, текущий список комментариев List = {A, B, C, D}. D — самый старый комментарий.

LPUSH 码哥字бец D C B A

Отображение первых двух комментариев:

LRANGE 码哥字бец 0 1
1) «A»
2) «B»

Согласно логике, вторая страница может быть получена с помощью LRANGE 码哥字бец 2 3. Если перед отображением второй страницы будет добавлен новый комментарий E, то он будет вставлен в начало List через LPUSH 码哥字бец E. List станет {E, A, B, C, D}. Теперь выполнение LRANGE 码��гиец 2 3 покажет, что B снова появился.

LRANGE 码��гец 2 3
1) «B»
2) «C»

Это происходит потому, что List использует позицию элемента для его сортировки. Как только новый элемент вставлен, List становится {E, A, B, C, D}, и предыдущие элементы перемещаются на одну позицию назад.

Причина, по которой List подходит для реализации некоторых списков, заключается в том, что нет необходимости в разбиении на страницы (например, всегда берётся только первые пять элементов) или частота обновления низкая (например, обновление один раз в день в полночь). Для списков, которые требуют разбиения на страницы и часто обновляются, следует использовать Sorted Set. Кроме того, если требуется поиск по временному диапазону, List также не подходит, и следует использовать Sorted Set, например, для поиска заказов по дате заключения сделки.

Рейтинг

Для сценариев, подобных списку последних, List и Sorted Set могут реализовать ранжирование. Зачем использовать List? Можно напрямую использовать Sorted Set, поскольку он также позволяет устанавливать вес для более гибкой сортировки. Причина в том, что объём памяти, занимаемый Sorted Set, намного больше, чем у List, поэтому List подходит, когда количество элементов невелико.

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

  • ZADD:

    • Мы добавляем «Цветы для стены» и «Поле цветов» в musicTop с весом 100000000 и 8999999 соответственно.
    ZADD musicTop 100000000 青花瓷 8999999 花田错
  • ZINCRBY:

    • «Цветы для стены» увеличивается на 1 каждый раз при прослушивании.
    > ZINCRBY musicTop 1 青花瓷
    100000001
  • ZRANGEBYSCORE:

    • Необходимо получить первые десять треков с наибольшим количеством прослушиваний. Максимальный вес сейчас равен N. Его можно получить с помощью следующей команды:
    ZRANGEBYSCORE musicTop N-9 N WITHSCORES

    Обратите внимание: как получить N?

  • ZREVRANGE:

    • Можно использовать команду ZREVRANGE key start stop [WITHSCORES]. Элементы сортируются от большего к меньшему по весу. Элементы с одинаковым весом сортируются в обратном лексикографическом порядке.
    > ZREVRANGE musicTop 0 0 WITHSCORES
    1) «青花瓷»
    2) 100000000

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

Агрегатная статистика

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

— пересечение (общие данные);

— разность (уникальные данные);

— объединение (все данные).

Какие сценарии используют пересечение, разность и объединение?

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

Пересечение — общие друзья

Например, общие друзья в QQ являются примером пересечения. Мы используем учётные записи в качестве ключей, а список друзей — в качестве значений в наборе. Имитируем два набора друзей для двух пользователей:

SADD user:码哥字бец R大 Linux大神 PHP之父
SADD user:大佬 Linux大神 Python大神 C++菜鸡

Пересечение представляет собой общие данные для двух наборов. Мы объединяем два набора с помощью SINTERSTORE и сохраняем результат в новом наборе.

Разность — ежедневное увеличение количества друзей

Например, подсчёт ежедневного увеличения количества регистраций в определённом приложении. Просто возьмите разницу между общим количеством регистраций за два дня. Пусть общее количество регистраций за 2021/06/01 хранится в user:20210601, а общее количество за 2021/06/02 — в user:20210602.

SDIFFSTORE  user:new  user:20210602 user:20210601

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

Объединение — общее увеличение количества друзей

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

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

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

Ограничение по номеру телефона: не более 5 SMS с одного номера телефона в течение 24 часов

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

Ограничения на SMS с проверочными кодами: одно и то же сообщение в течение 30 минут

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

Проверка на стороне клиента и сервера: отправка и проверка токена

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

Уникальность ограничения: ограничение количества запросов от одного пользователя WeChat ID

Если это продукт WeChat, можно использовать WeChat ID для идентификации. Затем одному пользователю WeChat ID разрешается отправлять ограниченное количество SMS в течение 24 часов.

Поэтапный процесс регистрации: разделение на шаги

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

Графическая проверка: запрос интерфейса после успешного прохождения проверки

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

IP и файлы cookie: ограничение максимального количества одинаковых IP/файлов cookie

Файлы cookie и IP позволяют легко идентифицировать одного и того же пользователя, а затем ограничивать его действия (например, не более 20 SMS в течение 24 часов). Однако файлы cookie можно очистить, а IP можно подделать, кроме того, существует вероятность совпадения локальных IP-адресов. Поэтому при использовании этого метода следует учитывать конкретные обстоятельства.

Система раннего предупреждения о проблемах с SMS: защита после возникновения проблем

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

Уязвимости безопасности

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

SQL-инъекция

Что такое SQL-инъекция?

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

Как работает SQL-инъекция?

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

На рисунке ниже показан этот сценарий.

Архитектура SQL-инъекции

Обычно передняя страница передаёт параметр name на серверную часть, которая затем использует SQL для запроса к таблице staff.

name = "田螺"; //переданный параметр
SQL= "select * from staff where name=" + name;  //запрос к базе данных на основе переданного параметра name

Поскольку SQL напрямую объединяет строки, если мы полностью доверяем параметрам, переданным передней страницей, возникает серьёзная проблема безопасности. Предположим, что передняя страница передала параметр '' or '1'='1', тогда SQL становится бессмысленным.

select * from staff where name='' or '1'='1';

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

Предотвращение SQL-инъекций

  • Используйте #{} вместо ${}

В MyBatis использование #{} вместо ${} может значительно снизить риск SQL-инъекций. Это связано с тем, что #{} является параметром-заполнителем, который автоматически добавляет кавычки к строковым значениям. Поскольку Mybatis использует предварительную компиляцию, параметры не подвергаются дальнейшей обработке SQL, что снижает риск SQL-инъекций.

  • #{} — это параметр-заполнитель, который заменяет значение переменной в SQL-запросе.
  • ${} — это простая замена строки, которая может привести к SQL-инъекциям.
  • Не раскрывайте ненужные журналы или конфиденциальную информацию, например, избегайте непосредственного ответа на сообщения об ошибках SQL.

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

  • Проверяйте входные параметры на наличие подозрительных символов, таких как union, or и т. д., которые могут использоваться для SQL-инъекций.

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

  • Реализуйте надлежащие механизмы контроля доступа.

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

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

JSON-сериализация

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

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

XSS (Cross-Site Scripting)

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

Предотвратить атаку XSS можно путём фильтрации пользовательского ввода, проверки ссылок и ограничения длины ввода.

CSRF (Cross-site Request Forgery)

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

Чтобы предотвратить атаку CSRF, можно проверять поле Referer в HTTP-запросах или добавлять токены в формы.

Уязвимости при загрузке файлов

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

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

Слив чувствительных данных

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

Основная тема текста — обеспечение безопасности пользовательских данных. В тексте рассматриваются различные виды уязвимостей программного обеспечения (XXE, DDoS, уязвимости фреймворков и приложений) и способы их устранения. Также обсуждаются вопросы проектирования системы для проведения «секундных» распродаж с учётом возможных проблем с высокой нагрузкой на систему.

XXE-уязвимость

В тексте описывается, что такое XXE (XML External Entity Injection), как она работает и какие последствия может иметь. Даются рекомендации по защите от этой уязвимости.

DDoS-атака

Текст объясняет, что такое DDoS (Distributed Denial of Service) атака, её цель и последствия. Описываются методы защиты от таких атак.

Уязвимости фреймворков и приложений

Приводятся примеры уязвимостей различных фреймворгов и приложений, таких как Struts, QQ Browser, Oracle GlassFish Server, WebLogic, Docker и WordPress.

Другие уязвимости

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

Секундные распродажи

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

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

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

Это типичный пример сценария «чтение-запись», где больше операций чтения, чем записи.

Проблемы с кешем

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

Процесс выглядит следующим образом:

  1. Запрос на «убийство» товара отправляется на сервер.
  2. Сервер получает запрос и проверяет, существует ли товар в кеше.
  3. Если товар существует в кеше, он участвует в «убийстве».
  4. Если товар не существует в кеше, сервер запрашивает базу данных.
  5. Если товар существует в базе данных, он помещается в кеш, а затем участвует в «убийстве».
  6. Если товар отсутствует в базе данных, сервер сообщает об ошибке.

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

Проблема с «проникновением» кеша

Например, когда товар A впервые «убивается», его нет в кеше, но он есть в базе данных. Хотя есть логика для помещения данных из базы данных в кеш после обнаружения товара в базе данных, в условиях высокой параллельной нагрузки может возникнуть проблема. Множество запросов одновременно пытаются найти товар в кеше и получить доступ к базе данных. База данных может не выдержать нагрузки и аварийно завершить работу. Как решить эту проблему? Лучше всего использовать распределённую блокировку.

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

Кажется, что блокировка здесь не нужна. Но если срок действия кеша установлен неправильно или кеш случайно удаляется, всё равно может возникнуть проблема с «проникновением», даже если блокировка используется. Фактически, блокировка похожа на страховку.

Проблема «проникновения» кеша

Если поступает большое количество запросов на идентификаторы товаров, которых нет ни в кеше, ни в базе данных, эти запросы будут «проникать» через кеш и напрямую обращаться к базе данных каждый раз. Поскольку ранее была добавлена блокировка, даже при большом количестве параллельных запросов база данных не выйдет из строя. Однако производительность обработки этих запросов не будет хорошей. Есть ли лучшее решение? Можно рассмотреть использование фильтра Блума.

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

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

Проблема с запасами

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

  1. Запас уменьшается в соответствии с запросом на «убийство».
  2. Если оплата не завершена в течение определённого времени, запас восстанавливается.

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

Уменьшение запасов в базе данных

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

update product set stock=stock-1 where id=123;

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

int stock = mapper.getStockById(123);
if(stock > 0) {
  int count = mapper.updateStock(123);
  if(count > 0) {
    addOrder(123);
  }
}

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

update product set stock=stock-1 where id=product and stock > 0;

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

Уменьшение запасов с помощью Redis

Метод incr в Redis является атомарным, и его можно использовать для уменьшения запасов. Псевдокод выглядит следующим образом:

 boolean exist = redisClient.query(productId,userId);
  if(exist) {
    return -1;
  }
  int stock = redisClient.queryStock(productId);
  if(stock <=0) {
    return 0;
  }
  redisClient.incrby(productId, -1);
  redisClient.add(productId,userId);
return 1;

Поток кода следующий:

  • Сначала проверяется, совершал ли текущий пользователь операцию «убийства» этого товара ранее. Если да, возвращается -1.
  • Затем проверяется наличие товара в кеше. Если запас меньше или равен нулю, возвращается 0, указывая на недостаток запасов.
  • Если запас достаточен, он уменьшается, и текущая операция «убийства» сохраняется. Затем возвращается 1, указывая на успех.

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

boolean exist = redisClient.query(productId,userId);
if(exist) {
    return -1;
}

synchronized(this) {
       int stock = redisClient.queryStock(productId);
       if(stock <=0) {
         return 0;
       }
       redisClient.incrby(productId, -1);
       redisClient.add(productId,userId);
   }

return 1;

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

boolean exist = redisClient.query(productId,userId);
if(exist) {
  return -1;
}
if(redisClient.incrby(productId, -1)<0) {
  return 0;
}
redisClient.add(productId,userId);
return 1;

Основной поток кода таков:

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

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

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

StringBuilder lua = new StringBuilder();
lua.append("if (redis.call('exists', KEYS[1]) == 1) then");
lua.append("    local stock = tonumber(redis.call('get', KEYS[1]));");
lua.append("    if (stock == -1) then");
lua.append("        return 1;");
lua.append("    end;");
lua.append("    if (stock > 0) then");
lua.append("        redis.call('incrby', KEYS[1], -1);");
lua.append("        return stock;");
lua.append("    end;");
lua.append("    return 0;");
lua.append("end;");
lua.append("return -1;");

Основной поток этого кода следующий:

  • Сначала проверяется, существует ли товар с идентификатором, и если он не существует, то сразу возвращается.
  • Затем извлекается количество товара с этим идентификатором и проверяется, равно ли оно -1. Если да, то сразу же возвращается, что означает отсутствие ограничения количества товара.
  • Если количество товара больше 0, то количество товара уменьшается на 1.
  • Если количество товара равно 0, то оно сразу же возвращается, указывая на то, что товара недостаточно.

Распределённая блокировка

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

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

Установка блокировки с помощью setNx

Используя распределённую блокировку Redis, можно подумать о команде setNx.

if (jedis.setnx(lockKey, val) == 1) {
   jedis.expire(lockKey, timeout);
}

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

Установка блокировки с использованием set

Команда set в Redis позволяет указать несколько параметров.

String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
if ("OK".equals(result)) {
    return true;
}
return false;

Здесь:

  • lockKey — идентификатор блокировки.
  • requestId — идентификатор запроса.
  • NX — операция установки ключа выполняется только в том случае, если ключ не существует.
  • PX — устанавливает срок действия ключа в миллисекундах.
  • expireTime — срок действия.

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

Снятие блокировки

Некоторые могут спросить: зачем нужен requestId при установке блокировки, если уже есть lockKey? Он используется при снятии блокировки.

if (jedis.get(lockKey).equals(requestId)) {
    jedis.del(lockKey);
    return true;
}
return false;

При снятии блокировки можно снять только свою блокировку, нельзя снять чужую блокировку. Зачем здесь используется requestId? Можно ли использовать userId?

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

Конечно, использование скрипта Lua также может предотвратить эту проблему:

if redis.call('get', KEYS[1]) == ARGV[1] then 
 return redis.call('del', KEYS[1]) 
else 
  return 0 
end

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

Самовращающаяся блокировка

Вышеупомянутый метод установки блокировки кажется хорошим, но если вы задумаетесь, только один запрос из 10 000 может быть успешным. Что произойдёт во время аукциона? Ответ: каждые 10 000 запросов один будет успешным, остальные 9 999 запросов завершатся неудачно.

Как решить эту проблему? Ответ: используйте самовращающуюся блокировку.

try {
  Long start = System.currentTimeMillis();
  while(true) {
      String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
     if ("OK".equals(result)) {
        return true;
     }
     
     long time = System.currentTimeMillis() - start;
      if (time>=timeout) {
          return false;
      }
      try {
          Thread.sleep(50);
      } catch (InterruptedException e) {
          e.printStackTrace();
      }
  }
 
} finally{
    unlock(lockKey,requestId);
}  
return false;

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

Redisson

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

MQ асинхронная обработка

Мы знаем, что в реальном сценарии аукциона, есть три основных процесса:

![Схема трёх основных процессов аукциона](images/Architecture/Схема трёх основных процессов аукциона.png)

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

![Процесс оформления заказа после аукциона](images/Architecture/Процесс оформления заказа после аукциона.png)

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

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

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

![Проблема потери сообщений — добавление таблицы отправки сообщений](images/Architecture/Проблема потери сообщений — добавление таблицы отправки сообщений.png)

Перед отправкой сообщения MQ в процессе производства, сообщение записывается в таблицу отправки сообщений, где оно находится в состоянии «ожидает обработки». Затем сообщение отправляется в MQ. После обработки бизнес-логики потребителем, вызывается интерфейс производителя для изменения состояния сообщения на «обработано». Если сообщение было записано в таблицу отправки сообщений после отправки в MQ и отправка в MQ не удалась, это приводит к потере сообщения. Как обработать эту ситуацию? Ответ: использовать job и добавить механизм повторных попыток.

![Проблема потери сообщений — использование job](images/Architecture/Проблема потери сообщений — использование job.png)

Job периодически проверяет таблицу отправки сообщений на наличие сообщений в состоянии «ожидает обработки» и повторно отправляет их в MQ.

Повторная обработка сообщений

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

![Повторная обработка сообщений — добавление таблицы обработки сообщений](images/Architecture/Повторная обработка сообщений — добавление таблицы обработки сообщений.png)

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

Нежелательные сообщения

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

Возможно, речь идёт о проблеме, связанной с повторной обработкой сообщений. Чтобы избежать нежелательных последствий, связанных с повторной обработкой, рекомендуется использовать механизм подтверждения получения сообщения (ACK). Это позволит отправителю сообщения убедиться, что сообщение было получено и обработано получателем. 这套方案表面上看起来没有问题,但如果出现了消息消费失败的情况.

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

Как решить эту проблему?

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

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

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

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

Как ограничить поток запросов?

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

Существует два распространённых способа ограничения потока запросов:

  • Ограничение на основе Nginx.
  • Ограничение на основе Redis.

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

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

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

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

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

В начале работы сервиса «12306» все пытались одновременно купить билеты, что приводило к сбоям в системе из-за высокой нагрузки. Позже сервис был переработан, и срок бронирования билетов был увеличен до 20 дней. Билеты можно было бронировать в определённые моменты времени, такие как 9:00, 10:00 и так далее. Эти изменения распределили нагрузку и снизили количество одновременных запросов.

Вместо технических мер можно использовать бизнес-меры, такие как ограничение доступа к акциям только для членов клуба или предоставление доступа только пользователям с определённым уровнем. Даже опытные пользователи вряд ли будут платить за участие в акции. ``` & 0xFF).append("."); ip.append(ipLong & 0xFF); return ip.toString(); }

public static void main(String[] args) { System.out.println(ip2Long("192.168.0.1")); System.out.println(long2Ip(3232235521L)); System.out.println(ip2Long("10.0.0.1")); }


**Вывод:**

3232235521
192.168.0.1
167772161

## Короткая ссылка

Общепринятое решение:

* **Генератор распределённых идентификаторов создаёт идентификатор.**
* **Идентификатор преобразуется в 62-битную строку.**
* **Запись в базу данных, определение срока действия в соответствии с требованиями бизнеса. Часть постоянных ссылок может быть сохранена.**

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

## Система красных пакетов

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

Технические трудности:

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

Невозможные методы:

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

Предлагаемые методы:

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

## Распределённая система планирования задач

Задачи опрашиваются или задачи опрашиваются + стратегия захвата:

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

## Push-уведомления в Weibo

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

Базовая схема:

* Схема push: схема push означает, что пользователь A следит за пользователем B, каждый раз, когда пользователь B отправляет сообщение, фоновый процесс просматривает список подписчиков пользователя B и отправляет уведомление каждому подписчику.
* Схема pull: схема pull противоположна схеме push. Каждый раз, когда пользователь обновляет свою ленту новостей, он просматривает всех людей, на которых он подписан, чтобы получить последние сообщения.

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

# Сценарии применения Redis

## Кэширование данных

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

## Распределённые блокировки

Тип String setnx метод, который может добавить успех только тогда, когда он не существует, и возвращает true.

```java
public static boolean getLock(String key, long expireTime) {
    Long flag = jedis.setnx(key, "1");
    if (flag == 1) {
        jedis.expire(key, expireTime);
    }
    
    return flag == 1;
    // NX Не существует, затем операция, EX устанавливает срок действия в секундах
    // return "OK".equals(jedis.set(key, requestId, "NX", "EX", expireTime));
}

public static void releaseLock(String key) {
    jedis.del(key);
}

Глобальные идентификаторы

Используя атомарность int типа incrby:

  • Распределение по таблицам ID: получение диапазона номеров за один раз.
  • Идентификатор заказа: получение диапазона номеров за один раз.

Счётчики

Тип int incr метод:

Например, количество прочтений статьи, количество лайков в Weibo, после записи в Redis задержка определённого времени перед синхронизацией с базой данных.

Ограничение скорости

Метод int incr:

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

Статистика битов

Строковый тип bitcount (1.6.6 представляет структуру данных bitmap). Символы хранятся в виде 8-битных двоичных данных.

В онлайн-статистике пользователей оставьте статистику пользователей:

setbit onlineusers 01 
setbit onlineusers 11 
setbit onlineusers 20

Поддерживает побитовые операции И, ИЛИ и т. д.:

  • BITOPANDdestkeykey[key...]: выполняет логическое И для одного или нескольких ключей и сохраняет результат в destkey.
  • BITOPORdestkeykey[key...]: Выполняет логическое ИЛИ для одного или нескольких ключей и сохраняет результат в destkey.
  • BITOPXORdestkeykey[key...]: Выполняет логическую операцию исключающего ИЛИ для одного или нескольких ключей и сохраняет результат в destkey.
  • BITOPNOTdestkeykey: Выполняет логическую инверсию для данного ключа и сохраняет результат в destkey.

Вычислите количество пользователей, которые были онлайн в течение 7 дней:

BITOP "AND" "7_days_both_online_users" "day_1_online_users" "day_2_online_users" ...  "day_7_online_users"

Корзина покупок

String или hash. Всё, что можно сделать с помощью строки, можно сделать и с помощью хэша.

Ключ: идентификатор пользователя. Поле: идентификатор товара. Значение: количество товара. +1: hincr. -1: hdecr. Удалить: hdel. Выбрать всё: hgetall. Количество товаров: hlen.

Временная шкала TimeLine

Сообщение пользователя TimeLine. Двусвязный список list, используемый непосредственно в качестве временной шкалы. Вставка упорядочена. Обычно существует три реализации TimeLine: push, pull и push-pull.

  • Push: после публикации контента отправьте его всем подписчикам, замените пространство на время, узким местом является запись.
  • Pull: подписчики читают контент из своего списка подписок, заменяют время пространством, узкое место — чтение.
  • Push-pull: после публикации контента отправляйте его активным подписчикам, неактивные подписчики используют pull.

Здесь рассматривается только реализация push, предполагается, что содержимое сообщения сохраняется в хэш-таблице (hashes), а временная шкала каждого пользователя сохраняется в списке (lists).

Очередь сообщений

List предоставляет две блокирующие операции всплывающего окна: blpop/brpop, которые могут устанавливать тайм-ауты:

  • blpop key1 timeout: удаляет и получает первый элемент списка, если список пуст, блокируется до истечения времени ожидания или обнаружения всплывающего элемента.
  • brpop key1 timeout удаляет и получает последний элемент списка, если список пуст, блокируется до истечения времени ожидания или обнаружения всплывающего элемента.

Лотерея

Встроенная функция случайного получения значения: spop myset.

Лайки, проверка, отметка

Если вышеупомянутый идентификатор Weibo равен t1001, а идентификатор пользователя — u3001, используйте like:t1001 для отслеживания всех пользователей, поставивших лайк на эту статью Weibo:

  • Поставьте лайк на этот Weibo: sadd like:t1001 u3001.
  • Отмените лайк: srem like:t1001 u3001.
  • Есть ли лайки: sismember like:t1001 u3001.
  • Все пользователи, поставившие лайк: smembers like:t1001.
  • Количество лайков: scard like:t1001.

Теги товаров

Используйте tags:i5001 для управления всеми тегами товаров.

  • Добавьте теги: sadd tags:i5001 Экран чёткий и тонкий.
  • Добавьте теги: sadd tags:i5001 Реальное цветное и чёткое изображение.
  • Добавьте теги: sadd tags:i5001 Процесс доведён до предела.

Фильтрация товаров

  • Получить разницу множеств: sdiff set1 set2.
  • Получите пересечение (intersection): sinter set1 set2. Получение объединения: sunion set1 set2

Если iPhone 11 поступил в продажу:

  • sadd brand:apple iPhone11
  • sadd brand:ios iPhone11
  • sad screensize:6.0-6.24 iPhone11
  • sad screentype:lcd iPhone 11

Фильтрация товаров «яблочные, с операционной системой iOS, экран от 6,0 до 6,24, экран LCD»:

  • sinter brand:apple brand:ios screensize:6.0-6.24 screentype:lcd

Модель пользователей для рекомендаций и подписок

Follow — подписаться, fans — фанаты.

Взаимная подписка:

sadd 1:follow 2sadd 2:fans 1sadd 1:fans 2sadd 2:follow 1

Я подписан на тех, кто подписан на меня (пересечение):

sinter 1:follow 2:fans

Люди, которых я могу знать (разность):

— пользователи, которых может знать пользователь 1 (sdiff 2:follow 1:follow) — пользователи, которых может знать пользователь 2 (sdiff 1:follow 2:follow)

Рейтинг

Увеличить на 1 количество кликов по новостям с ID 6001: zincrby hotNews:20190926 1 n6001.

Получить 15 новостей с наибольшим количеством кликов за сегодня: zrevrange hotNews:20190926 0 15 withscores.

Распределённые системы

Очередь сообщений — проблема идемпотентности

Платёж через WeChat

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

Вставка данных в базу

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

Запись в Redis

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

Другие решения

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

Очередь сообщений — потеря сообщений

Потеря сообщения при сохранении в очереди

Решение:

  1. Транзакции (не рекомендуется, асинхронный подход).

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

  1. Подтверждение (рекомендуется, асинхронный подход).

Можно использовать другой подход — подтверждение (confirm). Каждому отправленному сообщению присваивается уникальный идентификатор. Если сообщение было успешно доставлено в RabbitMQ, RabbitMQ отправит подтверждение (ack), что означает успешное получение сообщения. Если RabbitMQ не смог обработать сообщение, будет отправлен отрицательный ответ (nack), что означает необходимость повторной отправки сообщения.

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

Разница между транзакциями и подтверждением:

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

Потеря сообщений в очереди

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

Решения:

— Создавать очереди с настройкой на сохранение при создании. — Устанавливать режим доставки сообщений равным 2 при отправке сообщений. — Использовать подтверждение для повторной отправки сообщений.

Потребитель теряет сообщения

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

Решение:

— Отключить автоматическое подтверждение (ack) от RabbitMQ при создании очереди. После отправки сообщения RabbitMQ автоматически отправляет подтверждение производителю. — После обработки сообщения потребитель должен активно подтвердить (ack) RabbitMQ, чтобы сообщить, что сообщение обработано.

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

Вопрос: что делать, если сообщение постоянно обрабатывается повторно?

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

Очередь сообщений — неупорядоченная доставка

Очередь сообщений — накопление сообщений

Очередь сообщений — истечение срока действия сообщений

Очередь сообщений — переполнение очереди

Redis — потеря данных

Разделение базы данных и таблицы — масштабирование

Разделение базы данных и таблицы — уникальные идентификаторы

Распределённая транзакция

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/yu120-lemon-guide.git
git@api.gitlife.ru:oschina-mirror/yu120-lemon-guide.git
oschina-mirror
yu120-lemon-guide
yu120-lemon-guide
main