DEV Community

Cover image for Redis pub/sub with ruby
Davide Santangelo
Davide Santangelo

Posted on

Redis pub/sub with ruby

Redis is an in-memory data structure store that is often used as a database, cache, and message broker. One of its most powerful features is its publish-subscribe functionality, which allows you to implement messaging and communication between different parts of your application.

The publish-subscribe pattern in Redis is based on the concept of channels. A Redis client can subscribe to one or more channels, and other clients can publish messages to these channels. When a client publishes a message to a channel, all clients that are subscribed to that channel will receive the message in real-time. This allows for easy communication and messaging between different parts of your application.

To use Redis's publish-subscribe functionality in your Ruby application, you can use the redis gem, which provides a Ruby interface for communicating with a Redis server. With the redis gem, you can subscribe to Redis channels and receive messages published to those channels, as well as publish messages to channels for other clients to receive.

Here is an example of using the redis gem to implement a simple publish-subscribe system:

require 'redis'

# Connect to a Redis server
redis = Redis.new

# Subscribe to a Redis channel
redis.subscribe('channel1') do |on|
  on.message do |channel, message|
    # Print the message when it is received
    puts "Received message on channel #{channel}: #{message}"
  end
end

# Publish a message to the Redis channel
redis.publish('channel1', 'Hello, world!')
Enter fullscreen mode Exit fullscreen mode

In this example, the client subscribes to the channel1 channel, and then publishes a message to that channel. When the message is published, the client that is subscribed to the channel1 channel will receive the message and print it to the console.

One of the key benefits of using the publish-subscribe pattern in Redis is that it allows for easy communication between different parts of your application. For example, you could use Redis's publish-subscribe functionality to implement real-time notifications or updates in a web application. When something happens on the server, such as a new data being added to the database, you can publish a message to a Redis channel, and any clients that are subscribed to that channel will receive the message and update their interface accordingly.

Additionally, Redis's publish-subscribe functionality is very fast and efficient, because it is based on Redis's in-memory data store. This means that messages can be published and received almost instantly, with very low latency. This can be useful for implementing real-time communication and messaging in your applications.

Redis's publish-subscribe functionality also provides some advanced features, such as the ability to pattern-match on channel names, and the ability to use wildcards to subscribe to multiple channels at once. This allows you to implement more complex messaging and communication scenarios in your applications.

In conclusion, Redis's publish-subscribe functionality is a powerful and useful tool for implementing messaging and communication between different parts of your application. Whether you are building a web application, a mobile app, or any other kind of software, Redis's publish-subscribe functionality can help you implement real-time communication and messaging in a fast and efficient way.

Here is an example of how RSpec tests could be added to the previous code example:

require 'redis'
require 'rspec'

describe 'Redis pub/sub' do
  before(:each) do
    # create a new Redis client
    @redis = Redis.new

    # subscribe to the 'chat' channel
    @redis.subscribe('channel1') do |on|
      @message = nil

      on.message do |channel, message|
        # store the received message
        @message = message
      end
    end
  end

  it 'receives a published message' do
    # publish a message to the 'chat' channel
    @redis.publish('channel1', 'Hello, world!')

    # wait for the message to be received
    sleep(0.1)

    # expect the received message to be the one that was published
    expect(@message).to eq('Hello, world!')
  end
end
Enter fullscreen mode Exit fullscreen mode

In this code, the describe block defines a group of tests for the Redis pub/sub functionality. The before block is run before each test, and it creates a new Redis client and subscribes to the 'chat' channel. The it block defines a single test, which publishes a message to the 'channel1' channel and then checks that the message was received by the subscriber.

Note that the sleep call is used to wait for the message to be received, because the pub/sub functionality is asynchronous. This is not ideal and can make tests slower and less reliable. In a real application, it would be better to use some kind of synchronization mechanism to ensure that the message is received before checking the expected result.

https://redis.io/docs/manual/pubsub/

Latest comments (2)

Collapse
 
katafrakt profile image
Paweł Świątkowski

I recall using some helper such as wait_for for tests like this, instead of putting arbitrary sleeps ;)

Code would be something like this:

def wait_for(value, max)
  Timeout.timeout(max) do
     loop { break unless value.nil? }
  end
end

# and then
wait_for(@message, 0.5)
Enter fullscreen mode Exit fullscreen mode
Collapse
 
anykeyh profile image
Yacine Petitprez

This is very inefficient because of active waiting. The main thread will use 100% of the CPU while waiting.

Using Queue is the way to go:

before(:each) do
    # create a new Redis client
    @redis = Redis.new
    @queue = Queue.new

    # subscribe to the 'chat' channel
    @redis.subscribe('channel1') do |on|
      on.message do |channel, message|
        # store the received message
         @queue.push(message)
      end
    end
  end

  it 'receives a published message' do
    # publish a message to the 'chat' channel
    @redis.publish('channel1', 'Hello, world!')

    # expect the received message to be the one that was published
    expect(@queue.pop).to eq("Hello, world!")
  end

Enter fullscreen mode Exit fullscreen mode