高性能内存kv数据库Redis

引言

在当今数据驱动的时代,高效的数据存储和检索对于各类应用程序至关重要。Redis(Remote Dictionary Server)作为一款开源的内存键值数据库,凭借其出色的性能、丰富的数据结构和灵活的特性,在众多场景中得到了广泛应用。本文将深入探讨 Redis 的基本概念、核心特性、常用命令以及实际应用案例,帮助读者全面了解和掌握 Redis

一.Redis相关命令详解及其原理

1.redis是什么?

Redis 是 Remote Dictionary Service 的简称;也是远程字典服务,通过tcp和redis建立连接交互,通过字典的方式索引存储数据。
Redis 是内存数据库,KV 数据库,数据结构数据库,数据都在内存中。

2.redis中存储数据的数据结构都有哪些?

①string:是一个安全的二进制字符串。

②双端队列(链表)list :有序(插入有序)。
③散列表 hash :对顺序不关注, field 是唯一的;
④无序集合 set :对顺序不关注,里面的值都是唯一的;
⑤有序集合 zset :对顺序是关注的,里面的值是唯一的;根据
member 来确定唯一;根据 score 来确定有序;

3.redis的存储结构(KV)

4.reidis中value编码

数据量 的时候,存储效率高为主;
数据量 的时候,运行速度快;

5.string的基本原理和相关命令

5.1基本原理
字符数组,该字符串是动态字符串 raw ,字符串长度小于 1M
时,加倍扩容;超过 1M 每次只多扩 1M ;字符串最大长度为 512M
注意: redis 字符串是二进制安全字符串;可以存储图片,二进
制协议等二进制数据;
5.2基础命令
# 设置 key 的 value 值
SET key val
# 获取 key 的 value
GET key
# 执行原子加一的操作
INCR key
# 执行原子加一个整数的操作
INCRBY key increment
# 执行原子减一的操作
DECR key
# 执行原子减一个整数的操作
DECRBY key decrement
# 如果key不存在,这种情况下等同SET命令。当key存在时,什
么也不做
# set Not eXist   ok 这个命令是否执行了 0,1 是不是操
作结果是不是成功
SETNX key value
# 删除 key val 键值对
DEL key
# 设置或者清空key的value(字符串)在offset处的bit值。
setbit embstr raw int
# 动态字符串能够节约内存
SETBIT key offset value
# 返回key对应的string在offset处的bit值
GETBIT key offset
# 统计字符串被设置为1的bit数.
BITCOUNT key

我们来举一些简单的例子

5.3string存储结构
字符串长度小于等于 20 且能转成整数,则使用 int 存储
字符串长度小于等于 44 ,则使用 embstr 存储
字符串长度大于 44 ,则使用 raw 存储

5.4应用场景

对象存储

SET role:10001 '{["name"]:"lion",["sex"]:"male",
["age"]:30}'
SET role:10002 '{["name"]:"xiaoyu",["sex"]:"female",
["age"]:30}'
我们应该如何设置Key 才能让它更有意义呢?
单个功能的一个key:取有意义名字的key
相同功能的多个key:我们可以以:作为分割
执行上述命令
累加器
# 统计阅读数 累计加1
incr reads
# 累计加100
incrby reads 100

分布式锁

# 加锁   加锁 和 解析 redis 实现是 非公平锁     ectd
zk 用来实现公平锁
# 阻塞等待   阻塞连接的方式
# 介绍简单的原理: 事务
setnx lock 1   # 不存在才能设置 定义加锁行为 占用锁  
setnx lock uuid  # expire 30 过期
set lock uuid nx ex 30
# 释放锁
del lock
if (get(lock) == uuid)del(lock);

位运算

# 猜测一下 string 是用的 int 类型 还是 string 类型
# 月签到功能 10001 用户id 202106 2021年6月份的签到 6月
份的第1天
setbit sign:10001:202106 1 1
# 计算 2021年6月份 的签到情况
bitcount sign:10001:202106
# 获取 2021年6月份 第二天的签到情况 1 已签到 0 没有签到
getbit sign:10001:202106 2

6.list的基本原理和相关命令

6.1基本原理
双向链表实现,列表首尾操作(删除和增加)时间复杂度
O(1) ;查找中间元素时间复杂度为 O(n)
列表中数据是否压缩的依据:
1. 元素长度小于 48 ,不压缩;
2. 元素压缩前后长度差不超过 8 ,不压缩;
6.2基础命令
# 从队列的左侧入队一个或多个元素
LPUSH key value [value ...]
# 从队列的左侧弹出一个元素
LPOP key
# 从队列的右侧入队一个或多个元素
RPUSH key value [value ...]
# 从队列的右侧弹出一个元素
RPOP key 
# 返回从队列的 start 和 end 之间的元素 0, 1 2 负索引
LRANGE key start end
# 从存于 key 的列表里移除前 count 次出现的值为 value 的
元素
# list 没有去重功能   hash set zset
LREM key count value
# 它是 RPOP 的阻塞版本,因为这个命令会在给定list无法弹出
任何元素的时候阻塞连接
BRPOP key timeout  # 超时时间 + 延时队列

