这是我参与「第五届青训营 」伴学笔记创作活动的第 12 天
本文主要介绍在直播课上所学过的redis的内容。包括redis的实现框架,常用的数据类型及相应操作,最后介绍redis数据库一些常见的问题。
实现框架
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。其基本框架如下图所示:
redis持久化的方式有两种:RDB和AOF,其中RDB是通过保存数据库中的键值对来记录数据库的状态,其缺点是RDB 持久化是一定时间内做一次备份,如果redis意外down掉的话,就会丢失最后一次快照后的所有修改(数据有丢失)。
而另一种持久化方式 AOF 则是通过保存Redis服务器所执行的写命令来记录数据库状态,AOF 持久化的方法提供了多种的同步频率,即使使用默认的同步频率每秒同步一次,Redis 最多也就丢失 1 秒的数据而已。
redis常用的数据结构有string,hash,set,zset以及list,下面对这五种数据类型进行介绍。
常用数据类型
string
set key value
get key
strlen key # 值的字节长度
append key value # 给该key追加value
incr key # 将字符串值解析成整型,将其加一,最后将结果保存为新的字符串值
incrby key 50 # +50
decr key # -1
decrby key 50 # -50
setbit key offset value # offset表示偏移量
bitcount key [start end] # bitcount表示字节索引,查这个区间里一共有多少1
bitcount key 0 -1 # 表示value所有区间
bitop operation destkey key [key……] # 逻辑语句operation:and or not
man ascii # manual 查看Ascii码字符集(最多可表示127个字符)
应用场景包括:
-
字符串:键值对类型的数据,eg:session信息
-
数值:计数器/限流/秒杀
-
位图:统计用户某时间段内的登录次数/统计某时间段内的活跃用户数量/大数量库存备货
list
在redis中使用的list名称为quicklist,它由一个双向链表和listpack构成,如下图所示:
LINDEX key index
summary: Get an element from a list by its index
since: 1.0.0
LINSERT key BEFORE|AFTER pivot value
summary: Insert an element before or after another element in a list
since: 2.2.0
LLEN key
summary: Get the length of a list
since: 1.0.0
LPOP key
summary: Remove and get the first element in a list
since: 1.0.0
LPUSH key value [value ...]
summary: Prepend one or multiple values to a list
since: 1.0.0
RPOP key
summary: Remove and get the last element in a list
since: 1.0.0
RPUSH key value [value ...]
summary: Append one or multiple values to a list
since: 1.0.0
RPUSHX key value
summary: Append a value to a list, only if the list exists
since: 2.2.0
使用场景:消息通知
例如当文章更新时,将更新后的文章推送到ES,用户就能搜索到最新的文章数据
hash
HDEL key field [field ...]
HGET key field
summary: Get the value of a hash field
since: 2.0.0
HGETALL key
summary: Get all the fields and values in a hash
since: 2.0.0
HINCRBY key field increment
summary: Increment the integer value of a hash field by the given number
since: 2.0.0
HKEYS key
summary: Get all the fields in a hash
since: 2.0.0
HMGET key field [field ...]
summary: Get the values of all the given hash fields
since: 2.0.0
HMSET key field value [field value ...]
summary: Set multiple hash fields to multiple values
since: 2.0.0
HSET key field value
summary: Set the string value of a hash field
since: 2.0.0
HSETNX key field value
summary: Set the value of a hash field, only if the field does not exist
since: 2.0.0
HVALS key
summary: Get all the values in a hash
since: 2.0.0
其扩容有两种方式,分别是rehash和渐进式rehash:
rehash:将ht[0]中的数据全部迁移到ht[1]中。数据量小的场景下,直接将数据从ht[0]拷贝到ht[1]速度是较快的。但是在数据量大的场景下,例如存在上百万的KV时,迁移过程会明显阻塞用户的请求。
渐进式rehash:每次用户访问时都会迁移少量数据,将整个迁移过程平摊到所有的访问过程。
zset
zset使用的数据结构为zskiplist,是由跳表和dict构成的,适用排行榜、评论分页这种情况,其基本的操作方法有:
zadd key [NX|XX] [CN] [INCR] score member [score member……]
zrange # 从小到大排序
zrange key 0 1 # 从小到大取前两名
zrevrange key 0 1 # 从大到小取前两名
zincrby key increment member # 给某个成员的score加increment
Redis使用注意事项
大key
定义
| 数据类型 | 大key标准 |
|---|---|
| string类型 | value字节数大于10KB |
| hash/set/zset/list等复杂数据类型 | 元素个数大于5000个或总value字节数大于10MB |
危害
读取成本高
容易导致慢查询(过期、删除)
主从复制异常
无法正常响应请求
消除大key方法
- 拆分:将大key拆分成小key。例如将一个string拆分成多规格string
- 压缩:将value压缩后写入redis中,读取时解压后再使用。压缩算法可以实gzip,snappy,lz4等。通常情况下,一个压缩算法压缩率越高,则解压耗时越长。需要对实际数据进行测试,选择一个合适的算法,如果存储的是json字符串,可以考虑使用message pack进行序列化。
压缩不了才想拆分
-
对于集合类的结构,如hash\list\set\zset,可以使用
拆分:使用hash取余,位掩码的方式决定放在那个key中
区分冷热:如榜单场景使用zset,只缓存前十页数据,后续走db
热key
用户访问一个key的QPS特别高,导致server实例出现CPU突增或者不均的情况,热key没有明确的标准,QPS超过500就有可能被识别为热KEY
解决方式
-
设置localcache:在访问redis前,在业务服务侧设置localcache,降低访问redis的QPS,当localcache的缓存过期或未命中时,从redis中将数据更新到localcache中,Java的Guava,Go的Bigcache就是这类的localcache
-
拆分:将key:value的一个热key复制写入多份,访问的时候访问多个key,但是value是同一个值,以此将QPS分配到不同的实例上,以此降低负载。代价:更新时需要更新多个key,存在数据短暂不一致的风险。