分布式专题——2 深入理解Redis线程模型

发布于:2025-09-08 ⋅ 阅读:(14) ⋅ 点赞:(0)

1 Redis 简介

1.1 Redis 是什么?

  • Redis 全称 Remote Dictionary Server(远程字典服务),是一个开源的高性能 Key-Value 数据库;

  • 官网:Redis - The Real-time Data Platform

  • 引用官网上的⼀个问答,带你重新了解一下 Redis:

    在这里插入图片描述

    Redis 与其他键值存储有何不同?

    • 在键值数据库领域,Redis 有着不同的发展路径,其值可以包含更复杂的数据类型,并且针对这些数据类型定义了原子操作。Redis 的数据类型与基础数据结构紧密相关,会直接向程序员暴露这些数据类型,没有额外的抽象层。
    • Redis 是一种基于内存但可持久化到磁盘的数据库,因此它体现了一种不同的权衡:实现了极高的读写速度,但存在数据集不能超过内存大小的限制。内存数据库的另一个优势是,复杂数据结构在内存中的表示,与磁盘上的相同数据结构相比,操作起来要简单得多,所以 Redis 可以用很少的内部复杂度实现很多功能。同时,两种磁盘存储格式(RDB 和 AOF)不需要支持随机访问,因此它们很紧凑,并且始终以只追加的方式生成(即使是 AOF 日志轮转也是只追加操作,因为新版本是从内存中的数据副本生成的)。然而,与传统的磁盘存储系统相比,这种设计也带来了不同的挑战。由于内存是主要的数据表示形式,Redis 的操作必须谨慎处理,以确保磁盘上始终有数据集的更新版本。
  • 官方定位:Redis 作用被定位为三个方面,即 Cache(缓存)、Database(数据库)、Vector Search(向量搜索)。

1.2 2024年的Redis是什么样的?

  • 2023 年之前 Redis 是纯粹的开源数据库,近两年来,它从单纯的缓存产品,逐渐发展成一整套生态服务;
  • Redis 产品
    • Redis Cloud:基于 AWS、Azure 等公有云的云服务,提供完整企业服务,还包含企业级收费产品 Redis Enterprise;
    • Redis Enterprise Software:企业级软件产品;
    • Redis Enterprise for Kubernetes:面向 Kubernetes 环境的企业级产品;
    • Redis Insight:Redis 官方推出的图形化客户端,用于 Redis 服务的安装与管理,也可在 Redis Cloud 上直接使用,替代了以往的第三方客户端;
    • Redis OSS and Stack:在功能层面,形成了 Redis OSS(更完善的开源服务体系)和 Redis Stack(基于 Redis OSS 打造,用于 Redis Cloud 提供服务,在 Redis OSS 功能基础上提供诸多高级扩展功能)两套服务体系。

