DEV Community

ISNDEV
ISNDEV

Posted on

Caching, Queuing, and Pub/Sub: Supercharging Your C++ App with Redis and `qbm-redis`

Redis is more than a cache; it's a multi-tool for building high-performance systems. Discover how qb's asynchronous qbm-redis client makes it easy to leverage Redis for caching, message queuing, and real-time Pub/Sub in your C++ applications.

Target Audience: Intermediate to Advanced C++ developers building systems that require caching, job queues, or real-time messaging.

GitHub: https://github.com/isndev/qbm-redis


In modern distributed systems, performance and scalability often depend on fast access to data and efficient communication between components. Redis, the in-memory data structure store, is the industry standard for solving these problems.

The qbm-redis module brings the full power of Redis to the qb actor framework with a clean, asynchronous, and type-safe C++ client. It allows your actors to interact with Redis without ever blocking, enabling you to build incredibly responsive and scalable systems.

Let's explore three common patterns where qbm-redis shines.

1. High-Performance Caching

The most common use for Redis is as a distributed cache to reduce database load and speed up response times. qbm-redis makes this trivial.

The pattern is simple: before making an expensive database call or computation, check the cache.

// In a qb::Actor that needs to fetch user data...
#include <redis/redis.h>

class UserDataService : public qb::Actor {
private:
    qb::redis::tcp::client _redis{"tcp://localhost:6379"};
    // ... database connection member ...

public:
    bool onInit() override {
        if (!_redis.connect()) {
            qb::io::cerr() << "Failed to connect to Redis" << std::endl;
            return false;
        }
        // ...
        return true;
    }

    void handle_user_request(int user_id) {
        std::string cache_key = "user:" + std::to_string(user_id);

        // 1. Check the cache first (using async call with callback)
        _redis.get(cache_key, [this, user_id, cache_key](auto&& reply) {
            if (reply.ok() && reply.result().has_value()) {
                // Cache hit! Use the cached data.
                qb::io::cout() << "Cache HIT for user " << user_id << std::endl;
                std::string user_json = *reply.result();
                // ... process user_json and send response ...
                return;
            }

            // 2. Cache miss. Fetch from the database (this would be another async call).
            qb::io::cout() << "Cache MISS for user " << user_id << ". Fetching from DB." << std::endl;
            std::string data_from_db = "{\"id\": " + std::to_string(user_id) + ", \"name\": \"Alice\"}";

            // 3. Store the result in the cache for next time with an expiration.
            //    SETEX sets a key with a Time-To-Live (TTL).
            _redis.setex(cache_key, 300, data_from_db); // Cache for 5 minutes

            // ... process data_from_db and send response ...
        });
    }
};
Enter fullscreen mode Exit fullscreen mode

This simple pattern can dramatically improve your application's performance by offloading work from your primary database.

2. Reliable Job Queues with Lists

How do you distribute work among a pool of worker actors? A Redis List is a perfect tool for a reliable job queue. Producers LPUSH jobs onto one end of the list, and workers use BRPOP (blocking list pop) to atomically pull jobs from the other end.

BRPOP is a blocking command, but when used with qbm-redis in a qb actor, it's non-blocking from the framework's perspective. The actor remains responsive to other events while waiting for a job.

Based on examples/qbm/redis/example3_list_operations.cpp

// In a WorkerActor...
#include <redis/redis.h>

class WorkerActor : public qb::Actor, public qb::ICallback {
private:
    qb::redis::tcp::client _redis{"tcp://localhost:6379"};
    const std::string _queue_key = "jobs:image_processing";

public:
    bool onInit() override {
        _redis.connect();
        registerCallback(*this); // Periodically check for jobs
        return true;
    }

    // Periodically check for a new job
    void onCallback() override {
        // Use BRPOP with a timeout of 1 second.
        // The callback-based version is ideal here.
        _redis.brpop({_queue_key}, 1, [this](auto&& reply) {
            if (reply.ok() && reply.result().has_value()) {
                // Job received! The result is a pair of {key, value}
                auto& job_data = reply.result()->second;
                qb::io::cout() << "Worker " << id() << " processing job: " << job_data << std::endl;
                // ... do the actual work ...
            } else {
                // No job received in the last second.
                qb::io::cout() << "Worker " << id() << ": No jobs in queue." << std::endl;
            }
        });
    }
};

// In a ProducerActor...
void submit_job(const std::string& job_data) {
    // LPUSH pushes to the head of the list.
    _redis.lpush("jobs:image_processing", job_data);
}
Enter fullscreen mode Exit fullscreen mode

3. Real-Time Messaging with Publish/Subscribe

For broadcasting real-time events, Redis Pub/Sub is a powerful and simple solution. Publishers send messages to a "channel" without knowing who, if anyone, is listening. Subscribers express interest in one or more channels and receive messages as they are published.

qbm-redis provides a specialized cb_consumer client for handling subscriptions efficiently.

Based on examples/qbm/redis/example5_pubsub_example.cpp

#include <redis/redis.h>

// 1. The Subscriber Actor
class SubscriberActor : public qb::Actor {
private:
    // The cb_consumer takes a callback in its constructor for handling messages.
    qb::redis::tcp::cb_consumer _consumer{"tcp://localhost:6379", 
        [this](auto&& msg) {
            // This lambda is called by the consumer when a message arrives
            qb::io::cout() << "Subscriber " << id() << " received on '" 
                           << msg.channel << "': " << msg.message << std::endl;
        }
    };
public:
    bool onInit() override {
        _consumer.connect();
        // Subscribe to a channel
        _consumer.subscribe("app:notifications");
        return true;
    }
};

// 2. The Publisher Actor
class PublisherActor : public qb::Actor {
private:
    qb::redis::tcp::client _redis{"tcp://localhost:6379"};
public:
    bool onInit() override {
        _redis.connect();
        return true;
    }

    void send_notification(const std::string& message) {
        // Publish a message to the channel
        long subscribers_count = _redis.publish("app:notifications", message);
        qb::io::cout() << "Published notification to " << subscribers_count 
                       << " subscribers." << std::endl;
    }
};
Enter fullscreen mode Exit fullscreen mode

This pattern is perfect for building real-time dashboards, chat systems, or any application where components need to react instantly to events happening elsewhere in the system.

With qbm-redis, you can easily integrate these powerful Redis patterns into your high-performance C++ actor system, building applications that are fast, scalable, and resilient.

Explore the qbm-redis module on GitHub:

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.