Redis的Keyspace notifications功能初探

2023-12-13 09:18

本文主要是介绍Redis的Keyspace notifications功能初探,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

最近在做一套系统,其中要求若干个Worker服务器将心跳信息都上报给中央服务器。当一定时间中央服务器没有得到心跳信息时则认为该Worker失效了,发出告警。


满足这种需求的解决方法多种多样,我开始想到了memcache,上报一次心跳信息就刷新一次缓存,当缓存内心跳信息对象超时被删除,即认为对应的Worker失效。然而由于memcache的工作原理,删除都是被动的,我们无法及时判断数据是否过期,即便知道了数据过期,也没有一种机制来回调方法来执行自定义的处理动作。难道缓存架构就真的不行了吗?


答案是否定的。在Redis 2.8.0版本起,加入了“Keyspace notifications”(即“键空间通知”)的功能。如何理解该功能呢?我们来看下官方是怎么说的:

键空间通知,允许Redis客户端从“发布/订阅”通道中建立订阅关系,以便客户端能够在Redis中的数据因某种方式受到影响时收到相应事件。

可能接收到的事件举例如下:

影响一个给出的键的所有命令(会告诉你哪个键被执行了一个命令,这个命令是什么);

接收到了一个LPUSH操作的所有键(LPUSH命令:key v1 [v2 v3..]将指定的所有值从左到右进行压栈操作,形成一个栈,并将该栈命名为指定的key);

在数据库0中失效的所有键(不一定非得是数据库0,这里这样表述其实想表达可以知道影响的哪个数据库)。


看到这里我联想到,如果一条缓存数据失效了,通过订阅关系,客户端会收到消息,通过分析消息可以得知何种消息,分析消息内容可以知道是哪个key失效了。这样就可以间接实现开头所描述的功能。


接下来我们来看下实验的步骤:

1.准备redis服务器

作为开源软件,redis下载后得到的是源代码,使用tar -xzvf redis-2.8.9.tar.gz解压后对其进行编译,过程也很简单,make就可以了。编译完成之后可以使用自带的runtest进行测试,看是否编译成功。然后就是安装了,执行make PREFIX=/usr/local/redis-2.8.9 install,PREFIX参数指定的就是安装路径。现在安装的只有可执行文件,还没有配置文件。其实在源码目录中有一个模板redis.conf,我们对它进行改动就可以了。
其他配置我们不关心,但是官方文档中说Keyspace notifications功能默认是关闭的(默认地,Keyspace 时间通知功能是禁用的,因为它或多或少会使用一些CPU的资源),我们需要打开它。打开的方法也很简单,配置属性:notify-keyspace-events

默认配置是这样的:notify-keyspace-events ""
根据文档中的说明:
[plain]  view plain copy
在CODE上查看代码片 派生到我的代码片
  1. K     Keyspace events, published with __keyspace@<db>__ prefix.  
  2. E     Keyevent events, published with __keyevent@<db>__ prefix.  
  3. g     Generic commands (non-type specific) like DEL, EXPIRE, RENAME, ...  
  4. $     String commands  
  5. l     List commands  
  6. s     Set commands  
  7. h     Hash commands  
  8. z     Sorted set commands  
  9. x     Expired events (events generated every time a key expires)  
  10. e     Evicted events (events generated when a key is evicted for maxmemory)  
  11. A     Alias for g$lshzxe, so that the "AKE" string means all the events.  
我们配置为: notify-keyspace-events Ex,含义为:发布key事件,使用过期事件(当每一个key失效时,都会生成该事件)。

2.准备客户端和连接配置

本文中使用的客户端是Jedis,版本为2.4.2,为了代码的通用性,我使用spring来管理连接:

[html]  view plain copy
在CODE上查看代码片 派生到我的代码片
  1. <bean id="pool" class="redis.clients.jedis.JedisPool">  
  2.         <constructor-arg>  
  3.             <bean id="config" class="redis.clients.jedis.JedisPoolConfig">  
  4.                 <property name="maxIdle" value="0" />  
  5.                 <property name="maxTotal" value="20" />  
  6.                 <property name="maxWaitMillis" value="1000" />  
  7.                 <property name="testOnBorrow" value="true" />  
  8.         </bean>  
  9.         </constructor-arg>  
  10.         <constructor-arg>  
  11.             <value>192.168.1.100</value>  
  12.         </constructor-arg>  
  13.         <constructor-arg>  
  14.         <value>6379</value>  
  15.     </constructor-arg>  
  16. </bean>  
