Golang分布式应用之Redis怎么使用
1. Redis简介
Redis(Remote Dictionary Server),是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,常被称为数据结构服务器。Redis以其高速读写、丰富的数据类型支持、灵活的Lua脚本支持、简单稳定的主从复制特性等受到广泛的关注和使用。
1.1 Redis的数据类型
Redis支持多种数据类型,其中包括字符串、列表、哈希表、集合和有序集合。以下是对每种数据类型的简要描述:
- 字符串(string):字符串类型是Redis中最基本的数据类型之一,其它几种数据类型都是在字符串类型基础上进行改进的。字符串类型可以存储任何数据,包括二进制数据。
- 列表(list):列表类型是一组按照插入顺序排序的字符串组成的有序集合,可以添加、删除、访问列表中的元素,列表类型还提供了一些列操作,比如剪切、插入等等。
- 哈希表(hash):哈希类型是Redis中存储键值对的数据类型,其中每个键都对应一个值。哈希表可以存储对象类型的数据。
- 集合(set):集合类型是一组无序的字符串组成的集合,集合中元素不允许重复,并且支持交、并、差等操作。
- 有序集合(zset):有序集合类型是集合类型的一个变种,其中每个元素都要关联一个分值,有序集合中的元素按照其分值从小到大排列。
1.2 Redis的优点
- 内存存储:Redis将所有数据存储在内存中,读写速度极快。
- 支持多种数据类型:Redis支持多种数据类型,方便存储及查询。
- 支持事务:Redis支持事务操作,能确保一系列连续的操作的原子性。
- 支持持久化:Redis支持持久化操作,即将数据同步到磁盘上,并在Redis重启后能够恢复数据。
- 主从复制:Redis支持主从复制,能提供高可用性及数据备份。
- 应用场景广泛:Redis适用于多种场景,如缓存、消息队列、计数器、排行榜、实时消息等等。
2. 使用Redis
2.1 Redis的安装
要使用Redis需要先安装Redis,Redis安装包可以在官网下载。对于Ubuntu系统,可以通过以下命令安装Redis:
sudo apt-get update
sudo apt-get install redis-server
2.2 Redis的基本命令
以下是Redis的一些常用命令:
- SET key value:设置指定键的值。
- GET key:获取指定键的值。
- DEL key:删除指定键及其对应的值。
- MSET key1 value1 key2 value2 ...:一次设置多个键值对的值。
- MGET key1 key2 ...:一次获取多个键的值。
- INCR key:增加指定键的值。
- DECR key:减少指定键的值。
- EXISTS key:判断指定键是否存在。
2.3 Golang中的Redis操作
Golang中有多个第三方Redis库可以使用,其中比较广泛使用的有redigo和go-redis。以下是基于redigo的Redis操作示例:
package main
import (
"github.com/gomodule/redigo/redis"
"log"
)
func main() {
// 连接Redis
conn, err := redis.Dial("tcp", "localhost:6379")
if err != nil {
log.Fatalf("Failed to connect Redis: %s", err)
}
defer conn.Close()
// 设置键值对
if _, err := conn.Do("SET", "key1", "value1"); err != nil {
log.Fatalf("Failed to set value: %s", err)
}
// 获取键值对
value, err := redis.String(conn.Do("GET", "key1"))
if err != nil {
log.Fatalf("Failed to get value for key: %s", err)
}
log.Printf("The value for key1 is: %s\n", value)
}
以上代码中的redigo库通过Dial函数连接Redis,设置键值对使用的是Do函数,获取键值对使用的是String函数。
3. Redis集群
3.1 Redis集群概述
Redis集群是Redis提供的一种分布式方案,Redis集群可以自动将数据分布到多个节点上,提供高可用性及可扩展性。Redis集群采用分片的方式实现数据分布,在Redis集群中,每个节点都可以接收读写请求,数据写入时,Redis会根据数据键的hash值将其分配到不同的节点上。
3.2 Redis集群的搭建
以下是基于Docker的Redis集群搭建示例:
- 创建网络
docker network create redis-cluster
- 启动6个Redis实例
docker run --name redis1 --network redis-cluster -d redis:latest redis-server --appendonly yes
docker run --name redis2 --network redis-cluster -d redis:latest redis-server --appendonly yes
docker run --name redis3 --network redis-cluster -d redis:latest redis-server --appendonly yes
docker run --name redis4 --network redis-cluster -d redis:latest redis-server --appendonly yes
docker run --name redis5 --network redis-cluster -d redis:latest redis-server --appendonly yes
docker run --name redis6 --network redis-cluster -d redis:latest redis-server --appendonly yes
- 创建Redis集群
docker run -it --network redis-cluster --rm redis:latest redis-cli --cluster create \
172.18.0.2:6379 172.18.0.3:6379 172.18.0.4:6379 172.18.0.5:6379 172.18.0.6:6379 172.18.0.7:6379 \
--cluster-replicas 1
以上代码中的--cluster-replicas参数用于指定每个主节点的从节点个数。
3.3 Redis集群的测试
以下是通过redigo库测试Redis集群的示例:
package main
import (
"github.com/gomodule/redigo/redis"
"log"
)
func main() {
// 连接Redis集群
conn, err := redis.Dial("tcp", "172.18.0.2:6379")
if err != nil {
log.Fatalf("Failed to connect Redis cluster: %s", err)
}
defer conn.Close()
// 设置键值对
if _, err := conn.Do("SET", "key1", "value1"); err != nil {
log.Fatalf("Failed to set value: %s", err)
}
// 获取键值对
value, err := redis.String(conn.Do("GET", "key1"))
if err != nil {
log.Fatalf("Failed to get value for key: %s", err)
}
log.Printf("The value for key1 is: %s\n", value)
}
要连接Redis集群需要先连接任意一个节点,redigo库会自动将读写请求发送到正确的节点。
4. Redis分布式锁
4.1 Redis分布式锁的实现
Redis分布式锁是基于Redis的SETNX命令实现的,SETNX命令用于设置指定键的值,仅当该键不存在时才设置成功。通过SETNX命令设置一个指定键的值,可以实现分布式锁的获取,如果要释放分布式锁,只需要使用Redis的DEL命令删除该键即可。
4.2 Redis分布式锁的使用
以下是基于redigo库实现Redis分布式锁的示例:
package main
import (
"github.com/gomodule/redigo/redis"
"log"
"time"
)
func acquireLock(conn redis.Conn, lockname string, timeout int) (string, error) {
// 设置锁的唯一标识符
uuid := fmt.Sprintf("%s:%d", uuid.New().String(), time.Now().UnixNano()/1000000)
// 获取锁的超时时间
endTime := time.Now().Add(time.Duration(timeout) * time.Second)
// 循环尝试获取锁
for time.Now().Before(endTime) {
reply, err := redis.String(conn.Do("SET", lockname, uuid, "EX", 10, "NX"))
if err == nil && reply == "OK" {
return uuid, nil
}
// 等待一段时间再尝试获取锁
time.Sleep(50 * time.Millisecond)
}
return "", fmt.Errorf("Failed to acquire lock")
}
func releaseLock(conn redis.Conn, lockname string, uuid string) (bool, error) {
script := `
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end`
res, err := redis.Int(conn.Do("EVAL", script, 1, lockname, uuid))
if err != nil {
return false, err
}
return res == 1, nil
}
func main() {
// 连接Redis
conn, err := redis.Dial("tcp", "localhost:6379")
if err != nil {
log.Fatalf("Failed to connect Redis: %s", err)
}
defer conn.Close()
// 尝试获取锁
uuid, err := acquireLock(conn, "mylock", 10)
if err != nil {
log.Fatalf("Failed to acquire lock: %s", err)
}
log.Printf("Acquired lock with UUID: %s\n", uuid)
// 执行一些操作
// 释放锁
success, err := releaseLock(conn, "mylock", uuid)
if err != nil {
log.Fatalf("Failed to release lock: %s", err)
}
if success {
log.Println("Released lock successfully")
} else {
log.Println("Failed to release lock")
}
}
以上示例先通过acquireLock函数尝试获取锁,如果获取成功则执行一些操作,最后通过releaseLock函数释放锁。注意在acquireLock函数中设置了锁的超时时间,禁止锁无限制等待。另外注意,在使用分布式锁时要特别注意锁的竞争条件,保证代码正确性。