Use Cases of Redis In Software Systems

Overview

Redis is an in-memory data structure store that is often used as a database, cache, and message broker. There are several benefits to using Redis, including:

  • Speed: Redis is very fast, as it stores data in memory instead of on disk. This makes it well-suited for applications that require low-latency access to large amounts of data.
  • Flexibility: Redis supports a wide range of data structures, including strings, hashes, lists, sets, sorted sets, and more. This allows it to be used for a wide range of purposes, from simple key-value storage to complex data modeling.
  • Scalability: Redis supports replication and sharding, which allows it to scale horizontally to support very large amounts of data and high levels of throughput.
  • Multi-purpose: Redis can be used as a database, cache, and message broker, making it a versatile tool for many different types of applications.

In this post, we are going to discuss the different use cases of Redis in software systems.

Before you start, if you want to start a Redis instance quickly, here is the docker compose file:

version: '3'

services:
  redis:
    image: redis:6.2.7-alpine
    ports:
      - "6379:6379"

Redis as a database

As a database, Redis can be used to store and retrieve large amounts of data in real time. It is handy for storing data that is frequently accessed and updated, as it can retrieve and update data much faster than a traditional disk-based database.

Redis as a database example in Java

package com.datmt.java.redis_example.db;

import redis.clients.jedis.Jedis;

public class RedisDatabase {
    private static final String KEY = "my-password";
    private static final String MY_CAR_KEY = "my-car";

    public static void main(String[] args) {
        // Create a new Jedis client
        Jedis jedis = new Jedis("localhost", 6379);

        // Set the value of the key
        jedis.set(KEY, "123456");

        // Retrieve the value of the key
        String value = jedis.get(KEY);
        System.out.println("My password: " + value);

        jedis.del(KEY);

        //Try to get the key after deleted
        System.out.println("My password: " + jedis.get(KEY));
    }


}

Here, we create a new Jedis client and use it to store, retrieve, and delete data from a Redis database. We use the set and get methods to store and retrieve the value of a key named my-password.

We also use the del method to delete the key and its associated value from the database.

The code above produces the following output:

Basic database operations in Redis
Basic database operations in Redis

This demonstrates the basic operations that can be performed on a Redis database using the Jedis client. Redis also supports more advanced features such as transactions (we are going to try in the next section) and data structures, which can be used to implement more complex data storage and retrieval scenarios.

Redis transaction example in Java

package com.datmt.java.redis_example.db;


import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

public class RedisDatabaseTransaction {
    private static final String KEY_1 = "key-1";
    private static final String KEY_2 = "key-2";
    private static final String KEY_3 = "key-3";
    private static final String KEY_4 = "key-4";

    public static void main(String[] args) {
       happyTransaction();
        try {
            failedTransaction();
        } catch (Exception e) {
            //
        }
       var jedis = new Jedis("localhost", 6379) ;
        //try to get the data
        System.out.println("Key 1: " + jedis.get(KEY_1));
        System.out.println("Key 2: " + jedis.get(KEY_2));
        System.out.println("Key 3: " + jedis.get(KEY_3));
        System.out.println("Key 4: " + jedis.get(KEY_4));
    }

    private static void happyTransaction() {
        Jedis jedis = new Jedis("localhost", 6379);

        // Start a new transaction
        Transaction transaction = jedis.multi();

        // Queue up some commands to be executed in the transaction
        transaction.set(KEY_1, "value-1");
        transaction.set(KEY_2, "value-2");

        // Execute the transaction
        transaction.exec();
    }

    private static void failedTransaction () throws Exception {
        Jedis jedis = new Jedis("localhost", 6379);

        // Start a new transaction
        Transaction transaction = jedis.multi();

        // Queue up some commands to be executed in the transaction
        transaction.set(KEY_3, "value-3");
        transaction.set(KEY_4, "value-4");

        //simulate bad things happen
        if (true) {
            throw new RuntimeException("Something bad happened");
        }
        // Execute the transaction
        transaction.exec();
    }
}