2 Redis 是单线程还是多线程?

  • 整体来看,Redis 的整体线程模型可以简单解释为客户端多线程,服务端单线程

  • 客户端多线程:Redis 为了能够与更多的客户端进行连接,使用多线程来维护与客户端的 Socket 连接。在redis.conf中就有⼀个参数maxclients维护了最大客户端连接数;

    # Redis is mostly single threaded, however there are certain threaded
    # operations such as UNLINK, slow I/O accesses and other things that are
    # performed on side threads.
    #
    # Now it is also possible to handle Redis clients socket reads and writes
    # in different I/O threads. Since especially writing is so slow, normally
    # Redis users use pipelining in order to speed up the Redis performances per
    # core, and spawn multiple instances in order to scale more. Using I/O
    # threads it is possible to easily speedup two times Redis without resorting
    # to pipelining nor sharding of the instance.
    #
    # By default threading is disabled, we suggest enabling it only in machines
    # that have at least 4 or more cores, leaving at least one spare core.
    # Using more than 8 threads is unlikely to help much. We also recommend using
    # threaded I/O only if you actually have performance problems, with Redis
    # instances being able to use a quite big percentage of CPU time, otherwise
    # there is no point in using this feature.
    #
    # So for instance if you have a four cores boxes, try to use 2 or 3 I/O
    # threads, if you have a 8 cores, try to use 6 threads. In order to
    # enable I/O threads use the following configuration directive:
    #
    # io-threads 4
    
    # Set the max number of connected clients at the same time. By default
    # this limit is set to 10000 clients, however if the Redis server is not
    # able to configure the process file limit to allow for the specified limit
    # the max number of allowed clients is set to the current file limit
    # minus 32 (as Redis reserves a few file descriptors for internal uses).
    #
    # Once the limit is reached Redis will close all the new connections sending
    # an error 'max number of clients reached'.
    #
    # IMPORTANT: When Redis Cluster is used, the max number of connections is also
    # shared with the cluster bus: every node in the cluster will use two
    # connections, one incoming and another outgoing. It is important to size the
    # limit accordingly in case of very large clusters.
    #
    # maxclients 10000
    

    Redis 主要采用单线程架构,但存在某些多线程操作,例如 UNLINK 命令、缓慢的 I/O 访问以及其他在辅助线程中执行的任务。

    现在还可以通过不同的 I/O 线程处理 Redis 客户端套接字的读取与写入。由于写入操作尤其缓慢,通常 Redis 用户会采用管道技术(pipelining)来提升单核的 Redis 性能,并通过启动多个实例来实现扩展。使用 I/O 线程技术,无需借助管道或实例分片即可轻松实现两倍的性能提升。

    默认情况下多线程处于禁用状态,我们建议仅在至少拥有 4 个或更多核心的机器上启用,并确保至少保留一个空闲核心。使用超过 8 个线程通常带来的提升有限。我们同时建议仅在确实遇到性能问题时启用多线程 I/O——即当 Redis 实例已占用较高 CPU 时间占比时,否则启用此功能并无实际意义。

    举例而言:若您的设备为四核架构,可尝试使用 2 至 3 个 I/O 线程;若为八核架构,可尝试使用 6 个线程。如需启用 I/O 线程,请使用以下配置指令:

    io-threads 4

    设置同一时间最大客户端连接数。默认情况下该限制设为 10000 个客户端,但如果 Redis 服务器无法将进程文件限制配置为满足指定数值时,实际允许的最大客户端数将调整为当前文件限制数减 32(因 Redis 需保留部分文件描述符供内部使用)。

    当达到连接数上限后,Redis 将拒绝新连接并返回“达到最大客户端数”错误信息。

    重要提示:当使用 Redis 集群时,最大连接数同样适用于集群总线——集群中的每个节点会使用两个连接:一个入站连接和一个出站连接。在超大规模集群中,请务必据此相应调整连接数限制。

    maxclients 10000

  • 服务端单线程

    • 在服务端,Redis 响应网络 IO 和键值对读写的请求,则是由单个主线程完成的。Redis 基于epoll实现了 I/O 多路复用,这就可以让⼀个主线程同时响应多个客户端 Socket 连接的请求;

    • 在这种线程模型下,Redis 将客户端多个并发的请求转成了串行的执行方式。因此,在 Redis 中,完全不用考虑类似 MySQL 的脏读、幻读、不可重复读等并发问题。同时,串行化线程模型结合 Redis 基于内存工作的极高性能,使其成为解决诸多并发问题的有力工具;

      在这里插入图片描述

  • 版本演进带来的线程模型变化

    • Redis 4.X 以前:采用纯单线程模型;

    • Redis 5.x 及之后:进行了大的核心代码重构,使用一种全新的多线程机制以提升后台工作效率。像持久化(如 RDB、AOF 重写)、集群数据同步等较费时的操作,以及 FLUSHALL(可通过 FLUSHALL [ASYNC | SYNC] 选择异步或同步执行)这类操作,都由额外线程执行,避免了对主线程的影响;

  • Redis 为什么要使用全新的多线程机制呢?

    • 现代 CPU 多为多核架构,若 Redis 一直用单线程,就无法发挥多核 CPU 的性能优势,且耗时操作会影响主线程;
    • 不过,Redis 为保持快速,多线程推进很谨慎,核心线程仍保持单线程模型,因为对于现代 Redis,CPU 通常不是性能瓶颈,性能瓶颈多为内存和网络;
    • 另外,Redis 的这种核心线程以单线程为主的机制,还可减少线程上下文切换的性能消耗,若核心线程改为多线程并发执行,会带来资源竞争,大幅增加业务复杂性,影响执行效率;
  • 总结:

    • 对于现在的 Redis,并不是简单的单线程或多线程,而是一种混合线程模型核心逻辑始终保持单线程,多线程仅用于辅助功能
    • 核心流程始终保持单线程:Redis的键值对读写、命令执行(如GET/SET)等核心操作,无论哪个版本,始终由单个主线程串行执行。这也是 Redis 避免并发安全问题、保持高性能的关键——不需要加锁,也没有线程切换开销;
    • **多线程仅用于辅助功能:**版本演进中引入的多线程,从未触及核心命令执行逻辑,而是负责:
      • 网络I/O的读写(Redis 6.0+可配置I/O多线程);
      • 耗时的后台操作(如RDB生成、AOF重写、UNLINK异步删除);
      • 集群数据同步等非核心流程。

