Go-Redis

Redis 的使用

Redis 是一个开源的(BSD 许可)内存数据结构存储,可用作数据库、缓存和消息代理。它支持多种数据结构,如字符串、哈希、列表、集合、有序集合等。在 Gin 框架中集成 Redis 可以极大地提高应用程序的性能和可扩展性。

本教程将介绍如何在 Gin 应用程序中连接 Redis、进行基本操作(如设置和获取键值)、以及如何使用 Redis 进行会话管理和缓存。

1. 环境准备

在开始之前,请确保您的开发环境已准备就绪。

1.1 安装 Go Redis 客户端

我们将使用 go-redis/redis/v8 库作为 Redis 客户端。它是 Go 语言中最流行和功能最丰富的 Redis 客户端之一。

1
go get -u github.com/go-redis/redis/v8

1.2 安装和运行 Redis 服务器

您可以通过多种方式安装和运行 Redis 服务器:

  • Docker (推荐):最简单快捷的方式。

    1
    2
    3
    docker run --name my-redis -p 6379:6379 -d redis/redis-stack-server:latest
    # 或者只运行 Redis 核心
    # docker run --name my-redis -p 6379:6379 -d redis:latest

    这将启动一个名为 my-redis 的 Redis 容器,并将容器的 6379 端口映射到主机的 6379 端口。

  • 手动安装

    • macOS (Homebrew): brew install redis
    • Linux (apt/yum): sudo apt update && sudo apt install redis-serversudo yum install redis
    • Windows: 可以从官方网站下载或使用 WSL2。

安装完成后,确保 Redis 服务器正在运行(默认监听 6379 端口)。您可以使用 redis-cli ping 来测试连接。如果返回 PONG 则表示连接成功。

1.3 Redis 配置修改 (Windows)

在 Windows 系统中,Redis 通常以服务形式运行或通过命令行手动启动。它的行为通过 redis.windows.conf 文件进行配置。

1.3.1 找到 Redis 配置文件

如果您是手动安装 Redis for Windows,redis.windows.conf 文件通常位于您解压 Redis 安装包的根目录下。

例如:C:\Program Files\Redis\redis.windows.confC:\redis-x.x.x\redis.windows.conf

找到文件后,使用记事本、Notepad++ 或 VS Code 等文本编辑器打开它。

1.3.2 主要配置项更改

