0
0
LldHow-ToIntermediate ยท 4 min read

How to Design Pub Sub System LLD: Low-Level Design Guide

To design a Pub Sub system at low-level, create components for Publishers, Subscribers, and a Broker that manages message distribution. Use topics to categorize messages, and implement asynchronous message delivery with queues to ensure scalability and decoupling.
๐Ÿ“

Syntax

A Pub Sub system consists of three main parts:

  • Publisher: Sends messages to topics.
  • Subscriber: Registers to topics and receives messages.
  • Broker: Manages topics, subscriptions, and message delivery.

Messages are published to a Topic. Subscribers subscribe to topics to receive messages asynchronously.

java
import java.util.*;

class Broker {
    private Map<String, List<Subscriber>> topicSubscribers = new HashMap<>();

    public void subscribe(String topic, Subscriber subscriber) {
        topicSubscribers.computeIfAbsent(topic, k -> new ArrayList<>()).add(subscriber);
    }

    public void publish(String topic, String message) {
        List<Subscriber> subscribers = topicSubscribers.getOrDefault(topic, Collections.emptyList());
        for (Subscriber sub : subscribers) {
            sub.receive(message);
        }
    }
}

interface Subscriber {
    void receive(String message);
}

class Publisher {
    private Broker broker;
    public Publisher(Broker broker) {
        this.broker = broker;
    }
    public void publish(String topic, String message) {
        broker.publish(topic, message);
    }
}
๐Ÿ’ป

Example

This example shows a simple Pub Sub system where a publisher sends messages to a topic, and subscribers receive them asynchronously.

java
import java.util.*;

interface Subscriber {
    void receive(String message);
}

class Broker {
    private Map<String, List<Subscriber>> topicSubscribers = new HashMap<>();

    public void subscribe(String topic, Subscriber subscriber) {
        topicSubscribers.computeIfAbsent(topic, k -> new ArrayList<>()).add(subscriber);
    }

    public void publish(String topic, String message) {
        List<Subscriber> subscribers = topicSubscribers.getOrDefault(topic, Collections.emptyList());
        for (Subscriber sub : subscribers) {
            sub.receive(message);
        }
    }
}

class Publisher {
    private Broker broker;

    public Publisher(Broker broker) {
        this.broker = broker;
    }

    public void publish(String topic, String message) {
        broker.publish(topic, message);
    }
}

class SimpleSubscriber implements Subscriber {
    private String name;

    public SimpleSubscriber(String name) {
        this.name = name;
    }

    @Override
    public void receive(String message) {
        System.out.println(name + " received: " + message);
    }
}

public class PubSubDemo {
    public static void main(String[] args) {
        Broker broker = new Broker();
        Publisher publisher = new Publisher(broker);

        Subscriber sub1 = new SimpleSubscriber("Subscriber1");
        Subscriber sub2 = new SimpleSubscriber("Subscriber2");

        broker.subscribe("news", sub1);
        broker.subscribe("news", sub2);

        publisher.publish("news", "Hello Subscribers!");
    }
}
Output
Subscriber1 received: Hello Subscribers! Subscriber2 received: Hello Subscribers!
โš ๏ธ

Common Pitfalls

Common mistakes when designing Pub Sub systems include:

  • Not decoupling publishers and subscribers properly, causing tight coupling.
  • Blocking message delivery, which reduces scalability.
  • Not handling subscriber failures or slow consumers, leading to message loss or delays.
  • Ignoring message ordering or duplication issues.

Use asynchronous queues and retries to avoid these pitfalls.

java
/* Wrong: Direct call blocks publisher until subscriber finishes */
import java.util.*;

class Broker {
    private Map<String, List<Subscriber>> topicSubscribers = new HashMap<>();

    public void publish(String topic, String message) {
        List<Subscriber> subscribers = topicSubscribers.getOrDefault(topic, Collections.emptyList());
        for (Subscriber sub : subscribers) {
            sub.receive(message); // Blocking call
        }
    }
}

/* Right: Use a queue and worker threads for async delivery */
import java.util.concurrent.*;

class AsyncBroker {
    private Map<String, List<Subscriber>> topicSubscribers = new ConcurrentHashMap<>();
    private ExecutorService executor = Executors.newFixedThreadPool(4);

    public void subscribe(String topic, Subscriber subscriber) {
        topicSubscribers.computeIfAbsent(topic, k -> new CopyOnWriteArrayList<>()).add(subscriber);
    }

    public void publish(String topic, String message) {
        List<Subscriber> subscribers = topicSubscribers.getOrDefault(topic, Collections.emptyList());
        for (Subscriber sub : subscribers) {
            executor.submit(() -> sub.receive(message));
        }
    }
}
๐Ÿ“Š

Quick Reference

  • Publisher: Sends messages to topics.
  • Subscriber: Registers to topics and processes messages.
  • Broker: Manages subscriptions and delivers messages asynchronously.
  • Topics: Named channels for message categorization.
  • Asynchronous Delivery: Use queues and worker threads to avoid blocking.
  • Scalability: Decouple components and handle failures gracefully.
โœ…

Key Takeaways

Design Pub Sub with clear roles: Publisher, Subscriber, and Broker.
Use asynchronous message delivery to ensure scalability and decoupling.
Manage topics to organize messages and subscriptions effectively.
Handle subscriber failures and slow consumers to avoid message loss.
Avoid blocking calls in message delivery by using queues and worker threads.