举栗子

我们采用lpush从左边插入五个人名

我们采用的是从左边插入的方法 也就是所谓的头插法  简易链表展示如下

lion

xiaoyu-->lion

xiaolan-->xiaoyu-->lion

xiaohuang-->xiaolan-->xiaoyu-->lion

xiaobai-->xiaohuang-->xiaolan-->xiaoyu-->lion

 我们分别从左边和右边弹出一个元素 结果如下

6.3存储结构
/* Minimum ziplist size in bytes for attempting
compression. */
#define MIN_COMPRESS_BYTES 48
/* quicklistNode is a 32 byte struct describing a
ziplist for a quicklist.
* We use bit fields keep the quicklistNode at 32
bytes.
* count: 16 bits, max 65536 (max zl bytes is
65k, so max count actually < 32k).
* encoding: 2 bits, RAW=1, LZF=2.
* container: 2 bits, NONE=1, ZIPLIST=2.
* recompress: 1 bit, bool, true if node is
temporary decompressed for usage.
* attempted_compress: 1 bit, boolean, used for
verifying during testing.
* extra: 10 bits, free for future use; pads out
the remainder of 32 bits */
typedef struct quicklistNode {struct quicklistNode *prev;struct quicklistNode *next;unsigned char *zl;unsigned int sz;             /* ziplist size
in bytes */unsigned int count : 16;     /* count of
items in ziplist */unsigned int encoding : 2;   /* RAW==1 or
LZF==2 */unsigned int container : 2;  /* NONE==1 or
ZIPLIST==2 */unsigned int recompress : 1; /* was this node
previous compressed? */unsigned int attempted_compress : 1; /* node
can't compress; too small */unsigned int extra : 10; /* more bits to
steal for future usage */
} quicklistNode;
typedef struct quicklist {quicklistNode *head;quicklistNode *tail;unsigned long count;        /* total count of
all entries in all ziplists */unsigned long len;          /* number of
quicklistNodes */int fill : QL_FILL_BITS;              /* fill
factor for individual nodes */unsigned int compress : QL_COMP_BITS; /*
depth of end nodes not to compress;0=off */unsigned int bookmark_count: QL_BM_BITS;quicklistBookmark bookmarks[];
} quicklist;
6.4应用场景
栈(先进后出 FILO)
LPUSH + LPOP
# 或者
RPUSH + RPOP
队列(先进先出 FIFO)
LPUSH + RPOP
# 或者
RPUSH + LPOP
阻塞队列(blocking queue)
LPUSH + BRPOP
# 或者
RPUSH + BLPOP
异步消息队列
操作与队列一样,但是在不同系统间;生成者和消费者;
获取固定窗口记录(战绩)
lua脚本
实际项目中需要保证命令的原子性,所以一般用 lua 脚本 或者
使用 pipeline 命令;
-- redis lua脚本
local record = KEYS[1]
redis.call("LPUSH", "says", record)
redis.call("LTRIM", "says", 0, 4)

7.hash的基本原理和相关命令

7.1基本原理
散列表,在很多高级语言当中包含这种数据结构; c++
unordered_map 通过 key 快速索引 value
7.2基础命令
# 获取 key 对应 hash 中的 field 对应的值
HGET key field
# 设置 key 对应 hash 中的 field 对应的值
HSET key field value
# 设置多个hash键值对
HMSET key field1 value1 field2 value2 ... fieldn
valuen
# 获取多个field的值
HMGET key field1 field2 ... fieldn
# 给 key 对应 hash 中的 field 对应的值加一个整数值
HINCRBY key field increment
# 获取 key 对应的 hash 有多少个键值对
HLEN key
# 删除 key 对应的 hash 的键值对,该键为field
HDEL key field

举栗子

7.3存储结构
节点数量大于 512 hash-max-ziplist-entries ) 或所有字符串长
度大于 64 hash-max-ziplist-value ),则使用 dict 实现;

节点数量小于等于 512 且有一个字符串长度小于 64 ,则使用
ziplist 实现;
 7.4应用场景

存储对象

