Redis的线程模型

简介: Redis采用单线程模型确保操作的原子性,每次只执行一个操作,避免并发冲突。它通过MULTI/EXEC事务机制、Lua脚本和复合指令(如MSET、GETSET等)保证多个操作要么全成功,要么全失败,确保数据一致性。Redis事务在EXEC前失败则不执行任何操作,EXEC后失败不影响其他操作。Pipeline虽高效但不具备原子性,适合非热点时段的数据调整。Redis 7引入Function功能,支持函数复用,简化复杂业务逻辑。总结来说,Redis的单线程模型简单高效,适用于高并发场景,但仍需合理选择指令执行方式以发挥其性能优势。

Redis的线程模型

Redis的原子性是如何保证的?

Redis 是一个非常快的内存数据库,它的操作默认是 原子性的,意思是每个操作要么完全成功,要么完全不做,中间不会被打断或停止。也就是说,每次操作要么完全按计划执行完,要么什么都不做,这样可以保证数据的一致性和完整性。

Redis 的原子性主要靠这几个机制:

  • 单线程模型 🧵:Redis 每次只做一个操作,确保操作按顺序执行,不会被其他操作打断。

  • 事务机制(MULTI 和 EXEC) 🔒:Redis 可以把多个操作放一起做,要么全都成功,要么全都不做,确保操作过程中不会被中断。

  • Lua 脚本 📜:Redis 允许你写脚本,把复杂的操作作为一个整体来执行,确保不会被其他操作影响。这些机制帮助 Redis 在高并发的情况下保持数据的正确性和一致性。

复合指令

Redis内部提供了很多复合指令,他们是一个指令,可是明显干着多个指令的活。
比如 MSET(同时设置多个键值对)
(HMSET(设置哈希类型的多个字段值(已废弃,推荐使用HSET)))、
GETSET(设置新值并返回旧值)、
SETNX(键不存在时设置值,常用于分布式锁)、
SETEX(设置值并指定过期时间(秒),适用于缓存或会话管理)。
这些复合指令都能很好的保持原子性。

Redis事务

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

public class TransactionExample {
   
    public static void main(String[] args) {
   
        Jedis jedis = new Jedis("localhost", 6379);

        // 开启事务
        Transaction transaction = jedis.multi();

        // 添加多个操作
        transaction.set("key1", "value1");
        transaction.incr("counter");
        transaction.set("key2", "value2");

        // 提交事务
        transaction.exec();

        System.out.println("Key1: " + jedis.get("key1"));
        System.out.println("Counter: " + jedis.get("counter"));
    }
}
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 ...]   --监听某一个key的变化。key有变化后,就执行当前事务
  summary: Monitors changes to keys to determine the execution of a transaction.
  since: 2.2.0
  • 解析
    • MULTI开启事务,EXEC提交事务
    • 在事务执行期间,其他操作无法插入,确保事务的原子性

总结

  1. Redis事务可以通过Watch机制进一步保证在某个事务执行前,某个key不被修改。

    注意:UNWATCH取消监听,只在当前客户端有效。

  2. Redis事务失败如何回滚

    Redis中的事务回滚,不是回滚数据,而是回滚操作

    1.如果事务实在EXEC执行前失败(比如事务中的指令敲错了,或者指令的参数不对),那么整个事务的操作都不会执行。

    2.如果事务是在EXEC执行之后失败(比如指令操作的key类型不对),那么事务中的其他操作都会正常执行,不受影响。

  3. 事务执行过程中出现失败了怎么办

    1.只要客户端执行了EXEC指令,那么就算之后客户端的连接断开了,事务就会一直进行下去。

    2.事务有可能造成数据不一致。当EXEC指令执行后,Redis会先将事务中的所有操作都先记录到AOF文件中,然后再执行具体的操作。这时有一种可能,Redis保存了AOF记录后,事务的操作在执行过程中,服务就出现了非正常宕机(服务崩溃了,或者执行进程被kill -9了)。这就会造成AOF中记录的操作,与数据不符合。如果Redis发现这种情况,那么在下次服务启动时,就会出现错误,无法正常启动。这时,就要使用redis-check-aof工具修复AOF文件,将这些不完整的事务操作记录移除掉。这样下次服务就可以正常启动了。

  4. 事务机制优缺点,什么时候用事务

Pipeline

  1. 什么是管道

当客户端执行一个指令,数据包需要通过网络从Client传到Server,然后再从Server返回到Client。这个中间的时间消耗,就称为RTT(Rount Trip Time)。

redis的原生复合指令和事务,都是原子性的。但是pipeline不具备原子性。pipeline只是将多条命令发送到服务端,最终还是可能会被其他客户端的指令加塞的,虽然这种概率通常比较小。所以在pipeline中通常不建议进行复杂的数据操作。同时,这也表明,执行复合指令和事务,会阻塞其他命令执行,而执行pipeline不会。

pipeline的执行需要客户端和服务端同时完成,pipeline在执行过程中,会阻塞当前客户端。在pipeline中不建议拼装过多的指令。因为指令过多,会使客户端阻塞时间太长,同时服务端需要回复这个很繁忙的客户端,占用很多内存。

总体来说,pipeline机制适合做一些在非热点时段进行的数据调整任务。

LUA脚本

在 Lua 脚本中封装复杂业务逻辑,减少客户端的复杂性。

使用 Lua 脚本保证多个操作的原子性。

Redis Function

  • 什么是Function

