深入理解Redis线程模型

Redis数据

redis数据保存在内存,但是会持久化到硬盘

Redis线程

Redis的整体线程模型可以简单解释为 客户端多线程,服务端单线程。也就是可以多个客户端同时连接。

  • 核心线程模型:单线程 + 多路复用
    Redis 的主线程负责处理所有客户端请求,采用 Reactor 模式,使用 epoll(Linux)或 select/kqueue(其他平台) 实现 I/O 多路复用。

Redis4.X以前的版本,都是采⽤的纯单线程。但是在2018年10⽉份,Redis5.x版本进⾏了⼀次⼤的核⼼代码重构。 到Redis6.x和7.x版本中,开始⽤⼀种全新的多线程机制来提升后台⼯作。尤其在现在的Redis7.x版本中,Redis后端的很多⽐较费时的操作,⽐如持久化RDB,AOF⽂件、unlink异步删除、集群数据同步等,都是由额外的线程执⾏的。例如,对于 FLUSHALL操作,就已经提供了异步的⽅式。

使用Redis如何保证指令原⼦性

1. 复合指令

复制代码
Redis内部提供了很多复合指令,例如MSET(HMSET)、GETSET、SETNX、SETEX等都是原子性的。

2. Redis事务

复制代码
像MySQL⼀样,Redis也提供了事务机制,但是略有不同。redis的事务就像批处理文件,执行一批指令,即使中间有错误也会继续往下执行,不会自动回滚。
```bash
help @transactions

DISCARD (null)  -- 放弃事务 
summary: Discards a transaction.
since: 2.0.0
EXEC (null)  --执⾏事务 
summary: Executes all commands in a transaction.
since: 1.2.0

MULTI (null)  -- 
开启事务 
summary: Starts a transaction.
since: 1.2.0

UNWATCH (null)  --去掉监听 
summary: Forgets about watched keys of a transaction.
since: 2.2.0

WATCH key [key ...]   --监听某⼀个key的变化。如果没被修改 → 执行事务。如果有任意 key 被改 → 事务取消,不执行命令。unwatch后才可恢复
summary: Monitors changes to keys to determine the execution of a transaction.
since: 2.2.0
 ```
- Redis事务失败如何回滚
	Redis中结合watch实现的事务回滚,不是回滚数据,⽽是回滚操作。
	1. 如果事务是在EXEC执⾏前失败(⽐如事务中的指令敲错了,或者指令的参数不对),那么整个事务的操作都不会执⾏。
	2. 如果事务是在EXEC执⾏之后失败(⽐如指令操作的key类型不对),那么事务中的

其他操作都会正常执⾏,不受影响。

  • 事务执⾏过程中出现失败了怎么办
  1. 只要客户端执⾏了EXEC指令,那么就算之后客户端的连接断开了,事务就会⼀直进⾏下去。

  2. 事务有可能造成数据不⼀致。当EXEC指令执⾏后,Redis会先将事务中的所有操作都先记录到AOF⽂件中,然后再执⾏具体的操作。如果已写入aof但是事务在执行过程中服务端出现了非正常宕机,会造成AOF中记录的操作,与数据不符合。下次服务启动时,⽆法正常启动。可以redis-check-aof⼯具修复AOF⽂件,将这些不完整的事务操作记录移除掉,即可正常启动。

3. Pipeline

在 Redis 中,Pipeline(管道)是一种批量发送命令、减少网络开销的技术机制。与简单无watch的事务一样只保证一起执行,不保证一起成功一起失败。

在Linux上编辑⼀个⽂件 command.txt

bash 复制代码
set count 1
incr count
incr count
incr count

然后启动redis客户端携带参数

bash 复制代码
cat command.txt | redis-cli -a 123qweasd --pipe

4. lua脚本

lua语⾔最⼤的特点是他的线程模型是单线程的模式。

Redis 内置支持 Lua,可以通过 EVAL 命令执行一段脚本。脚本在 Redis 中一次性执行完,中途不会被其他命令打断。

bash 复制代码
eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second

lua脚本中,可以使⽤redis.call函数来调⽤Redis的命令。

bash 复制代码
eval "local initcount = redis.call('get', KEYS[1]) local a = tonumber(initcount) local b = tonumber(ARGV[1]) if a >= b then redis.call('set', KEYS[1], a) return 1 end redis.call('set', KEYS[1], b) return 0" 1 "stock_1" 10

不要在Lua脚本中出现死循环和耗时的运算,否则redis会阻塞,将不接受其他的命令。管道pipeline不会阻塞redis。

  • Redis中有⼀个配置参数lua-time-limit 5000来控制Lua脚本的最⻓控制时间。默认5秒钟。当lua脚本执⾏时间超过了这个时⻓,Redis会对其他操作返回⼀个BUSY错误,⽽不会⼀直阻塞。
  • 尽量使⽤只读脚本Redis 从 7.0 起支持 EVAL_RO 命令(只读版 EVAL), 强制执行只读 Lua 脚本,更安全,避免误写