hmset hash:10001 name lion age 18 sex male
# 与 string 比较
set hash:10001 '{["name"]:"lion",["sex"]:"male",
["age"]:18}'
# 假设现在修改 lion的年龄为19岁
# hash:hset hash:10001 age 19
# string:get hash:10001# 将得到的字符串调用json解密,取出字段,修改 age 值# 再调用json加密set hash:10001 '{["name"]:"lion",
["sex"]:"male",["age"]:19}'
购物车
# 将用户id作为 key
# 商品id作为 field
# 商品数量作为 value
# 注意:这些物品是按照我们添加顺序来显示的;# 添加商品:hmset MyCart:10001 40001 1 cost 5099 desc "戴尔
笔记本14-3400"lpush MyItem:10001 40001
# 增加数量:
hincrby MyCart:10001 40001 1hincrby MyCart:10001 40001 -1 // 减少数量1
# 显示所有物品数量:hlen MyCart:10001
# 删除商品:hdel MyCart:10001 40001lrem MyItem:10001 1 40001
# 获取所有物品:lrange MyItem:10001# 40001 40002 40003hget MyCart:10001 40001hget MyCart:10001 40002hget MyCart:10001 40003

8.set的基本原理和相关命令

8.1基本原理
集合;用来存储唯一性字段,不要求有序;
存储不需要有序,操作(交并差集的时候排序)?
 8.2基础命令
# 添加一个或多个指定的member元素到集合的 key中
SADD key member [member ...]
# 计算集合元素个数
SCARD key
# SMEMBERS key
SMEMBERS key
# 返回成员 member 是否是存储的集合 key的成员
SISMEMBER key member
# 随机返回key集合中的一个或者多个元素,不删除这些元素
SRANDMEMBER key [count]
# 从存储在key的集合中移除并返回一个或多个随机元素
SPOP key [count]# 返回一个集合与给定集合的差集的元素
SDIFF key [key ...]
# 返回指定所有的集合的成员的交集
SINTER key [key ...]
# 返回给定的多个集合的并集中的所有成员
SUNION key [key ...]

举栗子

8.3存储结构
元素都为整数且节点数量小于等于 512 set-max-intset
entries ),则使用整数数组存储;
元素当中有一个不是整数或者节点数量大于 512 ,则使用字典
存储;
8.4应用场景 

抽奖

# 添加抽奖用户sadd Award:1 10001 10002 10003 10004 10005
10006sadd Award:1 10009
# 查看所有抽奖用户smembers Award:1
# 抽取多名幸运用户srandmember Award:1 10
# 如果抽取一等奖1名,二等奖2名,三等奖3名,该如何操作?

共同关注

sadd follow:A mark king darren mole vico
sadd follow:C mark king darren
sinter follow:A follow:C
推荐好友
sadd follow:A mark king darren mole vico
sadd follow:C mark king darren
# C可能认识的人:sdiff follow:A follow:C

9.zset的基本原理和相关命令

9.1基本原理
有序集合;用来实现排行榜;它是一个有序唯一;
 9.2基础命令
# 添加到键为key有序集合(sorted set)里面
ZADD key [NX|XX] [CH] [INCR] score member [score
member ...]
# 从键为key有序集合中删除 member 的键值对
ZREM key member [member ...]
# 返回有序集key中,成员member的score值
ZSCORE key member
# 为有序集key的成员member的score值加上增量increment
ZINCRBY key increment member
# 返回key的有序集元素个数
ZCARD key
# 返回有序集key中成员member的排名
ZRANK key member
# 返回存储在有序集合key中的指定范围的元素   order by id
limit 1,100
ZRANGE key start stop [WITHSCORES]
# 返回有序集key中,指定区间内的成员(逆序)
ZREVRANGE key start stop [WITHSCORES]

举栗子

9.3存储结构
节点数量大于 128 或者有一个字符串长度大于 64 ,则使用跳表
skiplist );
节点数量小于等于 128 zset-max-ziplist-entries )且所有字符
串长度小于等于 64 zset-max-ziplist-value ),则使用
ziplist 存储;
数据少的时候,节省空间O(n)
数据多的时候,访问性能O(1) or O(logn)
 9.4应用场景

百度热搜

# 点击新闻:zincrby hot:20230612 1 10001zincrby hot:20230612 1 10002zincrby hot:20230612 1 10003zincrby hot:20230612 1 10004zincrby hot:20230612 1 10005zincrby hot:20230612 1 10006zincrby hot:20230612 1 10007zincrby hot:20230612 1 10008zincrby hot:20230612 1 10009zincrby hot:20230612 1 10010# 获取排行榜:zrevrange hot:20230612 0 9 withscores
延时队列
将消息序列化成一个字符串作为 zset member ;这个消息
的到期处理时间作为 score ,然后用多个线程轮询 zset 获取到
期的任务进行处理。
def delay(msg):msg.id = str(uuid.uuid4()) #保证 member 唯一value = json.dumps(msg)retry_ts = time.time() + 5 # 5s后重试redis.zadd("delay-queue", retry_ts, value)
# 使用连接池
def loop():while True:values = redis.zrangebyscore("delayqueue", 0, time.time(), start=0, num=1)if not values:time.sleep(1)continuevalue = values[0]success = redis.zrem("delay-queue",
value)if success:msg = json.loads(value)handle_msg(msg)
# 缺点:loop 是多线程竞争,两个线程都从zrangebyscore获
取到数据,但是zrem一个成功一个失败,
# 优化:为了避免多余的操作,可以使用lua脚本原子执行这两个
命令
# 解决:漏斗限流

 分布式定时器

