System Design
Publish-Subscribe pattern
Pub/Sub — bir xabarni ko’plab subscriber’larga yuborish.
Queue vs Pub/Sub
Queue (1-to-1)
Producer → Queue → 1 Consumer
(har bir message bitta consumer)
Pub/Sub (1-to-Many)
Publisher → Topic → Subscriber 1
→ Subscriber 2
→ Subscriber 3
(har bir message barcha subscriber'larga)
Use Cases
1. Real-time Notifications
// User likes post
publisher.publish('post:123:liked', { userId: 456 });
// Multiple subscribers
subscriber1.on('post:123:liked', () => {
// Update counter in UI
});
subscriber2.on('post:123:liked', () => {
// Send notification to author
});
subscriber3.on('post:123:liked', () => {
// Update analytics
});
2. Microservices Communication
Order Service → publishes "order.created"
↓
┌─────────────┼─────────────┐
▼ ▼ ▼
Email Service Inventory Analytics
(send receipt) (decrease) (track)
3. Cache Invalidation
// Update user
await db.updateUser(123, data);
publisher.publish('user:123:updated', data);
// All cache servers listen
subscriber.on('user:*:updated', (data) => {
cache.del(`user:${data.id}`);
});
Redis Pub/Sub
const redis = require('redis');
// Publisher
const pub = redis.createClient();
await pub.publish('notifications', JSON.stringify({ message: 'Hello' }));
// Subscriber
const sub = redis.createClient();
await sub.subscribe('notifications', (message) => {
console.log('Received:', JSON.parse(message));
});
// Pattern subscription
await sub.pSubscribe('user:*:updated', (message, channel) => {
console.log('Channel:', channel);
console.log('Message:', message);
});
Reliability: Redis pub/sub не гарантирует delivery (subscriber offline = message lost)
Reliable Pub/Sub (Redis Streams)
// Publisher
await redis.xAdd('events', '*', {
type: 'user.created',
data: JSON.stringify({ id: 123 })
});
// Consumer group (reliable)
await redis.xGroupCreate('events', 'processors', '0');
await redis.xReadGroup('processors', 'consumer1', {
key: 'events',
id: '>'
});
Guaranteed delivery
Consumer groups
Message persistence
Apache Kafka (Production-grade)
// Producer
await producer.send({
topic: 'user-events',
messages: [
{ key: '123', value: JSON.stringify({ type: 'created' }) }
]
});
// Consumer
await consumer.subscribe({ topic: 'user-events' });
await consumer.run({
eachMessage: async ({ topic, partition, message }) => {
console.log({
value: message.value.toString()
});
}
});
Features:
- Millions messages/sec
- Persistence (days/weeks)
- Consumer groups
- Exactly-once semantics
Best Practices
1. Idempotent subscribers
// Message may arrive multiple times
subscriber.on('order.created', async (order) => {
const exists = await db.exists(`processed:${order.id}`);
if (exists) return; // Skip
await processOrder(order);
await db.set(`processed:${order.id}`, true);
});
2. Error handling
subscriber.on('event', async (data) => {
try {
await process(data);
} catch (err) {
console.error('Failed to process:', err);
// Don't throw - other subscribers should still receive
}
});
3. Schema versioning
// Forward compatible
{
version: 2,
type: 'user.created',
data: { ... }
}
subscriber.on('user.created', (event) => {
if (event.version === 1) {
// Handle old format
} else if (event.version === 2) {
// Handle new format
}
});
Xulosa
Pub/Sub:
- 1-to-many broadcasting
- Real-time notifications
- Microservices communication
Tools:
- Redis Pub/Sub - simple, fast
- Redis Streams - reliable
- Kafka - production-grade
Keyingi dars: Event-Driven Architecture.