以下是一些您可能会经常更改或需要注意的重要配置项及其修改建议。请根据您的需求取消注释(删除行首的 #)并修改对应的值。

  • bind

    • 作用:指定 Redis 服务器监听的 IP 地址。

    • 修改:默认情况下,Redis for Windows 可能没有明确的 bind 设置,或者它会监听所有可用接口。如果您需要限制访问,可以将其设置为特定 IP 地址。

      1
      2
      # bind 127.0.0.1           # 默认可能没有这一行,或只监听本地
      bind 0.0.0.0 # 监听所有可用 IP (仅用于测试,生产环境请谨慎)
    • 重要提示:在没有密码或防火墙保护的情况下,将 bind 设置为 0.0.0.0 并暴露在公网是非常危险的。

  • port

    • 作用:指定 Redis 服务器监听的 TCP 端口。

    • 修改:如果您希望 Redis 监听非标准端口,可以在此修改

      1
      port 6379 # 保持默认或修改为其他端口,如 6380
  • protected-mode

    • 作用:保护模式,在没有 bind 设置和 requirepass(密码)设置的情况下,只允许本地回环地址(127.0.0.1)访问。

    • 修改:强烈建议保持为 yes。如果您需要从外部连接且不设置密码(不推荐),则必须将其设置为 no,但同时需要设置 Windows 防火墙规则来限制访问。

      1
      protected-mode yes
  • requirepass

    • 作用:设置 Redis 连接密码。

    • 修改:**在生产环境中,强烈建议设置一个强密码。**取消注释并替换 foobared 为您的密码。

      1
      requirepass your_strong_password_here # 设置您的密码
  • databases

    • 作用:设置可用的数据库数量。Redis 默认有 16 个数据库,索引从 0 到 15。

    • 修改:通常默认值已足够,根据您的应用程序需求调整。

      1
      databases 16
  • maxmemory

    • 作用:设置 Redis 可用的最大内存量。当达到此限制时,Redis 将根据 maxmemory-policy 配置来决定如何处理新数据。

    • 修改:在生产环境中,强烈建议设置此项,以防止 Redis 使用过多内存导致系统不稳定。

      1
      2
      3
      maxmemory 2gb # 设置最大内存为 2GB
      # 当达到 maxmemory 限制时,优先删除带有过期时间的键,并且删除最少使用的键
      maxmemory-policy allkeys-lru
  • daemonize

    • 作用:在 Linux/macOS 上,这会使 Redis 作为守护进程(后台进程)运行。在 Windows 上,这个设置通常不生效或行为不同,因为 Windows 服务或控制台程序有自己的后台机制。如果您是通过 redis-server.exe 直接运行,它通常会占用控制台窗口。如果您将其作为服务安装,则由服务管理器控制后台运行。

    • 修改

      :通常无需在 Windows 下修改此项,保持

      1
      no

      即可。

      代码段

      1
      daemonize no
  • loglevel

    • 作用:设置日志级别。可选值有 debug, verbose, notice, warning

    • 修改

      :开发/调试阶段可以设置为

      1
      debug

      1
      verbose

      生产环境通常设置为

      1
      notice

      1
      warning

      ,以减少日志量。

      代码段

      1
      loglevel notice

1.3.3 重启 Redis (Windows)

修改配置文件后,您需要重启 Redis 服务器以使更改生效。重启方式取决于您如何在 Windows 上运行 Redis。

1.3.3.1 作为 Windows 服务运行

如果您的 Redis 实例是作为 Windows 服务安装并运行的(这是推荐的生产环境部署方式),可以通过 Windows 的服务管理器来重启:

  1. 按下 Win + R 键,输入 services.msc,然后按回车键打开 服务 管理器。
  2. 在服务列表中找到名为 Redis 或类似名称(例如 Redis6379)的服务。
  3. 右键点击该服务,然后选择 重启
1.3.3.2 通过命令行手动运行

如果您是通过命令行直接运行 redis-server.exe,您需要先停止当前的进程,然后重新启动它:

  1. 停止当前 Redis 进程:

    • 打开一个新的命令提示符 (CMD) 或 PowerShell 窗口。

    • 使用

      1
      redis-cli.exe

      连接到您的 Redis 实例并发送

      1
      SHUTDOWN

      命令。

      DOS

      1
      redis-cli.exe -h 127.0.0.1 -p 6379 SHUTDOWN
      • 如果设置了密码,请添加

        1
        -a

        参数:

        DOS

        1
        redis-cli.exe -h 127.0.0.1 -p 6379 -a your_password SHUTDOWN
    • 如果 Redis 运行在控制台窗口中,发送 SHUTDOWN 命令后,该控制台窗口通常会自动关闭。

  2. 使用更新后的配置文件启动 Redis 服务器:

    • 打开一个新的命令提示符 (CMD) 或 PowerShell 窗口。

    • 导航到 redis-server.exe 所在的目录。

    • 执行以下命令,指定您的配置文件路径:

      DOS

      1
      2
      3
      redis-server.exe redis.windows.conf
      # 或如果配置文件在其他路径:
      # redis-server.exe C:\path\to\your\redis.windows.conf
    • 这将会在当前控制台窗口中启动 Redis。如果您想让它在后台运行(不占用控制台),可以考虑使用 start 命令(但通常建议安装为服务)。

重启后,您可以使用 redis-cli.exe 连接到 Redis 并执行 PING 命令或 CONFIG GET <config_name> 命令来验证新的配置是否已生效。

2. 基本 Redis 操作

在使用 Gin 之前,我们先了解 go-redis 库的基本操作。

2.1 连接 Redis

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import (
"context"
"fmt"

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

var ctx = context.Background()

func main() {
// 创建 Redis 客户端实例
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379", // Redis 服务器地址
Password: "", // 如果有密码,请填写
DB: 0, // 数据库索引,默认为 0
})

// PING 命令检查连接
pong, err := rdb.Ping(ctx).Result()
if err != nil {
fmt.Println("Error connecting to Redis:", err)
return
}
fmt.Println("Redis connected:", pong) // 应该输出 PONG
}