生产者将定时任务 hash 到不同的 redis 实体中,为每一个
redis 实体分配一个 dispatcher 进程,用来定时获取 redis 中超
时事件并发布到不同的消费者中;

时间窗口限流 

系统限定用户的某个行为在指定的时间范围内(动态)只能发
N 次;
# 指定用户 user_id 的某个行为 action 在特定时间内
period 只允许发生该行为做大次数 max_count
local function is_action_allowed(red, userid,
action, period, max_count)local key = tab_concat({"hist", userid,
action}, ":")local now = zv.time()red:init_pipeline()-- 记录行为red:zadd(key, now, now)-- 移除时间窗口之前的行为记录,剩下的都是时间窗口内的
记录red:zremrangebyscore(key, 0, now - period
*100)-- 获取时间窗口内的行为数量red:zcard(key)-- 设置过期时间,避免冷用户持续占用内存 时间窗口的长
度+1秒red:expire(key, period + 1)local res = red:commit_pipeline()return res[3] <= max_count
end# 维护一次时间窗口,将窗口外的记录全部清理掉,只保留窗口内
的记录;# 缺点:记录了所有时间窗口内的数据,如果这个量很大,不适合
做这样的限流;漏斗限流
# 注意:如果用 key + expire 操作也能实现,但是实现的是熔
断限流,这里是时间窗口限流的功能;score

 10.redis的抽象层次

补充:redis设置过期时间

①expire

②setex

③ex

二.协议与异步方式

1.redis pipeline

redis pipeline 是一个客户端提供的机制,而不是服务端提供
的;
pipeline 不具备事务性;
目的:节约网络传输时间;
通过一次发送多次请求命令,从而减少网络传输的时间
一次性发送多个请求包,redis按序响应
与http1.1解决的问题类似

2.发布订阅模式

2.1基本原理
为了支持消息的多播机制, redis 引入了发布订阅模块;
消息不一定可达;分布式消息队列;
stream 的方式确保一定可 达;
2.2基础命令 
# 订阅频道
subscribe 频道
# 订阅模式频道
psubscribe 频道
# 取消订阅频道
unsubscribe 频道
# 取消订阅模式频道
punsubscribe 频道
# 发布具体频道或模式频道的内容
publish 频道 内容
# 客户端收到具体频道内容
message 具体频道 内容
# 客户端收到模式频道内容
pmessage 模式频道 具体频道 内容

举栗子 

2.3应用场景
发布订阅功能一般要区别命令连接重新开启一个连接;因为命
令连接严格遵循请求回应模式;而 pubsub 能收到 redis 主动推
送的内容;所以实际项目中如果支持 pubsub 的话,需要 另开一
条连接 用于处理发布订阅;
2.4缺点
发布订阅的生产者传递过来一个消息, redis 会直接找到相应的
消费者并传递过去;假如没有消费者,消息直接丢弃;假如开
始有 2 个消费者,一个消费者突然挂掉了,另外一个消费者依然
能收到消息,但是如果刚挂掉的消费者重新连上后,在断开连
接期间的消息对于该消费者来说彻底丢失了;
另外, redis 停机重启, pubsub 的消息是不会持久化的,所有
的消息被直接丢弃;

3.redis事务

3.1什么是事务,事务具有什么特性
事务:用户定义一系列数据库操作,这些操作视为一个完整的
逻辑处理工作单元 ,要么全部执行,要么全部不执行,是不可
分割的工作单元。
MULTI 开启事务,事务执行过程中,单个命令是入队列操作,
直到调用 EXEC 才会一起执行;
乐观锁实现,所以失败需要重试,增加业务逻辑的复杂度;
acid
①原子性
②一致性
③隔离性
④持久性
3.2实现事务的基础命令(不常用)
 
MULTI
开启事务
begin / start transactionEXEC
提交事务
commitDISCARD
取消事务
rollbackWATCH
检测 key 的变动,若在事务执行中,key 变动则取消事务;在事
务开启前调用,乐观锁实现(cas);
若被取消则事务返回 nil ;
3.3应用场景

事务实现 zpop 

WATCH zset
element = ZRANGE zset 0 0
MULTI
ZREM zset element
EXEC
事务实现 加倍操作
WATCH score:10001
val = GET score:10001
MULTI
SET score:10001 val*2
EXEC

4.lua脚本(常用)

4.1lua脚本怎么实现
lua 脚本实现原子性;

