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
set count 1
incr count
incr count
incr count
然后启动redis客户端携带参数
cat command.txt | redis-cli -a 123qweasd --pipe
4. lua脚本
lua语⾔最⼤的特点是他的线程模型是单线程的模式。
Redis 内置支持 Lua,可以通过 EVAL 命令执行一段脚本。脚本在 Redis 中一次性执行完,中途不会被其他命令打断。
eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
lua脚本中,可以使⽤redis.call函数来调⽤Redis的命令。
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 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中
cat mylib.lua | redis-cli -a 123qweasd -x FUNCTION LOAD REPLACE
或
FUNCTION LOAD LUA "$(cat mylib.lua)"
调用函数
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
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 多路复用机制,可以在单线程中轻松支撑大量高并发请求,同时避免多线程带来的锁竞争和复杂性。