3 Redis 如何保证指令原子性

  • Redis 对于核心的读写键值操作是单线程处理的。当多个客户端同时发起读写请求时,Redis 会让这些请求排队串行执行。但要注意,这种串行执行是针对多个客户端之间的请求而言,Redis 并没有像 MySQL 那样,专门去保证单个客户端自身操作的原子性;

  • 来看一个例子:

    在这里插入图片描述

    • 初始有 set k1 1 操作将 k1 的值设为 1;
    • 然后 Client1Client2 都执行 incr k1incr 是将键值加 1 的操作),之后又都执行 get k1 获取 k1 的值;
    • 由于 Redis 单线程处理多个客户端请求,Client1Client2incr k1 操作会串行执行,但因为没有针对单个客户端操作的原子性保障,最终 get k1 得到的值可能不符合预期(比如可能不是预期的 3,具体结果取决于两个 incr 操作的执行顺序等因素);
  • 如何控制 Redis 指令的原子性是一个需要关注的问题。在不同的业务场景下,Redis 提供了不同的解决思路,我们需要根据项目实际情况灵活选择合适的方式来保证指令执行的原子性,以满足业务需求。

3.1 复合指令

  • Redis 内部提供了诸多复合指令,这类指令看似是一个指令,但实际上能完成多个指令的工作;
  • 例如:
    • MSET(用于同时设置多个键值对)
    • HMSET(用于同时设置哈希表中的多个字段值)
    • GETSET(先获取键的旧值,再设置新值)
    • SETNX(只有键不存在时才设置值)
    • SETEX(设置键值的同时指定过期时间)等
  • 这些复合指令能够很好地保持原子性,也就是说,这些复合指令的执行过程是不可分割的,在执行过程中不会被其他客户端的指令插入或打断,从而保证了操作的完整性和一致性。

3.2 Redis 事务

3.2.1 简介

  • 官网:Transactions | Docs

  • 基本命令MULTI(开启事务)、EXEC(执行事务)、DISCARD(放弃事务)、WATCH(监听某个或多个 key,若 key 被修改,事务执行会失败)、UNWATCH(去掉监听,仅在当前客户端有效);

  • 例:在 Redis 命令行中执行

    MULTI
    set k2 2
    incr k2
    get k2
    EXEC
    DISCARD
    
  • 但是,Redis 的事务和 MySQL 的事务,是不是同⼀回事呢?看下面这个例子:

    MULTI
    set k2 2
    incr k2
    get k2
    lpop k2 # lpop指令是针对List的操作,此处针对String类型的k2操作,肯定会报错
    incr k2
    get k2
    EXEC
    
    • 结果虽然会报错:WRONGTYPE Operation against a key holding the wrong kind of value
    • 但是这行错误的指令并没有让整个事务回滚,甚至后面的指令都没有受到影响;
    • 所以:Redis 事务并不像数据库事务那样保证事务中的指令一起成功或一起失败。Redis 事务的作用,仅仅只是保证事务中的原子操作是⼀起执行,而不会在执行过程中被其他指令加塞;

    事实上,在执行MULTI开启事务后,后续输入的指令,都会返回 QUEUED,表示 Redis 将这些操作排好了队,等到EXEC后一起执行。