2.2 设置和获取字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package main

import (
"context"
"fmt"
"time"

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

var ctx = context.Background()

func main() {
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
DB: 0,
})

// 设置一个键值对
err := rdb.Set(ctx, "mykey", "Hello Redis from Go!", 0).Err() // 0 表示永不过期
if err != nil {
fmt.Println("Error setting key:", err)
return
}
fmt.Println("Key 'mykey' set successfully.")

// 获取键的值
val, err := rdb.Get(ctx, "mykey").Result()
if err == redis.Nil { // 如果键不存在
fmt.Println("Key 'mykey' does not exist.")
} else if err != nil {
fmt.Println("Error getting key:", err)
return
} else {
fmt.Println("Value of 'mykey':", val)
}

// 删除键
_, err = rdb.Del(ctx, "mykey").Result()
if err != nil {
fmt.Println("Error deleting key:", err)
return
}
fmt.Println("Key 'mykey' deleted successfully.")

// 再次尝试获取已删除的键
val, err = rdb.Get(ctx, "mykey").Result()
if err == redis.Nil {
fmt.Println("Key 'mykey' is now deleted.")
}
}

2.3 设置过期时间

Redis 可以为键设置过期时间,使其在一段时间后自动删除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package main

import (
"context"
"fmt"
"time"

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

var ctx = context.Background()

func main() {
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
DB: 0,
})

// 设置一个带过期时间的键 (5 秒后过期)
err := rdb.Set(ctx, "expire_key", "This key will expire.", 5*time.Second).Err()
if err != nil {
fmt.Println("Error setting key with expiry:", err)
return
}
fmt.Println("Key 'expire_key' set with 5s expiry.")

// 立即获取
val, _ := rdb.Get(ctx, "expire_key").Result()
fmt.Println("Initial value of 'expire_key':", val)

// 等待 6 秒,让键过期
time.Sleep(6 * time.Second)

// 再次获取,应该已经过期
val, err = rdb.Get(ctx, "expire_key").Result()
if err == redis.Nil {
fmt.Println("Key 'expire_key' has expired.")
} else if err != nil {
fmt.Println("Error getting expired key:", err)
} else {
fmt.Println("Value of 'expire_key' (should be expired):", val)
}
}

3. 在 Gin 中集成 Redis

现在我们将把 Redis 功能集成到 Gin 应用程序中。

3.1 初始化 Redis 客户端

在 Gin 应用程序启动时,最好只初始化一次 Redis 客户端,并将其作为依赖注入到处理函数中,或者存储在一个全局变量中(但推荐依赖注入)。

我们通常将 Redis 客户端实例存储在 Gin 的 gin.Context 中,或者作为自定义结构体的字段传递。

main.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package main

