前言
使用redis首先要部署redis,载个安装包,部署下即可,本文不赘述了。redis官网:https://redis.io/
接着要下载golang的redis资源包,golang官方推荐的有redisgo和go-reids,个人认为go-redis的封装更加人性化,redisgo的调用是基于命令的,go-redis是基于方法的,所以本文先来介绍go-redis的使用。
2行代码来比较下2种资源包的调用方式:
redisgo: client.Do("SET", "mykey", "我是数据", "EX", "3600")
go-redis:client.Set("mykey", "我是数据", time.Hour)
同样是存储一个1小时后过期的数据,go-redis的调用方式明显更友好。
导入go-redis包
我们在cache包中创建个init函数,内容如下:
var RedisCache = &redis.Client{}
func init() {
RedisCache = redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",
Password: "125846whj",
DB: 0,
})
//ping
pong, err := RedisCache.Ping().Result()
if err != nil {
fmt.Println("ping error", err.Error())
return
}
fmt.Println("ping result:", pong)
}
解析:通过 redis.NewClient创建个redisClient,构建参数这里指定了redis服务地址、链接密码和数据库号。redis.Options 还有更多参数我们简单看下:
type Options struct {
// 网络类型,tcp或unix。
// 默认Tcp
Network string
// host:port address.
Addr string
// Dialer创建新的网络连接,并具有优先级
// 网络和地址选项。
Dialer func() (net.Conn, error)
// 建立新连接时调用的钩子。
OnConnect func(*Conn) error
// 可选的密码。中指定的密码必须匹配
// requirepass server configuration option.
Password string
// 连接到服务器后选择的数据库 0~15
DB int
// 失败链接前最大重试次数
// 默认值是不重试失败的链接
MaxRetries int
// Minimum backoff between each retry.
// Default is 8 milliseconds; -1 disables backoff.
MinRetryBackoff time.Duration
// Maximum backoff between each retry.
// Default is 512 milliseconds; -1 disables backoff.
MaxRetryBackoff time.Duration
// Dial timeout for establishing new connections.
// Default is 5 seconds.
DialTimeout time.Duration
// 套接字读取超时。如果到达,命令将失败
// with a timeout instead of blocking. Use value -1 for no timeout and 0 for default.
// Default is 3 seconds.
ReadTimeout time.Duration
// Timeout for socket writes. If reached, commands will fail
// with a timeout instead of blocking.
// Default is ReadTimeout.
WriteTimeout time.Duration
// Maximum number of socket connections.
// Default is 10 connections per every CPU as reported by runtime.NumCPU.
PoolSize int
// Minimum number of idle connections which is useful when establishing
// new connection is slow.
MinIdleConns int
// Connection age at which client retires (closes) the connection.
// Default is to not close aged connections.
MaxConnAge time.Duration
// Amount of time client waits for connection if all connections
// are busy before returning an error.
// Default is ReadTimeout + 1 second.
PoolTimeout time.Duration
// Amount of time after which client closes idle connections.
// Should be less than server's timeout.
// Default is 5 minutes. -1 disables idle timeout check.
IdleTimeout time.Duration
// Frequency of idle checks made by idle connections reaper.
// Default is 1 minute. -1 disables idle connections reaper,
// but idle connections are still discarded by the client
// if IdleTimeout is set.
IdleCheckFrequency time.Duration
// Enables read only queries on slave nodes.
readOnly bool
// TLS Config to use. When set TLS will be negotiated.
TLSConfig *tls.Config
}
1、String类型
字符串作为Redis最简单的类型,其底层实现只有一种数据结构,就是简单动态字符串(SDS)。作为万金油的字符串类型,可以支持struct结构,基本上string类型在传统系统可以解决80%以上的问题。我们看下golang如何使用字符串类型。
// Get 获取缓存数据
func Get(key string) (string, error) {
result, err := RedisCache.Get(key).Result()
return result, err
}
// Set 设置数据 过期时间默认24H
func Set(key, value string) error {
err := RedisCache.Set(key, value, time.Hour*24).Err()
return err
}
可以看到,string类型非常简单和其他sdk没有什么区别,可以直接调用 Set 方法。
Set(key string, value interface{}, expiration time.Duration)
参数分别为 key、value、expiration过期时间。
获取数据可以通过Get方法获取,返回数据类型及string类型。我们可以测试下,在main方法中定义一下内容:
func main() {
key := "string:key"
if cache.Set(key, "字符串作为Redis最简单的类型,其底层实现只有一种数据结构,就是简单动态字符串(SDS)。") != nil {
fmt.Println("缓存设置错误")
}
value, err := cache.Get(key)
if err != nil {
fmt.Println("get 缓存出错")
}
fmt.Printf("获取到缓存值: %s\n", value)
}
看下结果:
这里ping result : PONG是测试redis 链接是否成功,通过客户端发送 PING 服务端回复 PONG 的方式确认链接是否成功,成功后,设置key和value,设置成功后,通过Get取出对应的值。
2、struct 结构存储结构其实也是存储string,只是把struc序列化成json,等读取的时候再反序列化成struct;
序列化:json.Marshal
反序列化:json.Unmarshal
我们看下demo:
type User struct {
Name string `json:"name"`
Phone string `json:"phone"`
Age int64 `json:"age"`
}
func main() {
key := "string:user"
user := User{Name: "张三", Phone: "18234566897", Age: 28}
userJson, _ := json.Marshal(user)
if cache.Set(key, userJson) != nil {
fmt.Println("缓存设置错误")
}
value, err := cache.Get(key)
if err != nil {
fmt.Println("get 缓存出错")
}
fmt.Printf("获取到缓存值: %s\n", value)
var user2 User
json.Unmarshal([]byte(value), &user2)
fmt.Println("user2", user2)
}
我们定义一个User的struct,里面包含姓名、手机号、年龄三个属性,定义strng:user key。我先实例化一个user对象,赋值张三、18234566897、28岁。将user对象序列化成json字符串,并通过Set方法设置到缓存中。
缓存设置成功后,我们通过Get方法将值取出,并打印取出的值,通过反序列化方式,将字符串反序列化成
user2对象。
3、List 类型
List本身是按先进先出的顺序对数据进行存取的,可以通过Lpush从一端存入数据,通过RPop从一端消费数据。同时为了解决RPop在消费数据解决while(1)循环,导致消费者CPU一直消耗,Redis引入了新的方法BRPop,及阻塞式读取,客户端在没有读取到队列数据时,自动阻塞,直到有新的数据写入队列,在开始读取新数据。
我们在使用List类型时需要注意一个问题,及生产速度大于消费速度,这样会导致List中的数据越来越多,给Redis的内存带来很大压力,所以我们在使用List类型时需要考虑生产消费的能力。
这里我们重点将几个常用的方法Lpush、Rpop、BRpop、LLen、LRange;
func main() {
key := "string:list"
err := cache.RedisCache.LPush(key, "A", "B", "C", 20, "D", "E", "F").Err()
if err != nil {
fmt.Println("缓存设置错误", err)
}
value, err := cache.RPop(key)
if err != nil {
fmt.Println("get 缓存出错")
}
fmt.Printf("获取到缓存值: %s\n", value)
}
BRpop
func main() {
key := "string:list"
err := cache.RedisCache.LPush(key, "A", "B", "C", 20, "D", "E", "F").Err()
if err != nil {
fmt.Println("缓存设置错误", err)
}
for {
value, err := cache.BRPop(time.Second*10, key)
if err != nil {
fmt.Println("get 缓存出错", err)
break
}
fmt.Printf("获取到缓存值: %s\n", value)
}
fmt.Println("结束等等")
}
查看控制台结果:
阻塞住了,等待返回,这里我们设置了阻塞10s,10s后还没有取到值,我们就退出。
LLen、LRange;
这两个方法比较简单,我们看下。
func main() {
key := "string:list"
err := cache.RedisCache.LPush(key, "A", "B", "C", 20, "D", "E", "F").Err()
if err != nil {
fmt.Println("缓存设置错误", err)
}
lLen, _ := cache.LLen(key)
fmt.Printf("集合数据长度:%d\n", lLen)
lRange, _ := cache.LRange(key, 0, 3)
fmt.Println(lRange)
}
4、Hash类型
Redis hash 是一个 string 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象。
Redis 中每个 hash 可以存储 232 - 1 键值对(40多亿)。
Hash类型对应的底层数据结构是Hash表和压缩列表。
我们看下Hash类型常用的操作:HSet、HGet、HAll、HDel、HExists
HSet
用于同时将多个 field-value (字段-值)对设置到哈希表中,此方法会覆盖哈希表中已存在的字段。如果哈希表不存在,会创建一个空哈希表。
func main() {
key := "string:hash"
cache.HSet(key, "name", "张三")
cache.HSet(key, "phone", "18234554345")
cache.HSet(key, "age", "28")
//获取全部hash对象
all, _ := cache.HGetAll(key)
fmt.Println(all)
//修改已存在的字段
cache.HSet(key, "name", "李四")
//获取指定字段
name, _ := cache.HGet(key, "name")
fmt.Println(name)
existsName, _ := cache.HExists(key, "name")
existsId, _ := cache.HExists(key, "id")
fmt.Printf("name 字段是否存在 %v\n", existsName)
fmt.Printf("id 字段是否存在 %v\n", existsId)
cache.HDel(key, "name")
existsName, _ = cache.HExists(key, "name")
fmt.Printf("name 字段是否存在 %v\n", existsName)
getAll, _ := cache.HGetAll(key)
fmt.Println(getAll)
}
执行结果:
结果分析:
开始通过HSet方法设置name、phone、age三个字段,
通过HGetAll获取全部信息,控制台打印了map[age:28 name:张三 phone:18234554345]
修改name字段的值,改为李四,我们再次通过HGet获取name字段,可以看到现在取到的值为李四。张三被覆盖了。
通过HExists判断name和id字段判断是否存在,可以看到id不存在,name是存在的。
通过HDel方法,将name字段移除,在判断name字段是否存在,可以看到当前name字段不存在。
在次通过HGetAll打印信息,可以看到name已经没有了,只有两个字段了。
5、SET
Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。
SET常用方法:
- SADD:向集合添加一个或多个成员
- SCard: 获取集合的成员数
- SMembers:获取集合的所有成员
- SRem: 移除集合里的某个元素
- SPop: 移除并返回set的一个随机元素(SET是无序的)
- SDiff: 返回第一个集合与其他集合之间的差异。
- SDIFFSTORE: 返回给定所有集合的差集并存储在 destination 中
- SInter: 返回所有给定集合的交集
- Sunion: 返回所有给定集合的并集
func main() {
key := "string:set"
cache.RedisCache.SAdd(key, "phone")
err2 := cache.RedisCache.SAdd(key, "hahh").Err()
if err2 != nil {
fmt.Println(err2)
return
}
//获取全部hash对象
all, _ := cache.SCard(key)
fmt.Println(all)
members, err2 := cache.SMembers(key)
fmt.Println(members)
}
执行结果:
ping result: PONG
7
[张三 hahh 18234554345 28 name phone age]
这里只演示操作了三个函数:SAdd、SCard、Smember其他函数可以参考官方API。
6、有序集合(sorted set)Redis 有序集合和集合一样也是 string 类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。
有序集合的成员是唯一的,但分数(score)却可以重复。
sorted set 函数有很多,这里我们主要演示几个函数,通过sorted set 查看编程语言排行榜热度。
func main() {
key := "string:zset"
set := []redis.Z{
{Score: 80, Member: "Java"},
{Score: 90, Member: "Python"},
{Score: 95, Member: "Golang"},
{Score: 98, Member: "PHP"},
}
err := cache.ZAdd(key, set)
if err != nil {
fmt.Println(err)
}
scores, _ := cache.ZRevRangeWithScores(key, 0, 2)
fmt.Println(scores)
cache.ZIncrBy(key, 5, "Golang")
scores, _ = cache.ZRevRangeWithScores(key, 0, 2)
fmt.Println("加分后----")
fmt.Println(scores)
}
输出结果:
解析:我们初始化四门编程语言分别给定初始分数,获取此时排名前三名的编程语言;之后我们将golang语言增加5分,再次获取前三名编程语言,可以看到golang排在第一。
7、设置过期时间
操作string数据的时候,可以在方法里直接传入过期时间。但list、hash、set、zset都没有直接提供相应参数,但redis可以额外设置key的过期时间。
// Expire 给指定key 设置过期时间
func Expire(key string, duration time.Duration) error {
err := RedisCache.Expire(key, duration).Err()
return err
}
// ExpireAt 给指定Key 设置过期时间,时间格式为UNIX时间
func ExpireAt(key string, duration time.Time) error {
err := RedisCache.ExpireAt(key, duration).Err()
return err
}
// TTL 获取key的生存时间
func TTL(key string) (time.Duration, error) {
result, err := RedisCache.TTL(key).Result()
return result, err
}
8、完整代码
package cache
import (
"fmt"
"github.com/go-redis/redis"
"time"
)
var RedisCache = &redis.Client{}
func init() {
RedisCache = redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",
Password: "125846whj",
DB: 0,
})
//ping
pong, err := RedisCache.Ping().Result()
if err != nil {
fmt.Println("ping error", err.Error())
return
}
fmt.Println("ping result:", pong)
}
// Get 获取缓存数据
func Get(key string) (string, error) {
result, err := RedisCache.Get(key).Result()
return result, err
}
// Set 设置数据 过期时间默认24H
func Set(key string, value interface{}) error {
err := RedisCache.Set(key, value, time.Hour*24).Err()
return err
}
// LPush RPush 使用RPush命令往队列右边加入
func LPush(key string, value ...interface{}) error {
err := RedisCache.LPush(key, value).Err()
return err
}
// RPop LPop 取出并移除左边第一个元素
func RPop(key string) (interface{}, error) {
result, err := RedisCache.RPop(key).Result()
return result, err
}
// BRPop BLPop 取出并移除左边第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
func BRPop(timeout time.Duration, key string) (interface{}, error) {
result, err := RedisCache.BRPop(timeout, key).Result()
return result, err
}
// LLen 获取数据长度
func LLen(key string) (int64, error) {
result, err := RedisCache.LLen(key).Result()
return result, err
}
// LRange 获取数据列表
func LRange(key string, start, end int64) ([]string, error) {
result, err := RedisCache.LRange(key, start, end).Result()
return result, err
}
// HSet hash相关操作
//set hash 适合存储结构
func HSet(hashKey, key string, value interface{}) error {
err := RedisCache.HSet(hashKey, key, value).Err()
return err
}
// HGet get Hash
func HGet(hashKey, key string) (interface{}, error) {
result, err := RedisCache.HGet(hashKey, key).Result()
return result, err
}
// HGetAll 获取所以hash ,返回map
func HGetAll(hashKey string) (map[string]string, error) {
result, err := RedisCache.HGetAll(hashKey).Result()
return result, err
}
// HDel 删除一个或多个哈希表字段
func HDel(hashKey string, key ...string) error {
err := RedisCache.HDel(hashKey, key...).Err()
return err
}
// HExists 查看哈希表的指定字段是否存在
func HExists(hashKey, key string) (bool, error) {
result, err := RedisCache.HExists(hashKey, key).Result()
return result, err
}
// SAdd -----------------Set------------------------
// 添加Set
func SAdd(key string, values ...interface{}) error {
err := RedisCache.SAdd(key, values).Err()
return err
}
// SCard 获取集合的成员数
func SCard(key string) (int64, error) {
result, err := RedisCache.SCard(key).Result()
return result, err
}
// SMembers 获取集合的所有成员
func SMembers(key string) ([]string, error) {
result, err := RedisCache.SMembers(key).Result()
return result, err
}
// SRem 移除集合里的某个元素
func SRem(key string, value interface{}) error {
err := RedisCache.SRem(key, value).Err()
return err
}
// SPop 移除并返回set的一个随机元素(SET是无序的)
func SPop(key string) (interface{}, error) {
result, err := RedisCache.SPop(key).Result()
return result, err
}
// ZAdd ------------------ZSet-------------------------
func ZAdd(key string, values []redis.Z) error {
err := RedisCache.ZAdd(key, values...).Err()
return err
}
// ZIncrBy 给指定的key和值加分
func ZIncrBy(key string, score float64, value string) error {
err := RedisCache.ZIncrBy(key, score, value).Err()
return err
}
// ZRevRangeWithScores 取zSet里的前n名热度的数据
func ZRevRangeWithScores(key string, start, end int64) ([]redis.Z, error) {
result, err := RedisCache.ZRevRangeWithScores(key, start, end).Result()
return result, err
}
// Expire 给指定key 设置过期时间
func Expire(key string, duration time.Duration) error {
err := RedisCache.Expire(key, duration).Err()
return err
}
// ExpireAt 给指定Key 设置过期时间,时间格式为UNIX时间
func ExpireAt(key string, duration time.Time) error {
err := RedisCache.ExpireAt(key, duration).Err()
return err
}
// TTL 获取key的生存时间
func TTL(key string) (time.Duration, error) {
result, err := RedisCache.TTL(key).Result()
return result, err
}
更多内容关注公众号:杰子学编程
完