如果你觉得开发lua脚本有困难,那么在Redis7之后,提供了另外一种让程序员解脱的方法-Redis Function。

Redis Function允许将一些功能声明成一个统一的函数,提前加载到Redis服务端(可以由熟悉Redis的管理员加载)。客户端可以直接调用这些函数,而不需要再去开发函数的具体实现。

Redis Function更大的好处在于在Function中可以嵌套调用其他Function,从而更有利于代码复用。相比之下,lua脚本就无法进行复用。

  • Function注意点

1》Function同样也可以进行只读调用。

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

3》Function是要在服务端缓存的,所以不建议使用太多太大的Function。

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

总结

Redis的线程模型整体还是多线程的,只是后台执行指令的核心线程是单线程的。整个线程模型可以理解为还是以单线程为主。基于这种单线程为主的线程模型,不同客户端的各种指令都需要依次排队执行。

Redis这种以单线程为主的线程模型,相比其他中间件,还是非常简单的。这使得Redis处理线程并发问题,要简单高效很多。甚至在很多复杂业务场景下,Redis都是用来进行线程并发控制的很好的工具。但是,这并不意味着Redis就没有线程并发的问题。这时候选择合理的指令执行方式,就非常重要了。

另外,Redis这种比较简单的线程模型其实本身是不利于发挥多线程的并发优势的。而且Redis的应用场景又通常与高性能深度绑定在一起,所以,在使用Redis的时候,还是要时刻思考Redis的这些指令执行方式,这样才能最大限度发挥Redis高性能的优势。

目录
相关文章
|
6月前
|
监控 NoSQL 安全
如何在 Redis 中正确使用多线程?
【10月更文挑战第16天】正确使用 Redis 多线程需要综合考虑多个因素,并且需要在实践中不断摸索和总结经验。通过合理的配置和运用,多线程可以为 Redis 带来性能上的提升,同时也要注意避免可能出现的问题,以保障系统的稳定和可靠运行。
142 2
|
6月前
|
存储 NoSQL Redis
Redis 新版本引入多线程的利弊分析
【10月更文挑战第16天】Redis 新版本引入多线程是一个具有挑战性和机遇的改变。虽然多线程带来了一些潜在的问题和挑战,但也为 Redis 提供了进一步提升性能和扩展能力的可能性。在实际应用中,我们需要根据具体的需求和场景,综合评估多线程的利弊,谨慎地选择和使用 Redis 的新版本。同时,Redis 开发者也需要不断努力,优化和完善多线程机制,以提供更加稳定、高效和可靠的 Redis 服务。
146 1
|
2月前
|
存储 缓存 关系型数据库
MySQL底层概述—3.InnoDB线程模型
InnoDB存储引擎采用多线程模型,包含多个后台线程以处理不同任务。主要线程包括:IO Thread负责读写数据页和日志;Purge Thread回收已提交事务的undo日志;Page Cleaner Thread刷新脏页并清理redo日志;Master Thread调度其他线程,定时刷新脏页、回收undo日志、写入redo日志和合并写缓冲。各线程协同工作,确保数据一致性和高效性能。
MySQL底层概述—3.InnoDB线程模型
|
4月前
|
NoSQL Redis
单线程传奇Redis,为何引入多线程?
Redis 4.0 引入多线程支持,主要用于后台对象删除、处理阻塞命令和网络 I/O 等操作,以提高并发性和性能。尽管如此,Redis 仍保留单线程执行模型处理客户端请求,确保高效性和简单性。多线程仅用于优化后台任务,如异步删除过期对象和分担读写操作,从而提升整体性能。
104 1
|
6月前
|
并行计算 JavaScript 前端开发
单线程模型
【10月更文挑战第15天】
|
6月前
|
安全 Java
Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧
【10月更文挑战第20天】Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧,包括避免在循环外调用wait()、优先使用notifyAll()、确保线程安全及处理InterruptedException等,帮助读者更好地掌握这些方法的应用。
75 1
|
6月前
|
存储 运维 NoSQL
Redis为什么最开始被设计成单线程而不是多线程
总之,Redis采用单线程设计是基于对系统特性的深刻洞察和权衡的结果。这种设计不仅保持了Redis的高性能,还确保了其代码的简洁性、可维护性以及部署的便捷性,使之成为众多应用场景下的首选数据存储解决方案。
85 1
|
6月前
|
安全 调度 C#
STA模型、同步上下文和多线程、异步调度
【10月更文挑战第19天】本文介绍了 STA 模型、同步上下文和多线程、异步调度的概念及其优缺点。STA 模型适用于单线程环境,确保资源访问的顺序性;同步上下文和多线程提高了程序的并发性和响应性,但增加了复杂性;异步调度提升了程序的响应性和资源利用率,但也带来了编程复杂性和错误处理的挑战。选择合适的模型需根据具体应用场景和需求进行权衡。
117 0
|
2月前
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
本文详细介绍了如何在Linux中通过在业务线程中注册和处理信号。我们讨论了信号的基本概念,并通过完整的代码示例展示了在业务线程中注册和处理信号的方法。通过正确地使用信号处理机制,可以提高程序的健壮性和响应能力。希望本文能帮助您更好地理解和应用Linux信号处理,提高开发效率和代码质量。
68 17
|
2月前
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
通过本文,您可以了解如何在业务线程中注册和处理Linux信号。正确处理信号可以提高程序的健壮性和稳定性。希望这些内容能帮助您更好地理解和应用Linux信号处理机制。
68 26

热门文章

最新文章

下一篇
oss创建bucket