时间:2022-11-29 09:53:34 | 栏目:Redis | 点击:次
最近在开发一个定时活动,而且活动是多个场次的。这个是后就需要在活动开始的时候推送信息给客户端,结束的时候也要推送一次。简单的设计方案就是将配置缓存在redis,然后每隔一秒就轮询reids,获取配置信息,然后判断是不是到活动开始或者结束的时间点,然后推送给客户端。
但是,这里会有一个问题,如果没有到活动开始或结束的时间点,这里会造成很多无用的轮询操作。这个操作不但增大了对这个key的访问量,同时也会占用cpu,降低机器性能。
redis在2.8.0版本提供了一个键空间通知功能机制,对于这个功能的详细描述,可以查阅官方文档。简单总结就是,客户端可以订阅一个key,当这个可以发生改变时,redis会通知到已经订阅的客户端。
这个实现也很简单,我们可以通过一个demo来看看如何使用这个机制。
package main import ( "context" "fmt" "github.com/go-redis/redis/v8" "time" ) var redisCli *redis.Client func init() { // 连接redis redisCli = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "redis123", }) } /* * redis key 过期自动通知 */ func SetExpireEvent() { // 设置一个键,并且3秒钟之后过期 redisCli.Set(context.Background(), "test_expire_event_notify", "测试键值过期通知", 3*time.Second) } func SubExpireEvent() { // 订阅key过期事件 sub := redisCli2.Subscribe(context.Background(), "__keyevent@0__:expired") // 这里通过一个for循环监听redis-server发来的消息。 // 当客户端接收到redis-server发送的事件通知时, // 客户端会通过一个channel告知我们。我们再根据 // msg的channel字段来判断是不是我们期望收到的消息, // 然后再进行业务处理。 for { msg := <-sub.Channel() fmt.Println("Channel ", msg.Channel) fmt.Println("pattern ", msg.Pattern) fmt.Println("pattern ", msg.Payload) fmt.Println("PayloadSlice ", msg.PayloadSlice) } } func main() { SetExpireEvent() go SubExpireEvent() // 这里sleep是为了防止main方法直接推出 time.Sleep(10 * time.Second) }
代码结果输出如下:
上面代码实现逻辑很简单,核心逻辑就是订阅__keyevent@0__:expired这个事件,然后一个循环等待事件的通知。值得注意的是,要启用这个特性需要修改配置文件,启用notify-keyspace-events这个配置,可以参考配置文件中的注释对不同事件进行启用。
回到开始提及的业务场景,如何在这种场景中使用redis的机制呢?其实很简单,当活动配置到数据库之后,会有一个更新缓存的步骤。在将数据设置在活动缓存时,只要我们计算当前时间到活动开始/结束这个时间差,将这个差作为键的过期时间。
例如,活动id1的开始时间为t0, 结束时间为t2, 当前时间为t。这个时候就可以这么设置:
// 活动开始的key设置 redisCli.Set(context.Background(), "id1:start", "活动开始了", t0 - t) // 活动结束结束的key设置 redisCli.Set(context.Background(), "id1:start", "活动开始了", t1 - t)
通过这么设置,当活动开启/结束就可以接收到相应的通知了。
这种方案其实可以完全满足文中的需求场景,但是这种方案其实也存在一些问题。其实这些问题在redis文档中也有相应说明。
本文介绍了使用redis的键空间通知机制来实现了一种业务场景,当然这种方式并不是最好的,还有其他方式来实现。在实际开发中会有很多的因素要考虑,而且实现方式也是多种多样,这个就需要我们分析每一种方案的利弊,然后进行抉择。