高级架构师_Redis_第2章_数据类型与底层数据结构

news/2024/5/1 22:18:28/文章来源:https://blog.csdn.net/guan1843036360/article/details/127707588

高级架构师_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 的设计

  1. 用:分割
  2. 把表名转换为 key 前缀, 比如 user:
  3. 第二段放置主键值
  4. 第三段放置列名

比如:用户表 user, 转换为 redis 的 key-value 存储

useridusernamepasswordemail
9zhangfei1231@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 是个六位的串

常见操作命令如下表:

命令名称命令描述
setset key value赋值
getget key取值
getsetgetset key value取值并赋值
setnxsetnx key value当 key 不存在时才用赋值 set key value NX PX 3000 原子操作,px 设置毫秒数
appendappend key value向尾部追加值
strlenstrlen key获取字符串长度
incrincr key递增数字
incrbyincrby key increment增加指定的整数
decrdecr key递减数字
decrbydecrby 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 亿)

常见操作命令如下表:

命令名称命令格式描述
lpushlpush key v1 v2 v3 …从左侧插入列表
lpoplpop key从列表左侧取出
rpushrpush key v1 v2 v3 …从右侧插入列表
rpoprpop key从列表右侧取出
lpushxlpushx key value将值插入到列表头部
rpushxrpushx key value将值插入到列表尾部
blpopblpop key timeout从列表左侧取出,当列表为空时阻塞,可以设置最大阻塞时 间,单位为秒
brpopblpop key timeout从列表右侧取出,当列表为空时阻塞,可以设置最大阻塞时 间,单位为秒
llenllen key获得列表中元素个数
lindexlindex key index获得列表中下标为 index 的元素 index 从 0 开始
lrangelrange key start end返回列表中指定区间的元素,区间通过 start 和 end 指定
lremlrem key count value删除列表中与 value 相等的元素 当 count>0 时, lrem 会从列表左边开始删除;当 count<0 时, lrem 会从列表后边开始删除;当 count=0 时, lrem 删除所有值 为 value 的元素
lsetlset key index value将列表 index 位置的元素设置成 value 的值
ltrimltrim key start end对列表进行修剪,只保留 start 到 end 区间
rpoplpushrpoplpush key1 key2从 key1 列表右侧弹出并插入到 key2 列表左侧
brpoplpushbrpoplpush key1 key2从 key1 列表右侧弹出并插入到 key2 列表左侧,会阻塞
linsertlinsert 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
常见操作命令如下表:

命令名称命令格式描述
saddsadd key mem1 mem2 …为集合添加新成员
sremsrem key mem1 mem2 …删除集合中指定成员
smemberssmembers key获得集合中所有元素
spopspop key返回集合中一个随机元素,并将该元素删除
srandmembersrandmember key返回集合中一个随机元素,不会删除该元素
scardscard key获得集合中元素的数量
sismembersismember key member判断元素是否在集合内
sintersinter key1 key2 key3求多集合的交集
sdiffsdiff key1 key2 key3求多集合的差集
sunionsunion 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)
可按分数排序,分数可重复
常见操作命令如下表:

命令名称命令格式描述
zaddzadd key score1 member1 score2 member2 …为有序集合添加新成员
zremzrem key mem1 mem2 …删除有序集合中指定成员
zcardzcard key获得有序集合中的元素数量
zcountzcount key min max返回集合中 score 值在[min,max]区间 的元素数量
zincrbyzincrby key increment member在集合的 member 分值上加 increment
zscorezscore key member获得集合中 member 的分值
zrankzrank key member获得集合中 member 的排名(按分值从 小到大)
zrevrankzrevrank key member获得集合中 member 的排名(按分值从 大到小)
zrangezrange key start end获得集合中指定区间成员,按分数递增 排序
zrevrangezrevrange 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 多亿)。

image.png

常见操作命令

如下表:

命令名称命令格式描述
hsethset key field value赋值,不区别新增或修改
hmsethmset key field1 value1 field2 value2批量赋值
hsetnxhsetnx key field value赋值,如果 filed 存在则不操作
hexistshexists key filed查看某个 field 是否存在
hgethget key field获取一个字段值
hmgethmget key field1 field2 …获取多个字段值
hgetallhgetall key
hdelhdel key field1 field2…删除指定字段
hincrbyhincrby key field increment指定字段自增 increment
hlenhlen 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 表示在线

常见操作命令如下表:

命令名 称命令格式描述
setbitsetbit key offset value设置 key 在 offset 处的 bit 值(只能是 0 或者 1)。
getbitgetbit key offset获得 key 在 offset 处的 bit 值
bitcountbitcount key获得 key 的 bit 位为 1 的个数
bitposbitpos key value返回第一个被设置为 bit 值的索引值
bitopbitop 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 阶曲线是把多维转换成一维的一种方法。

image.png

Base32 编码

Base32 这种数据编码机制,主要用来把二进制数据编码成可见的字符串,

其编码规则是:任意给定一 个二进制数据,以 5 个位(bit)为一组进行切分(base64 以 6 个位(bit)为一组),对切分而成的每个组进行编 码得到 1 个可见字符。

Base32 编码表字符集中的字符总数为 32 个(0-9、b-z 去掉 a、i、l、o),这也是 Base32 名字的由来。

image.png

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、查找"附近的人"

常见操作命令如下表:

命令名称命令格式描述
geoaddgeoadd key 经度 纬度 成员名称 1 经度 1 纬度 1 成员名称 2 经度 2 纬度 2 …添加地理坐标
geohashgeohash key 成员名称 1 成员名称 2…返回标准的 geohash 串
geoposgeopos key 成员名称 1 成员名称 2…返回成员经纬度
geodistgeodist key 成员 1 成员 2 单位计算成员间距离
georadiusbymembergeoradiusbymember 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 指令追加消息时自动创建。

常见操作命令如下表:

命令名称命令格式描述
xaddxadd key id <*> field1 value1…将指定消息数据追加到指定队列(key)中,* 表示最新生成的 id(当前时间 + 序列号)
xreadxread [COUNT count] [BLOCK milliseconds] STREAMS key [key …] ID [ID …]从消息队列中读取,COUNT:读取条数, BLOCK:阻塞读(默认不阻塞)key:队列 名称 id:消息 id
xrangexrange key start end [COUNT]读取队列中给定 ID 范围的消息 COUNT:返 回消息条数(消息 id 从小到大)
xrevrangexrevrange key start end [COUNT]读取队列中给定 ID 范围的消息 COUNT:返 回消息条数(消息 id 从大到小)
xdelxdel key id删除队列的消息
xgroupxgroup create key groupname id创建一个新的消费组
xgroupxgroup destory key groupname删除指定消费组
xgroupxgroup delconsumer key groupname cname删除指定消费组中的某个消费者
xgroupxgroup setid key id修改指定消息的最大 id
xreadgroupxreadgroup 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 存储系统,数据结构如下:

image.png

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)。简单动态字符串结构 用于存储字符串和整型数据。

image.png

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,则从当前节点下降一层继续向后查找。

举例:

image.png

查找元素 9,按道理我们需要从头结点开始遍历,一共遍历 8 个结点才能找到元素 9。

第一次分层: 遍历 5 次找到元素 9(红色的线为查找路径)

image.png

第二次分层: 遍历 4 次找到元素 9

image.png

第三层分层: 遍历 4 次找到元素 9

image.png

这种数据结构,就是跳跃表,它具有二分查找的功能。

  • 插入与删除

上面例子中,9 个结点,一共 4 层,是理想的跳跃表。

通过抛硬币(概率 1/2)的方式来决定新插入结点跨越的层数:

正面:插入上层

背面:不插入

达到 1/2 概率(计算次数)(不知是否每次每层插入完都抛)

  • 删除

找到指定元素并删除每层的该元素即可

跳跃表特点:

每层都是一个有序链表

查找次数近似于层数(1/2) log2n

底层包含所有元素

空间复杂度 O(n)

Redis 跳跃表的实现

image.png

完整的跳跃表结构体:

image.png

跳跃表的优势:

  • 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

image.png

Redis 字典的实现

Redis 字典实现包括:字典(dict)、Hash 表(dictht)、Hash 表节点(dictEntry)。

image.png

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 冲突

image.png

dict 字典

image.png

type 字段,指向 dictType 结构体,里边包括了对该字典操作的函数指针

image.png

Redis 字典除了主数据库的 K-V 数据存储以外,还可以用于:散列表对象、哨兵模式中的主从节点管理等

在不同的应用中,字典的形态都可能不同,dictType 是为了实现各种形态的字典而抽象出来的操作函数 (多态)。

完整的 Redis 字典数据结构:

image.png

字典扩容

字典达到存储上限(阈值 0.75),需要 rehash(扩容)

扩容流程:

image.png

说明:

  • 初次申请默认容量为 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 存储结构体)

压缩列表的数据结构如下:

image.png

  • zlbytes:压缩列表的字节长度

  • zltail:压缩列表尾元素相对于压缩列表起始地址的偏移量

  • zllen:压缩列表的元素个数

  • entry1…entryX : 压缩列表的各个节点

  • zlend:压缩列表的结尾,占一个字节,恒为 0xFF(255)

  • entryX 元素的编码结构:

image.png

  • previous_entry_length:前一个元素的字节长度

  • encoding:表示当前元素的编码

  • content:数据内容

  • ziplist 结构体如下:

image.png

应用场景:

  • 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> 

image.png

应用场景:

可以保存类型为 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)

image.png

双向链表优势:

  1. 双向:链表具有前置节点和后置节点的引用,获取这两个节点时间复杂度都为 O(1)。
  2. 普通链表(单链表):节点类保留下一节点的引用。链表类只保留头节点的引用,只能从头节点插 入删除
  3. 无环:表头节点的 prev 指针和表尾节点的 next 指针都指向 NULL,对链表的访问都是以 NULL 结 束。 环状:头的前一个节点指向尾节点
  4. 带链表长度计数器:通过 len 属性获取链表长度的时间复杂度为 O(1)。
  5. 多态:链表节点使用 void* 指针来保存节点值,可以保存各种不同类型的值。

快速列表

quicklist 是一个双向链表,链表中的每个节点时一个 ziplist 结构。quicklist 中的每个节点 ziplist 都能够存 储多个数据元素。

image.png

quicklist 的结构定义如下:

image.png

quicklistNode 的结构定义如下:

image.png

数据压缩

quicklist 每个节点的实际数据存储结构为 ziplist,这种结构的优势在于节省存储空间。为了进一步降低 ziplist 的存储空间,还可以对 ziplist 进行压缩。

Redis 采用的压缩算法是 LZF。其基本思想是:数据与前 面重复的记录重复位置及长度,不重复的记录原始数据。

压缩过后的数据可以分成多个片段,每个片段有两个部分:解释字段和数据字段。

quicklistLZF 的结构 体如下:

typedef struct quicklistLZF { 
unsigned int sz; // LZF压缩后占用的字节数 
char compressed[]; // 柔性数组,指向数据部分 
} quicklistLZF;

应用场景

列表(List)的底层实现、发布与订阅、慢查询、监视器等功能。

7、流对象

stream 主要由:消息、生产者、消费者和消费组构成。

image.png

Redis Stream 的底层主要使用了 listpack(紧凑列表)和 Rax 树(基数树)。

listpack

listpack 表示一个字符串列表的序列化,listpack 可用于存储字符串或整数。用于存储 stream 的消息内 容。

结构如下图:

image.png

Rax 树

Rax 是一个有序字典树 (基数树 Radix Tree),按照 key 的字典序排列,支持快速地定位、插入和删除操 作。

image.png

Rax 被用在 Redis Stream 结构里面用于存储消息队列,在 Stream 里面消息 ID 的前缀 10 种 encoding 息可以理解为时间序列消息。

使用 Rax 结构 进行存储就可以快速地根据消息 ID 定位到具 体的消息,然后继续遍历指定消息 之后的所有消息。

image.png

应用场景: 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 函数,该函数的意义是:读取数据之前先检查一下它有没有失效,如果失效了就删 除它

image.png

主动删除

在 redis.conf 文件中可以配置主动删除策略,默认是 no-enviction(不删除)

maxmemory-policy allkeys-lru

1、LRU

LRU (Least recently used) 最近最少使用,算法根据数据的历史访问记录来进行淘汰数据,其核心思想 是“如果数据最近被访问过,那么将来被访问的几率也更高”。

最常见的实现是使用一个链表保存缓存数据,详细算法实现如下:

  • 新数据插入到链表头部;
  • 每当缓存命中(即缓存数据被访问),则将数据移到链表头部;
  • 当链表满的时候,将链表尾部的数据丢弃。
  • 在 Java 中可以使用 LinkHashMap(哈希链表)去实现 LRU

我们以用户信息的需求为例,来演示一下 LRU 算法的基本思路:

1.假设我们使用哈希链表来缓存用户信息,目前缓存了 4 个用户,这 4 个用户是按照时间顺序依次从链表 右端插入的。

image.png

2.此时,业务方访问用户 5,由于哈希链表中没有用户 5 的数据,我们从数据库中读取出来,插入到缓存 当中。这时候,链表中最右端是最新访问到的用户 5,最左端是最近最少访问的用户 1。

image.png

