目录
一、Redis 简介
二、Go中Redis的使用
1. 安装Go Redis包
2. 单机模式
连接示例
3. 哨兵模式
依赖
连接示例
三、Redis集群
1. 集群模式
集群部署
部署结构
使用redis-cli创建集群
连接示例
四、常用数据结构与操作
1. 字符串(String)
2. 哈希(Hash)
3. 列表(List)
4. 集合(Set)
5. 有序集合(ZSet)
五、事务与批量操作
1. 事务
2. 管道技术
六、高可用性
1. 复制(主从)
2. 故障转移
3. 连接池
七、监控与性能调优
1. 内置工具
2. 性能指标
3. 调试
八、实际案例
1. 高并发秒杀系统
说明
九、最佳实践
1. 数据过期时间
2. 内存管理
3. 日志配置
4. 安全性
5. 监控
6. 备份恢复
7. 连接池管理
8. 数据持久化
十、总结
在Go语言中使用Redis进行数据存储和管理可以带来高效和灵活的优势。下面的讲解包括单机模式、哨兵模式和集群模式的部署及使用。
Redis是一个高性能的内存数据库,支持多种数据结构,如字符串(String)、哈希(Hash)、列表(List)、集合(Set)、有序集合(ZSet)等。它还支持事务、批量操作和流式数据处理。Redis常用于缓存、实时数据处理、计数器、消息队列等场景。
推荐使用github.com/go-redis/redis/v9
,它支持单机、哨兵和集群模式。
安装命令:
go get github.com/go-redis/redis/v9
特点:单一Redis实例,简单易用,适合开发和测试环境。
package main
import (
"context"
"fmt"
"github.com/go-redis/redis/v9"
)
func main() {
ctx := context.Background()
client := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "",
DB: 0,
})
pong, err := client.Ping(ctx).Result()
if err != nil {
fmt.Printf("连接失败:%v\n", err)
return
}
fmt.Println(pong) // 输出:PONG
// 示例:设置和获取值
if err := client.Set(ctx, "key", "value", 0).Err(); err != nil {
fmt.Printf("设置失败:%v\n", err)
return
}
val, err := client.Get(ctx, "key").Result()
if err != nil {
fmt.Printf("获取失败:%v\n", err)
return
}
fmt.Printf("值:%v\n", val)
}
说明:
redis.NewClient
创建客户端。Ping
测试连接。Set
设置键值,Get
读取值。特点:提供高可用性,当主库故障时,自动故障转移。
package main
import (
"context"
"fmt"
"github.com/go-redis/redis/v9"
)
func main() {
ctx := context.Background()
client := redis.NewFailoverClient(&redis.FailoverOptions{
MasterName: "mymaster",
SentinelAddrs: []string{"localhost:26379", "localhost:26380", "localhost:26381"},
})
pong, err := client.Ping(ctx).Result()
if err != nil {
fmt.Printf("连接失败:%v\n", err)
return
}
fmt.Println(pong)
// 示例:设置和获取值
if err := client.Set(ctx, "key", "value", 0).Err(); err != nil {
fmt.Printf("设置失败:%v\n", err)
return
}
val, err := client.Get(ctx, "key").Result()
if err != nil {
fmt.Printf("获取失败:%v\n", err)
return
}
fmt.Printf("值:%v\n", val)
}
说明:
redis.NewFailoverClient
创建客户端。MasterName
和哨兵地址。特点:通过分片实现水平扩展,每个节点处理一部分数据,适合高并发场景。
redis-cli
创建集群redis-cli
命令创建集群。创建集局脚本:例如 start_cluster.sh
# 启动6个节点,端口分别为30001到30006
for port in {30001..30006}; do
redis-server --cluster-enabled yes --cluster-config-file node-${port}.conf --port ${port} --daemonize yes
done
# 创建集群
redis-cli --cluster create 127.0.0.1:30001 127.0.0.1:30002 127.0.0.1:30003 127.0.0.1:30004 127.0.0.1:30005 127.0.0.1:30006 --cluster-replicas 1
在上面的脚本中,node-${port}.conf
是 Redis 集群模式下每个节点的配置文件,用于指定节点的运行参数。如果这些配置文件不存在,Redis 会自动生成一个默认的配置文件,但为了确保集群部署的正确性,最好手动创建这些配置文件。
例如:
# node-30001.conf
cluster-enabled yes
port 30001
bind 127.0.0.1
daemonize yes
logfile /var/log/redis/redis_30001.log
dir ./data/30001
save 60 1
appendonly yes
解释:
cluster-enabled yes
: 启用集群模式。port
: 指定当前节点的端口。bind
: 绑定主机地址。daemonize yes
: 后台运行。logfile
: 指定日志文件路径。dir
: 指定数据文件存储路径。save
: 配置数据持久化策略。appendonly
: 启用 AOF 持久化。然后给脚本赋予执行权限并运行:
chmod +x start_cluster.sh
./start_cluster.sh
package main
import (
"context"
"fmt"
"github.com/go-redis/redis/v9"
)
func main() {
// 集群节点地址
addresses := []string{
"localhost:30001",
"localhost:30002",
"localhost:30003",
"localhost:30004",
"localhost:30005",
"localhost:30006",
}
clusterClient := redis.NewClusterClient(&redis.ClusterOptions{
Addrs: addresses,
})
pong, err := clusterClient.Ping(context.Background()).Result()
if err != nil {
fmt.Printf("连接失败:%v\n", err)
return
}
fmt.Println(pong)
// 设置键值对
if err := clusterClient.Set(context.Background(), "key", "value", 0).Err(); err != nil {
fmt.Printf("设置失败:%v\n", err)
return
}
// 获取值
val, err := clusterClient.Get(context.Background(), "key").Result()
if err != nil {
fmt.Printf("获取失败:%v\n", err)
return
}
fmt.Printf("值:%v\n", val)
}
说明:
redis.NewClusterClient
创建集群客户端。// 设置过期时间
err = client.Set(context.Background(), "key", "value", 10*time.Second).Err()
// 递增计数器
num, err := client.Incr(context.Background(), "counter").Result()
// 递减计数器
num, err := client.Decr(context.Background(), "counter").Result()
// 设置字段值
err = client.HSet(context.Background(), "hashKey", "field", "value").Err()
// 获取字段值
value, err := client.HGet(context.Background(), "hashKey", "field").Result()
// 从左边推入元素
err = client.LPush(context.Background(), "listKey", "value").Err()
// 弹出左边第一个元素
value, err := client.LPop(context.Background(), "listKey").Result()
// 添加元素
err = client.SAdd(context.Background(), "setKey", "element").Err()
// 移除元素
err = client.SRem(context.Background(), "setKey", "element").Err()
// 获取所有元素
members, err := client.SMembers(context.Background(), "setKey").Result()
// 添加元素并设置分数
err = client.ZAdd(context.Background(), "zsetKey", &redis.Z{Member: "element", Score: 100}).Err()
// 获取元素的分数
score, err := client.ZScore(context.Background(), "zsetKey", "element").Result()
// 获取排名
rank, err := client.ZRank(context.Background(), "zsetKey", "element").Result()
// 开始事务
ctx := context.Background()
tx, err := client.Tx(ctx)
// 执行事务中的操作
_, err = tx.Pipeline()(
function(ctx context.Context) (_redis.CMDCb, error) {
_, err := tx.Get(ctx, "balance").Result()
if err != nil {
return nil, err
}
_, err := tx.Incr(ctx, "balance").Result()
if err != nil {
return nil, err
}
return nil, nil
},
)
if err != nil {
fmt.Printf("事务执行失败:%v\n", err)
}
// 创建管道
pipe := client.Pipeline()
// 执行多个命令
cmds, err := pipe.Set(context.Background(), "key1", "value1", 0).
Set(context.Background(), "key2", "value2", 0).
Exec(context.Background())
if err != nil {
fmt.Printf("管道执行失败:%v\n", err)
return
}
// 打印结果
for _, cmd := range cmds {
fmt.Printf("%v\n", cmd)
}
// 配置连接池
pool := &redis.Pool{
Dial: func(context.Context) (redis.Conn, error) {
return client.DialContext(context.Background())
},
MaxActive: 10, // 最大活跃连接数
MaxIdle: 5, // 最大空闲连接数
IdleTimeout: 5 * time.Minute,
}
// 使用连接池
conn := pool.Get(context.Background())
defer conn.Close()
# 基准测试
redis-benchmark -h 127.0.0.1 -p 6379 -t set,lpush -n 10000 -q
info memory
查看内存状态。maxmemory-policy
避免内存溢出。expire
,控制键生命周期。slowlog
记录慢查询。blocked clients
和master_repl_offset
。###需求 在高并发下,确保商品库存正确。
###解决方案
package main
import (
"context"
"fmt"
"sync"
"time"
"github.com/go-redis/redis/v9"
)
// 秒杀函数
func doSecKill(ctx context.Context, client *redis.Client, productId string, userId string) (bool, error) {
// 锁名称:秒杀锁
lockKey := "lock:sec:kill"
// 商品库存Key
stockKey := "stock:" + productId
// 尝试获取锁,防止超卖
lock, err := client.LockNew(lockKey, 100*time.Millisecond).Acquire(ctx, time.Second*5).Result()
if err != nil {
return false, err
}
defer lock.Release(ctx)
// 检查库存是否充足
currentStock, err := client.HGet(ctx, stockKey, "quantity").Int64()
if err != nil || currentStock <= 0 {
return false, fmt.Errorf("库存不足")
}
// 减少库存
_, err = client.HIncrBy(ctx, stockKey, "quantity", -1).Result()
if err != nil {
return false, fmt.Errorf("秒杀失败:%v", err)
}
return true, nil
}
func main() {
client := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "",
DB: 0,
})
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func(userId int) {
defer wg.Done()
ctx := context.Background()
success, err := doSecKill(ctx, client, "product001", fmt.Sprintf("user%d", userId))
if success {
fmt.Printf("用户%d秒杀成功\n", userId)
} else {
fmt.Printf("用户%d秒杀失败:%v\n", userId, err)
}
}(i)
}
wg.Wait()
}
used_memory
,确保内存使用在可控范围内。maxmemory
和maxmemory-policy
。slowlog
跟踪慢查询。在Go语言中使用Redis,特别是结合哨兵和集群模式,可以实现高可用和高扩展性的系统。合理选择数据结构,使用事务和管道技术,可以提升性能。同时,注重监控和维护,确保Redis的稳定运行。