3.2.2 Watch 机制

  • Redis 通过这个机制保证在事务执行前,被监听的 key 不被修改。若执行事务前 key 被修改,事务会执行失败;

  • 看下面的例子:

    • 左侧客户端先获取 k2 的值,然后用 WATCH k2 监听 k2,接着开启事务(MULTI),在事务里对 k2 进行自增(incr k2)和获取操作,最后执行事务(EXEC);
    • 右侧客户端在左侧客户端执行事务前,修改了 k2 的值(set k2 3);
    • 当左侧客户端执行 EXEC 时,因为 k2 被右侧客户端修改了,所以事务执行失败(返回 (nil));

    在这里插入图片描述

  • 可以用UNWATCH命令取消对 key 的监听,但它只在当前客户端有效

    • 左侧客户端同样先获取 k2WATCH k2、开启事务并进行操作;
    • 右侧客户端执行 UNWATCH,然后修改 k2 的值(set k2 3);
    • 左侧客户端执行 EXEC 时,事务还是失败了。这是因为 UNWATCH 是在右侧客户端执行的,而 UNWATCH 只在当前客户端有效,所以右侧的 UNWATCH 无法取消左侧客户端对 k2 的监听,左侧客户端对 k2 的监听仍然存在,k2 被修改后事务就失败了;
    • 只有在左侧客户端步骤 3(MULTI 之前)执行 UNWATCH,才能取消左侧客户端对 k2 的监听,让后续事务可能执行成功;在右侧客户端执行 UNWATCH 是无效的;

    在这里插入图片描述

3.2.3 Redis 事务失败如何回滚

  • Redis 中的事务回滚,不是回滚数据,而是回滚操作;
  • 若事务在 EXEC 执行前失败(如指令错误、参数不对),整个事务的操作都不会执行;
  • 若事务在 EXEC 执行后失败(如操作的 key 类型不对),事务中的其他操作会正常执行,不受影响。

3.3.3 事务执行过程中出现失败了怎么办?

  • 只要客户端执行了 EXEC 指令,即便之后客户端连接断开,事务也会一直进行下去;
  • EXEC 执行后,Redis 先将事务操作记录到 AOF 文件,再执行操作。若 Reids 服务出现异常(如崩溃、被 kill),可能导致 AOF 记录与数据不符。此时需用 redis - check - aof 工具修复 AOF 文件,移除不完整事务操作记录,使服务能正常启动。

3.3 Pipeline

  • 官网:Redis pipelining | Docs

  • Pipeline 是 Redis 提供的一种机制,能让客户端把多个命令打包,一次性发送给服务器,服务器执行后再将结果批量返回给客户端。这种方式适合大批量数据快速写入 Redis 的场景;

  • 通过 redis - cli -- pipe 相关指令可使用该功能,-- pipe - timeout 还能设置管道模式下的超时时间;

  • 使用案例

    • 先在 Linux 上编辑一个 txt 文件,里面包含一系列 Redis 指令;

    • 然后通过 cat command.txt | redis - cli - a 密码 -- pipe 这样的命令,让 Redis 执行文件里的所有指令;

  • 核心作用:优化 RTT

    • RTT(Round - Trip Time,往返时间)指客户端发送指令到服务器,再到服务器返回结果给客户端的时间消耗;

    • 没有 Pipeline 时,每个命令都要经历一次 RTT,频繁发指令的话,RTT 消耗会很可观;

      在这里插入图片描述

    • 有 Pipeline 时,多个命令打包发送,只需少量的 RTT(甚至一次),大大减少了因多次网络往返带来的时间开销,提升了执行效率;

      在这里插入图片描述

  • 注意点

    • 原子性:Redis 的事务(Transaction)是原子性的,但 Pipeline 不具备原子性。Pipeline 只是把多条命令批量发送导服务端,这些命令最终可能会被其他客户端的指令“加塞”(不过这种概率通常较小),所以不建议在 Pipeline 中进行复杂的数据操作,因为数据一致性难以保证;

    • 客户端阻塞与资源占用

      • Pipeline 执行时,会阻塞当前客户端,直到服务器返回所有结果;
      • 如果 Pipeline 中封装过多指令,一方面客户端阻塞时间会太长;另一方面,服务器要回复这个“繁忙”的客户端,会占用很多内存;
    • 适用场景:Pipeline 适合在非热点时段进行数据调整任务,这样既利用了它提升效率的优势,又能避免在热点时段因客户端阻塞、服务器内存占用过多等问题,对正常业务造成影响。

