Redis在Go中的应用

Go 作为一种新兴的语言,由于其本身在网络编程方面表现出来的优势,现在受到越来越多开发者们的青睐。不过,也正是因为它太新,相比于一些很成熟的语言,它的第三方库在质量和数量上并不占优势。相信随着时间的变化,Go程序员的群体日益庞大,这个问题最终会得以解决。

这里主要是介绍第三方库go-redis/redis在Go中的应用。redis是一个高性能的key-value数据库,这个不用多说,现在很多公司都在使用redis。Go语言官方已经提供了对redis的支持,但是没有redis client的官方实现,而go-redis/redis 是一个很好用的第三方库,目前托管在Github上,相比其它关于Go的redis client,上手更简单,虽然它提供的文档不够丰富,但是方法命名上保持了与redis原生命令的一致,见名知义, 稍微研究下就可以马上着手使用。

准备工作

首先去Github上把go-redis/redis 通过git clone下载下来,并放到指定的目录。

1
git clone git@github.com:go-redis/redis.git $GOPATH/src/github.com/go-redis/redis

导入要用到的包

1
2
3
4
5
import (
"github.com/go-redis/redis"
"fmt"
"log"
)

如果是在本地测试,记得先打开redis-server。
现在连接到redis-server

1
2
3
4
5
6
7
client := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", //默认空密码
DB: 0, //使用默认数据库
})
defer client.Close() //最后关闭

测试连接。如果ping通会收到返回的信息PONG

1
2
3
4
5
pong, err := client.Ping().Result()
if err != nil {
log.Fatal(err)
}
fmt.Println("Connected result: ", pong)

常见数据类型的操作

strings的操作

String 是redis最基本的数据类型,一个key 对应 一个 value。value的最大数据长度是512M,只要不超过这个大小,可以直接存放任意格式的数据。

存储一个string

1
client.Set("str1", "hello redis",0) //忽略错误

读取设定的值

1
2
str := client.Get("str1")
fmt.Println(str)

删除string

1
client.Del("strtest")

lists的操作

在redis里,list是简单的字符串列表,可以添加一个元素到列表的头部和尾部。

插入一个值,如果key不存在,新建一个list。

1
client.LPush("list","one","two","three") //rpush则在尾部插入

删除list中的值

1
2
client.LRem("list",2,"three") //删除list中前2个value为 ‘three’的元素
client.LPop("list") //删除头部的值,同理RPop删除尾部的值。

读取list的值。LRange方法第二个参数start是list读取开始的位置,第三个参数end是结束的位置,通过这两个参数设定操作的范围。如果第三个参数的值超过list的的长度,则redis会从list的头部开始遍历,直到读取 end - start +1 个值。

1
2
3
4
5
6
7
8
9
10
list, _ := client.LRange("list", 0, 2).Result()
fmt.Println("List: ", list)
//output:
//List: [three two one]
client.LRem("list",1,"three") //删除一个“three”
list, _ := client.LRange("list", 0, 2).Result()
fmt.Println("List: ", list) //从输出发现list中只剩下two 和one,遍历完list后又从头开始遍历再输出一个two
//output:
//List: [two one two]

hashes的操作

Hash 非常适合存储对象,比如用户的注册信息等等。一个hash 有多个字段,存储的时候根据需要设定相应字段的值。

存储hash

1
2
3
4
5
user := make(map[string]interface{})
user["name"] = "jim"
user["gender"] = "man"
user["age"] = 23
client.HMSet("user",user)

存取单个字段

1
2
3
client.HSet("user", "name","tom")
name := client.HGet("user","name")
fmt.Print(name)

获取整个hash

1
2
3
4
5
6
hash, _ := client.HGetAll("user").Result()
for k, v:= range hash{
fmt.Printf("key: %v, value: %v ",k, v)
}
//output:
//key: name, value: jim key: age, value: 23 key: gender, value: man

sets的操作

Set是没有排序的字符串集合,它的特点是里面不允许有重复的元素。还有一种有有序的集合,操作与set类似,只不过多了一个score的属性,通过这个属性可以获取指定顺序的集合。

新建一个set

1
client.SAdd("set", 7, 6, 5, 3)

获得set的元素数量

1
count := client.SCard("set")

获取整个set

1
2
3
4
nums:= client.SMembers("set")
fmt.Println("Set:", nums)
//output:
//Set: [3 5 6 7]

查看数据库中所有的key

1
2
3
4
result, _ := client.Keys("*").Result()
fmt.Println("Redis value: ", result)
//output:
//Redis value: [pipe list str1 str2 set user]

管道和事务

管道

在redis中,管道(Pipleline)可以简单的理解为一系列命令的打包。通常,我们通过redis-cli与redis-server交互时,都是一个命令执行完后,明确收到redis-server的反馈信息时才进行下一个命令。这种交互是堵塞式的,效率比较低下。如:

1
2
3
4
5
6
7
client: INCR X
server: 1
client: INCR X
server: 2
client: INCR X
server: 3
...

使用管道后,多个命令可以放到一起一起执行,其实在管道中redis也是依次执行每个命令的,只是下一个命令不必等到上一个命令执行完反馈到client后再执行。

1
2
3
4
5
6
7
8
client: INCR X
client: INCR X
client: INCR X
...
server: 1
server: 2
server: 3
...

使用管道。下面代码首先新建一个键 pipe,然后通过三个命令使它的值每次递增1,把这几个命令打包成进一个管道,然后一起执行。

1
2
3
4
5
6
7
8
9
10
pl := client.Pipeline()
pl.Set("pipe", 0,0)
pl.Incr("pipe")
pl.Incr("pipe")
pl.Incr("pipe")
pl.Exec()
p,_ := client.Get("pipe").Result()
fmt.Println("Pipe: ",p)
//output:
//Piple: 3

事务

Redis也像其它数据库一样提供了事务机制,通过MULTI可以开启一个事务,通过EXEC提交一个事务,DISCARD则可以回滚一个操作,WATCH和UNWATCH可以监控和取消监控指定的key。事务与管理类似,也是多个命令的打包,然后放到一起执行。只不过只要有一个命令执行失败,所有操作都会回滚。在go-redis/redis中使用事务很简单。代码基本上与使用管道一样。

1
2
3
4
5
6
7
8
9
10
pl := client.TxPipeline() //此处只需把Pipleline()换成TxPipleline()
pl.Set("pipe", 0,0)
pl.Incr("pipe")
pl.Incr("pipe")
pl.Incr("pipe")
pl.Exec()
p,_ := client.Get("pipe").Result()
fmt.Println("Pipe: ",p)
//output:
//Transaction: 3

订阅

Redis中的订阅是一种消息通信机制。订阅者订阅了频道后,只要发送者通过这个频道发送消息,所有的订阅者就会收到消息。

下列代码通过Publish()创建了名为mychannel 的频道并发布。接着,开启一个goroutine用来订阅这个频道,Subscribe()方法返回一个Pubsub,通过Receive()来接受发布者发布的消息。为了保证在主线程在结束前goroutine收到信息,创建了一个空的chan。

1
2
3
4
5
6
7
8
9
10
done := make(chan struct{})
client.Publish("mychannel", "hello budy!\n")
go func() {
pubsub := client.Subscribe("mychannel")
msg,_ := pubsub.Receive()
fmt.Println("Receive from channel:", msg)
done <- struct {}{}
}()
<-done