3.接下来,业务方访问用户 2,哈希链表中存在用户 2 的数据,我们怎么做呢?我们把用户 2 从它的前驱 节点和后继节点之间移除,重新插入到链表最右端。这时候,链表中最右端变成了最新访问到的用户 2,最左端仍然是最近最少访问的用户 1。

image.png

image.png

4.接下来,业务方请求修改用户 4 的信息。同样道理,我们把用户 4 从原来的位置移动到链表最右侧,并 把用户信息的值更新。这时候,链表中最右端是最新访问到的用户 4,最左端仍然是最近最少访问的用 户 1。

image.png

5.业务访问用户 6,用户 6 在缓存里没有,需要插入到哈希链表。假设这时候缓存容量已经达到上限,必 须先删除最近最少访问的数据,那么位于哈希链表最左端的用户 1 就会被删除掉,然后再把用户 6 插入到 最右端。

image.png

image.png

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 做主从 + 哨兵 保证高可用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_412022.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringSecurity Oauth2实战 - 04 自定义AuthProvider实现登录认证

文章目录1. 搭建资源服务器1. Token存储配置类 TokenStoreAutoConfiguration2. 资源服务器配置类 ResourceServerAutoConfiguration3. 在META-INF/spring.factories文件下添加配置类2. 搭建授权服务器1. 密码加密配置类 PasswordEncodeConfig2. RestTemplateConfig3. 授权服务器…

SQL学习笔记(未完待续)

鉴于自己最近在做后端开发的工作时&#xff0c;发现自己的SQL能力实在太差&#xff0c;开始学习SQL语句基础&#xff0c;学习过程中在本博客进行笔记记录&#xff0c;课程参考&#xff1a;https://www.bilibili.com/video/BV1UE41147KC?p2 基本概念 DBMS: 数据库管理系统&am…

基于Python实现的文章整合搜索引擎网站(Scrapy+Django+MySQL)

目 录 摘 要… 1 1 概述… 6 2 技术选型… 6 2.1 Scrapy-Redis 分布式爬虫 … 6 2.1.1 Redis… 6 2.1.2 Scrapy… 7 2.2 MySQL 数据存储 … 8 2.3 Django 搭建搜索网站 … 8 2.4 ElasticSearch 搜索引擎 … 9 2.4.1 Elasticsearch-RTF… 9 2.4.2 Elasticsearch-head… 10 2.4.3…

Kotlin编程实战——集合(07)

一 概述 集合概述构造集合迭代器(Iterable<T>)区间与数列序列(Sequence<T>)集合操作概述集合转换集合过滤加减操作符分组(groupBy())取集合的一部分取单个元素集合排序集合聚合操作集合写操作List 相关操作Set 相关操作Map 相关操作 二 集合概述 set、list 、map…

【python】Numpy统计函数总结

文章目录函数列表相关系数直方图函数列表 最值amin, amax, nanmin, nanmax, 极差ptp分位数percentile∗^*∗ quantile∗^*∗,统计量中位数median∗^*∗&#xff1b;平均数mean∗^*∗&#xff1b;变化幅度var&#xff1b;加权平均average标准差std&#xff1b;协方差cov&#x…

运算放大器正反馈负反馈判别法

---------------------------------------------------------------------------------------------------------------- 反馈可分为负反馈和正反馈。前者使输出起到与输入相反的作用&#xff0c;使系统输出与系统目标的误差减小&#xff0c;系统趋于稳定&#xff1b;后者使输出…

浅谈java中的String

Java中的String类型不属于八大基本数据类型&#xff0c;而是一个引用数据类型&#xff0c;所以在定义一个String对象的时候如果不直接赋值给这个对象&#xff0c;它的默认值就是null。我们要怎么理解String类型的不可变&#xff0c;在JDK源码中String这个类的value方法被final关…

【C++】如何修改set的值

问题&#xff1a;尝试通过begin方法得到的迭代器去修改值&#xff0c;发现会报错。 set<string> st{"hello", "world", "good"}; set<string>::iterator it st.begin(); *it "test"; 原因&#xff1a;我们可以在源码里…

怎么搭建搜题接口api

怎么搭建搜题接口api 本平台优点&#xff1a; 多题库查题、独立后台、响应速度快、全网平台可查、功能最全&#xff01; 1.想要给自己的公众号获得查题接口&#xff0c;只需要两步&#xff01; 2.题库&#xff1a; 查题校园题库&#xff1a;查题校园题库后台&#xff08;点击…

RTSP协议学习Ubuntu环境准备

