深入理解 Redis7 线程模型

Redis 到底是单线程还是多线程?

总体来说,可以简单解释为客户端使用多线程,服务端核心线程使用单线程。

Redis 为了能够与更多客户端进行连接,使用多线程来维护与客户端的 Socket 连接,redis.conf 配置文件中的 maxclients 参数维护了最大客户端连接数。

服务端,Redis 响应网络 IO 和键值对读写的请求,是由单线程完成的。Redis 基于 epoll 实现了 IO 多路复用,这种模式可以将多个并发的请求转化为串行的执行方式。

注意: 在 Redis7.x 版本中,后端对于比较耗时的操作,可以使用多线程机制来提升后台的工作。redis.conf 配置文件中的 io-threads 参数可以设置线程数。

Redis I/O 多路复用模型(基于 epoll)

┌──────────────────────────────────────────────┐
│               多个客户端连接                │
│                                              │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐      │
│  │ Client A │ │ Client B │ │ Client C │ ...  │
│  └────┬─────┘ └────┬─────┘ └────┬─────┘      │
│       │            │            │            │
└───────┼────────────┼────────────┼────────────┘
        │            │            │
        ▼            ▼            ▼
  ┌──────────────────────────────────────────┐
  │               epoll 实例                │
  │(注册所有 client socket 的读/写事件)  │
  └────────────────┬───────────────────────┘
                   │
         ┌─────────▼─────────┐
         │ epoll_wait 阻塞等待 │◄─────┐
         └─────────┬─────────┘      │
                   │                │
        有 N 个 socket 准备好(A、B、C)
                   ▼                │
         ┌──────────────────────┐  │
         │ 事件循环(单线程)   │──┘
         │                      │
         │ for i in events:     │
         │   读取 socket 数据   │───► readQueryFromClient()
         │   解析 & 执行命令    │───► 执行命令(如 GET、SET)
         │   写入响应缓冲区     │───► 注册写事件
         └─────────┬────────────┘
                   │
           socket 可写后 epoll 通知
                   ▼
            sendReplyToClient()
                   │
              响应发给客户端
  • 客户端 A/B/C:每个连接对应一个 socket

  • epoll 实例:Redis 注册每个连接的“感兴趣事件”(比如可读、可写)

  • epoll_wait:系统调用,阻塞等待直到有事件发生

  • 事件循环:Redis 的主线程循环处理所有已就绪连接

  • readQueryFromClient():读取命令并解析

  • sendReplyToClient():把执行结果发送给客户端

Redis 为什么一直使用单线程?

因为经过测试发现,性能瓶颈并不在 CPU 上,而是因为内存和网络,所以核心线程改为多线程的需求并不急切。并且多线程还会带来线程上下文切换带来的性能消耗,资源竞争,反而会影响业务的执行效率。

Redis 为什么不能保证指令原子性?

因为同一个客户端在执行两条指令时,两条指令中间可能会其他客户端的指令。如下图所示,Client1 最终获得的结果是 3,因为在执行 get 指令之前,Client2 执行了一次 incr 操作。
在这里插入图片描述

Redis 如何保证指令原子性?

1、复合指令

例如 GETSET, MSET, HMSET, SETNX。

2、Redis 事务 (了解)
127.0.0.1:6379> 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 ...]
  summary: Monitors changes to keys to determine the execution of a transaction.
  since: 2.2.0
127.0.0.1:6379> MULTI #开启事务
OK
127.0.0.1:6379(TX)> set ec a 
QUEUED
127.0.0.1:6379(TX)> get ec
QUEUED
127.0.0.1:6379(TX)> EXEC #执行事务
1) OK
2) "a"

注意:事务中的存在语句执行报错,也不会影响其他语句的执行

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> set kk2 10
QUEUED
127.0.0.1:6379(TX)> get kk2
QUEUED
127.0.0.1:6379(TX)> lpush kk2 8
QUEUED
127.0.0.1:6379(TX)> incr kk2
QUEUED
127.0.0.1:6379(TX)> get kks
QUEUED
127.0.0.1:6379(TX)> get kk2
QUEUED
127.0.0.1:6379(TX)> EXEC
1) OK
2) "10"
3) (error) WRONGTYPE Operation against a key holding the wrong kind of value
4) (integer) 11
5) (nil)
6) "11"
3、pipline (管道) (了解)

在Linux上编辑一个文件 command.txt。文件中可以包含一系列的指令

set count 1
incr count
incr count
incr count

然后在客户端执行redis-cli指令时,就可以直接执行这个文件中的指令

[root@192-168-65-214 ~]# cat command.txt | redis-cli -a 123qweasd --pipe
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
All data transferred. Waiting for the last reply...
Last reply received from server.
errors: 0, replies: 4
[root@192-168-65-214 ~]# redis-cli -a 123qweasd
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> get count
"4"

4、lua 脚本 (重点)

Redis中对lua支持从2.6.0版本就已经开始了。具体参考指令可以使用 help eval指令查看

127.0.0.1:6379> help eval
  EVAL script numkeys [key [key ...]] [arg [arg ...]]
  summary: Executes a server-side Lua script.
  since: 2.6.0
  group: scripting
  • script:lua 脚本,这个脚本可以写入多条 redis 语句,redis 执行这些语句时不会插入其他线程的语句,保证了原子性
  • numkeys:key 的数量,可以是0
  • [key [key …]]:键名值
  • [arg [arg …]]:参数值‘

Redisson 大量使用了 lua 保证了 redis 语句的原子性

5、Redis Function

Redis7 之后提供了 Redis Function,即可以把 lua 语句写入一个函数,要使用这个语句,直接调用函数就可以。

示例:

1、在服务器上新增一个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)

2、使用Redis客户端,将这个函数加载到Redis中。

[root@192-168-65-214 myredis]# cat mylib.lua | redis-cli -a 123456 -x FUNCTION LOAD REPLACE
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
"mylib"

3、其他客户端就可以使用这个函数了

127.0.0.1:6379> FUNCTION HELP --这个指令可以查询 FUNCTION 相关命令

127.0.0.1:6379> FCALL my_hset 1 myhash myfield "some value" another_field "another value"
(integer) 3
127.0.0.1:6379> HGETALL myhash
1) "_last_modified_"
2) "1717748001"
3) "myfield"
4) "some value"
5) "another_field"
6) "another value"
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值