redis 中加载了一个 lua 虚拟机;用来执行 redis lua 脚本; redis
lua 脚本的执行是原子性的;当某个脚本正在执行的时候,不会
有其他命令或者脚本被执行;
lua 脚本当中的命令会直接修改数据状态;
lua 脚本 mysql 存储区别: MySQL 存储过程不具备事务性,所以
也不具备原子性;
注意 :如果项目中使用了 lua 脚本,不需要使用上面的事务命
令;
4.2.lua脚本一些基础命令 
# 从文件中读取 lua脚本内容
cat test1.lua | redis-cli script load --pipe
# 加载 lua脚本字符串 生成 sha1
> script load 'local val = KEYS[1]; return val'
"b8059ba43af6ffe8bed3db65bac35d452f8115d8"
# 检查脚本缓存中,是否有该 sha1 散列值的lua脚本
> script exists
"b8059ba43af6ffe8bed3db65bac35d452f8115d8"
1) (integer) 1
# 清除所有脚本缓存
> script flush
OK
# 如果当前脚本运行时间过长(死循环),可以通过 script kill
杀死当前运行的脚本
> script kill
(error) NOTBUSY No scripts in execution right
now.

 EVAL

# 测试使用
EVAL script numkeys key [key ...] arg [arg ...]
EVALSHA
# 线上使用
EVALSHA sha1 numkeys key [key ...] arg [arg ...]
举栗子
4.3应用场景
# 1: 项目启动时,建立redis连接并验证后,先加载所有项目中使
用的lua脚本(script load);
# 2: 项目中若需要热更新,通过redis-cli script flush;然
后可以通过订阅发布功能通知所有服务器重新加载lua脚本;
# 3:若项目中lua脚本发生阻塞,可通过script kill暂停当前阻
塞脚本的执行;

 5.ACID特性分析

A 原子性;事务是一个不可分割的工作单位,事务中的操作要
么全部成功,要么全部失败; redis 不支持回滚;即使事务队列
中的某个命令在执行期间出现了错误,整个事务也会继续执行
下去,直到将事务队列中的所有命令都执行完毕为止。
C 一致性;事务的前后,所有的数据都保持一个一致的状态,
不能违反数据的一致性检测;这里的一致性是指预期的一致性
而不是异常后的一致性;所以 redis 也不满足;这个争议很大:
redis 能确保事务执行前后的数据的完整约束;但是并不满足业
务功能上的一致性;比如转账功能,一个扣钱一个加钱;可能
出现扣钱执行错误,加钱执行正确,那么最终还是会加钱成
功;系统凭空多了钱;
I 隔离性;各个事务之间互相影响的程度; redis 是单线程执
行,天然具备隔离性;

D 持久性; redis 只有在 aof 持久化策略的时候,并且需要在
redis.conf appendfsync=always 才具备持久性;实际项
目中几乎不会使用 aof 持久化策略;
面试时候回答: lua 脚本满足原子性和隔离性;一致性和持久性
不满足;

6.redis异步连接 

6.1redis协议图
协议实现的第一步需要知道如何界定数据包:
1. 长度 + 二进制流
2. 二进制流 + 特殊分隔符
6.2异步连接 
同步连接方案采用阻塞 io 来实现;优点是代码书写是同步的,
业务逻辑没有割裂;缺点是阻塞当前线程,直至 redis 返回结
果;通常用多个线程来实现线程池来解决效率问题;
异步连接方案采用非阻塞 io 来实现;优点是没有阻塞当前线
程, redis 没有返回,依然可以往 redis 发送命令;缺点是代码
书写是异步的(回调函数),业务逻辑割裂,可以通过协程解
决( openresty skynet );配合 redis6.0 以后的 io 多线程(前
提是有大量并发请求),异步连接池,能更好解决应用层的数
据访问性能;
6.3实现方案 
hiredis + libevent
举栗子举栗子
同步sync
同步执行从0加到1000所花费时间100ms
异步async
异步执行从1000加到2000所花费时间为5ms
显然异步IO比同步IO的效率高很多
reactor补充
6.4原理是什么
hiredis 提供异步连接方式,提供 可以替换 IO 检测 的接口;
关键替换 addRead delRead addWrite delWrite
cleanup scheduleTimer ,这几个检测接口;其他 io 操作,
比如 connect read write close 等都交由 hiredis 来处
理;
同时需要提供连接建立成功以及断开连接的回调;
用户可以使用当前项目的网络框架来替换相应的操作;从而实
现跟项目网络层兼容的异步连接方案;

三.存储原理与数据模型

1.redis是单线程还是多线程

      

我们通过gdb调试redis 可以查看 起线程明显有多个

这些线程的作用分别如下图所示

 2.redis中说的单线程究竟是什么?

命令处理是在一个单线程中

处理客户端请求的核心流程是单线程的 。

即从接收客户端请求(网络读取 )、解析命令、执行命令到返回结果(网络写入 ),都由一个主线程顺序串行处理 。