文章目录RTSP协议学习Ubuntu环境准备RTSP协议概述Ubuntu环境准备一、Ubuntu安装FFmpeg二、安装ZLMediaKit1、获取代码2、强烈推荐3、编译器3.1、编译器版本要求3.2、安装编译器4、cmake5、依赖库5.1、依赖库列表5.2、安装依赖库6、构建和编译项目7、运行8、测试三、测试推流测试…

【Tomcat】解决Tomcat服务器乱码问题

俩地方开展出现乱码的原因1、以startup.bat文件打开的服务器出现乱码2、在IDEA中运行Tomcat服务器出现乱码问题3、有关社区版IDEA如何开发JavaWeb项目出现乱码的原因 使用了错误的字符编码去解码字节流&#xff0c;所以出现乱码咱思维要清晰&#xff0c;就去找字符编码是否与其…

【TS04——接口的多态——泛型接口】

接口的多态&#xff0c;同一个方法&#xff0c;传入不同的参数&#xff0c;他所对应的操作不同成为多态【参数不同】或者可以理解为同一个方法&#xff0c;返回不同的结果&#xff0c;称之多态。 interface IfnPerson {run():voidrun(id:number):voidrun(id:number,name:strin…

【生日快乐】Node.js 实战 第1章 欢迎进入Node.js 的世界 1.3 安装Node

Node.js 实战 文章目录Node.js 实战第1章 欢迎进入Node.js 的世界1.3 安装Node第1章 欢迎进入Node.js 的世界 1.3 安装Node 安装Node的最简单的方法是使用其官网上的安装程序。可以用对应Mac或 Windows的安装程序安装最新的当前版。 官网安装包下载地址&#xff1a;https://…

Jenkins部署详细教程

Jenkins简介 Jenkins 是一个可扩展的持续集成引擎。是一个自成一体的开源自动化服务器, 可用于自动化与构建、测试、交付或部署软件相关的各种任务; Jenkins是一个高度可扩展的产品, 其功能可以通过安装插件来扩展。 在gitlab里可以完成源代码的管理&#xff0c;但是对于研发…

[ACTF2020 新生赛]Exec1命令注入

1.来看题目如下 得到一个ping的输入框&#xff0c;老样子先检查网页源码看有没有什么好东西&#xff0c;得到一个链接&#xff0c;我们来访问一下 发现也没什么有用处的信息&#xff0c;于是看到题目的标题之后联想到了命令注入&#xff0c; 那么是怎么判断使用命令注入的呢&am…

MyBatis初步了解

1.Mybatis简介 1.1原始jdbc操作&#xff08;查询数据&#xff09; 1.2原始jdbc操作&#xff08;插入数据&#xff09; 1.3 原始jdbc操作的分析 原始jdbc开发存在的问题如下&#xff1a; ①数据库连接创建、释放频繁造成系统资源浪费从而影响系统性能 ②sql 语句在代码中硬编…

深入理解Java虚拟机:Java运行内存结构

本篇内容包括&#xff1a;JAVA 运行内存结构&#xff0c;即 程序计数器、Java 虚拟机栈、本地方法栈 、Java堆、方法区、运行时常量池 以及 直接内存等相关内容&#xff01; 一、JAVA 运行内存结构 Jvm 执行 Java 程序时&#xff0c;会把它所管理的内存划分为若干个不同的数据…

软件设计与体系——创建型模式

如果有兴趣了解更多相关内容&#xff0c;欢迎来我的个人网站看看&#xff1a;瞳孔的个人空间 创建型模式&#xff1a; 创建型模式抽象了实例化过程帮助系统独立于如何创建、组合和表示对象 一个类创建型模式使用继承改变被实例化的类类创建型模式使用继承改变被实例化的类对象…

自学Python需要掌握哪些知识点?怎么学?

其实这个问题你可以转换一下思路&#xff0c;自学python有什么合适的学习路线&#xff0c;基本路线里涉及到的都是需要掌握的~ 单个知识点那罗列起来可是太多了~ 另外你可以考虑下你自学python的目的和方向&#xff0c;是单纯兴趣还是说要学了就业~ 想往人工智能方向发展还是P…

如何实现CAN/LIN通信路由测试?

目前车载网络通信越来越复杂&#xff0c;通信总线、协议多种多样&#xff0c;网关作为信息交互的载体&#xff0c;主要通过报文路由转发、报文过滤、信号重组等方式实现不同网段、不同协议间的信息交互&#xff1b;在复杂的车载网络中&#xff0c;网关通信路由转发功能具有非常…