Redis的Keyspace notifications性能


1. 引入背景

公司外部有一套通信零碎,用于做内网和和外网的微服务网络买通(通过建设socket连贯);为了保护通信的稳固在通信终端中断时候疾速持续报警或者重连,所以打算客户端引入了心跳包机制,定期向服务端推送心跳包。

开始的时候咱们通过定时器轮询轮询服务最初的心跳工夫,进行判断连贯是否断开:

然而前面咱们发现这样的做法还是有点不好的中央:

  • 当保留Map的连贯对象(即保护的连接数变大后),遍历一次的工夫开始缓缓变长,从而导致实时性变差
  • 定时器的工夫不好设置,设置太快了性能就义太大了,太慢了实时性也升高了

所以咱们尝试寻找一个第三方服务,能够实现到过期后主动触发一个事件。

这个咱们想到了2个方向:
  • 通过mq做延时队列;
  • redis 的事件推送性能;

咱们抉择了后者。

2. Redis的Keyspace notifications性能介绍

在Redis 2.8.0版本起,退出了“Keyspace notifications”(即“键空间告诉”)的性能。

官网形容: 键空间告诉,容许Redis客户端从“公布/订阅”通道中建设订阅关系,以便客户端可能在Redis中的数据因某种形式受到影响时收到相应事件。

其实依据形容咱们并不难理解: 当redis中某个key产生了某种变动(某个事件),零碎会将这个事件推送到咱们指定的事件监听的过程中;

回归上述背景: 如果咱们把心跳放到redis中缓存起来,通过订阅关系,当key 过期时候,redis会推送一条信息到事件监听的客户端;而后通过剖析音讯能够得悉何种音讯,剖析音讯内容能够晓得是哪个key生效了。这样就能够间接实现结尾所形容的性能。

3. Redis的Keyspace notifications性能应用

Keyspace notifications 性能默认是敞开的(默认地,Keyspace 工夫告诉性能是禁用的,因为它或多或少会应用一些CPU的资源),咱们须要关上它。关上的办法也很简略,配置属性:notify-keyspace-events

redis.conf

notify-keyspace-events Ex

批改配置后,重启redis服务

增加过期事件订阅 开启一个终端,redis-cli 进入 redis 。开始订阅所有操作,期待接管音讯。

 vagrant@homestead  ~  redis-cli127.0.0.1:6379> psubscribe __keyevent@0__:expiredReading messages... (press Ctrl-C to quit)1) "psubscribe"2) "__keyevent@0__:expired"3) (integer) 1

再开启一个终端,redis-cli 进入 redis,新增一个 20秒过期的键:

127.0.0.1:6379> SETEX test 123 20OK

另外一边执行了阻塞订阅操作后的终端,20秒过期后有如下信息输入:

1) "pmessage"2) "__keyevent@0__:expired"3) "__keyevent@0__:expired"4) "test"

阐明:阐明对过期Key信息的订阅是胜利的。

4. 用golang写了个简略监听Demo

package mainimport (    "fmt"    "github.com/gomodule/redigo/redis"    "strconv"    "time"    "unsafe")type PSubscribeCallback func (pattern, channel, message string)type PSubscriber struct {    client redis.PubSubConn    cbMap map[string]PSubscribeCallback}func PConnect(ip, password string, port uint16) redis.Conn {    conn, err := redis.Dial("tcp", ip + ":" + strconv.Itoa(int(port)))    if err != nil {        print("redis dial failed.")    }    conn.Do("AUTH",password)    return conn}func (c *PSubscriber) ReceiveKeySpace(conn redis.Conn) {    c.client = redis.PubSubConn{conn}    c.cbMap = make(map[string]PSubscribeCallback)    go func() {        for {            switch res := c.client.Receive().(type) {            case redis.Message:                pattern := &res.Pattern                channel := &res.Channel                message := (*string)(unsafe.Pointer(&res.Data))                c.cbMap[*channel](*pattern, *channel, *message)            case redis.Subscription:                fmt.Printf("%s: %s %d\n", res.Channel, res.Kind, res.Count)            case error:                print("error handle...")                continue            }        }    }()}const expired = "__keyevent@0__:expired"func (c *PSubscriber)Psubscribe() {    err := c.client.PSubscribe(expired)    if err != nil{        print("redis Subscribe error.")    }    c.cbMap[expired] = PubCallback}func PubCallback(patter , channel, msg string){    print( "PubCallback patter : " + patter + " channel : ", channel, " message : ", msg)    // TODO:拿到msg后进行后续的业务代码}func main() {    var sub PSubscriber    conn := PConnect("192.168.11.213","", 6379)    sub.ReceiveKeySpace(conn)    sub.Psubscribe()    for{        time.Sleep(time.Second)    }}

5. Redis的Keyspace notifications性能注意事项

1. 过期事件:
  • 当某个命令拜访该密钥并发现该密钥已过期时。
  • 通过后盾零碎,在后盾逐渐查找过期密钥,以便可能收集从未拜访过的密钥。

官网文档可知

The expired events are generated when a key is accessed and is found to be expired by one of the above systems, as a result there are no guarantees that the Redis server will be able to generate the expired event at the time the key time to live reaches the value of zero.If no command targets the key constantly, and there are many keys with a TTL associated, there can be a significant delay between the time the key time to live drops to zero, and the time the expired event is generated.Basically expired events are generated when the Redis server deletes the key and not when the time to live theoretically reaches the value of zero.

其实这个不难理解:过期事件的触发,是在过期key被redis回收时才会触发 expired 推送。。即其实 Keyspace notifications 性能 redis 也无奈保障百分的实时性(具体起因可查考 redis 的回收机制 GC),但如果key不是特地特地多的时候,这个时候实时性还是挺高的。

2. 事件失落

当监听断开后,事件没有推送的端,当从新监听时候不会从新推送;这个如果须要做长久化的话可能还须要 依赖于 其余服务,比方:mysql等。。