"After all, the engineers only needed to refuse to fix anything, and modern industry would grind to a halt." -Michael Lewis

Enable Massive Growth

Subscribing to Redis Channels with Java, Spring Boot, and Lettuce

Apr 2021

The source code for what follows can be found on Github.

Pub/Sub in redis allows a publisher to send things to subscribers without knowing who is actually subscribed. In a previous post, we covered a simple unit test for publishing and subscribing to lettuce, but if you want to have a subscription initialized on application startup, and respond to events, we'll have to do a bit more, which I'll demonstrate here.

Subscribing on Application Startup

We will want to make sure we have the right configuration to connect to redis using lettuce with something like:

@Configuration
public class RedisConfig {

    @Bean("redis-primary-client")
    public RedisClient redisClient(RedisPrimaryConfig redisPrimaryConfig) {
        return RedisClient.create(
                // adjust things like thread pool size with client resources
                ClientResources.builder().build(),
                "redis://" + redisPrimaryConfig.getHost() + ":" + redisPrimaryConfig.getPort()
        );
    }
}

Where our RedisPrimaryConfig looks like:

@Configuration
@ConfigurationProperties(prefix = "redis-primary")
public class RedisPrimaryConfig {
    private String host;
    private Integer port;

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public Integer getPort() {
        return port;
    }

    public void setPort(Integer port) {
        this.port = port;
    }
}

And our application.yml has the host and port [this example is a locally redis instance]:

redis-primary:
  host: 127.0.0.1
  port: 6379

We can then add our RedisPubSubReactiveCommands bean to our RedisConfig configuration class:

    @Bean("redis-subscription-commands")
    public RedisPubSubReactiveCommands<String, String> redisPubSubReactiveCommands(RedisClient redisClient) {
        return redisClient.connectPubSub().reactive();
    }

With the boilerplate out of the way, we can finally leverage @PostConstruct to subscribe to one or more redis channels of our choosing, just after we initialize our IoC container and just before the application finishes starting up:

@Service
public class RedisSubscriptionInitializer {

    private final Logger LOG = LoggerFactory.getLogger(RedisSubscriptionInitializer.class);

    private final RedisPubSubReactiveCommands<String, String> redisPubSubReactiveCommands;

    public RedisSubscriptionInitializer(RedisPubSubReactiveCommands<String, String> redisPubSubReactiveCommands) {
        this.redisPubSubReactiveCommands = redisPubSubReactiveCommands;
    }

    @PostConstruct
    public void setupSubscriber() {
        redisPubSubReactiveCommands.subscribe("channel-1").subscribe();

        redisPubSubReactiveCommands.observeChannels().doOnNext(stringStringChannelMessage -> {
            if ("channel-1".equals(stringStringChannelMessage.getChannel())) {
                LOG.info("found message in channel 1: {}", stringStringChannelMessage.getMessage());
            }
        }).subscribe();
    }
}

In this case, we're just logging all the messages we get from channel-1, you could obviously introduce whatever code you want there [you could also do something other than doOnNext, for example flatMap].

If I start up this application and have my local redis instance up and running, I can:

$redis-cli publish channel-1 some-message-1
(integer) 1
$redis-cli publish channel-1 some-message-2
(integer) 1

Note that the response indicates how many subscribers the message was delivered to. I can then cross check the logs on my application:

[llEventLoop-5-2] c.n.r.s.RedisSubscriptionInitializer     : found message in channel 1: some-message-1
[llEventLoop-5-2] c.n.r.s.RedisSubscriptionInitializer     : found message in channel 1: some-message-2

Then, if I SIGINT the application and try sending another message, I will see that it delivers it to zero subscribers:

$ redis-cli publish channel-1 some-message-3
(integer) 0

So this should be a good starting point for you.

Nick Fisher is a software engineer in the Pacific Northwest. He focuses on building highly scalable and maintainable backend systems.