31 Practical Scheduled Task Case Study

31 Practical Scheduled Task Case Study #

During my development, I encountered a problem where the product required sending a reminder push notification to each patient who made an online appointment for a doctor’s visit one day before the appointment time to prevent patients from missing their appointment. At this time, we need to set a scheduled task for each person. As mentioned in the previous article, delayed queues can also achieve this, but the implementation of delayed queues requires starting an infinite loop task. Are there any other ways to implement it?

The answer is yes, and next we will use Keyspace Notifications to implement the scheduled task. A scheduled task refers to specifying a time to execute a certain task.

Enabling Keyspace Notifications #

By default, the Redis server does not enable Keyspace Notifications, and we need to enable it manually.

There are two ways to enable it:

  • Command-based setting
  • Configuration file-based setting

Next, we will look at each one of them.

Command-based Setting #

After connecting to the server using redis-cli, enter the command config set notify-keyspace-events Ex to directly enable the Keyspace Notifications feature. If “OK” is returned, it means the enabling is successful, as shown in the following command:

127.0.0.1:6379> config set notify-keyspace-events Ex
OK

Advantages:

  • Easy to set up and can be done without restarting the Redis server.

Disadvantages:

  • The configuration information set by this method is stored in memory, so after Redis server restarts, the configuration will be lost.

Configuration file-based Setting #

Find the Redis configuration file redis.conf and set the configuration item notify-keyspace-events Ex, and then restart the Redis server.

Advantages:

  • The configuration will not be lost no matter how many times the Redis server restarts.

Disadvantages:

  • Redis server needs to be restarted.

Configuration Explanation #

It can be seen that no matter which method is used, the notify-keyspace-events Ex is set. Among them, Ex indicates the key expiration event within the key event notification.

More configuration item explanations are as follows:

  • K: Keyspace notification, all notifications begin with __keyspace@<db>__
  • E: Key event notification, all notifications begin with __keyevent@<db>__
  • g: Notifications for general commands that are not specific to DEL, EXPIRE, RENAME, etc.
  • $: Notifications for string commands
  • l: Notifications for list commands
  • s: Notifications for set commands
  • h: Notifications for hash commands
  • z: Notifications for sorted set commands
  • x: Expiration events, sent whenever an expired key is deleted
  • e: Eviction events, sent whenever a key is deleted due to the maxmemory policy
  • A: Alias for the parameters g$lshzxe

The above configuration items can be freely combined. For example, the subscription of list events is El. However, it is important to note that if the value of notify-keyspace-event is set to empty, it means that no notifications are enabled, and if it has a value, it means that notifications are enabled.

Function Implementation #

To implement a scheduled task, we need to use the functionality of Pub/Sub subscribers and publishers. We use the subscriber to subscribe to the expiration events of elements, and then perform the fixed task. This is the implementation idea of a scheduled task.

Taking the problem mentioned at the beginning of this article as an example, we implemented this scheduled task as follows: First, we push the appointment time of each patient back one day, and then calculate the millisecond value between the current time and the target time (the time one day before the appointment). We set this value as the expiration time of the element in Redis. When this key expires, we can subscribe to this information using the subscriber mode, and then send a reminder message to this user, thus implementing the function of starting a separate distributed scheduled task for each patient.

Let’s simulate the implementation of this function using the command mode first. First, we open a client using redis-cli and listen for the key expiration events __keyevent@0__:expired. This listening value __keyevent@0__:expired is a fixed format, where 0 represents the first database. As we know, Redis has a total of 16 databases, with the default using the 0th database. We recommend opening a non-0 database specifically for implementing scheduled tasks to avoid many invalid event listeners.

The command for listening is as follows:

127.0.0.1:6379> psubscribe __keyevent@0__:expired
1) "psubscribe"
2) "__keyevent@0__:expired"
3) (integer) 1

At this time, we open another client and add two test data for testing. The commands are as follows:

127.0.0.1:6379> set key value ex 3
OK
127.0.0.1:6379> set user xiaoming ex 3
OK

After waiting for 3 seconds, we check the listening result as follows:

127.0.0.1:6379> psubscribe __keyevent@0__:expired
1) "psubscribe"
2) "__keyevent@0__:expired"
3) (integer) 1
1) "pmessage" 
2) "__keyevent@0__:expired"
3) "__keyevent@0__:expired"
4) "key" # Received the expired information key
1) "pmessage"
2) "__keyevent@0__:expired"
3) "__keyevent@0__:expired"
4) "user" # Received the expired information user

Two expired messages have been successfully received.

Code Implementation #

In this article, we use Jedis to implement a scheduled task. The code is as follows:

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPubSub;
import utils.JedisUtils;

/**
 * Scheduled Task
 */
public class TaskExample {
    public static final String _TOPIC = "__keyevent@0__:expired"; // Subscription channel name
    public static void main(String[] args) {
        Jedis jedis = JedisUtils.getJedis();
        // Perform scheduled task
        doTask(jedis);
    }

    /**
     * Subscribe to expired messages and perform scheduled tasks
     * @param jedis Redis client
     */
    public static void doTask(Jedis jedis) {
        // Subscribe to expired messages
        jedis.psubscribe(new JedisPubSub() {
            @Override
            public void onPMessage(String pattern, String channel, String message) {
                // Received message, perform scheduled task
                System.out.println("Received message: " + message);
            }
        }, _TOPIC);
    }
}

Summary #

In this article, by enabling Keyspace Notifications and using Pub/Sub message subscription, we can obtain events of key-value expiration. We utilize this mechanism to implement the functionality of starting a scheduled task for each person. In the expiration event, we can obtain the key value of the expired key, and we can store the user ID in the key value, for example, using the format “user_1001”, where the numeric part represents the user number. With this number, we can send notification messages to the corresponding person.