The example above demonstrates the concept of transaction in Redis. We created two methods to simulate the happy case and failed case. The happy transaction completes without exception and the failed case has a run time exception.

Running the code above would produce the following output:

Transaction in Redis
Transaction in Redis

As you can see, we were able to set the value of KEY_1 and KEY_2. However, in the case of KEY_3 and KEY_4, the transaction was not committed due to a RuntimeException. Thus, there were no value is written to Redis.

Redis as a cache

As a cache, Redis can be used to store frequently accessed data in memory, allowing it to be retrieved quickly. This can improve the performance of a system by reducing the number of expensive database queries that need to be made.

Redis as a cache example in Java

Let’s implement a simple caching scenario using Redis in Java.

Consider there is a long calculation (digging digital coin for example). Someone need to use a lot of computation resources to calculate the hash:

    private static String diggingBotCoin() {
        try {
            System.out.println("simulate long calculation");
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // Perform some computation to calculate the value
        return "Found the hash!!!!";
    }

After the calculation, we want to store this value in the cache so we don’t have to wait another long period to get the value.

Here is a simple example to demonstrate the caching mechanism using Redis

    private static void simpleCaching() {

        Jedis jedis = new Jedis("localhost", 6379);

        // Check if the value is in the cache
        if (jedis.exists(CACHE_KEY)) {
            // Retrieve the value from the cache
            String value = jedis.get(CACHE_KEY);
            System.out.println("Retrieved value from cache: " + value);
        } else {
            // Calculate the value
            String value = diggingBotCoin();
            // Store the value in the cache
            jedis.set(CACHE_KEY, value);
            System.out.println("Stored value in cache: " + value);
        }
    }

When calling the function simpleCaching, the value is not found in the cache. Thus, our program would run for about 5 seconds. However, the subsequent run returned almost instantly since the value is available in the Redis cache.

Data is found in the cache after the first run
Data is found in the cache after the first run

Using Redis as a cache allows us to efficiently retrieve frequently accessed data from the cache, improving the performance of our system. If the data changes, we can use the set method to update the value in the cache, ensuring that the cache always contains the most up-to-date information.

Redis as a message broker

As a message broker, Redis can be used to enable communication between different components of a system. It can be used to implement a message queue, allowing parties to send and receive messages asynchronously. This can improve the scalability and reliability of a system, as components can continue to operate even if other components are unavailable or experiencing high levels of traffic.

Use Redis as a message broker in Java

Here is a quick example of using Redis as a message broker in Java.

First, we create a subscribe to a channel called sample-channel. When messages is published to this channel, the subscriber will print out the messages.

package com.datmt.java.redis_example.broker;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPubSub;

public class RedisBrokerSubscriber {
    private static final String CHANNEL_NAME = "sample-channel";

    public static void main(String[] args) {
        // Create a new Jedis client
        Jedis jedis = new Jedis("localhost", 6379);

        // Subscribe to the channel
        jedis.subscribe(new MyMessageListener(), CHANNEL_NAME);
    }

    private static class MyMessageListener extends JedisPubSub {
        @Override
        public void onMessage(String channel, String message) {
            // Handle incoming message
            System.out.println("Received message: " + message);
        }
    }
}

Next, let’s create a publisher to send messages to the same channel.

package com.datmt.java.redis_example.broker;


import redis.clients.jedis.Jedis;

public class RedisBrokerPublisher {
    private static final String CHANNEL_NAME = "sample-channel";

    public static void main(String[] args) {
        // Create a new Jedis client
        Jedis jedis = new Jedis("localhost", 6379);

        // Subscribe to the channel
        jedis.publish(CHANNEL_NAME, "Hello, can you hear me?");
    }
}

Now, let’s run the consumer first. Next, run the publisher to send a message.

Switch to the consumer to see the message in the console:

Message received in the consumer
Message received by the consumer

Conclusion

Overall, Redis can play a crucial role in improving the performance, scalability, and reliability of a software system.

As usual, the code for this post is available here on Github

Leave a Comment