然后使用Spring Test和Junit来测试代码

[java]  view plain copy
在CODE上查看代码片 派生到我的代码片
  1. @RunWith(SpringJUnit4ClassRunner.class)  
  2. @ContextConfiguration("/applicationContext*.xml")  
  3. public class RedisSubscribeDemo {  
  4.       
  5.     private static final Log Log= LogFactory.getLog(RedisSubscribeDemo.class);  
  6.       
  7.     @Resource  
  8.     private JedisPool pool;  
  9.       
  10.     @Test  
  11.     public void doTest() throws InterruptedException {  
  12.         for (int i = 0; i < 1; i++) {  
  13.             TestThread thread= new TestThread(pool);  
  14.             thread.start();  
  15.         }  
  16.         Thread.sleep(50000L);  
  17.         Log.info("Test finish...");  
  18.     }  
  19. }  

由于要使用一定的延迟,我们把主要测试代码放到了TestThread中。当测试线程启动后,主线程停滞50秒,让我们有足够的时间来操作。

[java]  view plain copy
在CODE上查看代码片 派生到我的代码片
  1. public class TestThread extends Thread {  
  2.       
  3.     private Log log= LogFactory.getLog(TestThread.class);  
  4.       
  5.     private JedisPool pool;  
  6.       
  7.     public TestThread(JedisPool pool){  
  8.         log.info("loading test thread");  
  9.         this.pool= pool;  
  10.     }  
  11.   
  12.     @Override  
  13.     public void run() {  
  14.         Jedis jedis= pool.getResource();  
  15.         jedis.psubscribe(new MySubscribe(), "*");  
  16.         try {  
  17.             Thread.sleep(10000L);  
  18.         } catch (InterruptedException e) {  
  19.             log.info("延时失败", e);  
  20.         }  
  21.         jedis.close();  
  22.         log.info("Test run finished");  
  23.     }  
  24. }  
在测试线程中,我们将自定义的MySubscribe加入到了Jedis的模板订阅(即psubscribe,因为模板订阅的channel是支持星号'*'通配的,这样可以收集到多个通配通道的消息,而与之相反的还有一个subscribe,此订阅只能指定严格匹配的通道)中,同样为了测试过程能够将结果显示出来,在绑定了订阅后,对该线程进行了延时10秒。

[java]  view plain copy
在CODE上查看代码片 派生到我的代码片
  1. public class MySubscribe extends JedisPubSub {  
  2.       
  3.     private static final Log log= LogFactory.getLog(MySubscribe.class);  
  4.   
  5.     // 初始化按表达式的方式订阅时候的处理    
  6.     public void onPSubscribe(String pattern, int subscribedChannels) {    
  7.             log.info(pattern + "=" + subscribedChannels);  
  8.     }  
  9.       
  10.     // 取得按表达式的方式订阅的消息后的处理    
  11.     public void onPMessage(String pattern, String channel, String message) {    
  12.             log.info(pattern + "=" + channel + "=" + message);  
  13.     }  
  14.   
  15.     ...其他未用到的重写方法忽略  
  16. }  
作为Jedis自定义订阅,必须继承redis.clients.jedis.JedisPubSub类,在 psubscribe模式下,重点重写onPMessage方法,该方法为接收到模板订阅后处理事件的重要代码。pattern为在绑定订阅时使用的通配模板,channel为通配后符合条件的实际通道名称,message就不用多说了,就是事件消息内容。

3.实战

通过Redis自带的redis-cli命令,我们可以在服务端通过命令行的方式直接操作。我们运行上面的示例代码,然后迅速切换到redis-cli命令中,建立一个生命周期很短暂的数据:
[plain]  view plain copy
在CODE上查看代码片 派生到我的代码片
  1. 127.0.0.1:6379> set chaijunkun 123 PX 100  
PX参数指定生命周期单位为毫秒,100即声明周期,即100毫秒。key为chaijunkun的数据,其值为123。
当执行语句后,回显:
[plain]  view plain copy
在CODE上查看代码片 派生到我的代码片
  1. OK  

这时我们看实例程序的输出:
[plain]  view plain copy
在CODE上查看代码片 派生到我的代码片
  1. *=__keyevent@0__:expired=chaijunkun  