3.4 Lua 脚本

  • Redis 的事务和 Pipeline 机制在解决指令原子性等问题上有局限,且只能对现有指令拼凑,无法添加更多自定义复杂逻辑。而 Lua 脚本能弥补这些不足,所以 Redis 支持 Lua,且从 2.6.0 版本就开始支持,Redis 7.x 版本支持的 Lua 语言是 5.1 版本。

3.4.1 简介

  • Lua 是一种小巧的脚本语言,具备参数类型、作用域、函数等高级语言特性,语法简单,熟悉 Java 等语言的开发者能轻松上手;

    如果对 Lua 原生的语法感兴趣,推荐⼀个参考⽹站:LuatOS 文档。这个网站可以直接在线调试 Lua;

  • Lua 语言最大的特点是他的线程模型是单线程的模式。这使得 Lua 天生就非常适合⼀些单线程模型的中间件。比如 Redis、Nginx 等都非常适合接入 Lua 语言进行功能定制。所以,在 Redis 中执行一段 Lua 脚本,这个过程天然就是原子性的。

3.4.2 Redis 中执行 Lua 的方式

  • Redis 对 Lua 语言的 API 介绍参考官网:Redis Lua API reference | Docs

  • 通过 EVAL 指令执行 Lua 脚本,语法为 EVAL script numkeys [key key ...] [arg arg ...]

    • script:要执行的 Lua 脚本程序,会被运行在 Redis 服务器上下文中;

    • numkeys:指定键名参数的个数;

    • key [key ...]:脚本中用到的 Redis 键,可在 Lua 中通过全局变量 KEYS 数组(以 1 为基址)访问,如 KEYS[1]KEYS[2] 等;

    • arg [arg ...]:非键名的附加参数,可在 Lua 中通过全局变量 ARGV 数组访问,如 ARGV[1]ARGV[2] 等;

  • 例:返回一个包含 4 个元素的数组

    EVAL "return {KEYS[1], KEYS[2], ARGV[1], ARGV[2]}" 2 key1 key2 first second
    
    • KEYS[1]:第一个键名参数
    • KEYS[2]:第二个键名参数
    • ARGV[1]:第一个附加参数
    • ARGV[2]:第二个附加参数
    • 2numkeys 参数,表示后续有 2 个键名参数key1key2);
    • key1 key2:键名参数(通过 Lua 中的 KEYS 数组访问);
    • first second:非键名的附加参数(通过 Lua 中的 ARGV 数组访问);
  • 在 Lua 脚本中可使用 redis.call 函数调用 Redis 命令,例:

    redis.call('get', KEYS[1]) # 获取键值
    redis.call('set', KEYS[1], 10) # 设置键值
    
  • 使用 Lua 的注意点:

    • 避免死循环和耗时运算:若 Lua 脚本中出现死循环或耗时运算,Redis 会阻塞,不再接受其他命令。不过 Redis 有配置参数控制 Lua 脚本的最长执行时间(默认 5 秒),超过时长 Redis 会返回 BUSY 错误,避免一直阻塞;

      ################ NON-DETERMINISTIC LONG BLOCKING COMMANDS #####################
      
      # Maximum time in milliseconds for EVAL scripts, functions and in some cases
      # modules' commands before Redis can start processing or rejecting other clients.
      #
      # If the maximum execution time is reached Redis will start to reply to most
      # commands with a BUSY error.
      #
      # In this state Redis will only allow a handful of commands to be executed.
      # For instance, SCRIPT KILL, FUNCTION KILL, SHUTDOWN NOSAVE and possibly some
      # module specific 'allow-busy' commands.
      #
      # SCRIPT KILL and FUNCTION KILL will only be able to stop a script that did not
      # yet call any write commands, so SHUTDOWN NOSAVE may be the only way to stop
      # the server in the case a write command was already issued by the script when
      # the user doesn't want to wait for the natural termination of the script.
      #
      # The default is 5 seconds. It is possible to set it to 0 or a negative value
      # to disable this mechanism (uninterrupted execution). Note that in the past
      # this config had a different name, which is now an alias, so both of these do
      # the same:
      # lua-time-limit 5000
      # busy-reply-threshold 5000
      

      ################ 非确定性长阻塞命令 #####################

      对于 EVAL 脚本、函数以及某些情况下模块命令的最长执行时间(毫秒),超过该时限后 Redis 将开始处理或拒绝其他客户端请求。

      当达到最大执行时间时,Redis 将开始对大多数命令返回 BUSY 错误响应。

      在此状态下,Redis 仅允许执行少量特定命令。

      例如:SCRIPT KILL(终止脚本)、FUNCTION KILL(终止函数)、SHUTDOWN NOSAVE(强制关闭)以及某些模块特定的’allow-busy’(允许繁忙状态执行)命令。

      SCRIPT KILL 和 FUNCTION KILL 仅能终止尚未执行任何写入命令的脚本,因此当脚本已执行写入命令且用户不愿等待脚本自然终止时,SHUTDOWN NOSAVE 可能是停止服务器的唯一方式。

      默认限制为 5 秒。可设置为 0 或负值以禁用此机制(实现无中断执行)。请注意,该配置旧名称现已作为别名保留,因此以下两种配置方式等效:

      lua-time-limit 5000

      busy-reply-threshold 5000

    • 尽量使用只读脚本:只读脚本是 Redis 7 新增的,通过 EVAL_RO 触发,脚本上要加只读标志,且不允许执行修改数据库的操作,还可随时用 SCRIPT_KILL 命令停止。使用只读脚本,一方面能限制部分用户操作;另一方面,只读脚本通常可转移到备份节点执行,减轻 Redis 主节点压力;

    • 热点脚本缓存到服务端:可将热点 Lua 脚本缓存到 Redis 服务端,提升执行效率;

      # 将 Lua 脚本 "return 'Immabe a cached script'" 加载到 Redis 服务器中
      redis> SCRIPT LOAD "return 'Immabe a cached script'"
      # 返回一个 SHA1 哈希值 c664a3bf70bd1d45c4284ffebb65a6f2299bfc9f
      "c664a3bf70bd1d45c4284ffebb65a6f2299bfc9f"
      # 使用之前生成的 SHA1 哈希值来执行缓存的脚本,0 表示脚本不需要任何键(KEY)参数
      redis> EVALSHA c664a3bf70bd1d45c4284ffebb65a6f2299bfc9f 0
      # 返回脚本执行结果
      "Immabe a cached script"
      