import (
"context"
"fmt"
"log"
"net/http"
"time"

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

// 定义一个全局的 Redis 客户端变量 (或者通过依赖注入)
var Rdb *redis.Client
var Ctx = context.Background()

func initRedis() {
Rdb = redis.NewClient(&redis.Options{
Addr: "localhost:6379", // Redis 服务器地址
Password: "", // 如果有密码,请填写
DB: 0, // 数据库索引
})

// 测试连接
pong, err := Rdb.Ping(Ctx).Result()
if err != nil {
log.Fatalf("Could not connect to Redis: %v", err)
}
fmt.Println("Connected to Redis:", pong)
}

func main() {
// 初始化 Redis 客户端
initRedis()

router := gin.Default()

// 注册 Redis 相关的路由
router.GET("/set/:key/:value", setKeyValue)
router.GET("/get/:key", getKeyValue)
router.DELETE("/delete/:key", deleteKey)

fmt.Println("Gin server running on :8080")
router.Run(":8080")
}

3.2 创建 Gin 路由处理 Redis 操作

接下来,我们将为 Redis 操作创建 Gin 的路由处理函数。

main.go (续)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// setKeyValue 处理 /set/:key/:value 路由,将键值对存储到 Redis
func setKeyValue(c *gin.Context) {
key := c.Param("key")
value := c.Param("value")

// 设置键值对,并设置 1 分钟过期时间
err := Rdb.Set(Ctx, key, value, 1*time.Minute).Err()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to set key: %v", err)})
return
}

c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("Key '%s' set successfully with value '%s'", key, value)})
}

// getKeyValue 处理 /get/:key 路由,从 Redis 获取键的值
func getKeyValue(c *gin.Context) {
key := c.Param("key")

val, err := Rdb.Get(Ctx, key).Result()
if err == redis.Nil {
c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("Key '%s' not found", key)})
return
} else if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to get key: %v", err)})
return
}

c.JSON(http.StatusOK, gin.H{"key": key, "value": val})
}

// deleteKey 处理 /delete/:key 路由,从 Redis 删除键
func deleteKey(c *gin.Context) {
key := c.Param("key")

// Del 方法返回受影响的键数量
deleted, err := Rdb.Del(Ctx, key).Result()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to delete key: %v", err)})
return
}

if deleted == 0 {
c.JSON(http.StatusNotFound, gin.H{"message": fmt.Sprintf("Key '%s' not found or already deleted", key)})
} else {
c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("Key '%s' deleted successfully", key)})
}
}

3.3 处理错误

在实际应用中,处理 Redis 操作可能出现的错误至关重要。go-redis 库在键不存在时会返回 redis.Nil 错误,其他网络或 Redis 服务器错误则会返回不同的错误类型。务必对这些错误进行检查和处理。

4. 使用 Redis 作为缓存

Redis 最常见的用途之一是作为应用程序的缓存层。我们可以创建一个 Gin 中间件来实现简单的缓存功能。

4.1 实现缓存中间件

这个中间件将尝试从 Redis 获取响应,如果命中缓存,则直接返回;如果未命中,则继续执行后续处理函数,并将结果缓存起来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
package main

import (
"bytes"
"context"
"fmt"
"io/ioutil"
"log"
"net/http"
"time"

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

// ... (initRedis, Rdb, Ctx declarations from previous section)

// CacheMiddleware 是一个简单的缓存中间件
func CacheMiddleware(cacheDuration time.Duration) gin.HandlerFunc {
return func(c *gin.Context) {
// 生成缓存键:通常使用请求的 URL 或更复杂的哈希
cacheKey := "cache:" + c.Request.RequestURI
log.Printf("Attempting to fetch from cache for key: %s\n", cacheKey)

// 尝试从 Redis 获取缓存数据
cachedResponse, err := Rdb.Get(Ctx, cacheKey).Result()
if err == nil {
// 缓存命中!直接返回缓存的数据
log.Printf("Cache HIT for key: %s\n", cacheKey)
c.Header("X-Cache", "HIT")
c.Data(http.StatusOK, "application/json", []byte(cachedResponse))
c.Abort() // 阻止后续处理函数执行
return
} else if err == redis.Nil {
log.Printf("Cache MISS for key: %s\n", cacheKey)
c.Header("X-Cache", "MISS")
} else {
log.Printf("Error getting from Redis cache: %v\n", err)
// 如果 Redis 出错,不阻止请求继续执行
}

// 创建一个响应写入器,捕获响应内容
blw := &bodyLogWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer}
c.Writer = blw

// 继续处理请求
c.Next()

// 检查状态码,只缓存成功的响应 (200 OK)
if c.Writer.Status() == http.StatusOK {
// 将响应内容缓存到 Redis
err = Rdb.Set(Ctx, cacheKey, blw.body.String(), cacheDuration).Err()
if err != nil {
log.Printf("Error setting Redis cache: %v\n", err)
} else {
log.Printf("Response cached for key: %s, duration: %s\n", cacheKey, cacheDuration)
}
}
}
}