5. Redis Function

在 Redis 中,**Redis Function(函数)**是 Redis 7.0 引入的新功能,作为对传统 EVAL / EVALSHA 的增强 ------ 支持将 Lua 脚本函数化、模块化、持久化,从而带来更安全、结构化、更易部署和复用的脚本执行方式。

例如,可以在服务器上新增⼀个mylib.lua⽂件。在⽂件中定义函数。

lua 复制代码
#!lua name=mylib
local function my_hset(keys, args)
	local hash = keys[1]
	local time = redis.call('TIME')[1]
	return redis.call('HSET', hash, '_last_modified_', time, unpack(args))
end
redis.register_function('my_hset', my_hset)

注意,脚本第⼀⾏是指定函数的命名空间,不是注释,不能少

使⽤Redis客户端,将这个函数加载到Redis中

bash 复制代码
cat mylib.lua | redis-cli -a 123qweasd -x FUNCTION LOAD REPLACE

或

bash 复制代码
FUNCTION LOAD LUA "$(cat mylib.lua)"

调用函数

bash 复制代码
FUNCTION CALL my_hset 1 myhash myfile "some value"

Function注意点

  • Function同样也可以进⾏只读调⽤。
  • 如果在集群中使⽤Function,⽬前版本需要在各个节点都⼿动加载⼀次。Redis不会在集群中进⾏Function同步
  • Function是要在服务端缓存的,所以不建议使⽤太多太⼤的Function。
  • Function和Script⼀样,也有⼀系列的管理指令。使⽤指令 help @scripting ⾃⾏了解。

6. Redis指令原⼦性总结

以上介绍的各种机制,其实都是Redis改变指令执⾏顺序的⽅式。在这⼏种⼯具中,Lua脚本通常会是项⽬中⽤得最多的⽅式。在很多追求极致性能的⾼并发场景,Lua脚本都会担任很重要的⻆⾊。

Redis中的Bigkey问题

在Redis中,BigKey问题指的是某些键(Key)对应的值(Value)非常大,例如包含了大量元素的 list、set、hash、zset,或者是超长的字符串。这种"超大键"可能会引发性能、内存和可用性的问题。

在Redis客户端指令中,提供了两个扩展参数,可以帮助快速发现这些BigKey

bash 复制代码
redis-cli --help

--bigkeys
Sample Redis keys looking for keys with many 
elements (complexity).

--memkeys
Sample Redis keys looking for keys consuming a lot of memory.

Redis线程模型总结

Redis的线程模型整体还是多线程的,只是后台执⾏指令的核⼼线程是单线程的。整

个线程模型可以理解为还是以单线程为主。基于这种单线程为主的线程模型,不同

客户端的各种指令都需要依次排队执⾏。

Redis为什么使用单线程

Redis 在性能、实现复杂度和应用场景之间做了权衡。redis的性能瓶颈不在cpu而在内存。使用单线程,是因为绝大多数操作本身就非常快,再配合 I/O 多路复用机制,可以在单线程中轻松支撑大量高并发请求,同时避免多线程带来的锁竞争和复杂性。

相关推荐
2301_32241428041 小时前
基于SpringBoot+Vue的休闲零食超市管理系统的设计与实现59004-计算机毕设 原创(免费领源码+部署教程+开发环境)
java·vue.js·spring boot·redis·mysql·决策树·推荐算法
catoop4 小时前
API Key 设计与安全生成方案:结构拆解、数据库设计与 Java 实现
java·数据库·安全
Web极客码4 小时前
我用 Claude 分析 Redis 哨兵日志,实现跨机房数据不一致自动对齐
数据库·redis·php
库库的数据库笔记4 小时前
国产信创数据库的MySQL兼容性深度对比解析
大数据·数据库·mysql·oceanbase·政务
microrain4 小时前
SagooIoT 的缓存换后端只改一行配置,文件后端连 Redis 都不需要
物联网·缓存·golang·sagooiot
姚远Oracle ACE5 小时前
为什么无法连接到 PostgreSQL?
数据库·postgresql
ss2735 小时前
AI时代首先数据库-PostgreSQL实战笔记 | 22 PostgreSQL实例与集群
数据库·笔记·postgresql
ss2735 小时前
AI时代首先数据库-PostgreSQL实战笔记 | 21 PostgreSQL进程与内存架构介绍
数据库·笔记·postgresql
Henry-SAP6 小时前
SAP PP模块全景概览
数据库