Идемпотентность интерфейсов в распределенных системах

распределенный

Деловая сцена

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

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

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

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

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

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

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

Идемпотентность интерфейса

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

Определение в Википедии:

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

** Характеристика идемпотентной операции в программировании заключается в том, что любое ее многократное выполнение имеет тот же эффект, что и однократное выполнение. ** Идемпотентная функция или идемпотентный метод — это функция, которая может многократно выполняться с одними и теми же параметрами и достигать одного и того же результата. Эти функции не влияют на состояние системы, и не нужно беспокоиться, что повторное выполнение вызовет изменения в системе. Например, функция "setTrue()" является идемпотентной функцией. Независимо от того, сколько раз она выполняется, результат один и тот же. Гарантия идемпотентности более сложных операций реализуется за счет использования уникального номера транзакции (серийного номера).

Любое количество выполнений будет иметь тот же эффект, что и одно выполнение, что является основным свойством идемпотентности. На самом деле основной операцией в нашем программировании является CURD, в котором операция чтения (Retrieve) и операция удаления (Delete) естественно идемпотентны, а затрагиваемые — Create и Update.

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

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

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

идемпотентная реализация

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

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

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

Механизм токена

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

Основной процесс:

  1. Сервер предоставляет интерфейс для отправки токенов. Когда мы анализируем бизнес, у каких бизнесов есть проблемы с идемпотентом, мы должны получить токен перед выполнением бизнеса, и сервер сохранит токен в Redis. (Микросервисы определенно распределены, и если используется одна машина, применим кеш jvm).
  2. Затем при вызове запроса бизнес-интерфейса токен переносится в прошлое, обычно в заголовке запроса.
  3. Сервер оценивает, существует ли токен в Redis.Существование токена указывает на первый запрос.В это время токен в Redis удаляется, и бизнес продолжается.
  4. Если установлено, что токен не существует в Redis, это означает, что операция повторяется, и повторяющийся токен напрямую возвращается клиенту, что гарантирует, что бизнес-код не будет выполняться повторно.

Таблица дедупликации базы данных

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

CREATE TABLE `t_idempotent` (
  `id` int(11) NOT NULL COMMENT 'ID',
  `serial_no` varchar(255)  NOT NULL COMMENT '唯一序列号',
  `source_type` varchar(255)  NOT NULL COMMENT '资源类型',
  `status` int(4) DEFAULT NULL COMMENT '状态',
  `remark` varchar(255)  NOT NULL COMMENT '备注',
  `create_by` bigint(20) DEFAULT NULL COMMENT '创建人',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `modify_by` bigint(20) DEFAULT NULL COMMENT '修改人',
  `modify_time` datetime DEFAULT NULL COMMENT '修改时间',
  PRIMARY KEY (`id`)
  UNIQUE KEY `key_s` (`serial_no`,`source_type`, `remark`)  COMMENT '保证业务唯一性'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='幂等性校验表';

Мы обращаем внимание на следующие ключевые поля,

  • serial_no: значение уникального серийного номера, здесь я задаю его через аннотации@IdempotentKeyдля идентификации полей в объекте запроса и получения соответствующих значений путем их шифрования с помощью MD5.
  • source_type: тип бизнеса, который отличает разные бизнесы, заказы, платежи и т. д.
  • примечание: это строка, образованная путем склеивания полей идентификации, и сплайсер "|".

С момента создания данныхserial_no,source_type, remarkУникальный индекс, образованный комбинацией трех полей, поэтому идемпотентность интерфейса может быть достигнута путем дедупликации Конкретный дизайн кода выглядит следующим образом:

public class PaymentOrderReq {

    /**
     * 支付宝流水号
     */
    @IdempotentKey(order=1)
    private String alipayNo;

    /**
     * 支付订单ID
     */
    @IdempotentKey(order=2)
    private String paymentOrderNo;

    /**
     * 支付金额
     */
    private Long amount;
}

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

private void getIdempotentKeys(Object keySource, Idempotent idempotent) {
    TreeMap<Integer, Object> keyMap = new TreeMap<Integer, Object>();
    for (Field field : keySource.getClass().getDeclaredFields()) {
        if (field.isAnnotationPresent(IdempotentKey.class)) {
            try {
                field.setAccessible(true);
                keyMap.put(field.getAnnotation(IdempotentKey.class).order(),
                        field.get(keySource));
            } catch (IllegalArgumentException | IllegalAccessException e) {
                logger.error("", e);
                return;
            }
        }
    }
    generateIdempotentKey(idempotent, keyMap.values().toArray());
}

Сгенерировать идемпотентные ключи, если есть несколько ключей, которые можно соединить разделителем "|",

private void generateIdempotentKey(Idempotent idempotent, Object... keyObj) {
     if (keyObj.length == 0) {
         logger.info("idempotentkey is empty,{}", keyObj);
         return;
     }
     StringBuilder serialNo= new StringBuilder();
     for (Object key : keyObj) {
         serialNo.append(key.toString()).append("|");
     }
     idempotent.setRemark(serialNo.toString());
     idempotent.setSerialNo(md5(serialNo));
 }

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

public <T> void idempotentCheck(IdempotentTypeEnum idempotentType, T keyObj) throws IdempotentException {
    Idempotent idempotent = new Idempotent();
    getIdempotentKeys(keyObj, idempotent );
    if (StringUtils.isBlank(idempotent.getSerialNo())) {
        throw new ServiceException("fail to get idempotentkey");
    }
    idempotentEvent.setSourceType(idempotentType.name());
    try {
        idempotentMapper.saveIdempotent(idempotent);
    } catch (DuplicateKeyException e) {
        logger.error("idempotent check fail", e);
        throw new IdempotentException(idempotent);
    }
}

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

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

Реализация Redis

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

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

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

Государственный аппарат

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

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

public enum OrderStatusEnum {

    UN_SUBMIT(0, 0, "待提交"),
    UN_PADING(0, 1, "待支付"),
    PAYED(1, 2, "已支付待发货"),
    DELIVERING(2, 3, "已发货"),
    COMPLETE(3, 4, "已完成"),
    CANCEL(0, 5, "已取消"),
    ;

    //前置状态
    private int preStatus;

    //状态值
    private int status;

    //状态描述
    private String desc;

    OrderStatusEnum(int preStatus, int status, String desc) {
        this.preStatus = preStatus;
        this.status = status;
        this.desc = desc;
    }

    //...
}

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

Суммировать

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

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