// bodyLogWriter 是一个自定义的 ResponseWriter,用于捕获响应体
type bodyLogWriter struct {
gin.ResponseWriter
body *bytes.Buffer
}

func (w bodyLogWriter) Write(b []byte) (int, error) {
w.body.Write(b)
return w.ResponseWriter.Write(b)
}

func (w bodyLogWriter) WriteString(s string) (int, error) {
w.body.WriteString(s)
return w.ResponseWriter.WriteString(s)
}

// main 函数 (续)
func main() {
initRedis()
router := gin.Default()

// 应用全局 Logger 中间件
router.Use(gin.Logger())

// 设置一个带缓存的路由组
cachedGroup := router.Group("/cached")
// 将缓存中间件应用于此组,缓存 10 秒
cachedGroup.Use(CacheMiddleware(10 * time.Second))
{
cachedGroup.GET("/data", func(c *gin.Context) {
log.Println("Executing /cached/data handler (simulating slow operation)...")
time.Sleep(2 * time.Second) // 模拟一个耗时操作
c.JSON(http.StatusOK, gin.H{"data": "This is cached data!", "timestamp": time.Now().Format(time.RFC3339)})
})
}

// 非缓存路由
router.GET("/no-cache-data", func(c *gin.Context) {
log.Println("Executing /no-cache-data handler...")
c.JSON(http.StatusOK, gin.H{"data": "This data is not cached.", "timestamp": time.Now().Format(time.RFC3339)})
})

fmt.Println("Gin server running on :8080")
router.Run(":8080")
}

4.2 在路由中使用缓存

在上面的例子中,我们已经演示了如何将 CacheMiddleware 应用到一个路由组 (/cached)。所有该组下的路由都会受益于缓存。

测试缓存:

  1. 启动 Gin 服务器。
  2. 第一次访问 http://localhost:8080/cached/data:您会看到日志输出 Executing /cached/data handler...Cache MISS,请求会延迟 2 秒。
  3. 在 10 秒内再次访问 http://localhost:8080/cached/data:您会看到日志输出 Cache HIT,请求会立即返回,且 timestamp 值与第一次相同(因为它返回的是缓存的数据)。
  4. 等待 10 秒以上,再次访问 http://localhost:8080/cached/data:缓存过期,您会再次看到 Cache MISS 和 2 秒延迟,timestamp 值会更新。
  5. 访问 http://localhost:8080/no-cache-data:每次访问都会立即返回,没有缓存。

5. 高级用法(可选)

5.1 Redis 连接池

go-redis 客户端本身就包含了连接池管理。在 redis.NewClient 中,您可以配置 PoolSizeMinIdleConns 等选项来优化连接池行为。

1
2
3
4
5
6
7
8
9
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "",
DB: 0,
PoolSize: 10, // 最大连接数
MinIdleConns: 5, // 最小空闲连接数
PoolTimeout: 30 * time.Second, // 从连接池获取连接的超时时间
IdleTimeout: 5 * time.Minute, // 空闲连接的关闭时间
})

5.2 使用 Redis 实现分布式锁

当您有多个服务实例需要对共享资源进行互斥访问时,可以使用 Redis 实现分布式锁。go-redis 提供了 SetNX(Set if Not eXists)和 Expire 命令的封装,可以用来构建简单的锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import (
"time"
"github.com/go-redis/redis/v8"
)

// ObtainLock 尝试获取分布式锁
func ObtainLock(client *redis.Client, key string, value string, expiration time.Duration) (bool, error) {
// SetNX 命令在键不存在时设置键值,并设置过期时间
// 如果设置成功,返回 true;如果键已存在,返回 false
ok, err := client.SetNX(Ctx, key, value, expiration).Result()
if err != nil {
return false, err
}
return ok, nil
}

