Interview Trigger
看到这些关键词第一反应 → Idempotency
Payment / Transfer / Webhook / Retry / Replay / Kafka / RabbitMQ
四种实现方法
方法一: Request ID / Business ID
通过 request id 避免同一个操作被执行多次。金融系统最常见。
天然有唯一 ID:transfer_id, payment_id, order_id, withdrawal_id。执行前先检查是否已有对应 id 的记录,如果已有,直接返回第一次结果。
方法二: Processed Events Table
特别适合 Kafka / RabbitMQ consumer:
CREATE TABLE processed_events (
consumer_name VARCHAR(100) NOT NULL,
event_id VARCHAR(100) NOT NULL,
processed_at TIMESTAMP NOT NULL,
PRIMARY KEY (consumer_name, event_id)
);
-- 放在同一个 DB transaction:
BEGIN;
INSERT INTO processed_events (consumer_name, event_id)
VALUES ('reward-service', 'E123');
UPDATE user_points SET points = points + 100 WHERE user_id = 'U1';
COMMIT;
方法三: Unique Constraint
数据库强制幂等。Application-side check alone is not enough,数据库里用 unique constraint 作为最后一道防线:
CREATE UNIQUE INDEX uk_transfer_id ON transfers(transfer_id);
方法四: UPSERT
如果已经存在就安全地“不再创建”:
-- PostgreSQL
INSERT INTO processed_events (...) VALUES (...)
ON CONFLICT (consumer_name, event_id) DO NOTHING;
-- MySQL
INSERT IGNORE INTO processed_events(...) VALUES (...);
这四种方法很多时候是组合使用:
Business/Event ID → Unique Constraint → UPSERT
高频 Follow-up
Q1: Why not rely on Kafka Exactly Once?
Because exactly-once delivery across Kafka, databases, and external services is difficult. I prefer at-least-once delivery combined with idempotent business logic.
Q2: Where do you store the idempotency key?
Usually in the database together with the business record or in a dedicated processed-events table.
Q3: Should Redis store it?
可以,但 Redis 一般只是加速,真正 Source of Truth 还是 DB。