Ответ
Ключ идемпотентности — это уникальный идентификатор, который клиент передает серверу для обеспечения идемпотентности операции. Идемпотентность означает, что многократное выполнение одной и той же операции приводит к тому же результату, что и однократное.
Это критически важно для защиты от сбоев в распределенных системах, например:
- Сетевые ошибки: Клиент отправил запрос, но не получил ответ (например, из-за таймаута). Он не знает, была ли операция выполнена, и безопасно повторяет запрос с тем же ключом.
- Дублирование сообщений: В системах с очередями сообщений одно и то же событие может быть доставлено несколько раз.
- Защита от двойных списаний: В платежных системах это предотвращает повторное списание средств при повторной отправке запроса на оплату.
Как это работает:
- Клиент генерирует уникальный ключ (например, UUID) и отправляет его в заголовке запроса (например,
Idempotency-Key
). - Сервер перед выполнением операции проверяет, обрабатывался ли уже запрос с таким ключом.
- Если ключ новый: Сервер сохраняет ключ, выполняет операцию, сохраняет результат (или статус) и отправляет ответ клиенту.
- Если ключ уже существует: Сервер не выполняет операцию повторно, а сразу возвращает сохраненный ранее результат.
Пример потокобезопасной реализации в Go HTTP API:
В реальном приложении ключи нужно хранить в персистентном хранилище (Redis, PostgreSQL), но для демонстрации используем потокобезопасную map
.
package main
import (
"fmt"
"net/http"
"sync"
)
// В реальном приложении это будет Redis, база данных и т.д.
var (
processedKeys = make(map[string]bool)
mu sync.Mutex // Мьютекс для защиты доступа к processedKeys
)
func processPayment(w http.ResponseWriter, r *http.Request) {
idempotencyKey := r.Header.Get("Idempotency-Key")
if idempotencyKey == "" {
http.Error(w, "Header 'Idempotency-Key' is required", http.StatusBadRequest)
return
}
mu.Lock() // Блокируем доступ для других горутин
if _, exists := processedKeys[idempotencyKey]; exists {
mu.Unlock() // Не забываем разблокировать перед выходом
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, "Request already processed.")
return
}
// Ключ новый, помечаем его как обрабатываемый
processedKeys[idempotencyKey] = true
mu.Unlock() // Разблокируем, чтобы не блокировать другие запросы на время выполнения операции
// ... здесь логика выполнения платежа ...
// time.Sleep(2 * time.Second) // Симуляция долгой операции
w.WriteHeader(http.StatusCreated)
fmt.Fprintln(w, "Payment processed successfully.")
}