3.5 Redis Function

  • 简介:

    • 如果你觉得开发 Lua 脚本有点困难,那么在 Redis 7 之后,提供了另外⼀种让程序员解脱的方法:Redis Function;
    • Redis Function 是 Redis 7 及以后版本提供的功能,能将一些功能声明为统一的函数,提前加载到 Redis 服务端(可由熟悉 Redis 的管理员加载)。客户端可直接调用这些函数,无需再开发函数的具体实现;
    • 它的一大优势是在 Function 中可以嵌套调用其他 Function,更利于代码复用,而 Lua 脚本难以实现这样的复用;
  • 例:

    • 首先在服务器上新增一个 mylib.lua 文件,在文件中定义函数。该文件的第一行必须写: #!lua name=mylib,用于指定函数的命名空间,这不是注释,且这一步不能少。文件内容如下:

      • 定义了 my_hset 函数,该函数接收 keysargs 参数;
      • 获取第一个键名 hash,调用 redis.call('TIME') 获取时间,然后调用 HSET 命令,设置哈希表的 _last_modified_ 字段为当前时间,再设置其他由 args 传递的字段;
      • 最后用 redis.register_function('my_hset', my_hset) 注册函数;
      #!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 客户端,通过 cat mylib.lua | redis-cli -a 123qweasd -- FUNCTION LOAD REPLACE 命令,将这个函数加载到 Redis 中;

    • 加载后,其他客户端就可以直接调用这个函数,调用方式和 Lua 脚本类似,比如 FCALL my_hset 1 myhash myfield "some value",其中 1 是键名参数的个数,myhash 是键名,myfield"some value" 是参数;

  • Function 注意点

    • 只读调用:Function 同样可以进行只读调用;

    • 集群使用:如果在集群中使用 Function,目前版本需要在各个节点都手动加载一次,Redis 不会在集群中进行 Function 同步;

    • 服务端缓存:Function 是要在服务端缓存的,所以不建议使用太多太大的 Function,否则会占用过多服务端资源;

    • 管理指令:Function 和 Script 一样,也有一系列的管理指令,可使用 help @scripting 指令自行了解。