所有命令在这个过程中逐个执行,不会有两条命令被同时执行,确保了操作的原子性,也避免了线程安全问题和不必要的上下文切换。

比如执行SET key value 、GET key 等各种数据操作命令时,都是在这个单线程流程中完成 。

 3.命令处理为什么是单线程

3.1单线程的局限性(redis要避免)

不能有耗时的操作:cpu运算和阻塞的io

对于redis而言会影响性能

3.2redis有没有io密集型和cpu密集型(有的)

IO密集型分为磁盘IO和网络IO

redis在磁盘IO中可以fork子进程,在子进程中做持久化,还可以异步刷盘

redis在网络IO中服务多个客户,造成IO密集,数据请求和返回量比较大,redis会开启IO多线程处理这个问题

4.命令处理为什么不采用多线程

加锁复杂,锁的粒度不好控制

频繁的cpu上下文切换,抵消多线程的优势

5.单线程是怎么做到这么快的呢?

5.1采用了哪些机制

内存型数据库

数据的组织方式O(1):hashtable 可以扩容,缩容,还能渐进式rehash在cpu运算中,每步操作rehash,定时rehash 1ms 之类的.

数据高效:在执行效率与空间占用保持平衡,数据结构切换

高效的reactor网络模型

5.2做了哪些优化 

分治思想

把rehash分摊到每一个操作步骤当中

在定时器当中,以100为步长最大rehash 1ms

耗时阻塞的操作,在其他线程处理

对象类型采用不同的数据结构实现

 6.负载因子,扩缩容机制,渐进式rehash

负载因子
负载因子 = used / size used 是数组存储元素的个数,
size 是数组的长度;
负载因子越小,冲突越小;负载因子越大,冲突越大;
redis 的负载因子是 1

扩容 

如果负载因子 > 1 ,则会发生扩容;扩容的规则是翻倍;
如果正在 fork (在 rdb aof 复写以及 rdb-aof 混用情况下)
时,会阻止扩容;但是此时若负载因子 > 5 ,索引效率大大降
低, 则马上扩容;这里涉及到写时复制原理;

缩容

如果负载因子 < 0.1 ,则会发生缩容;缩容的规则是 恰好 包含
used 的 ;
恰好 的理解:假如此时数组存储元素个数为 9 ,恰好包含该元素
的就是 ,也就是 16

 

 渐进式rehash

hashtable 中的元素过多的时候,不能一次性 rehash
ht[1] ;这样会长期占用 redis ,其他命令得不到响应;所以需
要使用渐进式 rehash
rehash 步骤
ht[0] 中的元素重新经过 hash 函数生成 64 位整数,再对
ht[1] 长度进行取余,从而映射到 ht[1]
渐进式规则
1. 分治的思想,将 rehash 分到之后的每步增删改查的操作当
中;
2. 在定时器中,最大执行一毫秒 rehash ;每次步长 100 个数
组槽位;
面试
处于渐进式 rehash 阶段时,是否会发生扩容缩容?不会!

7.scan

scan cursor [MATCH pattern] [COUNT count] [TYPE
type]

采用高位进位加法的遍历顺序, rehash 后的槽位在遍历顺序上
是相邻的;
遍历目标是:不重复,不遗漏 ;
会出现一种重复的情况:在 scan 过程当中,发生两次缩容的时
候,会发生数据重复;
注意 :上课时有一个问题表述错误;关于 scan scan 要达到的
目的是从 scan 开始那刻起 redis 已经存在的数据进行遍历,不
会重复和遗漏(例外是 scan 过程中两次缩容可能造成数据重
复), 因为比如我 scan 已经快结束了,现在插入大量数据,这
些数据肯定遍历不到;
扩容和缩容造成映射算法发生改变,但是使用高位进位累加的
算法,可以对 scan 那刻起已经存在数据的遍历不会出错;

举栗子 

8.expire

# 只支持对最外层key过期;
expire key seconds
pexpire key milliseconds
ttl key
pttl key
惰性删除
分布在每一个命令操作时检查 key 是否过期;若过期删除 key
再进行命令操作;

定时删除

在定时器中检查库中指定个数(25)个 key; 

#define ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP 20 /*
Keys for each DB loop. */
/*The default effort is 1, and the maximum
configurable effort* is 10. */
config_keys_per_loop =
ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP +ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP/4*effort,
int activeExpireCycleTryExpire(redisDb *db,
dictEntry *de, long long now);

 举栗子

9.大Key

redis 实例中形成了很大的对象,比如一个很大的 hash 或很
大的 zset ,这样的对象在扩容的时候,会一次性申请更大的一块
内存,这会导致卡顿;如果这个大 key 被删除,内存会一次性
回收,卡顿现象会再次产生;
如果观察到 redis 的内存大起大落,极有可能因为大 key 导致
的;
# 每隔0.1秒 执行100条scan命令
redis-cli -h 127.0.0.1 --bigkeys -i 0.1

