redis
底层为C语言解决hash冲突类似于1.7的hashmap
String 是redis中使用最多的存储类型
1、数据结构(3.2之前):sds:simple dynamic string是一个二进制安全数组 sds:
2、数据结构(3.2之后) 根据存储的内容来
3、扩容机制 容量不够时,扩容为原来的两倍,直到1024k,不再成倍增加,而是以1024k的增加。
Go中也有很多比较流行的并且开源Redis库,比如go-redis或redigo,在github上,分别12.3k和8.6k的star数量(截止到2021.09.03)
在这里将以go-redis为例。
第一步:安装go-redis
请勿省略版本号
go get github.com/go-redis/redis/v8
第二步:连接Redis服务器 连接Redis服务器有两种方法,第一种使用redis.Options
,第二种就是使用redis.ParseURL
1、第一种
import "github.com/go-redis/redis/v8"
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password set
DB: 0, // use default DB
})
2、第二种
opt, err := redis.ParseURL("redis://<user>:<pass>@localhost:6379/<db>")
if err != nil {
panic(err)
}
rdb := redis.NewClient(opt)
那么,我这里使用从我的配置里读取,不会使用go读取配置文件可以参考
Go(三)Go配置文件
import (
"context"
"github.com/go-redis/redis/v8"
"log"
)
var rdb *redis.Client
// GetRedis 获取连接
func GetRedis() *redis.Client {
return rdb
}
func InitRedis() {
rdb = redis.NewClient(&redis.Options{
Addr: Redis.Host + ":" + Redis.Port,
Password: Redis.Password,
DB: Redis.DB,
})
_,err := rdb.Ping(context.Background()).Result()
if err != nil{
log.Printf("redis connect get failed.%v",err)
return
}
log.Printf("redis init success")
}
“Ping() 旧版本是不需要参数的,从v8版本开始 需要参数 context.Context ”
第三步:封装 存/获取值 函数
网上大部分教程都是在v8之前的,而在v8需要传入context.Context
,所以在存取时需要额外增加一个参数
1、存值
var ctx = context.Background()
func SetString(key string,value interface{}) error {
if key == "" || value == nil {
return errors.New(common.ArgsIsNull)
}
rdb := config.GetRedis()
err := rdb.Set(ctx,key,value,0).Err()
if err != nil {
return errors.New(err.Error())
}
log.Infof("push key: %v, value: %v",key,value)
return nil
}
2、取值
func GetString(key string) (interface{},error) {
if key == "" {
return "",errors.New(common.ArgsIsNull)
}
rdb := config.GetRedis()
res,err := rdb.Get(ctx,key).Result()
if err == redis.Nil{
return "",errors.New(fmt.Sprintf(common.ResIsNull,key))
}
if err != nil {
return "",errors.New(err.Error())
}
return res,nil
}
说明:
context
、键、值外,还需要传入一个过期时间第四步:使用
func Test(test *testing.T) {
err := SetString("1", "12")
if err != nil {
return
}
}
func Test1(test *testing.T) {
res,err := GetString("12")
if err != nil {
log.Debug(err)
}else {
log.Debug(res)
}
}
把值为12
push到键为1
,然后在redis中查看
1、redis控制台
Connecting ...
Connected.
redis:0>get 1
"12"
2、代码获取
=== RUN Test1
2021-09-03 20:30:38 DEBUG redis/redis_test.go:40 12
--- PASS: Test1 (0.00s)
PASS
Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。
值得注意的是:订阅者接收不到启动之前的消息。
func Receive(key ...string) error {
if len(key) <= 0 {
return errors.New(common.ArgsIsNull)
}
rdb := config.GetRedis()
sub := rdb.Subscribe(ctx, key...)
defer time.AfterFunc(time.Second, func() {
_ = sub.Close()
})
_, err := sub.Receive(ctx)
if err != nil {
return errors.New(err.Error())
}
// 设置channel参数 https://github.com/go-redis/redis/issues/1850
ch := sub.Channel(
redis.WithChannelSize(100),
redis.WithChannelHealthCheckInterval(10*time.Second),
redis.WithChannelSendTimeout(3000 * time.Second),
)
for msg := range ch {
// todo 执行操作
fmt.Println(msg.Channel, msg.Payload)
}
return nil
}
func Publish(key string, value interface{}) error {
if key == "" || value == nil {
return errors.New(common.ArgsIsNull)
}
rdb := config.GetRedis()
err := rdb.Publish(ctx, key, value).Err()
if err != nil {
return errors.New(err.Error())
}
log.Infof("Publish key: %v, value: %v", key, value)
return nil
}
注:如果需要Publish
自定义结构体,需要实现MarshalBinary
方法。
type Msg struct { Title string `form:"title" json:"title" binding:"required"` // 标题 Content string `form:"content" json:"content" binding:"required"` // 内容}
当我尝试Publish
自定结构体,就会这样的错误提示,这告诉我们需要将我们的结构体转为二进制形式
redis: can't marshal *dto.Msg (implement encoding.BinaryMarshaler)
这里有两种解决方法:1、我们可以把Msg
结构体实现MarshalBinary
方法,Redis将自动执行所有操作
func (m *Msg) MarshalBinary() (data []byte, err error) { return json.Marshal(m)}
2、或者可以选择在Publish
前自己手动转为二进制形式
func Test3(test *testing.T) { msg := &dto.Msg{Title: "123123",Content: "123123"} marshal, err1 := json.Marshal(msg) if err1 != nil { return } err := Publish(common.Sms,marshal) if err != nil { log.Debug(err) }}
1、为什么要使用mset或者mget
“减少带带宽和io,因为redis需要封装resp协议(需要封装tcp协议与ip协议,各需要20byte),如果使用set或get去设置或获取(k,v)会封装两次,而是用mset k1 v1 k2 v2,就会减少封装次数,减少带宽和io。 ”
2、现在系统有千万级的活跃用户,如何实现日活统计,为了增强用户粘性,要上线一个连续打卡发放积分的功能,怎么实现连续打卡用户统计。
“使用setbit设值、getbit、bitcount统计 参考:https://blog.csdn.net/hgd613/article/details/54095729 ”