优质校建设网站,有专门做预算的网站没,做网站排名优化有用吗,温岭高端网站设计哪家好redis为什么会发生并发安全问题#xff1f; 在redis中#xff0c;处理的数据都在内存中#xff0c;数据操作效率极高#xff0c;单线程的情况下#xff0c;qps轻松破10w。反而在使用多线程时#xff0c;为了保证线程安全#xff0c;采用了一些同步机制#xff0c;以及多…redis为什么会发生并发安全问题 在redis中处理的数据都在内存中数据操作效率极高单线程的情况下qps轻松破10w。反而在使用多线程时为了保证线程安全采用了一些同步机制以及多线程的上下文切换却对性能造成了一定的影响。 如此看来在单线程模式下redis的性能比较高且可以避免多线程情况下的线程安全问题。但是在redis使用过程中线程安全问题依旧存在此话怎讲呢 线程安全是站在reids的角度来说的redis使用单线程模型是不存在线程安全问题的以为他只有一个线程不存在多线程间数据的共享俗话说没有共享就没有伤害。 而线程不安全是站在客户端的角度说的redis是只有一个线程在工作但是客户单端却是有成千上万个的对于客户端来说redis是被共享的资源所以对于客户端来说依旧存在线程安全问题, 举例 1.商品库存开始为10此时客户端A和客户端B同时下单扣减库存。 2.两个客户端从redis获取到当前库存数为10。 3.两个客户端在本地将库存数减1然后写回redis。 4此时redis中存库数为9正常情况下应该是8造成超卖问题。 ———————————————— 版权声明本文为CSDN博主「wind_huise」的原创文章遵循CC 4.0 BY-SA版权协议转载请附上原文出处链接及本声明。 原文链接https://blog.csdn.net/weixin_45701550/article/details/126324924 读库存和写库存操作在redis中是单线程执行的是原子性的但是整个扣减库存的操作却不是原子性的这也是出现线程不安全的根本原因。
对于解决这个问题redis提供了一些复合命令将多个操作合并成一个操作命令此时这个复合命令就变成一个原子操作也就不会再出现上述的线程安全问题了。如果需要实现简单的原子性操作则可以使用Redis的单个指令例如SET、GET、DEL等来实现如果需要实现复杂数据结构的操作则可以考虑使用Redis事务、Lua脚本来实现。
Redis的事务模型
在Redis中事务是通过MULTI、EXEC、WATCH、UNWATCH等指令来实现的。在MULTI指令开始执行事务之前客户端发送的每个指令都不会立即执行而是被缓存到一个队列中直到EXEC指令被执行时所有缓存的指令才一起被执行。这种模型被称为乐观锁因为在执行事务期间Redis并不会对被监控的数据进行加锁而是在执行EXEC指令时进行条件检查如果检查失败则事务执行失败。这种设计可以减少锁的竞争提高Redis的并发性能。
流程如下 1、开启事务MULTI 客户端要使用一个命令显式地表示一个事务的开启。在Redis中这个命令就是MULTI。 2、指令入队暂时不执行 客户端把事务中本身要执行的具体操作例如增删改数据发送给服务器端。这些操作就是Redis本身提供的数据读写命令例如GET、SET等。不过这些命令虽然被客户端发送到了服务器端但Redis实例只是把这些命令暂存到一个命令队列中并不会立即执行。 3、执行指令EXEC 客户端向服务器端发送提交事务的命令让数据库实际执行第二步中发送的具体操作。Redis 提供的EXEC命令就是执行事务提交的。当服务器端收到EXEC命令后才会实际执行命令队列中的所有命令
Redis对于事务操作的支持比较局限最大的不足是不支持回滚。执行事务时Redis将这些命令依次执行如果有一条命令执行失败就会导致整个事务的失败但不能回滚已经执行成功的命令
在Redis的事务模型中由于不会对被监控的数据进行加锁因此如果事务执行失败只能够通过执行一系列的逆操作来恢复数据状态而无法直接回滚事务。这是因为对于一些操作例如INCRBY、RPUSH、SUNION、ZUNIONSTORE等Redis并没有提供对应的逆操作。因此如果使用Redis来实现复杂的事务操作可能需要自己实现回滚逻辑这会增加开发和维护的复杂度。
Lua脚本
基本原理为使脚本相当于一个redis命令可以结合redis原有命令自定义脚本逻辑
如何在redis中使用lua脚本可以参考https://redis.io/commands/eval/
Lua脚本之所以可以保证线程安全是因为我们可以把多个操作写成一个 lua 脚本使其具备原子性作为一个整体执行。再由于 redis 是单线程模型不同线程的 lua 脚本是依次执行的。也就是说只有一个线程原子性的多个操作执行完下一个线程才可以执行。实际上也是保证了在 redis 内部不同线程操作的串行执行从而能够解决并发安全问题。 【简单来说就是lua脚本包括业务的多个操作使得整个业务成为一个整体执行时相当于把整个lua脚本当成一个redis指令】
Lua脚本的优点
1.lua脚本是作为一个整体执行的所以中间不会被其他命令插入无需担心并发;
2.lua脚本把多条命令一次性打包而代码实现的事务需要向Redis发送多次请求所以可以有效减少网络开销;
3.lua脚本可以常驻在redis内存中所以在使用的时候可以直接拿来复用。
redis事务和Lua两者相同点
很好的实现了一致性、隔离性和持久性但没有实现原子性无论是redis事务还是lua脚本如果执行期间出现运行错误之前的执行过的命令是不会回滚的。
lua 脚本实现的原子性是假的原子性
因为当多个指令执行时lua脚本中的一条指令报错时后面的指令执行失败了但是前面的指令已经成功。且不会回滚。 其中报错原因包括 1、指令语法错误 2、语法是正确的但是类型不对比如对已经存在的string类型的key执行hset等 3、服务器挂掉了比如lua脚本执行了一半但是服务器挂掉了 前面两者我们可以通过仔细检查脚本逻辑,确保脚本中的所有命令都是正确的并且按照预期的顺序执行、使用redis.pcall处理潜在的错误以及适时使用事务可以编写出高效、可靠的Lua脚本,确保脚本的逻辑正确性和健壮性以避免潜在的问题。 redis.pcall在命令执行失败时不会引发错误而是返回一个包含错误信息的表。通过检查redis.pcall的返回值可以在脚本中处理错误情况从而避免脚本执行失败 Lua脚本回滚 在Lua脚本中我们可以使用Redis的WATCH指令它允许我们监视一个或多个键的变化。当我们监视的键发生变化时Redis会立刻中断正在执行的Lua脚本使得整个Lua脚本操作回滚这种方式可以实现Redis事务的回滚效果。
Lua脚本相对于Redis事务更实用的原因有以下几点 1、原子性保证使用Lua脚本可以将多个命令封装成一个原子操作确保这些命令在执行期间不会被其他命令插入从而保证操作的原子性。 2、减少网络开销在Lua脚本中多个命令可以一次性发送到Redis服务器并由Redis执行减少了网络开销。而Redis事务需要通过MULTI和EXEC命令来开启和提交事务增加了网络往返的次数。 3、高性能由于Lua脚本在Redis服务器端执行避免了客户端与服务器之间的通信。这样可以减少通信延迟并在服务器端以原生代码的方式执行提高了执行效率。相比之下Redis事务在客户端和服务器之间进行多次通信可能降低执行效率。 4、复杂逻辑支持Lua脚本提供了强大的编程能力可以实现复杂的业务逻辑。通过脚本的编写可以实现数据库操作的灵活性从而适应更多的场景需求。Redis事务对于复杂逻辑的支持相对较弱更适合简单的操作序列。 参考文章 使用redis怎么解决并发问题 redis为什么不支持事务(redis不支持的数据结构) Redis实例发生故障而Redis使用的RDB机制事务的原子性还能否得到保证 Redis不支持事务的解决方案(redis事务为什么不支持回滚) Redis实例发生故障而Redis使用的RDB机制事务的原子性还能否得到保证