Redis并发访问问题详细讲解

目录
  • 前言
  • 什么场景需要控制并发访问
  • 并发访问的控制方法
    • 1、加入锁机制
    • 2、操作原子化
  • 小结

前言

我们在使用Redis的过程中,难免会遇到并发访问及数据更新的问题。但很多场景对数据的并发修改是很敏感的,比如库存数据如果没有做好并发读取和更新的版本控制,就会导致严重的业务问题。今天就来说说应该如何做好并发访问及数据更新问题。

什么场景需要控制并发访问

需要控制并发访问,说明这些并发的访问可能会对其他的访问造成影响。比如上面提到的库存问题,若同一时期有多个客户端访问商品A的库存数据,并且可能要更更新库存数据,这时候就需要对并发访问进行控制了。

说到底,并发访问需要控制的就是对数据的更新动作。 一般来说,客户端要进行数据更新时可分为2个步骤:

  • 客户端读取Redis数据到本地。
  • 确认数据后,修改Redis的数据。

单个访问来看,这个过程并没什么问题。但是并发多了,一分为二的过程就会造成数据错误的问题。这里还是用库存的例子来说:

  • 时间a::客户端1读取到库存=10,我们需要对库存+1=11的操作。
  • 时间b:客户端2读取到的库存也是10,这次要对库存-1=9的操作。
  • 时间c:客户端1将+1后的值11写回到Redis中。
  • 时间d:客户端2将-1后的值9写回到Redis中。

这样下来,很明显能发现库存数据错了。10+1-1 = 10,正确库存是10,而上述场景最后为9。

由此可见,这个一分为二的操作不具有原子性,从而产生了错误的结果。类型这种场景很多,因此我们需要对这些并发访问的场景加以控制。

并发访问的控制方法

Redis并发访问的控制,总的来说有2种方式。分别是加入锁机制和让一系列操作原子化。

1、加入锁机制

首先第一点,加入锁机制是很常见的解决方案。简单来说就是一个客户端访问数据之前,先要获取锁,等数据操作完之后再解锁。而在这个客户端拥有锁的过程中,其他客户端如果也想访问修改该数据,必须得等锁释放了之后,获取到了锁才行。

加锁这个方案是可以解决并发访问的数据准确问题,但放在redis这个场景中并不是很好。首先,Redis作为缓存本身并发访问就很多,频繁的加锁解锁,会大大降低redis的访问性能;然后,Redis的客户端在要加锁时,需要用到分布式锁。我们又得用额外的精力去维护这个分布式锁。

2、操作原子化

操作原子化,也就是让要执行的一系列动作都保持原子性操作。它的优点就是不需要加入额外的锁机制。并发的数据准确性达到了,对Redis的性能也不会有太大的影响。

Redis要实现原子操作,总结有2种方式:

  • 单命令操作:也就是Redis中的INCR、HINCRBY等命令,直接将简单的加减操作合成一个命令执行;
  • Lua脚本:借助Lua脚本,让多个操作在Lua脚本上实现原子性操作。

1.单命令操作

首先,单命令操作,将数值的加减直接用Redis命令来执行。像string的加减可用INCR、DECR操作,hash列表field的加减可用HINCRBY操作。

比如下面截图,两个客户端在不同时刻读取的linux_pids a值为4,各自+1、-1后a值为4。结果是正确的。

由此可见,用Redis的INCR、DECR等命令可以解决数值简单增减的并发场景。但如果我们对数据的更新不仅仅是简单的加减操作时,Redis的这些命令就无能为力了。此时我们可以考虑另一种方案:Lua脚本。

2.Lua脚本

Lua语言是由C写的,因此支持多平台和系统。从Redis2.6开始,Redis就内置了Lua解释器,我们能直接用Redis客户端来执行lua脚本。

我们可以将需要执行的一系列操作用Lua脚本写好,然后用Redis执行它。Lua脚本的方法能保证原子性操作的原因是:Redis会将Lua脚本一次性执行,也就是说执行Lua脚本是0-1的操作,要么成功,要么失败。可以理解成MySQL的事务特性。

Redis使用lua脚本有2种方式:

  • 客户端中使用:用到script load脚本内容、evalsha等命令
  • 执行直接执行lua脚本

