Redis中的消息订阅与发布

消息通知

Redis 中可以使用链表来实现消息通知功能。链表使用 LPUSHRPOP 命令实现消息的入队和出队操作。但是有个问题,就是当链表中没有数据时调用 RPOP 命令会立刻失败返回,客户端无法阻塞等待。为了解决这个问题,可以使用 BRPOP 命令,当链表中没有数据时,客户端会阻塞等待。

BRPOP 命令使用示例
redis 复制代码
BRPOP key [key ...] timeout
  • key:要阻塞等待的键。
  • timeout:超时时间,单位为秒。如果填写 0,则会一直阻塞。

示例

go 复制代码
package main

import (
    "context"
    "fmt"
    "time"

    "github.com/go-redis/redis/v8"
)

func main() {
    // 创建 Redis 客户端
    ctx := context.Background()
    rdb := redis.NewClient(&redis.Options{
        Addr:     "localhost:6379",
        Password: "", // no password set
        DB:       0,  // use default DB
    })

    // 向队列中添加消息
    err := rdb.LPush(ctx, "myqueue", "message1").Err()
    if err != nil {
        panic(err)
    }

    // 使用 BRPOP 阻塞等待消息
    result, err := rdb.BRPop(ctx, 0, "myqueue").Result()
    if err != nil {
        panic(err)
    }

    // 输出结果
    fmt.Printf("Received message from %s: %s\n", result[0], result[1])
}

优先级队列

假设你的博客网站,每次有新文章时需要发送邮件通知,但是邮件发送需要一定的时间,如果采用 FIFO 的方式,可能会导致用户发送邮件的请求时间过长。同时每次有人进行注册都需要发送给注册用户一个确认邮件,如果用户注册时刚好也在发布文章,就会导致注册确认邮件等待很久才能发送,很显然这样的网站用户很难接受。所以,我们可以使用优先级队列,在队列中存储用户发送邮件的请求,并根据请求的优先级进行排序,优先级高的先发送。BRPOP 可以接受多个键,但是这些键之间并不是等价的,越靠近 BRPOP 的键优先级越高,借此特性可以实现区分优先级的任务队列。

优先级队列示例
go 复制代码
package main

import (
    "context"
    "fmt"
    "time"

    "github.com/go-redis/redis/v8"
)

func main() {
    // 创建 Redis 客户端
    ctx := context.Background()
    rdb := redis.NewClient(&redis.Options{
        Addr:     "localhost:6379",
        Password: "", // no password set
        DB:       0,  // use default DB
    })

    // 向不同优先级的队列中添加消息
    err := rdb.LPush(ctx, "queue:1", "high-priority-message").Err()
    if err != nil {
        panic(err)
    }
    err = rdb.LPush(ctx, "queue:2", "medium-priority-message").Err()
    if err != nil {
        panic(err)
    }
    err = rdb.LPush(ctx, "queue:3", "low-priority-message").Err()
    if err != nil {
        panic(err)
    }

    // 使用 BRPOP 阻塞等待多个队列的消息
    result, err := rdb.BRPop(ctx, 0, "queue:1", "queue:2", "queue:3").Result()
    if err != nil {
        panic(err)
    }

    // 输出结果
    fmt.Printf("Received message from %s: %s\n", result[0], result[1])
}

发布/订阅 模式

除了实现任务队列外,Redis 还提供了一组命令可以让开发者实现"发布/订阅"(publish/subscribe)模式。

"发布/订阅"模式中包含两种角色,分别是发布者和订阅者。订阅者可以订阅一个或若干个频道(channel),而发布者可以向指定的频道发送消息,所有订阅此频道的订阅者都会收到此消息。

  • 发出去的消息不会被持久化,也就是说当有客户端订阅 channel.1 后只能收到后续发布到该频道的消息,之前发送的就收不到了。
  • 执行 SUBSCRIBE 命令后客户端会进入订阅状态,处于此状态下客户端不能使用除 SUBSCRIBEUNSUBSCRIBEPSUBSCRIBEPUNSUBSCRIBE 这 4 个属于"发布/订阅"模式的命令之外的命令,否则会报错。
  • 进入订阅状态后客户端可能收到 3 种类型的回复。每种类型的回复都包含 3 个值,第一个值是消息的类型,根据消息类型的不同,第二、三个值的含义也不同。消息类型可能的取值有以下 3 个:
    1. subscribe。表示订阅成功后的反馈信息。第二个值是订阅成功的频道名称,第三个值是当前客户端订阅的频道数量。
    2. message。这个类型的回复是我们最关心的,它表示接收到的消息。第二个值表示产生消息的频道名称,第三个值是消息的内容。
    3. unsubscribe。表示成功取消订阅某个频道。第二个值是对应的频道名称,第三个值是当前客户端订阅的频道数量,当此值为 0 时客户端会退出订阅状态,之后就可以执行其他非"发布/订阅"模式的命令了。
