Redis高性能缓存 redigo使用| 青训营笔记
这是我参与「第三届青训营 -后端场」笔记创作活动的的第4篇笔记
为了减少读数据库的次数,提升服务性能,考虑使用redis作为缓存。在此记录一下redis的使用方法。
概述
Redis 是一个高性能的开源的、C语言写的Nosql(非关系型数据库),数据保存在内存中。
特点
- 数据保存在内存,存取速度快,并发能力强
- 它支持存储的value类型相对更多,包括string、list、set、 zset和hash。
- 支持持久化,可以将数据保存在硬盘的文件中
- 提供多种语言api
- 支持订阅/发布(subscribe/publish)功能
安装Redis
考虑使用docker进行安装
安装
拉取最新redis镜像
$ sudo docker pull redis
也可根据dockerhub选择想要的版本
$ sudo docker pull redis:7.0.0
查看本地镜像
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
redis latest 53aa81e8adfa 3 days ago 117MB
jaegertracing/all-in-one latest 7c909b27ae06 2 weeks ago 57.8MB
bitnami/etcd latest f849c073a49e 2 weeks ago 152MB
mysql latest 76152be68449 3 weeks ago 524MB
下载redis的配置文件
$ wget http://download.redis.io/redis-stable/redis.conf
$ sudo cp redis.conf /data/redis/
对配置文件进行修改
主要修改的配置如下
bind 127.0.0.1
:注释掉这部分,使redis可以外部访问daemonize no
:用守护线程的方式启动requirepass 你的密码
:给redis设置密码appendonly yes
:redis持久化,默认是notcp-keepalive 300
:防止出现远程主机强迫关闭了一个现有的连接的错误 默认是300
启动redis
$ sudo docker run -p 6379:6379 --name redis -v /data/redis/redis.conf:/etc/redis/redis.conf -v /data/redis/data:/data -d redis redis-server /etc/redis/redis.conf --appendonly yes
参数解释:
-p 6379:6379
:将容器的6379端口映射到宿主机6379端口--name redis
:容器名称-v /data/redis/redis.conf:/etc/redis/redis.conf
:将配置文件放至容器中的指定位置-v /data/redis/data:/data
:挂载目录redis-server /etc/redis/redis.conf
:按照配置文件启动--appendonly yes
:启动数据持久化
查看是否启动成功
$ sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a13b346c5d30 redis "docker-entrypoint.s…" 6 hours ago Up 6 hours 0.0.0.0:6379->6379/tcp, :::6379->6379/tcp redis
查看容器log
$ sudo docker logs redis
1:C 01 Jun 2022 02:09:54.592 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
1:C 01 Jun 2022 02:09:54.593 # Redis version=7.0.0, bits=64, commit=00000000, modified=0, pid=1, just started
1:C 01 Jun 2022 02:09:54.593 # Configuration loaded
1:M 01 Jun 2022 02:09:54.593 * monotonic clock: POSIX clock_gettime
1:M 01 Jun 2022 02:09:54.595 * Running mode=standalone, port=6379.
1:M 01 Jun 2022 02:09:54.595 # Server initialized
1:M 01 Jun 2022 02:09:54.595 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
1:M 01 Jun 2022 02:09:54.596 * The AOF directory appendonlydir doesn't exist
1:M 01 Jun 2022 02:09:54.599 * SYNC append only file rewrite performed
1:M 01 Jun 2022 02:09:54.602 * Ready to accept connections
1:M 01 Jun 2022 03:09:55.093 * 1 changes in 3600 seconds. Saving...
1:M 01 Jun 2022 03:09:55.095 * Background saving started by pid 23
23:C 01 Jun 2022 03:09:55.100 * DB saved on disk
23:C 01 Jun 2022 03:09:55.100 * Fork CoW for RDB: current 0 MB, peak 0 MB, average 0 MB
1:M 01 Jun 2022 03:09:55.196 * Background saving terminated with success
golang操作Redis
使用第三方库redigo
,
官方文档:redigo
github地址:github.com/garyburd/re…
go get -u github.com/gomodule/redigo/redis
连接redis
使用Dial
连接
package main
import (
"fmt"
"github.com/garyburd/redigo/redis"
)
func main() {
c, err := redis.Dial("tcp", "127.0.0.1:6379")
if err != nil {
fmt.Println("Connect to redis error", err)
return
}
defer c.Close()
}
如果在配置中设置了requirepass
需要进行auth
认证,例如密码设置为123
package main
import (
"fmt"
"github.com/garyburd/redigo/redis"
)
func main() {
c, err := redis.Dial("tcp", "127.0.0.1:6379")
if err != nil {
fmt.Println("Connect to redis error", err)
return
}
defer c.Close()
_, err = c.Do("AUTH", "123")
if err != nil {
fmt.Println("auth error", err)
return
}
}
使用pool连接
package main
import (
"fmt"
"github.com/gomodule/redigo/redis"
"time"
)
var rds redis.Conn
func RedisPollInit() *redis.Pool {
return &redis.Pool{
MaxIdle: 5, //最大空闲数
MaxActive: 0, //最大连接数,0不设上
Wait: true,
IdleTimeout: time.Duration(1) * time.Second, //空闲等待时间
Dial: func() (redis.Conn, error) {
c, err := redis.Dial("tcp", "127.0.0.1:6379") //redis IP地址
if err != nil {
fmt.Println(err)
return nil, err
}
return c, err
},
}
}
func RedisInit() {
rds = RedisPollInit().Get()
}
func RedisClose() {
_ = rds.Close()
}
string类型
示例
func StringOpiton(client redis.Conn) {
_, err := client.Do("set", "hello", "world")
if err != nil {
log.Fatal("set字符串失败,", err)
}
val, err := client.Do("get", "hello")
if err != nil {
log.Fatal("get字符串失败", err)
}
strVal, _ := redis.String(val, nil)
fmt.Println("get hello = ", strVal)
}
结果:
get hello = world
同时可以设置过期时间
func StringOpitonWithEX(client redis.Conn) {
//设置过期时间 单位为秒
_, err := client.Do("set", "helloex", "worldex", "EX", "2")
if err != nil {
log.Fatal("set字符串失败,", err)
return
}
val, err := client.Do("get", "helloex")
if err != nil {
log.Fatal("get字符串失败", err)
return
}
strVal, _ := redis.String(val, nil)
fmt.Println("get helloex = ", strVal)
time.Sleep(3 * time.Second) //过期
exist, _ := client.Do("exists", "helloex")
ex, _ := redis.Int(exist, nil)
fmt.Println("exist = ", ex == 1)
}
结果
get helloex = worldex
exist = false
list类型
示例
func ListOption(client redis.Conn) {
//删除key
client.Do("del", "list1")
// lpush
client.Do("lpush", "list1", "l1")
client.Do("lpush", "list1", "l2")
// rpush
client.Do("rpush", "list1", "l3")
// lrange list from end
list, _ := client.Do("lrange", "list1", "0", "200")
l, _ := redis.Values(list, nil)
for _, val := range l {
fmt.Println(string(val.([]byte)))
}
fmt.Println("begin pop")
client.Do("lpop", "list1")
list, _ = client.Do("lrange", "list1", "0", "200")
l, _ = redis.Values(list, nil)
for _, val := range l {
fmt.Println(string(val.([]byte)))
}
}
结果
l2
l1
l3
begin pop
l1
l3
list类型常见操作
rpush(key, value)//在名称为key的list尾添加一个值为value的元素
lpush(key, value)//在名称为key的list头添加一个值为value的 元素
llen(key)//返回名称为key的list的长度
lrange(key, start, end)//返回名称为key的list中start至end之间的元素
ltrim(key, start, end)//截取名称为key的list
lindex(key, index)//返回名称为key的list中index位置的元素
lset(key, index, value)//给名称为key的list中index位置的元素赋值
lrem(key, count, value)//删除count个key的list中值为value的元素
lpop(key)//返回并删除名称为key的list中的首元素
rpop(key)//返回并删除名称为key的list中的尾元素
blpop(key1, key2,… key N, timeout)//lpop命令的block版本。
brpop(key1, key2,… key N, timeout)//rpop的block版本。
rpoplpush(srckey, dstkey)//返回并删除名称为srckey的list的尾元素,并将该元素添加到名称为dstkey的list的头部
更多list示例可见golang-redis之list类型简单操作
哈希类型
示例
func HashOption(client redis.Conn) {
//删除key
client.Do("del", "user")
client.Do("hset", "user", "key1", "val1")
client.Do("hset", "user", "key2", "val2")
client.Do("hset", "user", "key3", "val3")
client.Do("hset", "user", "key4", "val4")
val, err := client.Do("hgetall", "user")
if err != nil {
fmt.Println("hget err")
}
m, _ := redis.StringMap(val, nil)
for k, v := range m {
fmt.Printf("key:%v,val:%v\n", k, v)
}
//del
fmt.Println("delete key4")
client.Do("hdel", "user", "key4")
val, err = client.Do("hgetall", "user")
if err != nil {
fmt.Println("hget err")
}
m, _ = redis.StringMap(val, nil)
for k, v := range m {
fmt.Printf("key:%v,val:%v\n", k, v)
}
}
结果:
key:key3,val:val3
key:key4,val:val4
key:key2,val:val2
key:key1,val:val1
delete key4
key:key2,val:val2
key:key1,val:val1
key:key3,val:val3
如果需要存储结构体则可以考虑使用json进行序列化后存储
func HashOption(client redis.Conn) {
//删除key
client.Do("del", "user")
user := User{"testname", 18}
data, _ := json.Marshal(user)
client.Do("hset", "user", "key1", data)
val, err := client.Do("hgetall", "user")
if err != nil {
fmt.Println("hget err")
}
m, _ := redis.StringMap(val, nil)
for k, v := range m {
u := User{}
_ = json.Unmarshal([]byte(v), &u)
fmt.Printf("key:%v,val:%+v\n", k, u)
}
}
结果
key:key1,val:{Name:testname Age:18}