要知道redis是有消息的发布和订阅功能的,我们可以利用它的发布和订阅功能非常简单地实现一些比较实用的功能。
打个比方,如何实现自动关闭超时未支付的订单?
我们通常的做法是写一个定时器,定时去扫描未支付的订单,当发现下单时间超过我们设置的阈值时就去关闭订单。 这样做有一个问题:订单可能会延时关闭,假如设置5分钟扫描一次未支付订单,未支付订单有效时间是15分钟,那么就有可能一些订单到了(15+5)分钟-1秒才会被关闭。
那么这个时候使用redis发布订阅功能就非常方便了,我们只需要在用户下订单的时候把订单号作为key写入redis,并设置一个15分钟的有效期。然后订阅这个key的过期事件,如果用户在15分钟之内支付了订单我们就直接删除这个key。如果到了15分钟key自动过期了,我们就会接收到redis的消息通知,这个时候就可以直接关闭订单了。
开启redis消息订阅
打开redis.config配置文件,搜索notify-keyspace-events
就会看到redis发布订阅的配置:
# It is possible to select the events that Redis will notify among a set
# of classes. Every class is identified by a single character:
#
# K Keyspace events, published with __keyspace@<db>__ prefix.
# E Keyevent events, published with __keyevent@<db>__ prefix.
# g Generic commands (non-type specific) like DEL, EXPIRE, RENAME, ...
# $ String commands
# l List commands
# s Set commands
# h Hash commands
# z Sorted set commands
# x Expired events (events generated every time a key expires)
# e Evicted events (events generated when a key is evicted for maxmemory)
# A Alias for g$lshzxe, so that the "AKE" string means all the events.
#
# The "notify-keyspace-events" takes as argument a string that is composed
# of zero or multiple characters. The empty string means that notifications
# are disabled.
#
# Example: to enable list and generic events, from the point of view of the
# event name, use:
#
# notify-keyspace-events Elg
#
# Example 2: to get the stream of the expired keys subscribing to channel
# name __keyevent@0__:expired use:
#
# notify-keyspace-events Ex
redis接收事件类型一共有两种,keyspace
和keyevent
。keyspace
是key触发的具体操作,keyevent
为操作影响的键名。g,$,l,s,h,z,x,e,A
表示监听什么样的事件。
举个例子我们配置订阅类型为KEx
,我们就可以接收到两种key过期后产生的消息。
notify-keyspace-events "KEx" #设置监听类型
重启redis。
开始监听redis消息订阅
开启三个客户端:
订阅redis key为kname
的事件
127.0.0.1:6379> subscribe __keyspace@0__:kname
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "__keyspace@0__:kname"
3) (integer) 1
订阅redis key 的过期事件
127.0.0.1:6379> subscribe __keyevent@0__:expired
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "__keyevent@0__:expired"
3) (integer) 1
设置kname为zhangsan过期时间为2秒
127.0.0.1:6379> set kname "zhansan" ex 2
两秒后订阅的客户端分别收到了redis的消息通知
127.0.0.1:6379> subscribe __keyspace@0__:kname
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "__keyspace@0__:kname"
3) (integer) 1
1) "message"
2) "__keyspace@0__:kname"
3) "expired" #监听到kname过期了
127.0.0.1:6379> subscribe __keyevent@0__:expired
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "__keyevent@0__:expired"
3) (integer) 1
1) "message"
2) "__keyevent@0__:expired"
3) "kname" #监听到有一个过期的key为kname
上面这个订阅到一个redis库的事件,要想订阅所有的redis库就需要使用通配符了。
psubscribe __key*@*__:* # 这里注意通配的命令是p开头
在springboot里如何订阅redis事件
添加pom依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
yaml文件配置
server:
port: 8098
spring:
application:
name: redis-subscribe-service
redis:
host: 127.0.0.1
port: 6379
password: red123456
编写订阅service
package com.me.binf.service;
import org.springframework.stereotype.Service;
@Service
public class MessageReceiver {
//接收消息的方法
public void receiveMessage(String message){
//message接收到的过期key
System.out.println("Redis 监听到过期的key有:"+message);
}
}
添加redis配置
package com.me.binf.config;
import com.icodingedu.supermall.service.MessageReceiver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
@Configuration
public class RedisMessageConfig {
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
MessageListenerAdapter listenerAdapter){
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
//订阅触发的通道
container.addMessageListener(listenerAdapter,
new PatternTopic("__keyevent@0__:expired"));
return container;
}
@Bean
MessageListenerAdapter listenerAdapter(MessageReceiver receiver){
return new MessageListenerAdapter(receiver,"receiveMessage");
}
}
设置kname过期后接收到消息:
Redis 监听到过期的key有:kname