Redis&&Goredis学习
Posted 中二病没有蛀牙
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis&&Goredis学习相关的知识,希望对你有一定的参考价值。
安装与连接
mac安装
brew install redis #安装
连接
本地连接测试
brew services start redis #用brew 启动redis
redis-server #启动本地的服务器
另开一个终端作为客户端
redis-cli -h 127.0.0.1 -p 6379 #客户端连接,redis默认端口自是6379
PING #检测是否启动
redis-cli shutdown #关闭
Nosql连接测试
在http://cloud-boe.bytedance.net/cache/favor平台上申请一个redis数据库
通过ip+port连接
redis-cli -h 10.225.152.XX -p 9354 #-h 后替换为自己的ip,-p后替换为自己的端口
键key命令
EXISTS key
检查key是否存在
TTL key
以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)。
返回值:
当 key 不存在时,返回 -2 。
当 key 存在但没有设置剩余生存时间时,返回 -1 。
否则,以秒为单位,返回 key 的剩余生存时间。
数据类型
redis都是键值对的形式
string
hash
list
Redis列表是简单的字符串列表,按照插入顺序排序。可以添加一个元素到列表的头部(左边)或者尾部(右边)
一个列表最多可以包含
2
32
−
1
2^{32} - 1
232−1 个元素 (4294967295, 每个列表超过40亿个元素)。
LPUSH key value #将一个值插入到已存在的列表头部
RPOP key #移除列表的最后一个元素,返回值为移除的元素。
其他:
- Redis的列表(list)经常被用作队列(queue),用于在不同程序之间有序地交换消息(message).
一个客户端通过LPUSH命令将消息放入队列中,而另一个客户端通过RPOP或者BRPOP命令取出队列中等待时间最长的消息.
map
set
zset(sorted set)
常见的应用:排行榜
Goredis
Abase/Redis GO Client
初始化
package kv
import (
"fmt"
"time"
"code.byted.org/kv/goredis"
)
var Cli *goredis.Client
const (
EmployeeRedisKey = "emk:%d"
ExpireTime = 48 * time.Hour
)
// exampleCmd: simple demo for proxy supported commands
func InitRedis() {
// init option
options := goredis.NewOption()
// set consul discovery, default false
options.SetServiceDiscoveryWithConsul()
// connection pool initial size, default 10
options.SetPoolInitSize(4)
// set auto load interval, default 30s
options.SetAutoLoadInterval(time.Second * 30)
// 关于retry, 目前建议先不要设置,重试在sdk外部来控制,sdk目前重试相对简单粗暴,可能会造成集群在异常情况下集群状态恶化。后续会对这块进行改进。
// 业务自己实现的重试也希望能限制重试次数,并且做好退避,控制流量,避免异常情况下流量加大,导致情况恶化
// Circuit Breader: max failure rate, min sample, time window, default 0.6, 50, 10s
options.SetCircuitBreakerParam(0.6, 50, time.Millisecond*10000)
// timeout parameter
options.DialTimeout = 50 * time.Millisecond
options.ReadTimeout = 50 * time.Millisecond
options.WriteTimeout = 50 * time.Millisecond
options.PoolTimeout = options.ReadTimeout + time.Second
options.IdleTimeout = 5 * time.Minute
options.LiveTimeout = time.Hour
// for more avaiabile option, highly recommend to read option.go
// init connect
var err error
Cli, err = goredis.NewClientWithOption("bytedance.redis.ylbexample", options)
if err != nil {
fmt.Println(err)
return
}
// command test
//fmt.Printf("[CMD] PING: %s\\n", Cli.Ping())
//fmt.Println(Cli.Set("name1","yanlgibing",0))
}
数据操作
Goredis中使用pipeline(管道)的好处:
- pipeline:管道使用的好处
- 普通请求:等待上一条命令应答后再执行,中间不仅仅多了RTT,而且还频繁的调用系统IO,发送网络请求;pineline:节省RTT,减少IO调用次数
- pipeline管道操作是需要客户端与服务端的支持,客户端将命令写入缓冲,最后再通过exec命令发送给服务端,服务端通过命令拆分,逐个执行返回结果。
- pipeline通过减少客户端与redis的通信次数来实现降低往返延时时间,而且Pipeline 实现的原理是队列,而队列的原理是先进先出,这样就保证数据的顺序性。
- 序列化的时候建议选择JSON Encoding 方法,几种方式和用处比较可以见这篇GoLang Redis存储结构体方式对比 - Go语言中文网 - Golang中文社区
- 而Jsoniter可以比标准库快6倍多
- jsoniter 参考json-iterator使用 | 六松岛-福小林
-
序列化Marshal
// 直接把结构体转化成字符串
MarshalToString(v interface{}) (string, error)
//把结构体转化成json,兼容go标准库encoding/json的序列化方法,返回一个字节切片和错误
Marshal(v interface{}) ([]byte, error)
//转化成字节切片,第一个参数是结构体对象,第二个参数是前缀字符串必须为"",第三个参数为缩进表示,只能是空格
MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) -
反序列化
//把字节切片的JSON格式转换成对应的结构体
Unmarshal(data []byte, v interface{}) error
//把字符串的JSON格式转换成对应的结构体
UnmarshalFromString(str string, v interface{}) error
-
一个使用pipe进行操作的demo:
func GetEmployee(ctx context.Context,emploeeIds []int64) (map[int64] *model.Employee,[]int64,error) {
pipe := Cli.Pipeline()
for _,v := range emploeeIds{
key := GetEmployeeKeyById(v)
pipe.Get(key)
}
cmds,err := pipe.Exec()
if err != nil{
logs.CtxError(ctx,"get redis failed: err=%v", err)
return nil,nil,err
}
cacheEmployeeMap := make(map[int64] *model.Employee)
var missIds []int64
for i,cmd := range cmds {
if cmd.Err() == redis.Nil{
missIds = append(missIds,emploeeIds[i])
continue
}
if cmd.Err() != nil {
logs.CtxError(ctx, "get redis failed: err=%v", cmd.Err())
continue
}
c := cmd.(*redis.StringCmd).Val()
var im model.Employee
if err := jsoniter.UnmarshalFromString(c,&im); err != nil{
logs.CtxError(ctx, "unmarshal failed: json=%s, err=%v", c, err)
continue
}
cacheEmployeeMap[im.Eid] = &im
}
return cacheEmployeeMap,missIds,nil
}
应用
- 做数据库缓存
redis的最常见应用就是做mysql的缓存,其基本原理如下:
对于一个sql语句格式的数据请求,首先计算该语句的MD5并据此得到结果集标识符,然后利用该标识符在Redis中查找该结果集。注意,结果集中的每一行都有一个相应的键,这些键都存储在一个Redis集合结构中。如果Redis中不存在这样一个集合,说明要找的结果集不在Redis中,所以需要执行相应的sql语句,在Mysql中查询到相应的结果集,然后按照上面所说的办法把结果集中的每一行以字符串或哈希的形式存入Redis。
需注意的是,更新数据的时候,需要先更新数据库 - 删缓存 - 再次访问 - 查询数据库 - 存入缓存
问题
大Key问题
-
概念:在Redis中,大key指的是key对应的value值所占的内存空间比较大,例如一个字符串类型的value最大可以存储512MB的内容,一个列表类型的value最多可以存储2的32次方-1个元素,一般情况下,我们认为字符串类型的key的value值超过10kb,就算大key。
-
危害
- 内存空间不均匀。
- 操作耗时,存在阻塞风险、
- 网络阻塞,每次获取大key产生的网络流量较大。
缓存雪崩
-
现象:
影响轻则,查询变慢,重则当请求并发更高时,出来大面积服务不可用。 -
原因:
同一时间缓存大面积失效,就像没有缓存一样,所有的请求直接打到数据库上来,DB扛不住挂了
缓存穿透 -
现象与原因:指用户不断发起请求的数据,在缓存和DB中都没有,比如DB中的用户ID是自增的,但是用户请求传了-1,或者是一个特别大的数字,这个时候用户很有可能就是一个攻击者,会导致DB的压力过大,严重的话就是把DB搞挂了。因为每次都绕开了缓存直接查询DB。
-
解决方案:
-
方法一:在接口层增加校验,不合法的参数直接返回。不相信任务调用方,根据自己提供的API接口规范来,作为被调用方,要考虑可能任何的参数传值。
-
方法二:在缓存查不到,DB中也没有的情况,可以将对应的key的value写为null,或者其他特殊值写入缓存,同时将过期失效时间设置短一点,以免影响正常情况。这样是可以防止反复用同一个ID来暴力攻击。
-
方法三:正常用户是不会这样暴力攻击,只有是恶意者才会这样做,可以在网关一个配置项,为每一个IP设置访问阀值。
-
方法四:高级用户布隆过滤器(Bloom Filter),这个也能很好地防止缓存穿透。原理就是利用高效的数据结构和算法快速判断出你这个Key是否在DB中存在,不存在你return就好了,存在你就去查了DB刷新KV再return。
缓存击穿
- 现象与原因:缓存击穿是指一个key是热点,不停地扛住大并发请求,全都集中访问此key,而当此key过期瞬间,持续的大并发就击穿缓存,全都打在DB上。就又引发雪崩的问题。
- 解决
- 加锁。大量并发只让一个去查,其他人等待,查到以后释放锁,其他人获取到锁,先查缓存,就会有数据,不用去db
- 在 redis、db 中间做一个二级缓存
以上是关于Redis&&Goredis学习的主要内容,如果未能解决你的问题,请参考以下文章
Nginx学习---Nginx&&Redis&&hcache三层缓存架构总结