订阅与发布消息示例
go 复制代码
package main

import (
    "context"
    "fmt"
    "time"

    "github.com/go-redis/redis/v8"
)

func main() {
    // 创建 Redis 客户端
    ctx := context.Background()
    rdb := redis.NewClient(&redis.Options{
        Addr:     "localhost:6379",
        Password: "", // no password set
        DB:       0,  // use default DB
    })

    // 订阅频道
    pubsub := rdb.Subscribe(ctx, "channel")
    defer pubsub.Close()

    // 启动一个 goroutine 来接收消息
    go func() {
        for {
            msg, err := pubsub.ReceiveMessage(ctx)
            if err != nil {
                panic(err)
            }
            fmt.Printf("Received message from %s: %s\n", msg.Channel, msg.Payload)
        }
    }()

    // 发布消息
    err := rdb.Publish(ctx, "channel", "Hello, Redis!").Err()
    if err != nil {
        panic(err)
    }

    // 等待一段时间以便接收消息
    time.Sleep(2 * time.Second)
}

按照规则订阅消息

除了可以使用 SUBSCRIBE 命令订阅指定名称的频道外,还可以使用 PSUBSCRIBE 命令订阅指定的规则。

按照规则订阅消息示例
go 复制代码
package main

import (
    "context"
    "fmt"
    "time"

    "github.com/go-redis/redis/v8"
)

func main() {
    // 创建 Redis 客户端
    ctx := context.Background()
    rdb := redis.NewClient(&redis.Options{
        Addr:     "localhost:6379",
        Password: "", // no password set
        DB:       0,  // use default DB
    })

    // 订阅频道规则
    pubsub := rdb.PSubscribe(ctx, "channel.*")
    defer pubsub.Close()

    // 启动一个 goroutine 来接收消息
    go func() {
        for {
            msg, err := pubsub.ReceiveMessage(ctx)
            if err != nil {
                panic(err)
            }
            fmt.Printf("Received message from %s: %s\n", msg.Channel, msg.Payload)
        }
    }()

    // 发布消息
    err := rdb.Publish(ctx, "channel.1", "Hello, Redis!").Err()
    if err != nil {
        panic(err)
    }

    // 等待一段时间以便接收消息
    time.Sleep(2 * time.Second)
}

取消订阅

PUNSUBSCRIBE 命令用于取消订阅。如果没有参数则会退订所有规则。

取消订阅示例
go 复制代码
package main

import (
    "context"
    "fmt"
    "time"

    "github.com/go-redis/redis/v8"
)

func main() {
    // 创建 Redis 客户端
    ctx := context.Background()
    rdb := redis.NewClient(&redis.Options{
        Addr:     "localhost:6379",
        Password: "", // no password set
        DB:       0,  // use default DB
    })

    // 订阅频道规则
    pubsub := rdb.PSubscribe(ctx, "channel.*")
    defer pubsub.Close()

    // 启动一个 goroutine 来接收消息
    go func() {
        for {
            msg, err := pubsub.ReceiveMessage(ctx)
            if err != nil {
                panic(err)
            }
            fmt.Printf("Received message from %s: %s\n", msg.Channel, msg.Payload)
        }
    }()

    // 发布消息
    err := rdb.Publish(ctx, "channel.1", "Hello, Redis!").Err()
    if err != nil {
        panic(err)
    }

    // 等待一段时间以便接收消息
    time.Sleep(2 * time.Second)

    // 取消订阅
    err = rdb.PUnsubscribe(ctx, "channel.*").Err()
    if err != nil {
        panic(err)
    }

    // 取消订阅后,客户端可以继续执行其他命令
    err = rdb.Set(ctx, "key", "value", 0).Err()
    if err != nil {
        panic(err)
    }

    fmt.Println("Set command executed successfully")
}
相关推荐
月光水岸New1 小时前
Ubuntu 中建的mysql数据库使用Navicat for MySQL连接不上
数据库·mysql·ubuntu
狄加山6751 小时前
数据库基础1
数据库
我爱松子鱼1 小时前
mysql之规则优化器RBO
数据库·mysql
chengooooooo1 小时前
苍穹外卖day8 地址上传 用户下单 订单支付
java·服务器·数据库
Rverdoser2 小时前
【SQL】多表查询案例
数据库·sql
Galeoto2 小时前
how to export a table in sqlite, and import into another
数据库·sqlite
希忘auto3 小时前
详解Redis在Centos上的安装
redis·centos
人间打气筒(Ada)3 小时前
MySQL主从架构
服务器·数据库·mysql
leegong231113 小时前
学习PostgreSQL专家认证
数据库·学习·postgresql
喝醉酒的小白3 小时前
PostgreSQL:更新字段慢
数据库·postgresql