Redis高性能缓存 redigo使用| 青训营笔记

470 阅读5分钟

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持久化,默认是no
  • tcp-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}