我们一般用第二种方式来执行。

客户端使用方法:

先用script load加载脚本命令,再用evalsha执行加载得到的sha1值。

127.0.0.1:6379> script load "return 'hello'"
"1b936e3fe509bcbc9cd0664897bbe8fd0cac101b"
127.0.0.1:6379> evalsha "1b936e3fe509bcbc9cd0664897bbe8fd0cac101b" 0
"hello"

再来看看Redis使用Lua脚本的语法:

redis-cli --eval {lua_path} KEYS[1] KEYS[2]... , ARGV[1] ARGV[2]...
 
--eval:          执行lua脚本的命令
{lua_path}:         lua脚本的路径
KEYS[1] KEYS[2]:       lua脚本中要操作的redis键,我们可以在lua脚本中用KEYS[1],KEYS[2],KEYS[3]指定多个
ARGV[1] ARGV[2]:    传入到lua脚本的参数,在脚本中用ARGV[1],ARGV[2]...来获取。

Redis使用lua脚本的场景很多,最经典的案例当属利用lua来控制某个IP的访问频率了。比如说需要防止恶意访问网站的行为,我们规定1分钟内访问次数不能超过30次,实现的方法有很多,比如说漏桶方案、令牌桶方案,但使用最多的还是Redis+lua的分布式限流方案。

我们用lua脚本(test_lua.script)来简单实现一下上述功能,就是1分钟内若访问次数超过30,直接拦截,否则访问次数+1:

-- 限流的key
local limit_key = KEYS[1]
-- 限流次数
local limit_nums = 30
-- 当前访问次数
local current_num = tonumber(redis.call('get', limit_key) or 0)
-- 超出限流次数
if current_num + 1 > limit_num
    then
    return '超出访问次数'
-- 没有超出限流数,访问次数+1
else
  redis.call("INCRBY", limit_key, "1")
  -- 第一次访问,设置过期时间
  if current_num == 0 then
    redis.call("expire", limit_key, "60")
  return current + 1
end

用Redis执行,命令如下:

redis-cli --eval test_lua.script limit_key

小结

本文介绍了Redis并发访问的控制问题,以及如何保证并发操作的原子化。原子化操作可通过单命令操作和Lua脚本的方式实现。我们在应对相关问题时,可根据需要选择对应方案解决之。

