简述

Go语言中操作Redis有很多库可供选择,其中比较流行的有go-redis、redigo和go-redis-cluster等。
这里以go-redis为例,介绍如何使用Go语言操作Redis。

go get github.com/go-redis/redis #安装 第三方库

简单案例:

package main

import (
    "fmt"
    "github.com/go-redis/redis"
)

func main() {
    // 创建Redis客户端
    client := redis.NewClient(&redis.Options{
        Addr:     "localhost:6379",
        Password: "", // Redis密码
        DB:       0,  // Redis数据库编号
    })

    // 测试连接
    pong, err := client.Ping().Result()
    fmt.Println(pong, err)

    // 设置键值对
    err = client.Set("key", "value", 0).Err()
    if err != nil {
        panic(err)
    }

    // 获取键值对
    val, err := client.Get("key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println("key", val)

    // 删除键值对
    err = client.Del("key").Err()
    if err != nil {
        panic(err)
    }
}****

需要注意的是,使用go-redis库操作Redis时,需要注意线程安全性,可以使用连接池等方式来保证线程安全。

redis 连接池

作用一:
在连接池中制定最高连接IO上限,避免过度开辟连接到redis服务端的IO资源。

作用二:
事先初始化一定数量的IO连接,放入到连接池中,当GO需要操作redis时,直接从池子中获取连接,提高了IO连接的效率。

简单案例:

package main

import (
    "fmt"
    "github.com/go-redis/redis"
    "sync"
)

func main() {
    // 创建Redis连接池
    pool := redis.NewClient(&redis.Options{
        Addr:     "localhost:6379",
        Password: "", // Redis密码
        DB:       0,  // Redis数据库编号
        PoolSize: 10, // 连接池大小
    })

    // 测试连接
    pong, err := pool.Ping().Result()
    fmt.Println(pong, err)

    // 并发操作Redis
    var wg sync.WaitGroup
    for i := 0; i < 100; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            err := pool.Set("key", "value", 0).Err()
            if err != nil {
                panic(err)
            }
            val, err := pool.Get("key").Result()
            if err != nil {
                panic(err)
            }
            fmt.Println("key", val)
            err = pool.Del("key").Err()
            if err != nil {
                panic(err)
            }
        }()
    }
    wg.Wait()

    // 关闭连接池
    pool.Close()
}

Go常见操作

// 获取
	val, err := pool.Get("wtt").Result()
	if err != nil {
		panic(err)
	}
	fmt.Printf("%T---%+v\n", val, val)

	// 设置
	err = pool.Set("key", "10", 0).Err()
	if err != nil {
		panic(err)
	}

	// 有效期设置
	err = pool.Set("key", "value", time.Second*10).Err()
	if err != nil {
		panic(err)
	}

	// 自增自减
	err = pool.Set("key", "10", 0).Err()
	if err != nil {
		panic(err)
	}
	err = pool.Incr("key").Err()
	if err != nil {
		panic(err) // 如果值不是 数字类型,则报错:ERR value is not an integer or out of range
	}
	err = pool.Decr("key").Err()
	if err != nil {
		panic(err)
	}

字符串

// 存储字符串
	err := client.Set("name", "Tom", 0).Err()
	if err != nil {
		panic(err)
	}

	// 读取字符串
	name, err := client.Get("name").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("name:", name)

	// 删除字符串
	err = client.Del("name").Err()
	if err != nil {
		panic(err)
	}

列表

// 存储列表
	err = client.LPush("list", "a", "b", "c").Err()
	if err != nil {
		panic(err)
	}

	// 读取列表
	list, err := client.LRange("list", 0, -1).Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("list:", list)

	// 删除列表
	err = client.Del("list").Err()
	if err != nil {
		panic(err)
	}

	// 获取列表长度
	length, err := client.LLen("list").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("list length:", length)

	// 获取列表中指定位置的元素
	element, err := client.LIndex("list", 1).Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("list[1]:", element)

	// 从列表头部弹出一个元素
	element, err = client.LPop("list").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("pop from left:", element)

	// 从列表尾部弹出一个元素
	element, err = client.RPop("list").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("pop from right:", element)

	// 将一个元素插入到列表头部,如果列表不存在则不插入
	err = client.LPushX("list", "d").Err()
	if err != nil {
		panic(err)
	}

	// 将一个元素插入到列表尾部,如果列表不存在则不插入
	err = client.RPushX("list", "e").Err()
	if err != nil {
		panic(err)
	}

	// 将一个元素插入到列表中指定元素的前面或后面
	err = client.LInsert("list", "BEFORE", "b", "x").Err()
	if err != nil {
		panic(err)
	}

	// 设置列表中指定位置的元素
	err = client.LSet("list", 1, "y").Err()
	if err != nil {
		panic(err)
	}

	// 截取列表,只保留指定范围内的元素
	err = client.LTrim("list", 1, 3).Err()
	if err != nil {
		panic(err)
	}

	// 阻塞式弹出元素,如果列表为空则一直等待,直到有元素可弹出
	result, err := client.BLPop(0, "list").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("blpop:", result)
}

集合