10.对象编码(补充)

 11.skiplist(用在zset中)

redis 跳表
从节约内存出发, redis 考虑牺牲一点时间复杂度让跳表结构更
加变扁平,就像二叉堆改成四叉堆结构;并且 redis 还限制了跳
表的最高层级为 32
节点数量大于 128 或者有一个字符串长度大于 64 ,则使用跳表
skiplist );

 数据结构

#define ZSKIPLIST_MAXLEVEL 32 /* Should be enough
for 2^64 elements */
#define ZSKIPLIST_P 0.25      /* Skiplist P = 1/4
*/
/* ZSETs use a specialized version of Skiplists
*/
typedef struct zskiplistNode {sds ele;double score; // WRN: score 只能是浮点数struct zskiplistNode *backward;struct zskiplistLevel {struct zskiplistNode *forward;unsigned long span; // 用于 zrank} level[];
} zskiplistNode;
typedef struct zskiplist {struct zskiplistNode *header, *tail;unsigned long length; // zcardint level; // 最高层
} zskiplist;
typedef struct zset {dict *dict; // 帮助快速索引到节点zskiplist *zsl;
} zset;

结构图
补充

12.建议阅读redis中的源码加强理解

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

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

相关文章

自动化测试概念篇

文章目录 目录1. 自动化1.1 自动化概念1.1.1 回归测试 1.2 自动化分类1.3 自动化测试金字塔 2. web自动化测试2.1 驱动2.1.1 安装驱动管理2.1.2 selenium库 3. Selenium3.1 一个简单的web自动化示例3.2 selenium驱动浏览器的工作原理 目录 自动化web自动化测试Selenium 1. 自…

《AI大模型应知应会100篇》第17篇:大模型的偏见与公平性问题

第17篇&#xff1a;大模型的偏见与公平性问题 摘要 在人工智能迅速发展的今天&#xff0c;大型语言模型&#xff08;LLM&#xff09;已经深入到我们的日常生活和工作中。然而&#xff0c;这些模型并非完美无缺&#xff0c;它们可能携带并放大数据中的偏见&#xff0c;导致不公…

【踩坑】GitHub Actions 运行的 Linux 环境中,文件名是大小写敏感的

在使用 VuePress 搭建个人博客并部署到 GitHub Pages 的过程中&#xff0c;我遇到了一个颇为棘手的问题&#xff1a;本地打包一切正常&#xff0c;但在 GitHub Actions 自动执行打包流程时&#xff0c;却提示找不到 README.md 文件&#xff0c;导致整个流程失败。经过一番深入排…

C# 13新特性 - .NET 9

转载&#xff1a; C# 13 中的新增功能 | Microsoft Learn C# 13 包括以下新增功能。 可以使用最新的 Visual Studio 2022 版本或 .NET 9 SDK 尝试这些功能&#xff1a;Introduced in Visual Studio 2022 Version 17.12 and newer when using C# 13 C# 13 中的新增功能 | Micr…

numpy.ma.masked_where:屏蔽满足条件的数组

1.函数功能 屏蔽满足条件的数组内容&#xff0c;返回值为掩码数组 2.语法结构 np.ma.masked_where(condition, a, copyTrue)3. 参数 参数含义condition屏蔽条件a要操作的数组copy布尔值&#xff0c;取值为True时&#xff0c;结果复制数组(原始数据不变)&#xff0c;否则返回…

【Redis】数据结构和内部编码

先来复习一下之前学过的几个基本的全局命令&#xff1a; keys&#xff1a;用来查看匹配规则的keyexists&#xff1a;用来判定执行key是否存在del&#xff1a;删除指定的keyexpire&#xff1a;给key设置过期时间ttl&#xff1a;查询key的过期时间type&#xff1a;查询key对应的…

OBOO鸥柏如何以智能教育室内外触摸屏一体机AI变革硬件

在AI技术蓬勃发展的当下&#xff0c;OBOO鸥柏室外触摸屏一体机通过融入AI科技&#xff0c;为教育领域带来了翻天覆地的变化。这款一体机不仅为高校和大学校园提供了革命性的数字化教学解决方案&#xff0c;更引领了引体向上成绩提升一体机带训室外终端屏幕设备的新潮流。其创新…

从零搭建高并发体育直播网站:架构设计、核心技术与性能优化实战

本文从技术视角拆解体育直播网站开发全流程&#xff0c;涵盖高并发架构设计、低延迟视频流传输、实时弹幕系统实现等核心模块&#xff0c;并附可复用的代码片段与优化方案。适合中高级开发者进阶实战参考。 一、需求分析与技术选型 1. 典型业务场景 核心需求&#xff1a;支持1…

【Python内置函数的深度解析与应用】id