3.6 总结

  • 以上介绍的各种机制,其实都是 Redis 改变指令执行顺序的方式。在这几种⼯具中,Lua 脚本通常会是项目中用得最多的方式。在很多追求极致性能的高并发场景,Lua 脚本都会担任很重要的角色。但是其他的各种方式也需要有了解,这样在面临真实的业务场景时,才有更多的方案可以选择。

4 Redis 中的 Bigkey 问题

  • Bigkey 指占用空间非常大的键。比如一个 List 类型的键包含 200 万个元素,或者一个 String 类型的键存储了一篇文章这样大量的数据;

  • 基于 Redis 以单线程为主的核心工作机制,Bigkey 非常容易造成 Redis 服务阻塞。因为 Redis 单线程处理命令时,操作 Bigkey(如读取、删除等)会耗费大量时间,在此期间,其他命令会被阻塞,无法得到及时处理,进而影响整个 Redis 服务的性能;

  • 在 Redis 客户端指令中,提供了两个扩展参数来帮助快速发现 Bigkey:

    • --bigkeys:用于抽样 Redis 键,寻找包含大量元素(具有高复杂性)的键;

    • --memkeys:用于抽样 Redis 键,寻找占用大量内存的键;

  • 对于 BigKey 的处理,后续会介绍。

5 总结

  • Redis 整体是多线程的,但后台执行指令的核心线程是单线程,整个线程模型以单线程为主。这就导致不同客户端的各种指令需要依次排队执行,并非并行处理;

  • Redis 这种以单线程为主的线程模型,相比其他中间件,结构非常简单。这使得 Redis 处理线程并发问题时,更加简单高效。在很多复杂业务场景下,Redis 能成为进行线程并发控制的良好工具。

  • 单线程模型的局限性与应对

    • 局限性:单线程模型本身不利于发挥多线程的并发优势,而 Redis 的应用场景又通常和高性能深度绑定;

    • 应对方式:在使用 Redis 时,要时刻思考指令的执行方式,选择合理的指令执行方式,这样才能最大限度发挥 Redis 高性能的优势,避免因线程模型的局限而影响性能。同时,这也说明 Redis 并非没有线程并发问题,合理的指令执行方式至关重要。


网站公告

今日签到

点亮在社区的每一天
去签到