到此这篇关于Redis并发访问问题详细讲解的文章就介绍到这了,更多相关Redis并发访问内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 浅谈Redis如何应对并发访问

    目录 前言 原子性操作 单命令模式 多命令模式 lua简介 建议 事务 加锁 总结 前言 项目中经常会遇到这种场景,我们需要先将Redis数据读取到本地,然后进行修改,修改完成后在将数据写回Redis,这种读取-修改-写回操作,我们称之为RMW操作.当有多个客户端对同一份数据执行RMW操作的话,Redis如何保证RMW操作涉及的代码以原子性方式执行? 原子性操作 Redis的原子性操作是一种无锁操作,即可以保证并发控制,还能减少系统对并发性能的影响, 单命令模式 把Redis多个操作实现成一个操

  • Redis并发访问问题详细讲解

    目录 前言 什么场景需要控制并发访问 并发访问的控制方法 1.加入锁机制 2.操作原子化 小结 前言 我们在使用Redis的过程中,难免会遇到并发访问及数据更新的问题.但很多场景对数据的并发修改是很敏感的,比如库存数据如果没有做好并发读取和更新的版本控制,就会导致严重的业务问题.今天就来说说应该如何做好并发访问及数据更新问题. 什么场景需要控制并发访问 需要控制并发访问,说明这些并发的访问可能会对其他的访问造成影响.比如上面提到的库存问题,若同一时期有多个客户端访问商品A的库存数据,并且可能要更

  • Redis缓存实例超详细讲解

    目录 1 前言 1.1 什么是缓存 1.2 缓存的作用及成本 1.3 Redis缓存模型 2 给商户信息添加缓存 3 缓存更新策略 3.1 更新策略介绍 3.2 主动更新策略 3.3 主动更新策略练习 4 缓存穿透及其解决方案 4.1 缓存穿透的概念 4.2 解决方案及实现 5 缓存雪崩的概念及其解决方案 6 缓存击穿及解决方案 6.1 什么是缓存击穿 6.2 缓存击穿解决方法 6.2.1 互斥锁 6.2.2 逻辑过期 1 前言 1.1 什么是缓存 缓存就是数据交换的缓冲区(称作Cache [

  • php 使用redis锁限制并发访问类示例

    本文介绍了php 使用redis锁限制并发访问类,并详细的介绍了并发访问限制方法. 1.并发访问限制问题 对于一些需要限制同一个用户并发访问的场景,如果用户并发请求多次,而服务器处理没有加锁限制,用户则可以多次请求成功. 例如换领优惠券,如果用户同一时间并发提交换领码,在没有加锁限制的情况下,用户则可以使用同一个换领码同时兑换到多张优惠券. 伪代码如下: if A(可以换领)     B(执行换领)     C(更新为已换领) D(结束) 如果用户并发提交换领码,都能通过可以换领(A)的判断,因

  • MySQL/Postgrsql 详细讲解如何用ODBC接口访问MySQL指南

    详细讲解如何用ODBC接口访问MySQL指南 MySQL的ODBC接口实现是通过安装MyODBC驱动,这个驱动程序是跨平台的.如果在Linux等Unix体系操作系统下使用,需要先安装Iodbc这些第三方ODBC标准支援平台. 简单的ASP示例代码:   复制代码 代码如下: <%   dim sql,mysql   set sql = server.createobject("adodb.connection")   mysql="driver={mysql odbc 

  • java、spring、springboot中整合Redis的详细讲解

    java整合Redis 1.引入依赖或者导入jar包 <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency> 2.代码实现 public class JedisTest { public static void main(String[]

  • Redis超详细讲解高可用主从复制基础与哨兵模式方案

    目录 高可用基础---主从复制 主从复制的原理 主从复制配置 示例 1.创建Redis实例 2.连接数据库并设置主从复制 高可用方案---哨兵模式sentinel 哨兵模式简介 哨兵工作原理 哨兵故障修复原理 sentinel.conf配置讲解 哨兵模式的优点 哨兵模式的缺点 高可用基础---主从复制 Redis的复制功能是支持将多个数据库之间进行数据同步,主数据库可以进行读写操作.当主数据库数据发生改变时会自动同步到从数据库,从数据库一般是只读的,会接收注数据库同步过来的数据. 一个主数据库可

  • Mysq详细讲解如何解决库存并发问题

    目录 面临的问题 如何实现 需求具体实现的方案 总结 面临的问题 长话短说,假设我们现在面临以下需求 商品的库存有两千,卖完为止 某商品本日的售卖只允许卖出一百,卖完为止 如何实现 我提出的方案也很简单,使用乐观锁的方式. 以下是具体的方案 -- stock: 当前库存数 number:扣减的数量 -- UPDATE t SET stock -= number WHERE stock >= number 外加上事务,便可以实现一个基本的库存扣减操作.大部分情况下,无需担心所谓的“并发问题”.事务

  • Python多进程并发与同步机制超详细讲解

    目录 多进程 僵尸进程 Process类 函数方式 继承方式 同步机制 状态管理Managers 在<多线程与同步>中介绍了多线程及存在的问题,而通过使用多进程而非线程可有效地绕过全局解释器锁. 因此,通过multiprocessing模块可充分地利用多核CPU的资源. 多进程 多进程是通过multiprocessing包来实现的,multiprocessing.Process对象(和多线程的threading.Thread类似)用来创建一个进程对象: 在类UNIX平台上,需要对每个Proce

  • GoLang并发机制探究goroutine原理详细讲解

    目录 1. 进程与线程 2. goroutine原理 3. 并发与并行 3.1 在1个逻辑处理器上运行Go程序 3.2 goroutine的停止与重新调度 3.3 在多个逻辑处理器上运行Go程序 通常程序会被编写为一个顺序执行并完成一个独立任务的代码.如果没有特别的需求,最好总是这样写代码,因为这种类型的程序通常很容易写,也很容易维护.不过也有一些情况下,并行执行多个任务会有更大的好处.一个例子是,Web 服务需要在各自独立的套接字(socket)上同时接收多个数据请求.每个套接字请求都是独立的

随机推荐