ลอจิกการลองใหม่ของเวบฮุก
เรียนรู้วิธีการจัดการการลองใหม่ของเวบฮุก, ใช้ความสามารถในการเป็นปัญหาเดียวกันเพื่อจัดการการจัดส่งที่ซ้ำกัน และสร้างระบบการประมวลผลเวบฮุกที่เชื่อถือได้
ภาพรวม
การจัดส่งเวบฮุกไม่รับประกันในครั้งแรก ปัญหาเครือข่าย, ความล้มเหลวของเซิร์ฟเวอร์ หรือข้อผิดพลาดในการประมวลผลสามารถทำให้เกิดความล้มเหลว คำแนะนำนี้ครอบคลุม:
- ลำดับการลองใหม่อัตโนมัติของ Omise และ exponential backoff
- การใช้ความสามารถในการเป็นปัญหาเดียวกันเพื่อจัดการเหตุการณ์ที่ซ้ำกัน
- การบันทึกและตรวจสอบการจัดส่งเวบฮุก
- ขั้นตอนการลองใหม่ด้วยตนเอง
- แนวทางปฏิบัติที่ดีสำหรับการจัดการความล้มเหลว
ตารางการลองใหม่
วิธีการลองใหม่เวบฮุก
เมื่อการจัดส่งเวบฮุกล้มเหลว (การตอบสนอง non-2xx หรือการหมดเวลา), Omise จะลองใหม่โดยอัตโนมัติด้วย exponential backoff:
ความพยายาม 1: ทันที (การจัดส่งเริ่มต้น)
ความพยายาม 2: หลังจาก 1 นาที
ความพยายาม 3: หลังจาก 5 นาที
ความพยายาม 4: หลังจาก 15 นาที
ความพยายาม 5: หลังจาก 1 ชั่วโมง
ความพยายาม 6: หลังจาก 3 ชั่วโมง
ความพยายาม 7: หลังจาก 6 ชั่วโมง
ความพยายาม 8: หลังจาก 12 ชั่วโมง
ความพยายาม 9: หลังจาก 24 ชั่วโมง
ความพยายาม 10: หลังจาก 48 ชั่วโมง
ความพยายามสุดท้าย: ทุกๆ 48 ชั่วโมงจนกระทั่ง 7 วันผ่านไป
พฤติกรรมการลองใหม่
ตัวบ่งชี้สำหรับการลองใหม่:
- รหัสสถานะ HTTP 400-599 (ข้อผิดพลาดของไคลเอนต์และเซิร์ฟเวอร์)
- Timeout เครือข่าย (ไม่มีการตอบสนองภายใน 10 วินาที)
- ความล้มเหลวในการเชื่อมต่ อ (ข้อผิดพลาด DNS, การปฏิเสธการเชื่อมต่อ)
เกณฑ์ความสำเร็จ:
- รหัสสถานะ HTTP 200-299
- การตอบสนองที่ได้รับภายใน 10 วินาที
ระยะเวลาการลองใหม่:
- การลองใหม่ดำเนินต่อไปเป็นเวลา 7 วันนับจากความพยายามเริ่มต้น
- หลังจาก 7 วัน, เวบฮุกจะถูกทำเครื่องหมายว่าล้มเหลวถาวร
ความเป็นปัญหาเดียวกัน
ความสามารถในการเป็นปัญหาเดียวกันช่วยให้การประมวลผลเวบฮุกหลายครั้งมีเอฟเฟกต์เดียวกับการประมวลผลครั้งเดียว นี่เป็นสิ่งสำคัญเพราะเวบฮุกสามารถจัดส่งหลายครั้งเนื่องจากการลองใหม่
เหตุใดความเป็นปัญหาเดียวกันจึงสำคัญ
สถานการณ์: การจัดส่งเวบฮุกล้มเหลวหลังจากประมวลผล แต่ก่อนการตอบสนอง
1. เวบฮุกได้รับ
2. ประจำถูกทำเครื่องหมายว่าจ่ายแล้ว ✓
3. อีเมลส่งให้ลูกค้า ✓
4. เซิร์ฟเวอร์ขัดข้องก่อนการตอบสนอง ✗
5. Omise ลองใหม่เวบฮุก
6. ไม่มีความสามารถในการเป็นปัญหาเดียวกัน:
- ประจำถูกทำเครื่องหมายว่าจ่ายแล้วอีกครั้ง (ซ้ำกัน)
- ลูกค้าได้รับอีเมลซ้ำ ✗
7. ด้วยความสามารถในการเป็นปัญหาเดียวกัน:
- จำหน่ายเหตุการณ์ที่ประมวลผลแล้ว
- ข้ามการประมวลผลที่ซ้ำกัน ✓
รูปแบบความสามารถในการเป็นปัญหาเดียวกัน
1. การติดตาม Event ID
// Node.js - การติดตาม Event ID ด้วย Redis
const redis = require('redis');
const client = redis.createClient();
async function processWebhookIdempotent(event) {
const eventId = event.id;
const processedKey = `webhook:processed:${eventId}`;
// ตรวจสอบว่าประมวลผลแล้ว
const isProcessed = await client.exists(processedKey);
if (isProcessed) {
console.log(`Event ${eventId} already processed, skipping`);
return { status: 'duplicate', eventId };
}
try {
// ประมวลผลเวบฮุก
await processWebhook(event);
// ทำเครื่องหมายเป็นประมวลผล (เก็บไว้ 7 วัน)
await client.setex(processedKey, 7 * 24 * 3600, JSON.stringify({
processed_at: new Date().toISOString(),
event_key: event.key
}));
return { status: 'success', eventId };
} catch (error) {
console.error(`Error processing event ${eventId}:`, error);
throw error;
}
}
2. ข้อจำกัดฐานข้อมูล
// Node.js - ความสามารถในการเป็นปัญหาเดียวกันที่ระดับฐานข้อมูลด้วย MongoDB
const mongoose = require('mongoose');
// โครงการเวบฮุกพร้อมข้อจำกัดแบบเฉพาะ
const WebhookEventSchema = new mongoose.Schema({
event_id: {
type: String,
required: true,
unique: true, // ป้องกันการประมวลผลที่ซ้ำกัน
index: true
},
event_key: { type: String, required: true },
processed_at: { type: Date, default: Date.now },
data: { type: mongoose.Schema.Types.Mixed },
status: {
type: String,
enum: ['processing', 'completed', 'failed'],
default: 'processing'
}
});
const WebhookEvent = mongoose.model('WebhookEvent', WebhookEventSchema);
async function processWebhookIdempotent(event) {
try {
// พยายามแทรกบันทึกเหตุการณ์
const webhookEvent = new WebhookEvent({
event_id: event.id,
event_key: event.key,
data: event.data
});
await webhookEvent.save();
} catch (error) {
// ข้อผิดพลาดคีย์ที่ซ้ำกัน - เหตุการณ์ประมวลผลแล้ว
if (error.code === 11000) {
console.log(`Event ${event.id} already processed`);
return { status: 'duplicate' };
}
throw error;
}
try {
// ประมวลผลเวบฮุก
await processWebhook(event);
// อัปเดตสถานะ
await WebhookEvent.updateOne(
{ event_id: event.id },
{ status: 'completed' }
);
return { status: 'success' };
} catch (error) {
// อัปเดตสถานะเป็นล้มเหลว
await WebhookEvent.updateOne(
{ event_id: event.id },
{ status: 'failed', error: error.message }
);
throw error;
}
}
การจัดการความล้มเหลว
การจัดการข้อผิดพลาดที่สง่างาม
ตอบสนองต่อเวบฮุกอย่างรวดเร็วแม้ว่าการประมวลผลจะล้มเหลว:
// Node.js - การจัดการข้อผิดพลาดที่สง่างาม
app.post('/webhooks/omise', async (req, res) => {
let event;
try {
// ตรวจสอบลายเซ็น
const signature = req.headers['x-omise-signature'];
if (!signature || !verifySignature(req.body, signature)) {
return res.status(401).json({ error: 'Invalid signature' });
}
event = JSON.parse(req.body.toString());
// ตอบสนองทันที (ก่อนการประมวลผล)
res.status(200).json({ received: true });
} catch (error) {
console.error('Webhook parsing error', error);
return res.status(400).json({ error: 'Invalid request' });
}
// ประมวลผลแบบไม่ซิงโครนัสด้วยการจัดการข้อผิดพลาด
processWebhookWithRetry(event)
.then(() => {
console.log('Webhook processed successfully');
})
.catch(error => {
console.error('Webhook processing failed', error);
// เข้าคิวสำหรับการตรวจสอบด้วยตนเอง
queueForManualReview(event, error);
});
});
คิวจดหมายที่ส่งไม่สำเร็จ
# Python - Dead letter queue ด้วย Redis
import redis
import json
from datetime import datetime
redis_client = redis.Redis(host='localhost', port=6379, decode_responses=True)
def queue_for_manual_review(event, error):
"""เพิ่มเวบฮุกที่ล้มเหลวลงในคิวตัวอักษรที่ตายแล้ว"""
failed_webhook = {
'event': event,
'error': str(error),
'failed_at': datetime.now().isoformat(),
'attempts': get_attempt_count(event['id'])
}
# เพิ่มลงในคิว (sorted set ตามเวลา)
redis_client.zadd(
'webhook:dead_letter_queue',
{json.dumps(failed_webhook): datetime.now().timestamp()}
)
# ส่งการแจ้งเตือนไปยังทีมการดำเนินการ
send_alert(f"Webhook {event['id']} failed after retries", failed_webhook)
def retry_failed_webhook(event_id):
"""ลองใหม่เวบฮุกที่ล้มเหลวด้วยตนเอง"""
items = redis_client.zrange('webhook:dead_letter_queue', 0, -1)
for item in items:
webhook = json.loads(item)
if webhook['event']['id'] == event_id:
try:
# ลองประมวลผลอีกครั้ง
process_webhook(webhook['event'])
# ลบออกจาก dead letter queue
redis_client.zrem('webhook:dead_letter_queue', item)
print(f"Successfully reprocessed webhook {event_id}")
return True
except Exception as e:
print(f"Retry failed: {e}")
return False
print(f"Webhook {event_id} not found in dead letter queue")
return False
การบันทึกและการตรวจสอบ
การบันทึกเวบฮุกที่ครอบคลุม
// Node.js - การบันทึกโครงสร้างด้วย Winston
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({
filename: 'webhooks-error.log',
level: 'error'
}),
new winston.transports.File({
filename: 'webhooks-combined.log'
})
]
});
// บันทึกการตรวจสอบ
logger.info('webhook.received', {
event_id: event.id,
event_key: event.key,
livemode: event.livemode,
ip: req.ip,
received_at: new Date().toISOString()
});
// บันทึกการตรวจสอบลายเซ็น
logger.info('webhook.signature_verified', {
event_id: event.id,
signature_present: !!signature
});
// บันทึกการเริ่มต้นการประมวลผล
logger.info('webhook.processing_started', {
event_id: event.id,
event_key: event.key,
started_at: new Date().toISOString()
});
// บันทึกข้อผิดพลาด
logger.error('webhook.processing_failed', {
event_id: event.id,
event_key: event.key,
error: error.message,
stack: error.stack,
attempt: attemptCount,
duration_ms: Date.now() - startTime
});
การลองใหม่ด้วยตนเอง
การลองใหม่จากแดชบอร์ด
ใช้แดชบอร์ด Omise เพื่อลองใหม่เวบฮุกที่ล้มเหลว:
- นำทางไปยังส่วนเวบฮุก
- เลือกจุดสิ้นสุดเวบฮุกของคุณ
- ดู "Recent Deliveries"
- ค้นหาการจัดส่งที่ล้มเหลว
- คลิก "Resend"
การลองใหม่ด้วยตนเอ ง API
// Node.js - การลองใหม่ด้วยตนเองผ่าน API
const Omise = require('omise');
const omise = Omise({ secretKey: process.env.OMISE_SECRET_KEY });
async function retryFailedWebhooks() {
try {
// ดึงเหตุการณ์ล่าสุด
const events = await omise.events.list({ limit: 100 });
for (const event of events.data) {
// ตรวจสอบว่าเหตุการณ์ต้องการการประมวลผลซ้ำ
const needsRetry = await checkIfNeedsRetry(event.id);
if (needsRetry) {
console.log(`Retrying event ${event.id}`);
try {
await processWebhook(event);
console.log(`Successfully processed ${event.id}`);
} catch (error) {
console.error(`Failed to process ${event.id}:`, error);
}
}
}
} catch (error) {
console.error('Error retrieving events:', error);
}
}
async function checkIfNeedsRetry(eventId) {
// ตรวจสอบว่าเหตุการณ์ประมวลผลเรียบร้อยแล้ว
const processed = await db.webhookEvents.findOne({
event_id: eventId,
status: 'completed'
});
return !processed;
}
retryFailedWebhooks();
แนวทางปฏิบัติที่ดี
รายการตรวจสอบการประมวลผลเวบฮุก
- ✓ ตอบภายใน 10 วินาที - ส่งกลับ 200 ทันที
- ✓ ใช้ความสามารถในการเป็นปัญหาเดียวกัน - ติดตาม ID เหตุการณ์หรือข้อจำกัดฐานข้อมูล
- ✓ ประมวลผลแบบไม่ซิงโครนัส - ใช้คิวหรือตัวเรียกใช้ในพื้นหลัง
- ✓ บันทึกอย่างครอบคลุม - ติดตามเหตุการณ์เวบฮุกทั้งหมดและผลลัพธ์
- ✓ ตรวจสอบเมตริก - ติดตามอัตราข้อผิดพลาด, ความล่าช้า, ซ้ำกัน
- ✓ จัดการซ้ำกันอย่างสง่างาม - ไม่ถือว่าเป็นข้อผิดพลาด
- ✓ ใช้ลอจิกการลองใหม่ - สำหรับความล้มเหลวชั่วคราวในการประมวลผล
- ✓ ใช้ dead letter queue - สำหรับเวบฮุกที่ล้มเหลวหลังจากการลองใหม่สูงสุด
- ✓ ตั้งค่าการแจ้งเตือน - สำหรับความล้มเหลวที่สำคัญหรืออัตราข้อผิดพลาดสูง
- ✓ ทดสอบสถานการณ์ความล้มเหลว - ให้แน่ใจว่ามีการเสื่อมคุณภาพอย่างสง่างาม
ทรัพยากรที่เกี่ยวข้อง
- การตั้งค่าเวบฮุก - การกำหนดค่าจุดสิ้นสุด
- ประเภทเหตุการณ์ - เหตุการณ์ที่พร้อมใช้งาน
- ความปลอดภัยเวบฮุก - การตรวจสอบลายเซ็น
- API Events - อ้างอิง Events API
- การทดสอบเวบฮุก - คำแนะนำการทดสอบ
ขั้นตอนถัดไป
- ใช้ความสามารถในการเป็นปัญหาเดียวกัน ในตัวจัดการเวบฮุก
- ตั้งค่า dead letter queue สำหรับเ วบฮุกที่ล้มเหลว
- ใช้ลอจิกการลองใหม่ สำหรับความล้มเหลวชั่วคราว
- ตั้งค่าการตรวจสอบสุขภาพ สำหรับเซิร์ฟเวอร์การผลิต
- ตั้งค่าการตรวจสอบ สำหรับปัญหาเวบฮุก
- ทดสอบกรณีของความล้มเหลว ก่อนไปใช้งาน
- ตั้งค่าการแจ้งเตือน สำหรับรูปแบบข้อผิดพลาดที่ผิดปกติ
เสร็จแล้ว ไปไปใช้งาน ดูProduction Checklist
ต้องการความปลอดภัย ดูความปลอดภัยเวบฮุก