// ReleaseLock 释放分布式锁
func ReleaseLock(client *redis.Client, key string, value string) (bool, error) {
// 使用 Lua 脚本确保原子性:检查值是否匹配,然后删除键
// 这是为了防止 A 释放了 B 的锁
script := `
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end
`
result, err := client.Eval(Ctx, script, []string{key}, value).Result()
if err != nil {
return false, err
}
return result.(int64) == 1, nil
}

// 在 Gin 路由中使用
// router.GET("/lock-resource", func(c *gin.Context) {
// lockKey := "my_resource_lock"
// lockValue := "unique_session_id_123" // 建议使用 UUID 或其他唯一标识
// lockTTL := 10 * time.Second
//
// locked, err := ObtainLock(Rdb, lockKey, lockValue, lockTTL)
// if err != nil {
// c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to obtain lock"})
// return
// }
//
// if !locked {
// c.JSON(http.StatusConflict, gin.H{"message": "Resource is currently locked by another process"})
// return
// }
//
// defer func() {
// released, releaseErr := ReleaseLock(Rdb, lockKey, lockValue)
// if releaseErr != nil {
// log.Printf("Failed to release lock for %s: %v", lockKey, releaseErr)
// } else if !released {
// log.Printf("Lock %s not released by current process (value mismatch)", lockKey)
// } else {
// log.Printf("Lock %s released successfully", lockKey)
// }
// }()
//
// // 执行需要加锁的业务逻辑
// time.Sleep(5 * time.Second) // 模拟业务操作
// c.JSON(http.StatusOK, gin.H{"message": "Resource accessed successfully with lock"})
// })

5.3 Pub/Sub(发布/订阅)

Redis 支持发布/订阅模式,允许客户端订阅频道并接收其他客户端发布的消息,这在实现实时通知或事件驱动架构时非常有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package main

import (
"context"
"fmt"
"log"
"net/http"
"time"

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

var Rdb *redis.Client
var Ctx = context.Background()

func initRedis() {
Rdb = redis.NewClient(&redis.Options{
Addr: "localhost:6379",
DB: 0,
})
_, err := Rdb.Ping(Ctx).Result()
if err != nil {
log.Fatalf("Could not connect to Redis: %v", err)
}
fmt.Println("Connected to Redis for Pub/Sub")
}

// SubscribeToChannel 订阅一个 Redis 频道
func SubscribeToChannel(channel string) {
pubsub := Rdb.Subscribe(Ctx, channel)
defer pubsub.Close() // 确保订阅器在退出时关闭

// 等待订阅就绪
_, err := pubsub.Receive(Ctx)
if err != nil {
log.Printf("Error receiving from pubsub: %v", err)
return
}
log.Printf("Subscribed to channel: %s", channel)

ch := pubsub.Channel() // 获取消息通道

for msg := range ch {
log.Printf("Received message from channel '%s': %s", msg.Channel, msg.Payload)
// 在这里处理接收到的消息,例如推送到 WebSocket 客户端
}
}

func main() {
initRedis()

router := gin.Default()

// 启动一个 goroutine 监听 Redis 消息
go SubscribeToChannel("my_chat_channel")

// 发布消息的路由
router.GET("/publish/:message", func(c *gin.Context) {
message := c.Param("message")
err := Rdb.Publish(Ctx, "my_chat_channel", message).Err()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to publish message: %v", err)})
return
}
c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("Message '%s' published to 'my_chat_channel'", message)})
})

fmt.Println("Gin server running on :8080")
router.Run(":8080")
}

测试 Pub/Sub:

  1. 运行上面的 Gin 应用程序。
  2. 访问 http://localhost:8080/publish/hello_world
  3. 查看应用程序的控制台输出,您会看到 Received message from channel 'my_chat_channel': hello_world,表明消息已成功发布并被订阅者接收。