Архитектура
Введение: здесь собраны обобщения, связанные с архитектурой, которые касаются технологий.
Задумывались ли вы о том, как разработать систему для предприятия крупного масштаба? Прежде чем приступить к основной разработке, мы должны выбрать подходящую структуру, которая обеспечит необходимые функции и свойства качества. Поэтому, прежде чем применять их в нашем дизайне, мы должны понять различные структуры.
Шаблон архитектуры — это универсальное и многократно используемое решение проблем, часто встречающихся в программных структурах в заданном контексте. Шаблоны архитектуры похожи на шаблоны проектирования программного обеспечения, но имеют более широкую область применения.
Этот шаблон также называют многоуровневой архитектурной структурой. Его можно использовать для создания программ, которые можно разделить на подзадачи, каждая из которых находится на определённом абстрактном уровне. Каждый уровень предоставляет услуги более высокого уровня следующему уровню.
В общих информационных системах наиболее распространёнными являются следующие четыре уровня:
Использование:
Эта архитектура состоит из двух частей: сервера и нескольких клиентов. Серверные компоненты предоставляют услуги множеству клиентских компонентов. Клиенты запрашивают услуги у сервера, и сервер предоставляет эти услуги клиентам. Кроме того, сервер постоянно слушает запросы клиентов.
Использование:
Эта архитектура состоит из двух сторон: главной и подчинённой. Главный компонент распределяет работу среди подчинённых компонентов и вычисляет окончательный результат, который предоставляется подчинёнными компонентами.
Использование:
Этот шаблон можно использовать при создании систем, генерирующих и обрабатывающих потоки данных. Каждая стадия обработки заключена в фильтрующий компонент. Данные передаются через каналы. Эти каналы могут использоваться для буферизации или синхронизации.
Использование:
Этот шаблон используется для создания распределённых систем с развязанными компонентами. Эти компоненты могут взаимодействовать друг с другом через удалённые вызовы служб. Компонент-посредник координирует связь между ними.
Сервер публикует свои функции (услуги и характеристики) посреднику. Клиент запрашивает услуги у посредника, который затем перенаправляет клиента на соответствующий сервис в своём центре регистрации.
Использование:
В этом шаблоне отдельный компонент называется равноправным узлом. Равноправный узел может выступать в роли клиента, запрашивающего услуги от других равноправных узлов, или в роли сервера, предоставляющего услуги другим равноправным узлам. Равноправные узлы могут играть роль клиента или сервера или обе роли одновременно и могут динамически изменять свою роль со временем.
Использование:
Основная цель этого шаблона — обработка событий, включая четыре основных компонента: источник события, прослушиватель события, канал и шина событий. Источник события публикует сообщения на определённый канал шины событий. Прослушиватели подписываются на определённые каналы. Прослушивателям сообщается о сообщениях, опубликованных на каналах, на которые они ранее подписались.
Использование:
Этот шаблон, также известный как MVC, разделяет интерактивное приложение на три части:
Это сделано для разделения представления информации и способа её представления, а также для принятия пользовательского ввода. Он разделяет компоненты и позволяет эффективно повторно использовать код.
Использование:
Этот шаблон полезен для проблем без определённой стратегии решения. Архитектура чёрной доски состоит из трёх основных частей.
Все компоненты имеют доступ к чёрной доске. Компоненты могут генерировать новые данные объекта, добавляемые на чёрную доску. Компоненты ищут конкретные типы данных на чёрной доске и сопоставляют их с существующими источниками знаний для поиска этих данных.
Использование:
Этот шаблон предназначен для разработки компонентов, интерпретирующих программы, написанные на специальном языке. В основном он определяет, как оценивать строки программы, то есть предложения или выражения, записанные на конкретном языке. Основная идея заключается в том, что для каждого символа языка существует классификация.
Использование:
На заре Интернета общий объём трафика веб-сайтов был небольшим, требовалось только одно приложение, объединяющее все функциональные возможности кода, что позволяло снизить затраты на разработку, развёртывание и обслуживание. Например, система электронной коммерции будет включать в себя множество модулей управления пользователями, управления товарами, управления заказами и управления логистикой, и мы создадим веб-проект, включающий все эти модули, а затем развернём его на одном сервере Tomcat.
Преимущества:
Недостатки:
По мере увеличения объёма трафика одно приложение может полагаться только на увеличение количества узлов для удовлетворения спроса, но в этот момент обнаруживается, что не все модули будут испытывать значительное увеличение трафика. Продолжая пример с системой электронной коммерции, увеличение трафика пользователей может повлиять только на модули управления пользователями и заказами, в то время как влияние на модуль сообщений будет относительно небольшим. В этом случае монолитный подход не сработает, и вертикальная архитектура станет необходимой. Так называемая вертикальная архитектура приложений означает разделение исходного единого приложения на несколько независимых приложений для повышения эффективности. Например, мы можем разделить вышеупомянутую систему электронной коммерции на:
После такого разделения, если трафик пользователей увеличится, нам нужно будет добавить только узлы системы электронной коммерции вместо узлов бэкенда и CMS.
Преимущества:
Недостатки:
Когда количество вертикальных приложений становится всё больше и больше, повторяющийся бизнес-код также будет увеличиваться. В этот момент мы можем рассмотреть возможность извлечения общего кода и создания независимого сервисного слоя в качестве универсального сервиса, который можно вызывать из переднего контроллера различных сервисов.
Так возникает новый тип распределённой системы. Она разделяет проект на два аспекта: представление и сервисный слой, сервисный слой содержит бизнес-логику. Уровень представления отвечает только за обработку взаимодействия с интерфейсом и вызывает различные сервисы сервисного уровня. 缺点
В распределённой архитектуре, когда сервисов становится всё больше, проблемы с оценкой ёмкости и расточительным использованием ресурсов небольших сервисов постепенно становятся очевидными, в этот момент требуется добавить диспетчерский центр для управления кластером в реальном времени. В этом случае ключевым моментом является использование центра управления ресурсами и регулирования, ориентированного на сервисы (сервис-ориентированной архитектуры, SOA).
Преимущества:
Недостатки:
https://juejin.cn/user/4177799914474653/posts
https://www.cnblogs.com/jiujuan/p/13280473.html
Микросервисная архитектура — это дальнейшее развитие сервис-ориентированной архитектуры (SOA), она подчёркивает необходимость «полного разделения» сервисов.
Преимущества:
Недостатки:
Группа небольших сервисов.
Ранее единый блок сервисов, который объединял все бизнес-возможности в одном блоке, микросервисы выступают за разделение этих блоков на отдельные независимые сервисы. Здесь ключевой особенностью является «малый» масштаб, но не существует чёткого определения того, насколько малым должен быть сервис. Это приводит к тому, что многие разработчики задаются вопросом о том, какой размер считать малым.
Независимые процессы.
Микросервисы работают в независимых процессах, таких как Java-программы, развёрнутые в Tomcat, или контейнеры Docker.
Лёгкое взаимодействие.
В микросервисной архитектуре используется облегчённое взаимодействие, например, HTTP, фиксированные форматы сообщений и уменьшение сложности форматов сообщений. Сервисы не связаны друг с другом, что делает взаимодействие максимально лёгким.
На основе бизнес-возможностей.
Микросервисы строятся на основе бизнес-возможностей, таких как пользовательские сервисы, сервисы аутентификации и сервисы товаров.
Раздельное развёртывание.
После разделения на микросервисы каждый отдел самостоятельно поддерживает свои микросервисы, разрабатывает и итеративно улучшает свои микросервисы. Они могут независимо развёртываться, и отделам не нужно специально согласовывать действия, что позволяет ускорить разработку, сделать её более гибкой, лёгкой и быстрой.
Отсутствие централизованного управления.
Раньше для одного монолитного приложения требовалась отдельная команда архитекторов, которая отвечала за общую архитектуру, общий технологический стек и общее хранилище. Микросервисы предлагают другой подход: каждая команда может выбирать наиболее подходящий технологический стек для своих нужд, даже если это означает использование разных хранилищ данных.
Закон Конвея гласит, что структура организации определяет структуру разрабатываемых и проектируемых систем, а также архитектуру организации.
Несколько команд совместно разрабатывают и поддерживают одно монолитное приложение. Если одна команда вносит изменения в приложение, добавляя новые функции или технологии, часто требуется сотрудничество и координация с другими командами для интеграции и выпуска приложения. Помимо высоких затрат на коммуникацию и координацию между командами, часто возникают конфликты между командами.
Решение этой проблемы — микросервисы:
Мы разделяем единое приложение на несколько независимых сервисов, каждый из которых находится под ответственностью отдельной команды. Взаимодействие между этими сервисами минимально, и когда команда A вносит изменения в свой сервис, другим командам не требуется вмешиваться, либо такое взаимодействие требует минимальных усилий и обычно происходит только на границах взаимодействия между двумя сервисами. Таким образом, мы видим соответствие закону Конвея между микросервисами и несколькими командами разработчиков.
Преимущества:
Сильная модульность границ.
Мы знаем, что при разработке программного обеспечения модульность очень важна. Сначала мы пишем программы и используем классы для модульности. Затем мы переходим к компонентам или библиотекам классов для достижения модульности. С помощью микросервисов мы достигаем ещё более высокого уровня модульности, где каждый сервис независим и управляется отдельной командой. Разработав один сервис, другие команды могут напрямую вызывать его без необходимости делиться через jar-файлы или исходный код. Границы микросервисов чётко определены.
Возможность независимого развёртывания.
Независимое развёртывание — ключевая особенность микросервисов. Каждая команда может разрабатывать и развёртывать сервисы в соответствии со своими бизнес-потребностями, без сильной зависимости от других команд. Это контрастирует с монолитными приложениями, где часто требуется участие нескольких команд для поддержки изменений.
Разнообразие технологий.
Микросервисы предоставляют возможность выбора различных технологических стеков в зависимости от потребностей каждой команды. Некоторые команды могут специализироваться на Java-разработке, в то время как другие могут предпочесть использовать Node.js для разработки сервисов. Однако разнообразие не всегда является преимуществом.
Недостатки:
Сложность распределённых систем.
Монолитные приложения имеют одну архитектуру, которую легко понять. Но с микросервисами, которые включают десятки или даже сотни сервисов в крупных компаниях, понимание всей системы становится сложным. Разработчикам трудно понять, как работает вся система.
Конечная согласованность.
Данные в микросервисах распределены, и каждая команда имеет свою собственную копию данных. Например, команда A имеет данные о заказах, а команда B также имеет данные о заказах. Возникает вопрос: следует ли синхронизировать изменения данных команды A с данными команды B? Если нет эффективного решения проблемы согласованности данных, это может привести к несогласованности данных.
Сложное управление инфраструктурой.
Управление ресурсами, планирование мощностей, мониторинг и обеспечение надёжности и стабильности распределённой системы представляют собой сложные задачи.
Трудности тестирования.
Для тестирования монолитной системы достаточно протестировать один блок. В распределённой системе каждый сервис тестируется отдельно, и требуется координация между командами для проведения интеграционного тестирования. Также это сервер, который имеет соответствующий клиент. Его особенность заключается в клиенте, у которого есть механизм кэширования. После успешного извлечения данные сохраняются в этом механизме. Даже если клиент теряет свой кэш, он может синхронизировать его с локальным файлом кэша. Такая конструкция очень умна. Даже если центр конфигурации Apollo выйдет из строя или клиентский сервис будет перезапущен, но поскольку локальный кэш всё ещё существует, можно продолжать предоставлять услуги извне, используя этот кэш. Это говорит о том, что центр конфигурации Apollo хорошо продуман с точки зрения высокой доступности.
Кроме того, в центре конфигурации есть два способа получения конфигурационных данных: pull и push. Apollo объединяет преимущества обоих методов. Когда разработчики или администраторы вносят изменения в центр конфигурации, служба центра конфигурации немедленно отправляет эти изменения клиенту Apollo через функцию push. Однако, учитывая возможность некоторых сетевых колебаний, клиент также имеет функцию периодического извлечения данных через pull. Таким образом, даже если push не был успешным, клиент всё равно будет периодически извлекать данные для синхронизации конфигурации с сервисом. Это ещё одна особенность Apollo с точки зрения высокой доступности.
Микросервисная архитектура
Микросервисы — это подход к разработке программного обеспечения, при котором приложение разбивается на небольшие независимые сервисы, каждый из которых выполняет свою конкретную задачу. Эти сервисы взаимодействуют друг с другом через хорошо определённые интерфейсы. Микросервисная архитектура обеспечивает гибкость, масштабируемость и устойчивость приложений.
Многоуровневый мониторинг микросервисов
Мониторинг микросервисов включает несколько уровней:
В микросервисных системах агенты мониторинга распределены по каждому сервису. Агенты собирают информацию о машинах и сервисах и отправляют её в фоновый мониторинг. Обычно используется очередь, такая как Kafka, для буферизации данных и обеспечения высокой доступности сообщений.
Для сбора журналов популярным решением является ELK (Elasticsearch, Logstash, Kibana). Elasticsearch — распределённая поисковая система, Logstash — агент сбора журналов, а Kibana — интерфейс запросов к журналам.
Метрики часто используют временные базы данных, такие как InfluxDB.
Агенты микросервисов, такие как Springboot, предоставляют функции проверки работоспособности для мониторинга использования процессора, памяти, JVM и других параметров. Nagios и Zabbix — популярные платформы для мониторинга работоспособности, которые регулярно проверяют состояние микросервисов и могут отправлять уведомления соответствующим специалистам. Вызов поведения восстановления
В настоящее время на рынке существует несколько основных вариантов выбора цепочки вызовов: zipkin, pinpoint, cat, skywalking. Среди них стоит отметить skywalking — новый инструмент цепочки вызовов, созданный китайскими разработчиками. Он использует открытый исходный код и основан на анализе цепочки вызовов с помощью внедрения байт-кода. Подключение к системе не требует изменения кода, а также поддерживается множеством плагинов. Пользовательский интерфейс в нескольких инструментах является более функциональным и привлекательным. В настоящее время skywalking включён в инкубационный проект Apache.
Преимущества 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:
Общий рабочий процесс выглядит следующим образом:
При переходе крупной монолитной системы на микросервисы многие архитекторы по-прежнему предпочитают сохранять базу данных без изменений. Хотя это может дать некоторые краткосрочные преимущества, такой подход может привести к серьёзным проблемам в долгосрочной перспективе, особенно в больших системах. Микросервисы будут сильно связаны на уровне базы данных, и вся цель перехода на микросервисную архитектуру может оказаться под угрозой.
Более эффективным подходом является предоставление собственной базы данных каждому микросервису. Таким образом, сервисы не будут тесно связаны друг с другом на уровне базы данных. Здесь термин «база данных» используется для обозначения логического разделения данных, что означает, что микросервисы могут совместно использовать одну физическую базу данных, но должны использовать отдельные структуры данных, коллекции или таблицы. Это также поможет гарантировать, что микросервисы правильно разделены на основе принципов предметно-ориентированного проектирования.
Преимущества:
В микросервисной архитектуре, особенно при использовании отдельной базы данных для каждого сервиса, микросервисы должны обмениваться данными. Для эластичных, высокодоступных и отказоустойчивых систем они должны взаимодействовать через асинхронную передачу событий. В этом случае может потребоваться выполнить операцию, аналогичную обновлению базы данных и отправке сообщения, но использование двухфазной блокировки (2PL) в распределённых сценариях с большими объёмами данных может быть невозможным, поскольку оно не масштабируется. А большинство NoSQL баз данных даже не поддерживают двухфазную блокировку или не могут реализовать распределённые транзакции. В этих сценариях можно использовать событийный паттерн на основе событий. В традиционных базах данных непосредственно хранятся текущие «состояния» бизнес-сущностей, а в источнике событий сохраняются все обновления состояний или другие важные события, а не сами сущности. Это означает, что все изменения бизнес-сущности будут сохранены в виде серии неизменяемых событий. Поскольку данные хранятся в виде последовательности событий, а не прямых обновлений, различные сервисы могут вычислять требуемое состояние данных, воспроизводя события из хранилища.
Преимущества:
Недостатки:
Когда следует использовать источник событий:
Когда не следует использовать источник событий:
Если мы используем источник событий, то чтение данных из хранилища становится сложным. Чтобы получить сущности из хранилища данных, нам нужно обработать все события сущностей. Иногда у нас также могут быть разные требования к согласованности и пропускной способности для операций чтения и записи.
В этом случае мы можем использовать CQRS. В этой модели данные разделяются на две части: одна для модификации (команды), а другая — для чтения (запросы). У CQRS есть два варианта, которые иногда путают: простой и продвинутый.
В своей простой форме используются разные сущности или ORM-модели для операций чтения и записи:
Это помогает усилить принцип единой ответственности и разделить области внимания, обеспечивая более лаконичный дизайн.
Продвинутая форма включает использование разных хранилищ данных для операций чтения и записи. Продвинутый CQRS обычно сочетается с паттерном источника событий. Для различных сценариев используются разные типы хранилищ для записи и чтения данных. Хранилище для записи является «системой записей», то есть основным источником всей системы.
Для приложений с частым чтением или микросервисных архитектур OLTP-базы данных (любая реляционная или нереляционная база данных, предоставляющая гарантии ACID) или распределённые системы обмена сообщениями могут использоваться в качестве хранилища для записи. Для приложений с частой записью и высокой масштабируемостью требуется использовать горизонтально масштабируемые базы данных для записи (например, глобально управляемые публичные облачные базы данных). Стандартизированные данные сохраняются в хранилище для записи.
Оптимизированные для поиска (например, Apache Solr, Elasticsearch) или операции чтения (KV-хранилища, документно-ориентированные базы данных) нереляционные базы данных часто используются в качестве хранилищ для чтения. Многие случаи используют читаемые масштабируемые реляционные базы данных при необходимости SQL-запросов. Нестандартизованные и специально оптимизированные данные сохраняются в хранилищах для чтения.
Данные асинхронно копируются из хранилища для записи в хранилище для чтения, поэтому между ними существует задержка, но они в конечном итоге согласованы.
Преимущества:
Недостатки:
Когда использовать CQRS:
Когда не использовать CQRS:
Использование отдельных баз данных для каждого микросервиса делает управление распределёнными транзакциями сложной задачей. Вы не можете использовать традиционный двухфазный протокол фиксации, потому что он либо немасштабируем (реляционные базы данных), либо не поддерживается (большинство нереляционных баз данных).
Однако вы всё ещё можете использовать паттерн Saga для управления распределёнными транзакциями в микросервисной архитектуре. Saga — это старый паттерн, разработанный в 1987 году для реляционных баз данных и представляющий собой альтернативу большим транзакциям. Однако этот современный вариант очень эффективен для распределённых транзакций. Паттерн Saga представляет собой локальную последовательность транзакций, где каждый шаг обновляет данные в отдельном микросервисе и публикует событие или сообщение. Первый шаг в Saga инициируется внешним запросом (событием или действием), и после завершения локальной транзакции (данные сохранены в хранилище данных, и сообщение или событие опубликовано) публикация сообщения или события запускает следующий шаг в Saga.
Если локальная транзакция завершается неудачно, Saga выполняет серию компенсирующих транзакций для отката изменений предыдущей локальной транзакции.
Существует два основных способа координации управления транзакциями Saga:
Преимущества:
Недостатки:
Когда использовать Saga:
Когда не использовать Saga:
В современных бизнес-приложениях, особенно в микросервисных архитектурах, клиентские приложения и серверные службы разделены и независимы, соединяясь через API или GraphQL. Если приложение также имеет мобильные клиенты, такие как мобильные приложения, совместное использование одних и тех же серверных микросервисов для веб- и мобильных клиентов может вызвать проблемы. Мобильные и веб-клиенты имеют разные экраны, дисплеи, производительность, энергопотребление и пропускную способность сети, и их потребности в API различаются.
Паттерн BFF подходит для случаев, когда требуется индивидуальная оптимизация серверной части для конкретных пользовательских интерфейсов. Он также предлагает другие преимущества, такие как инкапсуляция для последующих микросервисов, уменьшая частоту связи между пользовательским интерфейсом и последующими микросервисами. Кроме того, в сценариях с высокими требованиями к безопасности BFF обеспечивает дополнительную безопасность для развёртывания последующих микросервисов в демилитаризованной зоне (DMZ).
Преимущества:
Недостатки:
Когда использовать BFF:
Когда не использовать BFF:
Примеры подходящих технологий:
В микросервисной архитектуре клиентские приложения обычно подключаются к множеству микросервисов. Если микросервисы являются мелкозернистыми (FaaS), клиент может подключаться к большому количеству микросервисов, что становится громоздким и сложным. Кроме того, эти сервисы и их API постоянно развиваются. Крупные предприятия также хотят иметь другие сквозные функции (SSL-терминация, аутентификация, авторизация, регулирование, логирование и т.д.).
Одним из возможных решений этих проблем является использование API Gateway. API Gateway действует как фасад между клиентскими приложениями и серверными микросервисами, выступая в роли обратного прокси, направляя запросы клиентов к соответствующим микросервисам. Он также поддерживает объединение клиентских запросов и возврат агрегированных ответов клиентам. Он также предоставляет необходимые сквозные функции.
Преимущества:
Недостатки:
Когда использовать API Gateway:
Когда не использовать API Gateway:
一旦迁移了所有的功能,遗留单体应用程序就会被“扼杀(Strangler)”,即退役。
Преимущества:
Недостатки:
В Strangler подходе, большие бэкенд монолитные приложения постепенно переносятся на микросервисы. Если бэкенд приложение небольшое, то полная замена может быть более предпочтительной. Также важно учитывать возможность перехвата запросов к устаревшему монолитному приложению.
В архитектуре микросервисов, микросервисы взаимодействуют друг с другом через синхронные вызовы. Однако, если возникают временные сбои (например, проблемы с сетевым подключением, медленные ответы или временная недоступность), повторные попытки могут помочь решить проблему. Но в случае серьёзных проблем (когда микросервис полностью выходит из строя), повторные попытки не имеют смысла и приводят к потере ценных ресурсов (блокировка потоков и трата процессорного времени). Кроме того, сбой одного сервиса может вызвать каскадный сбой всей системы. В таких случаях, быстрое завершение работы является более эффективным решением.
Для предотвращения каскадных сбоев используется «режим обрыва» (или «отсекатель»). Микросервис действует как прокси, направляя запросы к другому микросервису. Он работает аналогично электрическому прерывателю цепи, анализируя количество недавних сбоев и принимая решение о продолжении запроса или немедленном возврате исключения.
Режим обрыва имеет три состояния:
Преимущества:
Недостатки:
Этот подход рекомендуется использовать в микросервисных архитектурах с тесной связью между сервисами и при зависимости микросервиса от нескольких других сервисов.
Не рекомендуется применять в слабосвязанных микросервисных архитектурах и в случаях, когда микросервисы не зависят от других сервисов.
Каждое бизнес-приложение имеет множество параметров конфигурации для различных инфраструктур (базы данных, сети, адреса служб, учётные данные, пути сертификатов и т. д.). В корпоративных приложениях, развёрнутых в разных средах (локальная, Dev, Prod), часто используется внутренняя конфигурация. Это может привести к серьёзным проблемам безопасности, так как производственные учётные данные могут быть легко скомпрометированы. Кроме того, любое изменение параметров конфигурации требует перестройки приложения, что становится ещё более сложным в микросервисной архитектуре, где может быть сотни сервисов.
Более безопасным подходом является внешняя конфигурация, которая отделяет процесс сборки от среды выполнения и использует файлы конфигурации только во время выполнения или через переменные среды.
Преимущества:
Недостатки:
Рекомендуется использовать внешнюю конфигурацию для всех важных производственных приложений. Не рекомендуется применять её в процессе разработки концепции.
В микросервисной архитектуре обычно существует множество микросервисов, разработанных разными командами. Эти микросервисы совместно работают для удовлетворения бизнес-требований (например, клиентских запросов) и взаимодействуют синхронно или асинхронно. Интеграционное тестирование микросервисов-потребителей может быть сложной задачей. Обычно используются TestDouble для ускорения и снижения стоимости тестирования. Однако TestDouble не всегда могут точно представлять реальных поставщиков услуг, и если поставщик услуг изменяет свой API или сообщения, TestDouble может не обнаружить эти изменения. Альтернативой является сквозное тестирование, которое обязательно перед выпуском в производство, но оно может быть хрупким, медленным, дорогостоящим и не заменяет интеграционное тестирование (Test Pyramid).
Контрактное тестирование на стороне потребителя может помочь в этом. Здесь команда, ответственная за потребительский микросервис, разрабатывает набор тестов, включающий ожидаемые запросы и ответы (синхронные) или сообщения (асинхронные), для конкретного сервисного микросервиса. Эти наборы тестов называются явными соглашениями. Для сервисных микросервисов все тесты соглашений потребителей добавляются в их автоматизированные тесты. Когда выполняются автоматические тесты определённого сервисного микросервиса, они также запускают свои собственные тесты и тесты соглашений, обеспечивая проверку взаимодействия. Таким образом, контрактное тестирование может автоматически поддерживать целостность коммуникации микросервисов.
Преимущества:
Недостатки:
Рекомендуется применять контрактное тестирование в крупных корпоративных приложениях, где разные команды разрабатывают различные сервисы. Не рекомендуется использовать его в небольших приложениях, разрабатываемых одной командой, или если сервисные микросервисы находятся в активном развитии и нестабильны.
Мониторинг играет ключевую роль в обнаружении потенциальных проблем в работе системы. Он позволяет отслеживать ключевые показатели, такие как загрузка процессора, использование памяти, сетевые операции и другие метрики, которые могут указывать на возможные сбои.
При обнаружении признаков сбоя, мониторинг предоставляет информацию, необходимую для принятия мер по устранению проблемы. Это помогает предотвратить более серьёзные последствия и обеспечивает непрерывность работы системы.
Трассировка цепочки вызовов позволяет определить последовательность событий, приведших к возникновению проблемы. Она включает в себя идентификацию конкретных вызовов, выполненных в системе, и анализ их взаимодействия.
Это помогает выявить источник проблемы и определить, какие компоненты системы могут быть ответственны за неё. Трассировка также позволяет оценить влияние проблемы на работу системы и принять меры для её устранения.
Логирование является важным инструментом для анализа проблем в системе. Оно позволяет записывать события, происходящие в системе, включая ошибки, предупреждения и информационные сообщения.
Анализ логов позволяет выявить закономерности и тенденции, связанные с проблемами в системе. Это позволяет определить причины возникновения проблем и разработать меры по их устранению.
Шлюз выполняет функции управления доступом к сервисам и обеспечения безопасности. Он контролирует доступ к сервисам на основе определённых правил и политик.
Администрирование сервисов включает в себя управление конфигурацией, обновление версий и обеспечение надёжности работы сервисов. Шлюз обеспечивает централизованное управление этими процессами.
Регистрация и обнаружение сервисов позволяют компонентам системы находить и взаимодействовать с другими сервисами. Это особенно важно в распределённых системах, где сервисы могут находиться на разных узлах.
Динамическое масштабирование позволяет автоматически увеличивать или уменьшать количество экземпляров сервисов в зависимости от нагрузки. Это обеспечивает гибкость и адаптивность системы к изменяющимся условиям.
Отключение сервиса применяется в случае его полной неработоспособности. Понижение уровня обслуживания позволяет временно ограничить функциональность сервиса для уменьшения нагрузки на него.
Ограничение трафика используется для контроля количества запросов, обрабатываемых сервисом, чтобы предотвратить перегрузку. Эти меры помогают обеспечить стабильность и надёжность работы системы в условиях возможных проблем.
Тестирование является неотъемлемой частью процесса разработки и сопровождения системы. Оно включает в себя различные виды тестирования, такие как модульное, интеграционное и системное тестирование.
Модульное тестирование проверяет отдельные компоненты системы, интеграционное — взаимодействие между компонентами, а системное — работу всей системы в целом. Тестирование помогает выявить и исправить ошибки до выпуска системы в эксплуатацию. FaaS (Function as a Service) — сервис, предоставляющий платформу для создания, запуска и управления функциями. Поддерживает множество языков программирования, таких как Java, Node.js, Dart и другие, что облегчает участие разработчиков в тестировании на различных этапах. FaaS основан на идее событийно-управляемого подхода: функция выполняется только при срабатывании определённого события, в противном случае ресурсы сервера не используются.
BaaS (Backend as a Service) — набор сторонних базовых услуг, предоставляемых для вызова функций, таких как аутентификация, логирование, база данных и т. д. Эти услуги предоставляются непосредственно поставщиками услуг и могут быть легко вызваны разработчиками без необходимости самостоятельной реализации.
Существует три основных модели обнаружения сервисов:
Традиционная централизованная модель прокси. Прокси-сервер выступает в качестве посредника между потребителями и производителями услуг. Он выполняет функции обнаружения сервисов и балансировки нагрузки. Эта модель часто используется в крупных компаниях, таких как eBay и Слетать.ру.
Модель с клиентским встроенным прокси. В этой модели прокси-серверы интегрированы в клиентские приложения. Они выполняют обнаружение сервисов и балансировку нагрузки. Примеры включают Netflix Eureka и Ribbon.
Модель хост-независимого процесса-прокси. В этой модели каждый хост имеет свой собственный независимый процесс-прокси. Это обеспечивает более гибкое управление сервисами и балансировкой нагрузки. Примерами являются Linkerd и Envoy.
Сервисная сетка представляет собой реализацию третьей модели, где каждый хост оснащён независимым процессом-прокси для обеспечения обнаружения сервисов и балансировки нагрузки.
Для оценки пропускной способности системы необходимо учитывать следующие факторы:
В зависимости от этих факторов пропускная способность системы может быть ограничена либо количеством одновременных запросов (конкуренцией), либо скоростью обработки каждого запроса (QPS или TPS).
При проектировании системы важно учитывать эти факторы и проводить предварительную оценку пропускной способности, чтобы обеспечить эффективную работу системы под нагрузкой. Тестирование производительности программного обеспечения
Тестирование производительности системы — это проверка того, как новый модуль влияет на общую производительность системы. Для этого необходимо провести базовое тестирование, чтобы определить показатели производительности до добавления нового модуля. Затем следует включить новый модуль и сравнить показатели производительности с базовыми значениями.
Нагрузочное тестирование
Нагрузочное тестирование проводится на ранних этапах разработки для оценки производительности системы при заданной нагрузке. Нагрузка может быть представлена в виде таких показателей, как время отклика, пропускная способность, параллельная обработка, использование ресурсов и т. д.
Цель нагрузочного тестирования — выявить изменения в производительности системы под различными углами нагрузки и получить данные о производительности для оптимизации.
Определение модели сетевой инфраструктуры. Разработка сценариев нагрузки для тестирования пропускной способности системы. Создание инструментов для внедрения нагрузки. Разработка инструментов сбора данных о производительности.
Параллельное тестирование
Параллельное тестирование проводится на поздних этапах разработки для проверки системы на наличие проблем, связанных с параллельной обработкой. Цель такого тестирования — не только оценить производительность, но и обнаружить проблемы, вызванные параллелизмом, такие как утечки памяти, блокировки потоков и конфликты ресурсов.
Проектирование моделей параллельных транзакций. Составление сценариев тестирования для параллельного доступа. Разработка методов анализа проблем.
Конфигурационное тестирование
Конфигурационное тестирование также проводится на поздних этапах разработки и направлено на определение оптимального распределения ресурсов системы. Это помогает найти наилучшие конфигурации для различных аппаратных и программных компонентов, таких как оборудование, сеть, операционная система, сервер приложений и база данных.
Установление стандартов для настройки ресурсов. Подготовка сценариев тестирования конфигураций.
Тестирование на прочность
Прочность системы проверяется путём анализа особых сценариев, включая экстремальные условия, такие как аварийные ситуации или изменение ресурсов. Цель — убедиться в надёжности системы в экстремальных условиях.
Стресс-тестирование
Стресс-тестирование проводится на поздних этапах разработки, чтобы оценить способность системы обрабатывать большое количество запросов без ошибок или сбоев. Система тестируется на предельных нагрузках, чтобы убедиться в её стабильности и надёжности.
Стабильность системы
Проверка стабильности системы осуществляется на поздних этапах разработки. Тестирование включает в себя мониторинг работы системы под постоянной нагрузкой в течение длительного времени, чтобы выявить возможные проблемы с производительностью или надёжностью.
Принципы разделения интерфейса и сервера
Безопасность API
Безопасность является ключевым аспектом разработки 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; }
/**
/**
/**
В тексте запроса представлен фрагмент кода на языке 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), который был запрошен.
Если вы используете открытый исходный код WeChat разработки, убедитесь, что он поддерживает версию 3.
На заднем плане публичного аккаунта — разработка — базовая конфигурация — конфигурация сервера включите и заполните информацию о сервере.
На заднем фоне публичного аккаунта — разработка — основная конфигурация — информация о разработке публичного аккаунта настройте ключ разработчика и одновременно заполните белый список IP-адресов.
На заднем фоне публичного аккаунта — публичный аккаунт — настройка — функциональная настройка установите безопасное доменное имя для интерфейса 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 для создания предоплаченного заказа и возврата номера заказа! Основные параметры, связанные с интерфейсом оформления заказа, включают только несколько важных параметров:
Параметр запроса | Обязательный | Тип | Описание |
---|---|---|---|
appid | Да | String | Идентификатор публичного аккаунта |
mch_id | Да | String | Номер магазина |
nonce_str | Да | String | Случайная строка длиной не более 32 символов |
sign | Да | Строка | Подпись, по умолчанию используется MD5 для шифрования |
out_trade_no | Да | Строка | Внутренний номер системы заказа |
total_fee | Да | Int | Общая сумма заказа в единицах |
notify_url | Да | Строка | Интерфейс уведомления об оплате |
Подпись также относительно универсальна и включает в себя nonce_str
, который является важной частью обеспечения непредсказуемости подписи:
String
добавляется в конец строки вместе с ключом key=секретный ключ
магазина.Откройте платёж 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:
Если количество объектов, которые необходимо сохранить, слишком велико, можно использовать метод шардинга (разделения данных на части), устанавливая несколько ключей (например, по одному ключу на провинцию).
После успешной вставки возвращается значение: (integer) N, где N — количество успешно вставленных объектов.
Анализ исходного кода показывает, что Redis использует упорядоченное множество (zset) для хранения объектов местоположения. Каждый элемент в упорядоченном множестве представляет собой объект с местоположением, а значение score элемента соответствует 52-битному значению geohash местоположения.
Примечание:
Краткое описание алгоритма GEOADD:
Команда 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 — метры, километры, футы или мили.
Дополнительные параметры:
Из-за наличия параметров 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 можно описать следующим образом:
Это описание не очень понятно, поэтому мы проиллюстрируем алгоритм с помощью двух диаграмм:
Рисунок 1: Центр слева — это центр поиска, зелёная окружность — целевая область, все точки — объекты поиска, красные точки — удовлетворяющие условиям объекты. При реальном поиске сначала вычисляется уровень geohash на основе радиуса поиска (рисунок 2), затем определяется положение сетки geohash (красная сетка на рисунке 2); затем в каждой сетке geohash выбираются точки (синие и красные точки), и, наконец, отфильтровываются точки, находящиеся в пределах радиуса поиска (красные точки).
Типичные сценарии включают:
Обычно мы сталкиваемся с огромным количеством пользователей и большим объёмом трафика, например, миллионы или десятки миллионов пользователей или трафик в сотни миллионов или даже миллиарды запросов. Поэтому важно выбрать эффективную структуру данных для сбора и анализа больших объёмов информации, таких как миллиарды записей.
Чтобы выбрать подходящую структуру данных, необходимо понимать распространённые методы статистики и применять подходящие данные для решения конкретных задач. Существует четыре основных типа статистики:
Что такое двоичная статистика?
Значения элементов в наборе данных имеют только два возможных значения: 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.
Подсчёт количества дней, когда пользователь был онлайн
В системе подсчёта количества дней, проведённых пользователем онлайн, каждый день обозначается одним битом. В году 365 дней, поэтому требуется всего 365 бит. В месяце максимум 31 день, поэтому достаточно 31 бита.
Например, чтобы узнать, сколько дней пользователь с идентификатором 89757 был онлайн в мае 2021 года, можно использовать следующий подход:
Таким образом, мы можем отслеживать активность пользователей в течение месяца.
Определение даты первого посещения сайта
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:
Объединяются несколько 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:
ZADD musicTop 100000000 青花瓷 8999999 花田错
ZINCRBY:
> ZINCRBY musicTop 1 青花瓷
100000001
ZRANGEBYSCORE:
ZRANGEBYSCORE musicTop N-9 N WITHSCORES
Обратите внимание: как получить N?
ZREVRANGE:
> 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 с целью получения несанкционированного доступа к системным данным. В настоящее время это один из самых популярных методов, используемых хакерами для атак на базы данных.
Рассмотрим типичный сценарий использования: пользователь вводит имя сотрудника в поле поиска на веб-странице, а сервер приложений выполняет запрос к базе данных для получения соответствующей информации о сотруднике.
На рисунке ниже показан этот сценарий.
Обычно передняя страница передаёт параметр 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 вернёт всю информацию о сотрудниках, что означает, что злоумышленник получил несанкционированный доступ к конфиденциальной информации других пользователей.
В MyBatis использование #{}
вместо ${}
может значительно снизить риск SQL-инъекций. Это связано с тем, что #{}
является параметром-заполнителем, который автоматически добавляет кавычки к строковым значениям. Поскольку Mybatis использует предварительную компиляцию, параметры не подвергаются дальнейшей обработке SQL, что снижает риск SQL-инъекций.
- #{} — это параметр-заполнитель, который заменяет значение переменной в SQL-запросе.
- ${} — это простая замена строки, которая может привести к SQL-инъекциям.
Если возникает ошибка 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 (XML External Entity Injection), как она работает и какие последствия может иметь. Даются рекомендации по защите от этой уязвимости.
Текст объясняет, что такое DDoS (Distributed Denial of Service) атака, её цель и последствия. Описываются методы защиты от таких атак.
Приводятся примеры уязвимостей различных фреймворгов и приложений, таких как Struts, QQ Browser, Oracle GlassFish Server, WebLogic, Docker и WordPress.
Описываются другие типы уязвимостей, такие как слабые пароли, проблемы с сертификатами и отсутствие авторизации.
Обсуждаются сложности, связанные с проектированием системы для секундных распродаж, и предлагаются решения для обеспечения её надёжности и эффективности.
В тексте также используются технические термины и аббревиатуры, которые не были переведены. ### В процессе «убийства» товара система обычно сначала проверяет, достаточно ли запасов
При «убийстве» товара система сначала проверяет наличие товара на складе. Если товара достаточно, то можно оформить заказ. В противном случае система сразу сообщает, что товар распродан. Это связано с тем, что большое количество пользователей пытаются купить небольшое количество товаров, и лишь немногие из них могут быть успешными. Поэтому в большинстве случаев при «убийстве» товара запасы фактически недостаточны, и система сразу же сообщает о распроданном товаре.
Это типичный пример сценария «чтение-запись», где больше операций чтения, чем записи.
Обычно нам нужно сохранить информацию о товарах в кеше Redis, включая идентификатор товара, название, характеристики, запас и т. д. Однако эта информация также должна быть доступна в базе данных, поскольку кеш не всегда надёжен. Когда пользователь нажимает кнопку «убить» товар, передаётся параметр идентификатора товара. Затем сервер должен проверить, является ли товар действительным.
Процесс выглядит следующим образом:
На первый взгляд этот процесс кажется нормальным, но более глубокий анализ выявляет некоторые проблемы.
Например, когда товар A впервые «убивается», его нет в кеше, но он есть в базе данных. Хотя есть логика для помещения данных из базы данных в кеш после обнаружения товара в базе данных, в условиях высокой параллельной нагрузки может возникнуть проблема. Множество запросов одновременно пытаются найти товар в кеше и получить доступ к базе данных. База данных может не выдержать нагрузки и аварийно завершить работу. Как решить эту проблему? Лучше всего использовать распределённую блокировку.
Конечно, для этой ситуации лучше всего предварительно «разогреть» кеш перед запуском проекта. То есть заранее синхронизировать все товары с кешем, чтобы товары могли быть напрямую получены из кеша, избегая проблемы с «проникновением».
Кажется, что блокировка здесь не нужна. Но если срок действия кеша установлен неправильно или кеш случайно удаляется, всё равно может возникнуть проблема с «проникновением», даже если блокировка используется. Фактически, блокировка похожа на страховку.
Если поступает большое количество запросов на идентификаторы товаров, которых нет ни в кеше, ни в базе данных, эти запросы будут «проникать» через кеш и напрямую обращаться к базе данных каждый раз. Поскольку ранее была добавлена блокировка, даже при большом количестве параллельных запросов база данных не выйдет из строя. Однако производительность обработки этих запросов не будет хорошей. Есть ли лучшее решение? Можно рассмотреть использование фильтра Блума.
Система сначала ищет идентификатор товара в фильтре Блума и разрешает поиск данных в кеше, если идентификатор найден. Если идентификатор не найден, система немедленно возвращает ошибку. Хотя фильтр Блума может решить проблему «проникновения», он также может вызвать другую проблему: как синхронизировать данные в фильтре с данными в кеше? Если данные в кеше обновляются, их необходимо своевременно синхронизировать с фильтром. Если синхронизация не удалась, необходимо добавить механизм повторных попыток и обеспечить согласованность данных между источниками. Очевидно, что это невозможно. Поэтому фильтр Блума обычно используется в ситуациях, когда данные в кеше редко обновляются. Если данные часто обновляются, как справиться с ситуацией? В этом случае следует также кэшировать идентификаторы несуществующих товаров.
В следующий раз, когда поступит запрос на несуществующий идентификатор товара, его можно будет найти в кеше, хотя данные будут особыми, указывая на отсутствие товара. Следует обратить внимание, что время истечения срока действия таких специальных данных должно быть относительно коротким.
Проблема с запасами кажется простой, но на самом деле она имеет свои особенности. В реальном сценарии «убийства» товаров запасы не просто уменьшаются после продажи. Если пользователь не завершает оплату в течение определённого периода времени, уменьшенные запасы должны быть восстановлены. Поэтому вводится понятие предварительного уменьшения запасов. Основной процесс предварительного уменьшения запасов выглядит следующим образом:
Здесь также есть несколько моментов, требующих внимания. Помимо предварительного уменьшения и восстановления запасов, также необходимо учитывать проблемы недостаточного запаса и чрезмерной продажи.
Использование базы данных для уменьшения запасов — самый простой способ. Предположим, что 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, можно предотвратить ситуацию чрезмерной продажи. Однако частые обращения к базе данных могут привести к перегрузке системы, особенно в условиях высокого параллелизма, что может вызвать каскадные сбои. Кроме того, существует вероятность одновременного выполнения нескольких запросов, конкурирующих за блокировку, что приводит к взаимному ожиданию и возможным тупиковым ситуациям.
Метод 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;
Поток кода следующий:
Многие разработчики могут начать писать код таким образом. Однако при тщательном рассмотрении обнаруживается проблема. Что, если в условиях высокого параллелизма несколько запросов одновременно проверяют запас, который в данный момент превышает ноль? Из-за неатомарности операций запроса и обновления запасов может возникнуть ситуация чрезмерной продажи, когда запас становится отрицательным. Конечно, кто-то может предложить использовать блокировку, например, с ключевым словом 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;
Основной поток кода таков:
Этот подход кажется хорошим, но в условиях высокого параллелизма множество запросов могут одновременно пытаться уменьшить запас, и большинство результатов 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;");
Основной поток этого кода следующий:
Распределённая блокировка
Ранее я упоминал, что при проведении аукциона необходимо сначала проверить, существует ли товар в кэше, а если нет, то извлечь его из базы данных. Если товар существует в базе данных, он помещается в кэш, а затем возвращается. Если товара нет в базе данных, то сразу происходит возврат с ошибкой.
Представьте себе, что во время высокой параллельной нагрузки большое количество запросов будет проверять несуществующий товар в кеше. Все эти запросы будут напрямую отправляться в базу данных. База данных не сможет выдержать нагрузку и выйдет из строя. Как решить эту проблему? Для этого используется распределённая блокировка 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;
Здесь:
Поскольку эта команда представляет собой одно действие, она является атомарной.
Снятие блокировки
Некоторые могут спросить: зачем нужен 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 асинхронная обработка
Мы знаем, что в реальном сценарии аукциона, есть три основных процесса:

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

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

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

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

Потребитель читает сообщение и сначала проверяет таблицу обработки сообщений, чтобы определить, существует ли сообщение. Если оно существует, это означает повторную обработку, и потребитель сразу возвращает результат. Если сообщение не существует, потребитель выполняет операцию оформления заказа, затем записывает сообщение в таблицу обработки сообщений и возвращает результат. Важно отметить, что оформление заказа и запись в таблицу обработки должны выполняться в одной транзакции для обеспечения атомарности.
Нежелательные сообщения
Ассистент: К сожалению, без дополнительного контекста сложно понять, о чём идёт речь в этом отрывке. Пожалуйста, уточните запрос.
Возможно, речь идёт о проблеме, связанной с повторной обработкой сообщений. Чтобы избежать нежелательных последствий, связанных с повторной обработкой, рекомендуется использовать механизм подтверждения получения сообщения (ACK). Это позволит отправителю сообщения убедиться, что сообщение было получено и обработано получателем. 这套方案表面上看起来没有问题,但如果出现了消息消费失败的情况.
Например: из-за некоторых причин, потребитель сообщений постоянно терпит неудачу, не может выполнить обратный вызов состояния изменения интерфейса, и задание будет постоянно пытаться повторно отправить сообщение. В результате будет создано большое количество ненужных сообщений.
Как решить эту проблему?
Каждый раз при повторной попытке задания необходимо сначала проверить, достигло ли количество отправленных сообщений в таблице максимальное ограничение. Если да, то сразу же вернуться. Если нет, то увеличить счётчик на 1, а затем отправить сообщение. Таким образом, если возникнет исключение, будет создано только небольшое количество ненужных сообщений, что не повлияет на нормальную работу.
Обычно, если пользователь успешно совершает покупку во время акции, но оплата не завершена в течение 15 минут после оформления заказа, заказ автоматически отменяется, и товар возвращается на склад. Как реализовать функцию автоматического отмены заказа через 15 минут, если оплата не была произведена?
Мы можем использовать очередь с задержкой. RocketMQ имеет встроенную очередь с задержкой. Когда пользователь совершает покупку, создаётся заказ, который находится в состоянии ожидания оплаты. Затем сообщение отправляется в очередь с задержкой. По истечении времени задержки сообщение считывается потребителем, и проверяется состояние заказа. Если заказ всё ещё ожидает оплаты, он будет отменён. Если заказ уже оплачен, сообщение будет проигнорировано. После оплаты статус заказа изменяется на «оплачен».
Через акции можно купить товары по очень низким ценам, но некоторые опытные пользователи могут имитировать действия обычных пользователей, чтобы обойти систему и приобрести товары напрямую через API. Если это делается вручную, обычно можно нажать кнопку «Купить» только один раз в секунду. Однако, если это делается через сервер, можно отправлять тысячи запросов в секунду. Это различие очевидно, и если не принять меры, большая часть товаров может быть куплена ботами, а не обычными пользователями. Поэтому необходимо идентифицировать эти незаконные запросы и ввести ограничения.
Существует два распространённых способа ограничения потока запросов:
Ограничение для одного и того же пользователя: чтобы предотвратить слишком частые запросы от одного пользователя, можно ограничить только этого пользователя. Например, разрешить только пять запросов в минуту.
Ограничение для одного 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:
Тип int incr метод:
Например, количество прочтений статьи, количество лайков в Weibo, после записи в Redis задержка определённого времени перед синхронизацией с базой данных.
Метод int incr:
Используйте IP-адрес и другую информацию в качестве ключа, увеличивайте счётчик каждый раз при доступе и возвращайте false, если превышено пороговое значение.
Строковый тип bitcount (1.6.6 представляет структуру данных bitmap). Символы хранятся в виде 8-битных двоичных данных.
В онлайн-статистике пользователей оставьте статистику пользователей:
setbit onlineusers 01
setbit onlineusers 11
setbit onlineusers 20
Поддерживает побитовые операции И, ИЛИ и т. д.:
Вычислите количество пользователей, которые были онлайн в течение 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. Двусвязный список list, используемый непосредственно в качестве временной шкалы. Вставка упорядочена. Обычно существует три реализации TimeLine: push, pull и push-pull.
Здесь рассматривается только реализация push, предполагается, что содержимое сообщения сохраняется в хэш-таблице (hashes), а временная шкала каждого пользователя сохраняется в списке (lists).
List предоставляет две блокирующие операции всплывающего окна: blpop/brpop, которые могут устанавливать тайм-ауты:
Встроенная функция случайного получения значения: spop myset.
Если вышеупомянутый идентификатор Weibo равен t1001, а идентификатор пользователя — u3001, используйте like:t1001 для отслеживания всех пользователей, поставивших лайк на эту статью Weibo:
Используйте tags:i5001 для управления всеми тегами товаров.
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 2
— sadd 2:fans 1
— sadd 1:fans 2
— sadd 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 сказано, что уведомления о результатах платежа могут приходить несколько раз. Разработчикам необходимо самостоятельно обеспечить идемпотентность. В первый раз можно сразу изменить статус заказа (например, «в обработке» → «успешно обработан»), а во второй раз проверить статус и, если он не «в обработке», не выполнять обработку заказа.
При каждой вставке данных сначала проверяется наличие этой записи в базе данных. Если запись есть, выполняется обновление.
Операция Set в Redis обладает свойством идемпотентности, поэтому нет необходимости беспокоиться о проблеме идемпотентности при записи данных в Redis.
— При отправке каждой записи добавлять глобальный уникальный идентификатор, например, ID заказа. При каждом получении проверять наличие этого идентификатора в Redis, и если его там нет, обрабатывать сообщение и сохранять идентификатор в Redis. Если идентификатор уже есть в Redis, значит, сообщение уже было обработано, и его следует проигнорировать. — Для разных сценариев могут быть разные подходы к обеспечению идемпотентности. Можно выбрать наиболее подходящий вариант, а приведённые выше решения являются лишь примерами возможных подходов.
Решение:
Для RabbitMQ транзакции запускаются с помощью команды channel.txselect
. Если сообщение не удалось отправить в очередь, производитель получит ошибку и выполнит откат транзакции с помощью channel.txRollback
, после чего повторит отправку сообщения. Если сообщение успешно отправлено, можно выполнить фиксацию транзакции с помощью channel.txCommit
. Однако это синхронная операция, которая может повлиять на производительность.
Можно использовать другой подход — подтверждение (confirm
). Каждому отправленному сообщению присваивается уникальный идентификатор. Если сообщение было успешно доставлено в RabbitMQ, RabbitMQ отправит подтверждение (ack
), что означает успешное получение сообщения. Если RabbitMQ не смог обработать сообщение, будет отправлен отрицательный ответ (nack
), что означает необходимость повторной отправки сообщения.
Также можно настроить тайм-аут и использовать уникальный идентификатор сообщения для реализации механизма повторной попытки после истечения времени ожидания. Однако может возникнуть проблема, когда вызов метода ack
завершается неудачно, что может привести к дублированию сообщения. В этом случае необходимо обеспечить идемпотентную обработку сообщения потребителем.
Разница между транзакциями и подтверждением:
— Транзакция является синхронной операцией, и после фиксации транзакции производитель будет заблокирован до её завершения. — Подтверждение является асинхронным подходом, но существует вероятность того, что подтверждение не будет получено. Необходимо учитывать сценарии, в которых подтверждение не получено.
Сообщения в очереди могут храниться в памяти или быть сохранены на диск (например, в базе данных). Обычно сообщения хранятся как в памяти, так и на диске. Если сообщения находятся только в памяти, то при перезагрузке машины все сообщения будут потеряны. Если же сообщения сохраняются на диск, то может произойти сбой в работе очереди, и сообщения не будут сохранены на диск.
Решения:
— Создавать очереди с настройкой на сохранение при создании. — Устанавливать режим доставки сообщений равным 2 при отправке сообщений. — Использовать подтверждение для повторной отправки сообщений.
Потребитель получает сообщение, но прежде чем начать его обработку, процесс завершается из-за ошибки, и потребитель не успевает повторно получить сообщение.
Решение:
— Отключить автоматическое подтверждение (ack
) от RabbitMQ при создании очереди. После отправки сообщения RabbitMQ автоматически отправляет подтверждение производителю.
— После обработки сообщения потребитель должен активно подтвердить (ack
) RabbitMQ, чтобы сообщить, что сообщение обработано.
Вопрос: в чём недостаток активного подтверждения? Если активное подтверждение завершится неудачно, сообщение может быть повторно обработано. В таком случае потребуется обработка сообщения с учётом идемпотентности.
Вопрос: что делать, если сообщение постоянно обрабатывается повторно?
Необходимо отслеживать количество повторных обработок и, если оно превышает определённое значение, считать сообщение потерянным и регистрировать его в журнале ошибок или отправлять уведомление ответственному лицу.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )