정확히 한 번 vs 적어도 한 번
1) 의미론에 대해 토론하는 이유
배달 의미론은 수신자가 충돌 및 복귀 할 때 메시지를 얼마나 자주 볼 것인지를 결정합니다
반복없이 최대한 한 번에 손실이 발생할 수 있습니다 (거의 허용되지 않음).
적어도 한 번은 잃지 않지만 복제가 가능합니다 (대부분의 중개인/대기열의 기본값).
정확히 한 번-각 메시지는 관찰 된 효과 측면에서 정확히 한 번 처리됩니다.
주요 진실: 글로벌 거래와 동기식 일관성이없는 분산 세계에서는 "깨끗한" 엔드 투 엔드 정확히 한 번은 달성 할 수 없습니다. 우리는 효과적으로 정확하게 한 번만 구축합니다. 운송에 대한 반복을 허용하지만 관찰 된 효과가 "한 번처럼" 만들도록 처리를 imempotent로 만듭니다.
2) 실패 패턴과 중복이 발생하는 위치
다음으로 인해 재생이 나타납니다
손실 ack/커밋 (프로듀서/브로커/컨 서머는 "확인을 듣지 못했습니다").
지도자/복제품의 재선거, 네트워크 중단 후 복구.
모든 지역에서 타임 아웃/후퇴 (kliyent → 브로커 → konsyumer → 싱크).
결과: 운송의 "고유성" 에 의존 할 수 없습니다. 효과 관리: 데이터베이스에 쓰기, 돈을 삭제, 편지 발송 등
3) 공급자와 정확히 무엇인가
3. 1 카프카
벽돌 제공:- Idempotent Producer ('활성화. demempotence = 참 ') -수축 할 때 생산자 측의 중복을 방지합니다.
- 트랜잭션-메시지를 여러 배치로 원자 적으로 게시하고 소비 오프셋 ("갭" 이없는 읽기 프로세스 쓰기 패턴) 을 커밋합니다.
- 압축-마지막 값을 키별로 저장합니다.
그러나 "체인의 끝" (싱크: DB/결제/메일) 에는 여전히 demempotency가 필요합니다. 그렇지 않으면 핸들러의 더블은 효과를 두 배로 만듭니다.
3. 2 NATS/토끼/SQS
기본값은 ack/reduction에서 적어도 한 번입니다. 정확히 한 번은 열쇠, 데드 스토어, upsert와 같은 응용 프로그램 수준에서 달성됩니다.
결론: 정확히 한 번의 전송은 정확히 한 번의 효과입니다. 후자는 핸들러에서 수행됩니다.
4) 적어도 한 번 이상 효과적으로 정확하게 한 번 구축하는 방법
4. 1 이데올로기 키
각 명령/이벤트에는 'payment _ id', 'order _ id # step', 'saga _ id # n' 과 같은 자연스러운 키가 있습니다. 핸들러:- "이미 보았습니까?" 확인 -TTL/Retsch의 Dedup-stor (Redis/DB).
- 당신이 보았을 때, 이전에 계산 된 결과를 반복하거나 작동하지 않습니다.
lua
-- SET key if not exists; expires in 24h local ok = redis.call("SET", KEYS[1], ARGV[1], "NX", "EX", 86400)
if ok then return "PROCESS" else return "SKIP" end
4. 베이스에서 2 업저트 (dempotent cink)
출품작은 버전/양을 확인하는 UPSERT/ON CONFLICT를 통해 이루어집니다.
PostgreSQL:sql
INSERT INTO payments(id, status, amount, updated_at)
VALUES ($1, $2, $3, now())
ON CONFLICT (id) DO UPDATE
SET status = EXCLUDED.status,
updated_at = now()
WHERE payments.status <> EXCLUDED.status;
4. 3 거래 전송/받은 편지함
인터페이스: 비즈니스 트랜잭션 및 이벤트 게시 항목은 동일한 데이터베이스 트랜잭션에서 발생합니 백그라운드 게시자는 아웃 박스를 읽고 브로커에게 보냅니다 → 상태와 이벤트 사이에 불일치가 없습니다.
받은 편지함: 들어오는 명령의 경우 '메시지 _ id' 및 실행 전의 결과를 저장하십시오. 재 처리는 기록을보고 부작용을 반복하지 않습니다.
4. 4 일관된 체인 처리 (읽기 → 프로세스 → 쓰기)
Kafka: 거래는 하나의 원자 블록에서 "오프셋 → 는 → 커밋의 결과를 기록했습니다".
트랜잭션없이: "먼저 결과/받은 편지함을 적은 다음 ack"; 충돌시 복제본에는받은 편지함이 표시되고 종료되지 않습니다.
4. 5 SAGA/오프셋
demempotency가 불가능한 경우 (외부 제공 업체가 돈을 썼을 때) 보상 작업 (환불/공허) 과 dempotent 외부 API를 사용합니다 (동일한 'Idempotency-Key' 로 반복되는 'POST' 는 동일한 결과를 제공합니다).
5) 적어도 한 번은 충분할 때
키 기반 압축으로 캐시/구체화 된보기의 업데이트.
재증가가 허용되는 카운터/메트릭스 (또는 버전으로 델타 저장).
보조 문자가 중요하지 않은 위치를 알기 (어쨌든 키를 넣는 것이 좋습니다).
규칙: 이중이 비즈니스 의미를 변경하지 않거나 → 적어도 한 번 + 부분 보호를 쉽게 찾을 수 있습니다.
6) 성능 및 비용
정확히 한 번 ("효과적으로") 더 많은 비용이 듭니다. 추가 레코드 (받은 편지함/전송), 저장 키, 거래, 진단이 더 어렵습니다.
적어도 한 번은 더 저렴하고 단순하며 처리량/p99에서 더 좋습니다.
평가: 이중 × 확률의 두 배 대 보호 비용.
7) 샘플 구성 및 코드
7. 1 카프카 생산자 (demempotence + traveles)
properties enable.idempotence=true acks=all retries=INT_MAX max.in.flight.requests.per.connection=5 transactional.id=orders-writer-1
java producer.initTransactions();
producer.beginTransaction();
producer.send(recordA);
producer.send(recordB);
// также можно atomically commit consumer offsets producer.commitTransaction();
7. 받은 편지함 콘솔 2 개 (의사 코드)
pseudo if (inbox.exists(msg.id)) return inbox.result(msg.id)
begin tx if!inbox.insert(msg.id) then return inbox.result(msg.id)
result = handle(msg)
sink.upsert(result) # идемпотентный синк inbox.set_result(msg.id, result)
commit ack(msg)
7. 3 HTIdempotency-Key (외부 애플리케이션)
POST /payments
Idempotency-Key: 7f1c-42-...
Body: { "payment_id": "p-123", "amount": 10.00 }
동일한 키로 반복되는 POST → 동일한 결과/상태.
8) 관찰 가능성 및 지표
'복제 _ trides _ total' -이중이 몇 번이나 잡혔습니까 (받은 편지함/Redis에 따름).
'idempotency _ hit _ rate' - demempotency에 의한 반복 "저장" 비율.
'txn _ reditate _ rate' (Kafka/DB) -롤백의 비율.
'outbox _ backlog- 출판 지연.
(PHP 3 = 3.0.6, PHP 4)
감사 로그: '메시지 _ id', 'demempotency _ key', 'saga _ id', '시도'.
9) 테스트 플레이 북 (게임 데이)
재생 보내기: 인공 타임 아웃이있는 프로듀서 재생.
"싱크대와 ack" 사이의 충돌: 받은 편지함/Upsert가 이중을 방지해야합니다.
재배송: 브로커의 재배송 증가; 데드 업을 확인하십시오.
외부 API의 이념성: 동일한 키로 반복 된 POST가 동일한 답변입니다.
납 변경/네트워크 중단: Kafka 거래/소비자 행동을 확인하십시오.
10) 반 패턴
운송에 동의하십시오: "우리는 정확히 한 번만 Kafka를 가지고 있으므로 키없이 할 수 있습니다" -아니요.
기록하기 전에 No-op ack: 포장되었지만 싱크대는 → 손실을 떨어 뜨 렸습니다.
DLQ/지터 퇴각 부족: 끝없는 리플레이와 폭풍.
자연 키 대신 랜덤 UUID: 중복 할 것이 없습니다.
인덱스가없는 생산 테이블 (핫 락 및 p99 테일) 과 함께받은 편지함/편지함 혼합.
외부 제공 업체에서 imempotent API가없는 비즈니스 거래.
11) 선택 점검표
1. 이중 가격 (돈/법률/UX) vs 보호 가격 (대기 시간/복잡성/비용).
2. 자연스러운 이벤트/운영 키가 있습니까? 그렇지 않은 경우 안정적인 것을 생각해보십시오.
3. 싱크대는 Upsert/verioning을 지원합니까? 그렇지 않으면-받은 편지함 + 보
4. 글로벌 거래가 필요합니까? 그렇지 않은 경우 SAGA로 분류하십시오.
5. 재생/장기 유지가 필요하십니까? 카프카 + 전송. 빠른 RPC/낮은 대기 시간이 필요하십니까? NATS + Idempotency-Key.
6. 멀티 테넌시 및 할당량: 키/공간 격리.
7. 관찰 가능성: demotency 및 백 로그 메트릭이 포함되어 있습니다.
12) FAQ
Q: "수학적" 정확히 한 번 엔드 투 엔드를 달성 할 수 있습니까?
A: 하나의 일관된 상점과 거래가있는 좁은 시나리오에서만. 일반적으로 아니오; demempotency를 통해 효과적으로 정확하게 한 번 사용하
Q: 더 빠른 것은 무엇입니까?
A: 적어도 한 번. 정확히 한 번 트랜잭션/키 스토리지 → p99 및 비용을 추가합니다.
Q: demempotence 키를 어디에 저장해야합니까?
A: TTL 또는받은 편지함 테이블 (PK = 메시지 _ id) 이있는 빠른 정지 (Redis). 지불의 경우-더 긴 (일/주).
Q: TTL 디드 업 키를 선택하는 방법?
A: 최소 = 최대 재전송 시간 + 운영 마진 (일반적으로 24-72 시간). 금융을 위해-더.
Q: Kafka의 키로 압축하면 키가 필요합니까?
A: 예. 컴팩트는 스토리지를 줄이지 만 동기화를 dempotent로 만들지는 않습니다.
13) 총계
적어도 한 번은 기본적이고 신뢰할 수있는 운송 의미론입니다.
프로세서 수준: Idempotency-Key, 받은 편지함/전송률, Upsert/버전, SAGA/보상에서 비즈니스 효과로 정확히 한 번 달성됩니다.
선택은 작동 용이성의 중복 위험을 타협하는 것입니다. 자연 건반을 디자인하고, 타박상을 만들고, 관찰 가능성을 추가하고, 정기적으로 게임 일을 플레이하십시오. 그러면 파이프 라인은 retra와 고장의 폭풍에서도 예측 가능하고 안전합니다.