目录 前言&#xff1a;技术背景与价值当前技术痛点解决方案概述目标读者说明 一、技术原理剖析核心概念图解关键技术模块技术选型对比 二、实战演示环境配置要求核心代码实现1. 基础身份验证2. 不可变对象优化3. 对象生命周期追踪 运行结果验证 三、性能对比测试方法论量化数据…

3.vtkProp 和vtkProp3D

文章目录 vtkProp 和vtkProp3D使用vtkProp3D使用vtkPro vtkProp 和vtkProp3D vtkProp 和 vtkProp3D 都是VTK&#xff08;Visualization Toolkit&#xff09;库中的类&#xff0c;它们用于在渲染场景中表示可视化元素。理解这两个类的区别和用途对于有效地使用VTK进行三维数据可…

【ZYNQ Linux移植】2-获取设备树

0 写在前面 这是一个系列博客&#xff0c;详细介绍如何在 ZYNQ 与 ZYNQ MP 平台上如何移植 Linux 系统。目前网络上的大部分教程都是全程基于 Petalinux 的开发&#xff0c;虽然这样简化了开发流程&#xff0c;但对于初学者深入理解掌握 Linux 是不利的&#xff0c;所以&#x…

基础算法篇(5)(蓝桥杯常考点)—动态规划(C/C++)

文章目录 动态规划前言线性dp路径类dp经典线性dp背包问题分类01背包问题完全背包问题多重背包分组背包问题混合背包问题多维费用的背包问题区间dp 动态规划 前言 在竞赛中&#xff0c;如果遇到动态规划的题目&#xff0c;只要不是经典题型&#xff0c;那么大概率就是以压轴题的…

obsidian写文章的图床设置方法

目标 要达成的需求&#xff1a; 复制到obsidian的图片&#xff0c;自动上传到Picgo配置的图床。可以自定义大小。可以一键下载当前文章的图片到本地。 obsidian配置图床 安装并配置插件 image auto upload plugin&#xff0c;配置信息如下图。 滚轮alt自定义大小 安装并…

QPaintDevice绘图设备

1.QPixmap 对不同平台做了显示的优化&#xff0c;可以将画的图保存到磁盘上 头文件&#xff1a; #include"QPixmap" #include"QPainter" 1.1QPixmap画图 代码&#xff1a; //Pixmap绘图设备QPixmap pix(300,300);//声明画家QPainter painter(&pix…

数据结构有哪些类型(对于数据结构的简述)

在学习计算机时&#xff0c;数据结构是不可忽视的一点&#xff0c;从考研时的408课程&#xff0c;再到工作中编写软件&#xff0c;网站&#xff0c;要想在计算机领域站住脚跟&#xff0c;数据结构是必备的 在这里&#xff0c;我对于数据结构进行了汇总&#xff0c;并简要描述&…

L2TP实验(无图后补)

拓扑图 一、搭建拓扑并配置基础 IP 地址 设备选型与拓扑搭建&#xff1a;在 eNSP 中&#xff0c;拖入所需设备&#xff0c;包括 LAC&#xff08;L2TP Access Concentrator&#xff0c;L2TP 接入集中器 &#xff09;、LNS&#xff08;L2TP Network Server&#xff0c;L2TP 网络服…

【C#】CAN通信的使用

在C#中实现CAN通信通常需要借助第三方库或硬件设备的驱动程序&#xff0c;因为C#本身并没有直接内置支持CAN通信的功能。以下是一个关于如何使用C#实现CAN通信的基本指南&#xff0c;包括所需的步骤和常用工具。 1. 硬件准备 要进行CAN通信&#xff0c;首先需要一个支持CAN协…

02_C++入门案例习题while循环练习案例:猜数字

案例描述&#xff1a;系统随机生成一个1到100之间的数字&#xff0c;玩家进行猜测&#xff0c;如果猜错&#xff0c;提示玩家数字过大或过小&#xff0c;如果猜对恭喜玩家胜利&#xff0c;并且退出游戏。 需要引入随机数种子 #include <cstdlib> #include <ctime>…

深入理解哈希冲突:原理、解决方案及 Java 实践

概述&#xff1a;在计算机科学领域&#xff0c;哈希表是一种非常重要的数据结构&#xff0c;它通过哈希函数将键映射到存储桶中&#xff0c;从而实现快速的数据查找、插入和删除操作。然而&#xff0c;哈希表在实际应用中会面临 哈希冲突的问题。本文将深入探讨哈希冲突的原理、…

opencv(C++)处理图像颜色

文章目录 介绍使用策略设计模式比较颜色实现方案计算两个颜色向量之间的距离1. 简单方法&#xff1a;曼哈顿距离计算&#xff08;Manhattan Distance&#xff09;2.使用 OpenCV 的 cv::norm 函数3.使用 OpenCV 的 cv::absdiff 函数错误示例 使用 OpenCV 函数实现颜色检测实现方…