使用redis持久化数据时,当内存资源比较紧缺时,会导致部分使用不太频繁的redis数据丢失,这种情况下,需要监控redis的数据,以便在redis数据意外丢失的情况下,及时补救。另外还有一种应用场景是倒计时功能,例如淘宝的还剩几天自动确认收货,在这种情况下,需要程序在某个倒计时的时机自动触发。下面介绍的redis 键空间通知(keyspace notifications)的功能可以完成此类功能

键空间通知使得客户端可以通过订阅频道或模式, 来接收那些以某种方式改动了 Redis 数据集的事件,例如redis的缓存过期事件,缓存移除事件等等,因为开启键空间通知功能需要消耗一些 CPU , 所以在默认配置下, 该功能处于关闭状态,可以通过修改 redis.conf 文件, 或者直接使用 CONFIG SET 命令来开启或关闭键空间通知功能:

  • 当 notify-keyspace-events 选项的参数为空字符串时,功能关闭
  • 另一方面,当参数不是空字符串时,功能开启

这里为了演示,我开启了所有通知,在生产环境下,只需要开启需要的通知即可,命令如下:

    CONFIG SET notify-keyspace-events KEA

然后是订阅客户端代码:

using System;

namespace StackExchange.Redis
{
    static class RedisKeyspaceNotifications
    {
        /// <summary>
        /// NOTE: For this sample to work, you need to go to the Azure Portal and configure keyspace notifications with "Kxge$" to
        ///       1) turn on expiration notifications (x), 
        ///       2) general command notices (g) and 
        ///       3) Evicted events (e).
        ///       4) STRING operations ($).
        /// IMPORTANT - MAKE SURE YOU UNDERSTAND THE PERFORMANCE IMPACT OF TURNING ON KEYSPACE NOTIFICATIONS BEFORE PROCEEDING
        /// See http://redis.io/topics/notifications for more details
        public static void NotificationsExample(ConnectionMultiplexer connection)
        {
            var subscriber = connection.GetSubscriber();
            int db = 0; //what Redis DB do you want notifications on?
            string notificationChannel = "__keyspace@" + db + "__:*";

            //you only have to do this once, then your callback will be invoked.
            subscriber.Subscribe(notificationChannel, (channel, notificationType) =>
            {
                // IS YOUR CALLBACK NOT GETTING CALLED???? 
                // -> See comments above about enabling keyspace notifications on your redis instance
                var key = GetKey(channel);
                switch (notificationType) // use "Kxge" keyspace notification options to enable all of the below...
                {
                    case "expire": // requires the "Kg" keyspace notification options to be enabled
                        Console.WriteLine("Expiration Set for Key: " + key);
                        break;
                    case "expired": // requires the "Kx" keyspace notification options to be enabled
                        Console.WriteLine("Key EXPIRED: " + key);
                        break;
                    case "rename_from": // requires the "Kg" keyspace notification option to be enabled
                        Console.WriteLine("Key RENAME(From): " + key);
                        break;
                    case "rename_to": // requires the "Kg" keyspace notification option to be enabled
                        Console.WriteLine("Key RENAME(To): " + key);
                        break;
                    case "del": // requires the "Kg" keyspace notification option to be enabled
                        Console.WriteLine("KEY DELETED: " + key);
                        break;
                    case "evicted": // requires the "Ke" keyspace notification option to be enabled
                        Console.WriteLine("KEY EVICTED: " + key);
                        break;
                    case "set": // requires the "K$" keyspace notification option to be enabled for STRING operations
                        Console.WriteLine("KEY SET: " + key);
                        break;
                    default:
                        Console.WriteLine("Unhandled notificationType: " + notificationType);
                        break;
                }
            });

            Console.WriteLine("Subscribed to notifications...");

            // setup for delete notification example
            connection.GetDatabase(db).StringSet("DeleteExample", "Anything");

            // key rename callbacks example
            connection.GetDatabase(db).StringSet("{RenameExample}From", "Anything");
            connection.GetDatabase(db).KeyRename("{RenameExample}From", "{RenameExample}To");

            var random = new Random();
            //add some keys that will expire to test the above callback configured above.
            for (int i = 0; i < 10; i++)
            {
                var expiry = TimeSpan.FromSeconds(random.Next(2, 10));
                connection.GetDatabase(db).StringSet("foo" + i, "bar", expiry);
            }

            // should result in a delete notification callback.
            connection.GetDatabase(db).KeyDelete("DeleteExample");
        }

        private static string GetKey(string channel)
        {
            var index = channel.IndexOf(':');
            if (index >= 0 && index < channel.Length - 1)
                return channel.Substring(index + 1);

            //we didn't find the delimeter, so just return the whole thing
            return channel;
        }
    }
}

以上代码需要引用 StackExchange.Redis

延伸阅读:

键空间通知

Redis Keyspace Notifications