// 存储集合
	err = client.SAdd("set", "a", "b", "c").Err()
	if err != nil {
		panic(err)
	}

	// 读取集合
	set, err := client.SMembers("set").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("set:", set)

	// 删除集合
	err = client.Del("set").Err()
	if err != nil {
		panic(err)
	}

	// 获取集合中元素的数量
	count, err := client.SCard("set").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("set count:", count)

	// 判断元素是否在集合中
	exists, err := client.SIsMember("set", "a").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("a exists in set:", exists)

	// 从集合中随机弹出一个元素
	element, err := client.SPop("set").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("pop from set:", element)

	// 从集合中随机获取一个元素
	element, err = client.SRandMember("set").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("random element from set:", element)

	// 从集合中移除指定元素
	err = client.SRem("set", "b").Err()
	if err != nil {
		panic(err)
	}

	// 获取多个集合的交集
	intersection, err := client.SInter("set1", "set2").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("set1 ∩ set2:", intersection)

	// 获取多个集合的并集
	union, err := client.SUnion("set1", "set2").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("set1 ∪ set2:", union)

	// 获取多个集合的差集
	difference, err := client.SDiff("set1", "set2").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("set1 - set2:", difference)

散列

// 存储散列
	err = client.HSet("hash", "name", "Tom").Err()
	if err != nil {
		panic(err)
	}

	// 读取散列
	hash, err := client.HGetAll("hash").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("hash:", hash)

	// 删除散列
	err = client.Del("hash").Err()
	if err != nil {
		panic(err)
	}

	// 获取散列中指定字段的值
	value, err := client.HGet("hash", "field1").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("hash[field1]:", value)

	// 获取散列中所有字段和值
	all, err := client.HGetAll("hash").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("hash:", all)

	// 获取散列中所有字段
	fields, err := client.HKeys("hash").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("hash fields:", fields)

	// 获取散列中所有值
	values, err := client.HVals("hash").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("hash values:", values)

	// 判断散列中是否存在指定字段
	exists, err := client.HExists("hash", "field1").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("field1 exists in hash:", exists)

	// 删除散列中指定字段
	err = client.HDel("hash", "field2").Err()
	if err != nil {
		panic(err)
	}

	// 获取散列中字段的数量
	count, err := client.HLen("hash").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("hash count:", count)

有序集合

// 存储有序集合
	err = client.ZAdd("zset", 
        redis.Z{Score: 1, Member: "a"}, 
        redis.Z{Score: 2, Member: "b"}, 
        redis.Z{Score: 3, Member: "c"},
    ).Err()
	if err != nil {
		panic(err)
	}

	// 读取有序集合
	zset, err := client.ZRangeWithScores("zset", 0, -1).Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("zset:", zset)

	// 删除有序集合
	err = client.Del("zset").Err()
	if err != nil {
		panic(err)
	}


	// 获取有序集合中元素的数量
	count, err := client.ZCard("zset").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("zset count:", count)

	// 获取有序集合中指定成员的排名
	rank, err := client.ZRank("zset", "b").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("b rank in zset:", rank)

	// 获取有序集合中指定成员的分数
	score, err := client.ZScore("zset", "c").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("c score in zset:", score)

	// 获取有序集合中指定排名范围内的成员和分数
	rangeByRank, err := client.ZRangeWithScores("zset", 0, 1).Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("zset rank 0-1:", rangeByRank)

	// 获取有序集合中指定分数范围内的成员和分数
	rangeByScore, err := client.ZRangeByScoreWithScores("zset", &redis.ZRangeBy{
		Min: "2",
		Max: "3",
	}).Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("zset score 2-3:", rangeByScore)

	// 从有序集合中移除指定成员
	err = client.ZRem("zset", "b").Err()
	if err != nil {
		panic(err)
	}

	// 获取有序集合中指定排名范围内的成员和分数,并按分数从大到小排序
	rangeByRankDesc, err := client.ZRevRangeWithScores("zset", 0, 1).Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("zset rank 0-1 desc:", rangeByRankDesc)

redis 发布订阅(消息队列)

Redis的发布订阅(Publish/Subscribe)是一种消息传递模式,它包含两个主要的角色:发布者和订阅者。
发布者将消息发送到指定的频道(Channel),订阅者可以订阅一个或多个频道,从而接收发布者发送的消息。

  • 发布者(生产者)
func main() {
	// 连接Redis
	client := redis.NewClient(&redis.Options{
		Addr:     "localhost:6666",
		Password: "123456", // Redis密码
		DB:       0,        // Redis数据库编号
	})

	// 发布消息
	for i := 0; i < 10; i++ {
		msg := fmt.Sprintf("消息%d", i)
		err := client.Publish("mychannel", msg).Err()
		if err != nil {
			panic(err)
		}
		time.Sleep(time.Second)
	}

	fmt.Println("消息发布完成")
}
  • 订阅者(消费值)
func main() {
	// 连接Redis
	client := redis.NewClient(&redis.Options{
		Addr:     "localhost:6666",
		Password: "123456", // Redis密码
		DB:       0,        // Redis数据库编号
	})

	// 订阅频道
	pubsub := client.Subscribe("mychannel")
	defer pubsub.Close()

	// 接收消息
	for {
		msg, err := pubsub.ReceiveMessage()
		if err != nil {
			panic(err)
		}
		fmt.Println("Received message:", msg.Payload)
	}
}