从输出可以看出,之前指定的通配符为*,通配任何通道;之后是实际的通道名称:__keyevent@0__:expired,这里我们可以看到订阅收到了一个keyevent位于数据库0,事件类型为:expired,是一个过期事件;最后是chaijunkun,这个是过期数据的key。
在官方文档中,keyevent通道的格式永远是这样的:
__keyevent@<db>__:prefix
对于数据过期事件,我们在绑定订阅时通配模板也可以精确地写成:
__keyevent@*__:expired
通过示例代码,我们可以看到确实印证了之前的构想,实现了数据过期的事件触发(event)或者说回调(callback)。

4.其他应用

之前的代码中,对于事件的发布都是由Redis自己生成的。实际上在命令中主动发布自定义消息也是可以的,在publish命令的帮助中我们看到:
[plain]  view plain copy
在CODE上查看代码片 派生到我的代码片
  1. 127.0.0.1:6379> help publish  
  2.   
  3.   PUBLISH channel message  
  4.   summary: Post a message to a channel  
  5.   since: 2.0.0  
  6.   group: pubsub  

通过参数,可以自定义通道名称和通道消息。而在Jedis中,发布API甚至做到了字节数据的级别:
jedis.publish(byte[] channel, byte[] message)
因此我们可以构想,自定一套通讯协议,channel为命令字,message为消息体,我们可以通过redis这种简单的发布/订阅机制实现消息的分发。

这篇关于Redis的Keyspace notifications功能初探的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/487903

相关文章

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

Spring框架5 - 容器的扩展功能 (ApplicationContext)

private static ApplicationContext applicationContext;static {applicationContext = new ClassPathXmlApplicationContext("bean.xml");} BeanFactory的功能扩展类ApplicationContext进行深度的分析。ApplicationConext与 BeanF

JavaFX应用更新检测功能(在线自动更新方案)

JavaFX开发的桌面应用属于C端,一般来说需要版本检测和自动更新功能,这里记录一下一种版本检测和自动更新的方法。 1. 整体方案 JavaFX.应用版本检测、自动更新主要涉及一下步骤: 读取本地应用版本拉取远程版本并比较两个版本如果需要升级,那么拉取更新历史弹出升级控制窗口用户选择升级时,拉取升级包解压,重启应用用户选择忽略时,本地版本标志为忽略版本用户选择取消时,隐藏升级控制窗口 2.

Android 10.0 mtk平板camera2横屏预览旋转90度横屏拍照图片旋转90度功能实现

1.前言 在10.0的系统rom定制化开发中,在进行一些平板等默认横屏的设备开发的过程中,需要在进入camera2的 时候,默认预览图像也是需要横屏显示的,在上一篇已经实现了横屏预览功能,然后发现横屏预览后,拍照保存的图片 依然是竖屏的,所以说同样需要将图片也保存为横屏图标了,所以就需要看下mtk的camera2的相关横屏保存图片功能, 如何实现实现横屏保存图片功能 如图所示: 2.mtk

Spring+MyBatis+jeasyui 功能树列表

java代码@EnablePaging@RequestMapping(value = "/queryFunctionList.html")@ResponseBodypublic Map<String, Object> queryFunctionList() {String parentId = "";List<FunctionDisplay> tables = query(parent

PostgreSQL核心功能特性与使用领域及场景分析

PostgreSQL有什么优点? 开源和免费 PostgreSQL是一个开源的数据库管理系统,可以免费使用和修改。这降低了企业的成本,并为开发者提供了一个活跃的社区和丰富的资源。 高度兼容 PostgreSQL支持多种操作系统(如Linux、Windows、macOS等)和编程语言(如C、C++、Java、Python、Ruby等),并提供了多种接口(如JDBC、ODBC、ADO.NET等

寻迹模块TCRT5000的应用原理和功能实现(基于STM32)

目录 概述 1 认识TCRT5000 1.1 模块介绍 1.2 电气特性 2 系统应用 2.1 系统架构 2.2 STM32Cube创建工程 3 功能实现 3.1 代码实现 3.2 源代码文件 4 功能测试 4.1 检测黑线状态 4.2 未检测黑线状态 概述 本文主要介绍TCRT5000模块的使用原理,包括该模块的硬件实现方式,电路实现原理,还使用STM32类

nginx介绍及常用功能

什么是nginx nginx跟Apache一样,是一个web服务器(网站服务器),通过HTTP协议提供各种网络服务。 Apache:重量级的,不支持高并发的服务器。在Apache上运行数以万计的并发访问,会导致服务器消耗大量内存。操作系统对其进行进程或线程间的切换也消耗了大量的CPU资源,导致HTTP请求的平均响应速度降低。这些都决定了Apache不可能成为高性能WEB服务器  nginx: