高级架构师_Redis_第2章_数据类型与底层数据结构
文章目录
- 高级架构师_Redis_第2章_数据类型与底层数据结构
- 第二章:数据类型与底层数据结构
- 本章学习目标:
- 第一节:Redis 数据类型选择和应用场景
- 1.1 Redis 的 Key 的设计
- 1.2 String 字符串类型
- 1.3 List 列表类型
- 1.4 Set 集合类型
- 1.5 ZSet 有序集合类型
- 1.6 Hash 类型(散列表)
- 1.7 Bitmap 位图类型
- 1.8 Geo 地理位置类型
- 1.9 Stream 数据流类型
- 第二节 Redis 底层数据结构
- 2.1 RedisDB 结构
- 2.2 RedisObject 结构
- 2.2.1 结构信息概览
- 2.2.2 7 种 type 类型
- 2.2.3 10 种 encoding
- 第三节缓存过期和淘汰策略
- 3.1 Maxmemory
- 3.2 expire 数据结构
- 3.3 删除策略
第二章:数据类型与底层数据结构
Redis数据结构及操作
本章学习目标:
- 掌握Redis五种基本数据类型的用法和常见命令的使用
- 了解bitmap、geo、stream的使用
- 理解Redis底层数据结构(Hash、跳跃表、quicklist)
- 了解RedisDB和RedisObject
- 理解LRU算法
- 理解Redis缓存淘汰策略
- 能够较正确的应用Redis缓存淘汰策略
第一节:Redis 数据类型选择和应用场景
Redis 是一个 Key-Value 的存储系统,使用 ANSI C 语言编写。
key 的类型是字符串。
value 的数据类型有:
- 常用的:string 字符串类型、list 列表类型、set 集合类型、sortedset(zset)有序集合类型、hash 类型。
- 不常见的:bitmap 位图类型、geo 地理位置类型。
Redis5.0 新增一种:stream 类型
注意:Redis 中命令是忽略大小写,(set SET),key 是不忽略大小写的 (NAME name)
1.1 Redis 的 Key 的设计
- 用:分割
- 把表名转换为 key 前缀, 比如 user:
- 第二段放置主键值
- 第三段放置列名
比如:用户表 user, 转换为 redis 的 key-value 存储
userid | username | password | |
---|---|---|---|
9 | zhangfei | 123 | 1@qq.com |
username 的 key: user:9:username
json 类型存储 {userid:9,username:zhangfei}
email 的 key user:9:email
表示明确:看 key 知道意思 不易被覆盖
1.2 String 字符串类型
Redis 的 String 能表达 3 种值的类型:字符串、整数、浮点数 100.01 是个六位的串
常见操作命令如下表:
命令名称 | 命令描述 | |
---|---|---|
set | set key value | 赋值 |
get | get key | 取值 |
getset | getset key value | 取值并赋值 |
setnx | setnx key value | 当 key 不存在时才用赋值 set key value NX PX 3000 原子操作,px 设置毫秒数 |
append | append key value | 向尾部追加值 |
strlen | strlen key | 获取字符串长度 |
incr | incr key | 递增数字 |
incrby | incrby key increment | 增加指定的整数 |
decr | decr key | 递减数字 |
decrby | decrby key decrement | 减少指定的整数 |
应用场景
-
1、key 和命令是字符串
-
2、普通的赋值
-
3、incr 用于乐观锁 incr:递增数字,可用于实现乐观锁 watch(事务)
-
4、setnx 用于分布式锁 当 value 不存在时采用赋值,可用于实现分布式锁
命令操作演示
127.0.0.1:6379> set weather cloudy
OK
127.0.0.1:6379> get weather
"cloudy"
127.0.0.1:6379> getset weather sunny
"cloudy"
127.0.0.1:6379> setnx weather rainy #没有值则成功,有则失败
(integer) 0
127.0.0.1:6379> get weather
"sunny"
127.0.0.1:6379> setnx sky blue
(integer) 1
127.0.0.1:6379> get sky
"blue"
127.0.0.1:6379> set sky white nx px 20000 #没有值则成功,有则失败,过期时间20s
(nil)
127.0.0.1:6379> set cloud white nx px 20000
OK
127.0.0.1:6379> set cloud white1 nx px 20000
(nil)
127.0.0.1:6379> set cloud white1 nx px 20000
OK
127.0.0.1:6379> get cloud
"white1"
127.0.0.1:6379> append tree green
(integer) 5
127.0.0.1:6379> get tree
"green"
127.0.0.1:6379>
127.0.0.1:6379> strlen tree
(integer) 5
127.0.0.1:6379> incr wind
(integer) 1
127.0.0.1:6379> incr wind
(integer) 2
127.0.0.1:6379> incrby wind 10
(integer) 12
127.0.0.1:6379> decr wind
(integer) 11
127.0.0.1:6379> decrby wind 5
(integer) 6
127.0.0.1:6379>
1.3 List 列表类型
list 列表类型可以存储有序、可重复的元素
获取头部或尾部附近的记录是极快的,采用双端列表
list 的元素个数最多为 2^32-1 个(40 亿)
常见操作命令如下表:
命令名称 | 命令格式 | 描述 |
---|---|---|
lpush | lpush key v1 v2 v3 … | 从左侧插入列表 |
lpop | lpop key | 从列表左侧取出 |
rpush | rpush key v1 v2 v3 … | 从右侧插入列表 |
rpop | rpop key | 从列表右侧取出 |
lpushx | lpushx key value | 将值插入到列表头部 |
rpushx | rpushx key value | 将值插入到列表尾部 |
blpop | blpop key timeout | 从列表左侧取出,当列表为空时阻塞,可以设置最大阻塞时 间,单位为秒 |
brpop | blpop key timeout | 从列表右侧取出,当列表为空时阻塞,可以设置最大阻塞时 间,单位为秒 |
llen | llen key | 获得列表中元素个数 |
lindex | lindex key index | 获得列表中下标为 index 的元素 index 从 0 开始 |
lrange | lrange key start end | 返回列表中指定区间的元素,区间通过 start 和 end 指定 |
lrem | lrem key count value | 删除列表中与 value 相等的元素 当 count>0 时, lrem 会从列表左边开始删除;当 count<0 时, lrem 会从列表后边开始删除;当 count=0 时, lrem 删除所有值 为 value 的元素 |
lset | lset key index value | 将列表 index 位置的元素设置成 value 的值 |
ltrim | ltrim key start end | 对列表进行修剪,只保留 start 到 end 区间 |
rpoplpush | rpoplpush key1 key2 | 从 key1 列表右侧弹出并插入到 key2 列表左侧 |
brpoplpush | brpoplpush key1 key2 | 从 key1 列表右侧弹出并插入到 key2 列表左侧,会阻塞 |
linsert | linsert key BEFORE/AFTER pivot value | 将 value 插入到列表,且位于值 pivot 之前或之后 |
应用场景:
1、作为栈或队列使用
列表有序可以作为栈和队列使用
2、可用于各种列表,比如用户列表、商品列表、评论列表等。
举例:
127.0.0.1:6379> lpush list:my 1 2 3 4 5
(integer) 5
127.0.0.1:6379> lpop list:my
"5"
127.0.0.1:6379> lpop list:my
"4"
127.0.0.1:6379> rpush list:my 11 13 15
(integer) 6
127.0.0.1:6379> rpop list:my
"15"
127.0.0.1:6379> lrange list:my 0 3
1) "3"
2) "2"
3) "1"
4) "11"
127.0.0.1:6379> lpushx list:my f
(integer) 6
127.0.0.1:6379> lrange list:my 0 -1
1) "f"
2) "3"
3) "2"
4) "1"
5) "11"
6) "13"
127.0.0.1:6379> rpushx list:my l
(integer) 7
127.0.0.1:6379> lrange list:my 0 -1
1) "f"
2) "3"
3) "2"
4) "1"
5) "11"
6) "13"
7) "l"
127.0.0.1:6379> blpop list:my 20
1) "list:my"
2) "f"
127.0.0.1:6379> llen list:my
(integer) 6
127.0.0.1:6379> lindex list:my 3
"11"
127.0.0.1:6379> lrem list:my -1 11
(integer) 1
127.0.0.1:6379> lrange list:my 0 -1
1) "3"
2) "2"
3) "1"
4) "13"
5) "l"
127.0.0.1:6379> lset list:my 3 5
OK
127.0.0.1:6379> lrange list:my 0 -1
1) "3"
2) "2"
3) "1"
4) "5"
5) "l"
127.0.0.1:6379> ltrim list:my 1 3
OK
127.0.0.1:6379> lrange list:my 0 -1
1) "2"
2) "1"
3) "5"
127.0.0.1:6379> linsert list:my before 5 6
(integer) 4
127.0.0.1:6379> lrange list:my 0 -1
1) "2"
2) "1"
3) "6"
4) "5"
127.0.0.1:6379>
1.4 Set 集合类型
Set:无序、唯一元素
集合中最大的成员数为 2^32 - 1
常见操作命令如下表:
命令名称 | 命令格式 | 描述 |
---|---|---|
sadd | sadd key mem1 mem2 … | 为集合添加新成员 |
srem | srem key mem1 mem2 … | 删除集合中指定成员 |
smembers | smembers key | 获得集合中所有元素 |
spop | spop key | 返回集合中一个随机元素,并将该元素删除 |
srandmember | srandmember key | 返回集合中一个随机元素,不会删除该元素 |
scard | scard key | 获得集合中元素的数量 |
sismember | sismember key member | 判断元素是否在集合内 |
sinter | sinter key1 key2 key3 | 求多集合的交集 |
sdiff | sdiff key1 key2 key3 | 求多集合的差集 |
sunion | sunion key1 key2 key3 | 求多集合的并集 |
应用场景:
适用于不能重复的且不需要顺序的数据结构
比如:关注的用户,还可以通过 spop 进行随机抽奖
操作演示
127.0.0.1:6379> sadd myset a b c
(integer) 3
127.0.0.1:6379> smembers myset
1) "a"
2) "b"
3) "c"
127.0.0.1:6379> srem myset a
(integer) 1
127.0.0.1:6379> spop myset
"c"
127.0.0.1:6379> srandmember myset
"b"
127.0.0.1:6379> scard myset
(integer) 1
127.0.0.1:6379> sismember b
(error) ERR wrong number of arguments for 'sismember' command
127.0.0.1:6379> sismember myset b
(integer) 1
127.0.0.1:6379> smembers myset
1) "b"
127.0.0.1:6379> sadd myset2 b c d
(integer) 3
127.0.0.1:6379> sinter myset myset2
1) "b"
127.0.0.1:6379> sdiff myset myset2
(empty list or set)
127.0.0.1:6379> sunion myset myset2
1) "b"
2) "c"
3) "d"
127.0.0.1:6379>
1.5 ZSet 有序集合类型
SortedSet(ZSet) 有序集合: 元素本身是无序不重复的
每个元素关联一个分数(score)
可按分数排序,分数可重复
常见操作命令如下表:
命令名称 | 命令格式 | 描述 |
---|---|---|
zadd | zadd key score1 member1 score2 member2 … | 为有序集合添加新成员 |
zrem | zrem key mem1 mem2 … | 删除有序集合中指定成员 |
zcard | zcard key | 获得有序集合中的元素数量 |
zcount | zcount key min max | 返回集合中 score 值在[min,max]区间 的元素数量 |
zincrby | zincrby key increment member | 在集合的 member 分值上加 increment |
zscore | zscore key member | 获得集合中 member 的分值 |
zrank | zrank key member | 获得集合中 member 的排名(按分值从 小到大) |
zrevrank | zrevrank key member | 获得集合中 member 的排名(按分值从 大到小) |
zrange | zrange key start end | 获得集合中指定区间成员,按分数递增 排序 |
zrevrange | zrevrange key start end | 获得集合中指定区间成员,按分数递减 排序 |
应用场景:
由于可以按照分值排序,所以适用于各种排行榜。比如:点击排行榜、销量排行榜、关注排行榜等。
操作演示
127.0.0.1:6379> zadd myscore 90 english 80 chemistry 70 geography
(integer) 3
127.0.0.1:6379> zadd yourscore 91 english 81 chemistry 71 geography
(integer) 3
127.0.0.1:6379> zadd hisscore 92 english 82 chemistry 72 geography
(integer) 3
127.0.0.1:6379> zrank myscore english
(integer) 2
127.0.0.1:6379> zrevrank myscore english
(integer) 0
127.0.0.1:6379> zrange yourscore 0 -1
1) "geography"
2) "chemistry"
3) "english"
127.0.0.1:6379> zrevrange yourscore 0 -1
1) "english"
2) "chemistry"
3) "geography"
127.0.0.1:6379> zscore myscore english
"90"
127.0.0.1:6379> zincrby myscore 10 english
"100"
127.0.0.1:6379> zcount myscore 80 100
(integer) 2
127.0.0.1:6379> zcard myscore
(integer) 3
127.0.0.1:6379> zrem myscore english
(integer) 1
127.0.0.1:6379> zrange myscore 0 -1
1) "geography"
2) "chemistry"
127.0.0.1:6379>
1.6 Hash 类型(散列表)
Redis hash 是一个 string 类型的 field 和 value 的映射表,它提供了字段和字段值的映射。
每个 hash 可以存储 2^32 - 1 键值对(40 多亿)。
常见操作命令
如下表:
命令名称 | 命令格式 | 描述 |
---|---|---|
hset | hset key field value | 赋值,不区别新增或修改 |
hmset | hmset key field1 value1 field2 value2 | 批量赋值 |
hsetnx | hsetnx key field value | 赋值,如果 filed 存在则不操作 |
hexists | hexists key filed | 查看某个 field 是否存在 |
hget | hget key field | 获取一个字段值 |
hmget | hmget key field1 field2 … | 获取多个字段值 |
hgetall | hgetall key | |
hdel | hdel key field1 field2… | 删除指定字段 |
hincrby | hincrby key field increment | 指定字段自增 increment |
hlen | hlen key | 获得字段数量 |
应用场景
对象的存储 ,表数据的映射
127.0.0.1:6379> hmset user:001 username xiaozhi password 1234 age 10 sex m
OK
127.0.0.1:6379> hset user:002 usename xiaoxia
(integer) 1
127.0.0.1:6379> hsetnx user:002 username xiaoxia2
(integer) 1
127.0.0.1:6379> hexists user:001 username
(integer) 1
127.0.0.1:6379> hget user:001 username
"xiaozhi"
127.0.0.1:6379> hmget user:001 username
1) "xiaozhi"
127.0.0.1:6379> hmget user:001 username password age
1) "xiaozhi"
2) "1234"
3) "10"
127.0.0.1:6379> hgetall user:001
1) "username"
2) "xiaozhi"
3) "password"
4) "1234"
5) "age"
6) "10"
7) "sex"
8) "m"
127.0.0.1:6379> hdel user:001 sex
(integer) 1
127.0.0.1:6379> hincrby user:001 password 10
(integer) 1244
127.0.0.1:6379> hlen user:001
(integer) 3
127.0.0.1:6379>
1.7 Bitmap 位图类型
bitmap 是进行位操作的
通过一个 bit 位来表示某个元素对应的值或者状态,其中的 key 就是对应元素本身。
bitmap 本身会极大的节省储存空间。
应用场景:
- 1、用户每月签到,用户 id 为 key , 日期作为偏移量 1 表示签到
- 2、统计活跃用户, 日期为 key,用户 id 为偏移量 1 表示活跃
- 3、查询用户在线状态, 日期为 key,用户 id 为偏移量 1 表示在线
常见操作命令如下表:
命令名 称 | 命令格式 | 描述 |
---|---|---|
setbit | setbit key offset value | 设置 key 在 offset 处的 bit 值(只能是 0 或者 1)。 |
getbit | getbit key offset | 获得 key 在 offset 处的 bit 值 |
bitcount | bitcount key | 获得 key 的 bit 位为 1 的个数 |
bitpos | bitpos key value | 返回第一个被设置为 bit 值的索引值 |
bitop | bitop and[or/xor/not] destkey key [key …] | 对多个 key 进行逻辑运算后存入 destkey 中 |
操作演示
127.0.0.1:6379> setbit user:sign:1000 20200101 1 #id为1000的用户20200101签到
(integer) 0
127.0.0.1:6379> setbit user:sign:1000 20200103 1 #id为1000的用户20200103签到
(integer) 0
127.0.0.1:6379> getbit user:sign:1000 20200101 #获得id为1000的用户20200101签到状态 1 表示签到
(integer) 1
127.0.0.1:6379> getbit user:sign:1000 20200102 #获得id为1000的用户20200102签到状态 0表示未签到
(integer) 0
127.0.0.1:6379> bitcount user:sign:1000 # 获得id为1000的用户签到次数
(integer) 2
127.0.0.1:6379> bitpos user:sign:1000 1 #id为1000的用户第一次签到的日期
(integer) 20200101
127.0.0.1:6379> setbit 20200201 1000 1 #20200201的1000号用户上线
(integer) 0
127.0.0.1:6379> setbit 20200202 1001 1 #20200202的1000号用户上线
(integer) 0
127.0.0.1:6379> setbit 20200201 1002 1 #20200201的1002号用户上线
(integer) 0
127.0.0.1:6379> bitcount 20200201 #20200201的上线用户有2个
(integer) 2
127.0.0.1:6379> bitop or desk1 20200201 20200202 #合并20200201的用户和20200202上线 了的用户
(integer) 126
127.0.0.1:6379> bitcount desk1 #统计20200201和20200202都上线的用 户个数
(integer) 3
1.8 Geo 地理位置类型
geo 是 Redis 用来处理位置信息的。在 Redis3.2 中正式使用。主要是利用了 Z 阶曲线、Base32 编码和 geohash 算法
Z 阶曲线
在 x 轴和 y 轴上将十进制数转化为二进制数,采用 x 轴和 y 轴对应的二进制数依次交叉后得到一个六位数编 码。
把数字从小到大依次连起来的曲线称为 Z 阶曲线,Z 阶曲线是把多维转换成一维的一种方法。
Base32 编码
Base32 这种数据编码机制,主要用来把二进制数据编码成可见的字符串,
其编码规则是:任意给定一 个二进制数据,以 5 个位(bit)为一组进行切分(base64 以 6 个位(bit)为一组),对切分而成的每个组进行编 码得到 1 个可见字符。
Base32 编码表字符集中的字符总数为 32 个(0-9、b-z 去掉 a、i、l、o),这也是 Base32 名字的由来。
geohash 算法 Gustavo 在 2008 年 2 月上线了 geohash.org 网站。**Geohash 是一种地理位置信息编码方法。 经过 geohash 映射后,地球上任意位置的经纬度坐标可以表示成一个较短的字符串。可以方便的存储在数据 库中,附在邮件上,以及方便的使用在其他服务中。**以北京的坐标举例,[39.928167,116.389550]可以 转换成 wx4g0s8q3jf9 。
Redis 中经纬度使用 52 位的整数进行编码,放进 zset 中,zset 的 value 元素是 key,score 是 GeoHash 的 52 位整数值。在使用 Redis 进行 Geo 查询时,其内部对应的操作其实只是 zset(skiplist)的操作。通过 zset 的 score 进行排序就可以得到坐标附近的其它元素,通过将 score 还原成坐标值就可以得到元素的原始坐 标。
应用场景:
1、记录地理位置
2、计算距离
3、查找"附近的人"
常见操作命令如下表:
命令名称 | 命令格式 | 描述 |
---|---|---|
geoadd | geoadd key 经度 纬度 成员名称 1 经度 1 纬度 1 成员名称 2 经度 2 纬度 2 … | 添加地理坐标 |
geohash | geohash key 成员名称 1 成员名称 2… | 返回标准的 geohash 串 |
geopos | geopos key 成员名称 1 成员名称 2… | 返回成员经纬度 |
geodist | geodist key 成员 1 成员 2 单位 | 计算成员间距离 |
georadiusbymember | georadiusbymember key 成员 值单位 count 数 asc[desc] | 根据成员查找附近 的成员 |
操作演示
127.0.0.1:6379> geoadd myloc 116.31 40.05 beijing 116.38 40.08 tianjin
(integer) 2
127.0.0.1:6379> geohash myloc beijing tianjin
1) "wx4eydyk5m0"
2) "wx4u0236ft0"
127.0.0.1:6379> geopos myloc beijing tianjin
1) 1) "116.31000012159347534"2) "40.04999982043828055"
2) 1) "116.38000041246414185"2) "40.08000078008021916"
127.0.0.1:6379> geodist myloc beijing tianjin km
"6.8294"
127.0.0.1:6379> georadiusbymember myloc beijing 20 km withcoord withdist count 3 asc
# 获得距离beijing 20km以内的按由近到远的顺序排出前三名的成员名称、距离及经纬度
#withcoord : 获得经纬度 withdist:获得距离 withhash:获得geohash码
1) 1) "beijing"2) "0.0000"3) 1) "116.31000012159347534"2) "40.04999982043828055"
2) 1) "tianjin"2) "6.8294"3) 1) "116.38000041246414185"2) "40.08000078008021916"
127.0.0.1:6379> georadiusbymember myloc beijing 20 km count 3 asc
1) "beijing"
2) "tianjin"
127.0.0.1:6379>
1.9 Stream 数据流类型
stream 是 Redis5.0 后新增的数据结构,用于可持久化的消息队列。
几乎满足了消息队列具备的全部内容,包括:
- 消息 ID 的序列化生成
- 消息遍历
- 消息的阻塞和非阻塞读取
- 消息的分组消费
- 未完成消息的处理
- 消息队列监控
每个 Stream 都有唯一的名称,它就是 Redis 的 key,首次使用 xadd 指令追加消息时自动创建。
常见操作命令如下表:
命令名称 | 命令格式 | 描述 |
---|---|---|
xadd | xadd key id <*> field1 value1… | 将指定消息数据追加到指定队列(key)中,* 表示最新生成的 id(当前时间 + 序列号) |
xread | xread [COUNT count] [BLOCK milliseconds] STREAMS key [key …] ID [ID …] | 从消息队列中读取,COUNT:读取条数, BLOCK:阻塞读(默认不阻塞)key:队列 名称 id:消息 id |
xrange | xrange key start end [COUNT] | 读取队列中给定 ID 范围的消息 COUNT:返 回消息条数(消息 id 从小到大) |
xrevrange | xrevrange key start end [COUNT] | 读取队列中给定 ID 范围的消息 COUNT:返 回消息条数(消息 id 从大到小) |
xdel | xdel key id | 删除队列的消息 |
xgroup | xgroup create key groupname id | 创建一个新的消费组 |
xgroup | xgroup destory key groupname | 删除指定消费组 |
xgroup | xgroup delconsumer key groupname cname | 删除指定消费组中的某个消费者 |
xgroup | xgroup setid key id | 修改指定消息的最大 id |
xreadgroup | xreadgroup group groupname consumer COUNT streams key | 从队列中的消费组中创建消费者并消费数据 (consumer 不存在则创建) |
应用场景:
消息队列的使用
127.0.0.1:6379> xadd topic:001 * name zhangfei age 23
"1627544298648-0"
127.0.0.1:6379> xadd topic:001 * name zhaoyun age 24 name diaochan age 16
"1627544346099-0"
127.0.0.1:6379> xrange topic:001 - +
1) 1) "1627544298648-0"2) 1) "name"2) "zhangfei"3) "age"4) "23"
2) 1) "1627544346099-0"2) 1) "name"2) "zhaoyun"3) "age"4) "24"5) "name"6) "diaochan"7) "age"8) "16"
127.0.0.1:6379> xread COUNT 1 streams topic:001 0
1) 1) "topic:001"2) 1) 1) "1627544298648-0"2) 1) "name"2) "zhangfei"3) "age"4) "23"
##创建的group1
127.0.0.1:6379> xgroup create topic:001 group1 0
OK
# 创建cus1加入到group1 消费 没有被消费过的消息 消费第一条
127.0.0.1:6379> xreadgroup group group1 cus1 count 1 streams topic:001 >
1) 1) "topic:001"2) 1) 1) "1627544298648-0"2) 1) "name"2) "zhangfei"3) "age"4) "23"
#继续消费 第二条
127.0.0.1:6379> xreadgroup group group1 cus1 count 1 streams topic:001 >
1) 1) "topic:001"2) 1) 1) "1627544346099-0"2) 1) "name"2) "zhaoyun"3) "age"4) "24"5) "name"6) "diaochan"7) "age"8) "16"
#没有可消费
127.0.0.1:6379> xreadgroup group group1 cus1 count 1 streams topic:001 >
(nil)
127.0.0.1:6379>
第二节 Redis 底层数据结构
Redis 作为 Key-Value 存储系统,数据结构如下:
Redis 没有表的概念,Redis 实例所对应的 db 以编号区分,db 本身就是 key 的命名空间。
比如:user:1000 作为 key 值,表示在 user 这个命名空间下 id 为 1000 的元素,类似于 user 表的 id=1000 的 行。
2.1 RedisDB 结构
-
Redis 中存在“数据库”的概念,该结构由 redis.h 中的 redisDb 定义。
-
当 redis 服务器初始化时,会预先分配 16 个数据库
-
所有数据库保存到结构 redisServer 的一个成员 redisServer.db 数组中
-
redisClient 中存在一个名叫 db 的指针指向当前使用的数据库
RedisDB 结构体源码:
typedef struct redisDb {int id; //id 是数据库序号,为 0-15(默认 Redis 有 16 个数据库)long avg_ttl; //存储的数据库对象的平均 ttl(time to live),用于统计dict *dict; //存储数据库所有的 key-valuedict *expires; //存储 key 的过期时间dict *blocking_keys;//blpop 存储阻塞 key 和客户端对象dict *ready_keys;//阻塞后 push 响应阻塞客户端 存储阻塞后 push 的 key 和客户端对象dict *watched_keys;//存储 watch 监控的的 key 和客户端对象} redisDb;
id
id 是数据库序号,为 0-15(默认 Redis 有 16 个数据库)
dict
存储数据库所有的 key-value,后面要详细讲解
expires
存储 key 的过期时间,后面要详细讲解
2.2 RedisObject 结构
-
对应于 Key 的 Value 对象结构
-
包含字符串对象,列表对象,哈希对象,集合对象和有序集合对象
2.2.1 结构信息概览
typedef struct redisObject {
unsigned type:4;//类型 对象类型
unsigned encoding:4;//编码
void *ptr;//指向底层实现数据结构的指针
//...
int refcount;//引用计数 //...
unsigned lru:LRU_BITS; //LRU_BITS为24bit 记录最后一次被命令程序访问的时间 //...}robj;
4 位 type
-
type 字段表示对象的类型,占 4 位;
-
REDIS_STRING(字符串)、REDIS_LIST (列表)、REDIS_HASH(哈希)、REDIS_SET(集合)、REDIS_ZSET(有 序集合)。
-
当我们执行 type 命令时,便是通过读取 RedisObject 的 type 字段获得对象的类型
127.0.0.1:6379> type xiaogang
string
4 位 encoding
-
encoding 表示对象的内部编码,占 4 位
-
每个对象有不同的实现编码
-
Redis 可以根据不同的使用场景来为对象设置不同的编码,大大提高了 Redis 的灵活性和效率。
-
通过 object encoding 命令,可以查看对象采用的编码方式
127.0.0.1:6379> object encoding xiaogang
"embstr"
24 位 LRU
lru 记录的是对象最后一次被命令程序访问的时间,( 4.0 版本占 24 位,2.6 版本占 22 位)。
高 16 位存储一个分钟数级别的时间戳,低 8 位存储访问计数(lfu : 最近访问次数)
-
lru----> 高 16 位: 最后被访问的时间
-
lfu-----> 低 8 位:最近访问次数
**refcount **
-
refcount记录的是该对象被引用的次数,类型为整型。
-
refcount 的作用,主要在于对象的引用计数和内存回收。
-
当对象的 refcount>1 时,称为共享对象
-
Redis 为了节省内存,当有一些对象重复出现时,新的程序不会创建新的对象,而是仍然使用原来的对 象。
ptr
ptr 指针指向具体的数据,比如:set hello world,ptr 指向包含字符串 world 的 SDS。
2.2.2 7 种 type 类型
字符串对象
C 语言: 字符数组 “\0”
Redis 使用了 SDS(Simple Dynamic String)。简单动态字符串结构 用于存储字符串和整型数据。
struct sdshdr{
//记录buf数组中已使用字节的数量
int len;
//记录 buf 数组中未使用字节的数量
int free;
//字符数组,用于保存字符串
char buf[];}
buf[] 的长度=len+free+1
SDS 的优势:
-
1、SDS 在 C 字符串的基础上加入了 free 和 len 字段,获取字符串长度:SDS 是 O(1),C 字符串是 O(n)。 buf 数组的长度=free+len+1
-
2、 SDS 由于记录了长度,在可能造成缓冲区溢出时会自动重新分配内存,杜绝了缓冲区溢出。
-
3、可以存取二进制数据,以字符串长度 len 来作为结束标识
C: 0 空字符串 二进制数据包括空字符串,所以没有办法存取二进制数据
SDS : 非二进制 \0
二进制: 字符串长度 len 可以存二进制数据
-
使用场景:
-
- SDS 的主要应用在:存储字符串和整型数据、存储 key、AOF 缓冲区和用户输入缓冲。
跳跃表(重点)
跳跃表是有序集合(sorted-set)的底层实现,效率高,实现简单。
跳跃表的基本思想: 将有序链表中的部分节点分层,每一层都是一个有序链表。
查找
在查找时优先从最高层开始向后查找,当到达某个节点时,如果 next 节点值大于要查找的值或 next 指针 指向 null,则从当前节点下降一层继续向后查找。
举例:
查找元素 9,按道理我们需要从头结点开始遍历,一共遍历 8 个结点才能找到元素 9。
第一次分层: 遍历 5 次找到元素 9(红色的线为查找路径)
第二次分层: 遍历 4 次找到元素 9
第三层分层: 遍历 4 次找到元素 9
这种数据结构,就是跳跃表,它具有二分查找的功能。
- 插入与删除
上面例子中,9 个结点,一共 4 层,是理想的跳跃表。
通过抛硬币(概率 1/2)的方式来决定新插入结点跨越的层数:
正面:插入上层
背面:不插入
达到 1/2 概率(计算次数)(不知是否每次每层插入完都抛)
- 删除
找到指定元素并删除每层的该元素即可
跳跃表特点:
每层都是一个有序链表
查找次数近似于层数(1/2) log2n
底层包含所有元素
空间复杂度 O(n)
Redis 跳跃表的实现
完整的跳跃表结构体:
跳跃表的优势:
- 1、可以快速查找到需要的节点 O(logn)
- 2、可以在 O(1)的时间复杂度下,快速获得跳跃表的头节点、尾结点、长度和高度。
应用场景:有序集合的实现
字典(散列表 Hash)(重点 + 难点)
-
字典 dict 又称散列表(hash),是用来存储键值对的一种数据结构。
-
Redis 整个数据库是用字典来存储的。(K-V 结构)
-
对 Redis 进行 CURD 操作其实就是对字典中的数据进行 CURD 操作。
数组
-
数组:用来存储数据的容器,采用头指针 + 偏移量的方式能够以 O(1)的时间复杂度定位到数据所在的内 存地址。
-
Redis 海量存储 快速查找
Hash 函数
-
Hash(散列),作用是把任意长度的输入通过散列算法转换成固定类型、固定长度的散列值。
-
hash 函数可以把 Redis 里的 key:包括字符串、整数、浮点数统一转换成整数。
-
key=100.1 String “100.1” 5 位长度的字符串
-
Redis-cli :times 33(hash 算法)
-
Redis-Server : MurmurHash(hash 算法)
数组下标
(hash 值 % 数组容量得到的余数) = hash(key)% 数组容量
如 6 = hash(aa)%1000=1006%1000=6
Hash 冲突
-
不同的 key 经过计算后出现数组下标一致,称为 Hash 冲突。
-
采用单链表在相同的下标位置处存储原始 key 和 value
-
当根据 key 找 Value 时,找到数组下标,遍历单链表可以找出 key 相同的 value
-
如图 name 和 sname 存在 hash 后下标都是 3 的情况
-
显然 sname 存储的值不应该覆盖 name 的值 ,通过维护一个链表节点存储 key-value 的形式来存储
name:zhangfei sname:zhaoyun,查找的时候再比较 key 即可取出 value
Redis 字典的实现
Redis 字典实现包括:字典(dict)、Hash 表(dictht)、Hash 表节点(dictEntry)。
Hash 表
typedef struct dictht {
dictEntry **table; // 哈希表数组
unsigned long size; // 哈希表数组的大小
unsigned long sizemask; // 用于映射位置的掩码值永远等于(size-1)
unsigned long used; // 哈希表已有节点的数量,包含 next 单链表数据
} dictht;
- 1、hash 表的数组初始容量为 4,随着 k-v 存储量的增加需要对 hash 表数组进行扩容,新扩容量为当前量 的一倍,即 4,8,16,32
- 2、索引值=Hash 值&掩码值(Hash 值与 Hash 表容量取余)
Hash 表节点
typedef struct dictEntry {
void *key; // 键
union { // 值v的类型可以是以下4种类型
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
struct dictEntry *next; // 指向下一个哈希表节点,形成单向链表 解决hash冲突
} dictEntry;
12345678910
key 字段存储的是键值对中的键
v 字段是个联合体,存储的是键值对中的值。
next 指向下一个哈希表节点,用于解决 hash 冲突
dict 字典
type 字段,指向 dictType 结构体,里边包括了对该字典操作的函数指针
Redis 字典除了主数据库的 K-V 数据存储以外,还可以用于:散列表对象、哨兵模式中的主从节点管理等
在不同的应用中,字典的形态都可能不同,dictType 是为了实现各种形态的字典而抽象出来的操作函数 (多态)。
完整的 Redis 字典数据结构:
字典扩容
字典达到存储上限(阈值 0.75),需要 rehash(扩容)
扩容流程:
说明:
- 初次申请默认容量为 4 个 dictEntry,非初次申请为当前 hash 表容量的一倍。
- rehashidx=0 表示要进行 rehash 操作。
- 新增加的数据在新的 hash 表 h[1]
- 修改、删除、查询在老 hash 表 h[0]、新 hash 表 h[1]中(rehash 中)
- 将老的 hash 表 h[0]的数据重新计算索引值后全部迁移到新的 hash 表 h[1]中,这个过程称为 rehash。
渐进式 rehash
-
当数据量巨大时 rehash 的过程是非常缓慢的,所以需要进行优化。
-
服务器忙,则只对一个节点进行 rehash
-
服务器闲,可批量 rehash(100 节点)
-
应用场景:
-
- 1、主数据库的 K-V 数据存储
- 2、散列表对象(hash)
- 3、哨兵模式中的主从节点管理
压缩列表
压缩列表(ziplist)是由一系列特殊编码的连续内存块组成的顺序型数据结构 节省内存 是一个字节数组,可以包含多个节点(entry)。每个节点可以保存一个字节数组或一个整数。 (value encoding 存储结构体)
压缩列表的数据结构如下:
-
zlbytes:压缩列表的字节长度
-
zltail:压缩列表尾元素相对于压缩列表起始地址的偏移量
-
zllen:压缩列表的元素个数
-
entry1…entryX : 压缩列表的各个节点
-
zlend:压缩列表的结尾,占一个字节,恒为 0xFF(255)
-
entryX 元素的编码结构:
-
previous_entry_length:前一个元素的字节长度
-
encoding:表示当前元素的编码
-
content:数据内容
-
ziplist 结构体如下:
应用场景:
-
sorted-set 和 hash 元素个数少且是小整数或短字符串(直接使用)
-
list 用快速链表(quicklist)数据结构存储,而快速链表是双向列表与压缩列表的组合。(间接使用)
整数集合
整数集合(intset)是一个有序的(整数升序)、存储整数的连续存储结构。(value encoding 存储结构体)
当 Redis 集合类型的元素都是整数并且都处在 64 位有符号整数范围内(2^64),使用该结构体存储。
127.0.0.1:6379> sadd set:001 1 3 5 6 2
(integer) 5
127.0.0.1:6379> object encoding set:001
"intset"
127.0.0.1:6379> sadd set:004 1 100000000000000000000000000 9999999999
(integer) 3
127.0.0.1:6379> object encoding set:004
"hashtable"
127.0.0.1:6379>
应用场景:
可以保存类型为 int16_t、int32_t 或者 int64_t 的整数值,并且保证集合中不会出现重复元素。
快速列表(重要)
快速列表(quicklist)是 Redis 底层重要的数据结构。是列表的底层实现。(在 Redis3.2 之前,Redis 采 用双向链表(adlist)和压缩列表(ziplist)实现。)
在 Redis3.2 以后结合 adlist 和 ziplist 的优势 Redis 设 计出了 quicklist。
127.0.0.1:6379> lpush list:001 1 2 5 4 3
(integer) 5
127.0.0.1:6379> object encoding list:001
"quicklist"
双向链表(adlist)
双向链表优势:
- 双向:链表具有前置节点和后置节点的引用,获取这两个节点时间复杂度都为 O(1)。
- 普通链表(单链表):节点类保留下一节点的引用。链表类只保留头节点的引用,只能从头节点插 入删除
- 无环:表头节点的 prev 指针和表尾节点的 next 指针都指向 NULL,对链表的访问都是以 NULL 结 束。 环状:头的前一个节点指向尾节点
- 带链表长度计数器:通过 len 属性获取链表长度的时间复杂度为 O(1)。
- 多态:链表节点使用 void* 指针来保存节点值,可以保存各种不同类型的值。
快速列表
quicklist 是一个双向链表,链表中的每个节点时一个 ziplist 结构。quicklist 中的每个节点 ziplist 都能够存 储多个数据元素。
quicklist 的结构定义如下:
quicklistNode 的结构定义如下:
数据压缩
quicklist 每个节点的实际数据存储结构为 ziplist,这种结构的优势在于节省存储空间。为了进一步降低 ziplist 的存储空间,还可以对 ziplist 进行压缩。
Redis 采用的压缩算法是 LZF。其基本思想是:数据与前 面重复的记录重复位置及长度,不重复的记录原始数据。
压缩过后的数据可以分成多个片段,每个片段有两个部分:解释字段和数据字段。
quicklistLZF 的结构 体如下:
typedef struct quicklistLZF {
unsigned int sz; // LZF压缩后占用的字节数
char compressed[]; // 柔性数组,指向数据部分
} quicklistLZF;
应用场景
列表(List)的底层实现、发布与订阅、慢查询、监视器等功能。
7、流对象
stream 主要由:消息、生产者、消费者和消费组构成。
Redis Stream 的底层主要使用了 listpack(紧凑列表)和 Rax 树(基数树)。
listpack
listpack 表示一个字符串列表的序列化,listpack 可用于存储字符串或整数。用于存储 stream 的消息内 容。
结构如下图:
Rax 树
Rax 是一个有序字典树 (基数树 Radix Tree),按照 key 的字典序排列,支持快速地定位、插入和删除操 作。
Rax 被用在 Redis Stream 结构里面用于存储消息队列,在 Stream 里面消息 ID 的前缀 10 种 encoding 息可以理解为时间序列消息。
使用 Rax 结构 进行存储就可以快速地根据消息 ID 定位到具 体的消息,然后继续遍历指定消息 之后的所有消息。
应用场景: stream 的底层实现
2.2.3 10 种 encoding
-
encoding 表示对象的内部编码,占 4 位。
-
Redis 通过 encoding 属性为对象设置不同的编码 对于少的和小的数据,Redis 采用小的和压缩的存储方式,体现 Redis 的灵活性 大大提高了 Redis 的存储量和执行效率
-
比如 Set 对象:
-
-
intset : 元素是 64 位以内的整数
-
hashtable:元素是 64 位以外的整数
-
如下所示:
➜ bin ./redis-cli
127.0.0.1:6379> sadd set:001 1 3 5 6 2
(integer) 0
127.0.0.1:6379> object encoding set:001
"intset"
127.0.0.1:6379> sadd set:004 1 100000000000000000000000000 9999999999
(integer) 0
127.0.0.1:6379> object encoding set:004
"hashtable"
127.0.0.1:6379>
String
int、raw、embstr
int
REDIS_ENCODING_INT(int 类型的整数)
127.0.0.1:6379> set n1 123
OK127.0.0.1:6379> object encoding n1 "int"
embstr
REDIS_ENCODING_EMBSTR(编码的简单动态字符串)
小字符串 长度小于 44 个字节
127.0.0.1:6379> set name:001 zhangfei
OK
127.0.0.1:6379> object encoding name:001 "embstr"
raw
REDIS_ENCODING_RAW (简单动态字符串)
大字符串 长度大于 44 个字节
127.0.0.1:6379> set address:001asdasdasdasdasdasdsadasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdas dasdasdas
OK127.0.0.1:6379> object encoding address:001 "raw"
list
列表的编码是 quicklist。 REDIS_ENCODING_QUICKLIST(快速列表)
127.0.0.1:6379> lpush list:0 1 2 3 4 5
(integer) 5
127.0.0.1:6379> object encoding list:0
"quicklist"
127.0.0.1:6379>
hash
散列的编码是字典和压缩列表
dict
REDIS_ENCODING_HT(字典)
当散列表元素的个数比较多或元素不是小整数或短字符串时。
127.0.0.1:6379> hmset user:003
username111111111111111111111111111111111111111111111111111111111111111111111111
11111111111111111111111111111111 zhangfei password 111 num
2300000000000000000000000000000000000000000000000000
OK
127.0.0.1:6379> object encoding user:003
"hashtable
ziplist
REDIS_ENCODING_ZIPLIST(压缩列表)
当散列表元素的个数比较少,且元素都是小整数或短字符串时。
127.0.0.1:6379> hmset user:1 name zhangsan hobby tableball love reading
OK
127.0.0.1:6379> object encoding user:1
"ziplist"
set
集合的编码是整形集合和字典
intset
REDIS_ENCODING_INTSET(整数集合)
当 Redis 集合类型的元素都是整数并且都处在 64 位有符号整数范围内(<18446744073709551616)
127.0.0.1:6379> sadd set:1 1 2 3 4
(integer) 4
127.0.0.1:6379> object encoding set:1
"intset"
127.0.0.1:6379>
dict
REDIS_ENCODING_HT(字典)
当 Redis 集合类型的元素是非整数或都处在 64 位有符号整数范围外(>18446744073709551616)
127.0.0.1:6379> sadd set:004 1 100000000000000000000000000 9999999999
(integer) 0
127.0.0.1:6379> object encoding set:004
"hashtable"
127.0.0.1:6379>
zset
有序集合的编码是压缩列表和跳跃表 + 字典
REDIS_ENCODING_ZIPLIST(压缩列表) 当元素的个数比较少,且元素都是小整数或短字符串时。
127.0.0.1:6379> zadd mysocre 80 english 70 chemistry 90 chines 88 history
(integer) 4
127.0.0.1:6379> object encoding myscore
"ziplist"
skiplist + dict
REDIS_ENCODING_SKIPLIST(跳跃表 + 字典)
当元素的个数比较多或元素不是小整数或短字符串时。
127.0.0.1:6379> zadd hit:2 100 item111111111111111111
111111111111111111111111111111111111111111111111111
1111111 1111111111111111111111111111111111 20 item2 45 item3 (integer) 3
127.0.0.1:6379> object encoding hit:2 "skiplist"
第三节缓存过期和淘汰策略
-
Redis 性能高:
-
官方数据 读:110000 次/s 写:81000 次/s
-
长期使用,key 会不断增加,Redis 作为缓存使用,物理内存也会满
-
造成内存与硬盘交换(swap)虚拟内存 ,频繁 IO 性能急剧下降
3.1 Maxmemory
不设置的场景
-
Redis 的 key 是固定的,不会增加
-
Redis 作为 DB 使用(字典表),保证数据的完整性,不能淘汰 , 可以做集群,横向扩展
-
缓存淘汰策略:禁止驱逐 (默认)
设置的场景
Redis 是作为缓存使用,不断增加 Key
maxmemory : 默认为 0 不限制
-
问题:达到物理内存后性能急剧下架,甚至崩溃 内存与硬盘交换(swap) 虚拟内存 ,频繁 IO 性能急剧下降
-
设置多少?
-
与业务有关
-
只有 1 个 Redis 实例,保证系统运行 1 G ,剩下的就都可以设置 物理内存的 3/4
slaver : 留出一定的内存
如何设置
在 redis.conf 中
maxmemory 1024mb
命令: 获得 maxmemory 数
127.0.0.1:6379> config get maxmemory
1) "maxmemory"
2) "0"
设置 maxmemory 后,当趋近 maxmemory 时,通过缓存淘汰策略,从内存中删除对象
不设置 maxmemory 无最大内存限制 maxmemory-policy noeviction (禁止驱逐) 不淘汰
设置 maxmemory 则 maxmemory-policy 要配置
3.2 expire 数据结构
在 Redis 中可以使用 expire 命令设置一个键的存活时间(ttl: time to live),过了这段时间,该键就会自动 被删除。
expire 的使用
expire 命令的使用方法如下:
expire key ttl(单位秒)
127.0.0.1:6379> get sky
"blue"
127.0.0.1:6379> expire sky 60
(integer) 1
127.0.0.1:6379> ttl sky
(integer) 55 #50s
127.0.0.1:6379> ttl sky
(integer) -2 #失效
127.0.0.1:6379> ttl weather
(integer) -1 #永久
expire 原理
typedef struct redisDb {
dict *dict; -- key Value
dict *expires; -- key ttl
dict *blocking_keys;
dict *ready_keys;
dict *watched_keys;
int id;
} redisDb;
上面的代码是 Redis 中关于数据库的结构体定义,这个结构体定义中除了 id 以外都是指向字典的指针, 其中我们只看 dict 和 expires。
dict 用来维护一个 Redis 数据库中包含的所有 Key-Value 键值对,expires 则用于维护一个 Redis 数据 库中设置了失效时间的键(即 key 与失效时间的映射)。
当我们使用 expire 命令设置一个 key 的失效时间时,Redis 首先到 dict 这个字典表中查找要设置的 key 是 否存在,如果存在就将这个 key 和失效时间添加到 expires 这个字典表。
当我们使用 setex 命令向系统插入数据时,Redis 首先将 Key 和 Value 添加到 dict 这个字典表中,然后 将 Key 和失效时间添加到 expires 这个字典表中。
简单地总结来说就是,设置了失效时间的 key 和具体的失效时间全部都维护在 expires 这个字典表中。
3.3 删除策略
Redis 的数据删除有定时删除、惰性删除和主动删除三种方式。
Redis 目前采用惰性删除 + 主动删除的方式。
定时删除
在设置键的过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,立即执行对键的删除 操作。
需要创建定时器,而且消耗 CPU,一般不推荐使用。
惰性删除
在 key 被访问时如果发现它已经失效,那么就删除它。
调用 expireIfNeeded 函数,该函数的意义是:读取数据之前先检查一下它有没有失效,如果失效了就删 除它
主动删除
在 redis.conf 文件中可以配置主动删除策略,默认是 no-enviction(不删除)
maxmemory-policy allkeys-lru
1、LRU
LRU (Least recently used) 最近最少使用,算法根据数据的历史访问记录来进行淘汰数据,其核心思想 是“如果数据最近被访问过,那么将来被访问的几率也更高”。
最常见的实现是使用一个链表保存缓存数据,详细算法实现如下:
- 新数据插入到链表头部;
- 每当缓存命中(即缓存数据被访问),则将数据移到链表头部;
- 当链表满的时候,将链表尾部的数据丢弃。
- 在 Java 中可以使用 LinkHashMap(哈希链表)去实现 LRU
我们以用户信息的需求为例,来演示一下 LRU 算法的基本思路:
1.假设我们使用哈希链表来缓存用户信息,目前缓存了 4 个用户,这 4 个用户是按照时间顺序依次从链表 右端插入的。
2.此时,业务方访问用户 5,由于哈希链表中没有用户 5 的数据,我们从数据库中读取出来,插入到缓存 当中。这时候,链表中最右端是最新访问到的用户 5,最左端是最近最少访问的用户 1。
3.接下来,业务方访问用户 2,哈希链表中存在用户 2 的数据,我们怎么做呢?我们把用户 2 从它的前驱 节点和后继节点之间移除,重新插入到链表最右端。这时候,链表中最右端变成了最新访问到的用户 2,最左端仍然是最近最少访问的用户 1。
4.接下来,业务方请求修改用户 4 的信息。同样道理,我们把用户 4 从原来的位置移动到链表最右侧,并 把用户信息的值更新。这时候,链表中最右端是最新访问到的用户 4,最左端仍然是最近最少访问的用 户 1。
5.业务访问用户 6,用户 6 在缓存里没有,需要插入到哈希链表。假设这时候缓存容量已经达到上限,必 须先删除最近最少访问的数据,那么位于哈希链表最左端的用户 1 就会被删除掉,然后再把用户 6 插入到 最右端。
Redis 的 LRU 数据淘汰机制
在服务器配置中保存了 lru 计数器 server.lrulock,会定时(redis 定时程序 serverCorn())更新, server.lrulock 的值是根据 server.unixtime 计算出来的。
另外,从 struct redisObject 中可以发现,每一个 redis 对象都会设置相应的 lru。可以想象的是,每一 次访问数据的时候,会更新 redisObject.lru。
LRU 数据淘汰机制是这样的:在数据集中随机挑选几个键值对,取出其中 lru 最大的键值对淘汰。
不会遍历所有的 key
用当前时间-最近访问 越大 说明 访问间隔时间越长
volatile-lru
从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
allkeys-lru
从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
2、LFU
LFU (Least frequently used) 最不经常使用,如果一个数据在最近一段时间内使用次数很少,那么在将 来一段时间内被使用的可能性也很小。
volatile-lfu
allkeys-lfu
3、random
随机
volatile-random
从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-random
从数据集(server.db[i].dict)中任意选择数据淘汰
4、ttl
volatile-ttl
从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰 redis 数据集数据结构中保存了键值对过期时间的表,即 redisDb.expires。
TTL 数据淘汰机制:从过期时间的表中随机挑选几个键值对,取出其中 ttl 最小的键值对淘汰。
5 、noenviction
禁止驱逐数据,不删除 (默认)
缓存淘汰策略的选择
-
allkeys-lru : 在不确定时一般采用的策略。 冷热数据交换
-
volatile-lru : 比 allkeys-lru 性能差 存 : 过期时间
-
allkeys-random : 希望请求符合平均分布(每个元素以相同的概率被访问)
-
自己控制:volatile-ttl 缓存穿透
案例分享:字典库失效
key-Value 业务表存 code 显示 文字
拉勾早期将字典库,设置了 maxmemory,并设置缓存淘汰策略为 allkeys-lru 结果造成字典库某些字段失效,缓存击穿 , DB 压力剧增,差点宕机。
分析:
字典库 : Redis 做 DB 使用,要保证数据的完整性 maxmemory 设置较小,采用 allkeys-lru,会对没有经常访问的字典库随机淘汰 当再次访问时会缓存击穿,请求会打到 DB 上。
解决方案:
- 1、不设置 maxmemory
- 2、使用 noenviction 策略
Redis 是作为 DB 使用的,要保证数据的完整性,所以不能删除数据。 可以将原始数据源(XML)在系统启动时一次性加载到 Redis 中。
Redis 做主从 + 哨兵 保证高可用