redis 管道

类似于mysql的存储过程, Redis管道是一种优化Redis操作的方法,它可以将多个Redis命令打包成一个请求一次性发送给Redis服务器,
从而减少了网络通信的开销,提高了Redis的性能。

func main() {
    // 批量设置键值对
    pipe := client.Pipeline() // 生成一个管道

    pipe.Set("key1", "value1", 0)
    pipe.Set("key2", "value2", 0)

    _, err := pipe.Exec() // 将管道中 存储的命令 一次性都执行
    if err != nil {
        panic(err)
    }
}

redis 事务

Redis事务是一组命令的集合,这些命令可以作为一个单独的操作来执行,要么全部执行成功,要么全部执行失败,
不存在部分执行的情况。Redis事务的执行是原子性的,即在事务执行期间,其他客户端不能对其中的数据进行修改。

Redis事务的执行分为三个步骤:=开始事务、执行事务、提交事务=。
在执行事务期间,客户端可以向事务中添加多个命令,这些命令不会立即执行,而是在执行事务时一起执行。

  • MULTI:开启事务
  • EXEC:执行事务(让开启事务之后的一系列的redis语句 对redis客户端的数据生效)
  • DISCARD:取消事务
  • WATCH:监视事务中的键变化,一旦有改变则取消事务。
func main() {
    // 连接Redis
    client := redis.NewClient(&redis.Options{
        Addr:     "localhost:6379",
        Password: "", // Redis密码
        DB:       0,  // Redis数据库编号
    })

    // 开始事务
    tx := client.TxPipeline()
    defer tx.Close()

    // 添加命令
    tx.Set("key1", "value1", 0)
    tx.Set("key2", "value2", 0)

    // 执行事务
    _, err := tx.Exec()
    if err != nil {
        panic(err)
    }
    fmt.Println("Transaction executed")
}

思考: redis 事务操作的时候 会 锁着 待操作的 数据吗?

Redis在执行事务期间=不会锁定待操作的数据=,因此在事务执行期间,其他客户端仍然可以对其中的数据进行修改。

Redis的事务是通过将多个命令打包成一个原子操作来实现的,这些命令在执行时会被放入一个队列中,
直到执行EXEC命令时才会一起执行。因此,在事务执行期间,其他客户端可以对其中的数据进行修改,
但是这些修改不会影响到事务的执行结果。

如果需要保证事务的执行结果不受其他客户端的影响,可以使用Redis的 乐观锁机制,即在执行事务前获取数据的版本号
然后在执行事务时检查数据的 版本号 是否发生变化,如果没有变化,则执行事务,否则放弃事务。

总之,=Redis的事务机制并不是通过锁定待操作的数据来实现的,而是通过将多个命令打包成一个原子操作来实现的=。

redis 分布式事务锁

Redis分布式事务锁是一种 基于Redis 实现的分布式锁,它可以用于解决分布式系统中的 并发问题。
在分布式系统中,多个进程或线程可能同时访问同一个资源,如果不加控制,就会出现数据不一致的问题。
分布式锁就是为了解决这个问题而设计的。

Redis分布式事务锁的实现原理是利用Redis的事务和Lua脚本功能。具体实现步骤如下:
1、生成一个唯一的标识符,作为锁的名称。
2、使用Redis的SETNX命令尝试获取锁,如果返回值为1,则表示获取锁成功,否则表示锁已经被其他进程或线程占用。
3、如果获取锁成功,则设置锁的过期时间,防止锁一直被占用。
4、执行业务逻辑。
5、释放锁,使用Redis的DEL命令删除锁。

下面是一个使用Go语言实现Redis分布式事务锁的示例代码:

package main

import (
	"fmt"
	"time"

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

func main() {
	// 创建Redis客户端
	client := redis.NewClient(&redis.Options{
		Addr:     "localhost:6379",
		Password: "", // Redis无密码
		DB:       0,  // 默认数据库
	})

	// 生成一个唯一的标识符,作为锁的名称
	lockKey := "lock"

	// 设置锁的过期时间
	lockExpire := 10 * time.Second

	// 尝试获取锁
	for {
		// 使用Redis的SETNX命令尝试获取锁
		ok, err := client.SetNX(lockKey, 1, lockExpire).Result()
		if err != nil {
			panic(err)
		}

		// 如果获取锁成功,则执行业务逻辑
		if ok {
			fmt.Println("get lock success")

			// 执行业务逻辑
			// ...

			// 释放锁
			err := client.Del(lockKey).Err()
			if err != nil {
				panic(err)
			}
			fmt.Println("release lock success")
			break
		}

		// 如果获取锁失败,则等待一段时间后重试
		time.Sleep(100 * time.Millisecond)
	}
}

说明:
需要注意的是,Redis分布式事务锁虽然可以解决分布式系统中的并发问题,但也存在一些缺点,比如可能会出现死锁、锁竞争等问题,需要根据具体情况进行调整和优化。