Hone logo
Hone
Problems

Implement a Pub/Sub Messaging System in Go

This challenge asks you to build a fundamental component of many distributed systems: a publish-subscribe (pub/sub) messaging system. A pub/sub system allows publishers to send messages without knowing who the subscribers are, and subscribers to receive messages without knowing who the publishers are. This decoupling is crucial for building scalable and resilient applications.

Problem Description

Your task is to implement a concurrent, in-memory publish-subscribe messaging system in Go. The system should support the following operations:

  • Publish: A publisher sends a message to a specific topic.
  • Subscribe: A subscriber registers interest in a specific topic, and will receive messages published to that topic.
  • Unsubscribe: A subscriber can stop receiving messages for a topic.

The system should be thread-safe, meaning multiple goroutines can publish and subscribe concurrently without data corruption or race conditions. Messages should be delivered to all active subscribers of a topic.

Key Requirements:

  1. Publisher Interface: Define an interface for publishing messages.
  2. Subscriber Interface: Define an interface for receiving messages.
  3. PubSub Structure: Implement a central PubSub structure that manages topics, subscribers, and message routing.
  4. Topic-Based Routing: Messages published to a topic should only be delivered to subscribers of that same topic.
  5. Concurrency Safety: The PubSub system must be safe for concurrent use by multiple goroutines.
  6. Channel-Based Delivery: Subscribers should receive messages via Go channels.
  7. Unsubscribe Mechanism: Subscribers must be able to cleanly unsubscribe from topics.

Expected Behavior:

  • When a message is published to a topic, all currently subscribed subscribers for that topic should receive the message.
  • Subscribers should receive messages in the order they are published to a given topic.
  • Unsubscribing should immediately stop message delivery for that subscriber on that topic.
  • Publishing to a topic with no subscribers should not cause an error.

Edge Cases:

  • Subscribing to a topic that doesn't exist yet.
  • Unsubscribing from a topic the subscriber is not subscribed to.
  • Publishing or subscribing with empty topic names.
  • Handling a large number of publishers, subscribers, and messages.

Examples

Example 1: Basic Publish and Subscribe

// Assume a PubSub instance `ps` is created and initialized

// Create a subscriber
sub1Chan := make(chan string)
ps.Subscribe("news", sub1Chan)

// Publish a message
ps.Publish("news", "Breaking news: Go 2.0 announced!")

// In a separate goroutine (or after a short delay to allow delivery)
// read from sub1Chan: "Breaking news: Go 2.0 announced!"

// Subscribe another
sub2Chan := make(chan string)
ps.Subscribe("news", sub2Chan)

// Publish another message
ps.Publish("news", "Weather update: Sunny day expected.")

// Read from sub1Chan: "Weather update: Sunny day expected."
// Read from sub2Chan: "Weather update: Sunny day expected."

Explanation: The first message is published to "news" and received by sub1Chan. The second message is published to "news" and received by both sub1Chan and sub2Chan.

Example 2: Unsubscribing

// Assume a PubSub instance `ps` is created and initialized

subChanA := make(chan string)
ps.Subscribe("alerts", subChanA)

subChanB := make(chan string)
ps.Subscribe("alerts", subChanB)

ps.Publish("alerts", "System warning: High CPU usage.")
// Both subChanA and subChanB receive the message.

ps.Unsubscribe("alerts", subChanA)

ps.Publish("alerts", "Critical alert: Disk space low.")
// Only subChanB receives this message.

Explanation: Initially, both subscribers receive the "System warning". After subChanA unsubscribes, it no longer receives messages, so only subChanB receives the "Critical alert".

Example 3: Multiple Topics and Unsubscribe from Non-Existent Topic

// Assume a PubSub instance `ps` is created and initialized

subFinance := make(chan string)
ps.Subscribe("finance", subFinance)

subSports := make(chan string)
ps.Subscribe("sports", subSports)

ps.Publish("finance", "Dow Jones up 200 points.")
// subFinance receives: "Dow Jones up 200 points."

ps.Publish("sports", "Local team wins championship!")
// subSports receives: "Local team wins championship!"

ps.Unsubscribe("finance", subSports) // No-op, subSports is not subscribed to finance
ps.Unsubscribe("sports", subFinance) // No-op, subFinance is not subscribed to sports

ps.Publish("finance", "Inflation rate stabilizes.")
// subFinance receives: "Inflation rate stabilizes."
// subSports receives nothing.

Explanation: Messages are routed correctly to their respective topics. Attempting to unsubscribe a subscriber from a topic they are not subscribed to has no effect.

Constraints

  • The PubSub system must be implemented entirely in memory. No external message brokers (like Kafka, RabbitMQ) or persistent storage should be used.
  • The system should handle at least 1000 concurrent publishers and 1000 concurrent subscribers without significant performance degradation.
  • Message delivery should be best-effort, meaning message loss due to concurrent access is not expected, but external factors like channel buffer overflows (if not managed) are potential concerns to be mindful of.
  • Topic names and message payloads will be represented as string types.
  • The system should be able to handle an unbounded number of topics.

Notes

  • Consider using Go's built-in concurrency primitives like sync.Mutex, sync.RWMutex, and channels.
  • A map is likely a good data structure to store topics and their associated subscribers.
  • Think about how to manage the lifecycle of subscriber channels, especially when unsubscribing.
  • How will you ensure that publishing and subscribing operations are atomic and safe from race conditions?
  • The Subscribe method should return an identifier or the channel itself, which can then be used for Unsubscribe. For simplicity, you can just pass the channel directly to Unsubscribe.
Loading editor...
go