Redis如何保证并发访问时的数据正确性

目录

背景

原子操作

Redis 的两种原子操作方法

单命令操作(用的少)

Lua脚本

分布式加锁

锁操作的原子性

分布式锁的可靠性

RedLock算法实现的三个步骤


背景

​ 在使用Redis时不可避免地会遇到并发访问的问题,比如多个用户同时下单,就会对缓存中的商品库存数据进行并发更新。一旦有了并发写操作,数据就会被修改,如果没有做好并发控制,就会导致数据被修改错误,影响到业务的正常使用。(例如秒杀场景下的超卖情况) (即并发修改Redis中的数据)

​ 为了保证并发访问(并发修改数据)的正确性,Redis提供了两个方法,分别是加锁和原子操作。

原子操作:是指执行过程中保持原子性的操作,而且原子操作执行时并不需要再加锁,实现了无锁操作。这样既保证了并发控制,还能减少对系统并发性能的影响。(在原子操作执行过程中,不会有其他Redis命令被执行)

Redis加锁会有两个问题,一方面是加锁操作多,会降低系统的并发访问性能。另一方面Redis客户端加锁时,需要用到分布式锁,而这需要额外的存储系统来提供加解锁的操作。

原子操作

​ 并发控制针对的操作范围主要是数据修改操作。当有多个客户端对同一份数据执行RMW(Read-Modify-Write)操作时,我们就需要RMW操作涉及的代码以原子性方式执行。访问同一份数据的RMW操作代码,就叫做临界区代码,并且同一时间只会有一个客户端的临界区代码会被执行。RMW操作即是指客户端要对数据做修改操作时所需要执行的步骤,即要先读取Redis中的内存数据到客户端中,然后根据读取到的数据判断是否要修改,然后在本地修改,最后写入到Redis服务中。而这部分操作就是指临界区的操作逻辑了。

Redis 的两种原子操作方法

​ 为了实现并发控制要求的临界区代码互斥执行。Redis的原子操作采用了两种方法:

  • 把多个操作封装成Redis中的一条命令,也就是单命令操作;
  • 把多个操作写到一个Lua脚本中,以原子性方式执行单个Lua脚本。

单命令操作(用的少)

​虽然Redis的单个命令可以原子性执行(Redis是单线程执行的,同一时间最多只有一条Redis命令被执行),但实际操作中数据修改包含了多个命令的操作,包括数据读取、数据增减、写回数据三个操作。

​ 这种情况就需要使用Redis提供的单命令操作了。例如INCR/DECR命令就可以实现数据的增减操作,而且因为它们本身就是单个命令操作(这两个命令它里面就做到了读取当前数据,当前数据+1,然后写到Redis中),所以在执行它们时,就保证了它们的互斥性。

Lua脚本

​ 如果只是简单的增减操作,那么就可以使用单命令保证其原子性了。但是可能会有更复杂的判断逻辑或者其它操作,那么就需要通过封装多个命令在Lua脚本执行操作了。(它是把一批Redis命令封装到一个脚本中,然后由Redis当做一条命令去执行的。Redis命令是单线程执行的(同一时间最多执行一个命令,其他命令将被阻塞直到当前命令执行完成)(而LUA脚本能够把多条Redis命令整合成一个单命令去执行)故他能保证在执行当前LUA脚本期间,其它客户端或者本客户端发送的其他命令或者LUA脚本将被阻塞,直到当前LUA脚本被执行完,这样在LUA脚本执行期间,就不会出现并发修改当前数据的问题

​ Redis 会把整个 Lua 脚本作为一个整体执行,在LUA脚本执行的过程中不会被其他命令打断,Redis也不会去执行其他的命令,从而保证了 Lua 脚本中操作的原子性。但是如果把很多操作都放在 Lua 脚本中原子执行,会导致 Redis 执行脚本的时间增加,同样也会降低 Redis 的并发性能。所以在编写Lua脚本时需要避免把不需要做并发控制的操作写入脚本中。

LUA脚本的简介

Redis从2.6.0版本开始提供了eval命令,通过内置的Lua解释器,可以让用户执行一段Lua脚本并返回数据。因为Redis单线程模型的特点,可以保证多个命令的原子性.

LUA脚本的原子执行

Lua脚本在Redis中是以原子方式执行的,在Redis服务器执行EVAL命令时,在命令执行完毕并向调用者返回结果之前,只会执行当前命令指定的Lua脚本包含的所有逻辑,其它客户端发送的命令将被阻塞,直到EVAL命令执行完毕为止。因此LUA脚本不宜编写一些过于复杂了逻辑,必须尽量保证Lua脚本的效率,否则会影响其它客户端。

Redis命令是单线程执行的(同一时间最多执行一个命令,其他命令将被阻塞直到当前命令执行完成)(而LUA脚本能够把多条Redis命令整合成一个单命令去执行)故他能保证在执行当前LUA脚本期间,其它客户端或者本客户端发送的其他命令或者LUA脚本将被阻塞,直到当前LUA脚本被执行完,那么意味着这个LUA脚本执行期间能做到(Read-Modify-Write)(取出数据,根据数据判断是否修改数据,再写入数据)不会有间隙问题(不可能在当前LUA脚本执行期间,lua脚本取出指定数据时,指定数据被其他命令修改的情况,LUA脚本中取出的数据就是最新的数据),LUA脚本执行期间不可能会有其他Redis命令能够去执行(也就不可能出现并发修改数据的问题)

Redis Lua脚本执行遇到异常时,已经执行过的逻辑是不会回滚的

使用lua脚本的好处

  1. lua脚本是作为一个整体执行的.所以中间不会被其他命令插入
  2. 可以把多条命令一次性打包,所以可以有效减少网络开销;
  3. lua脚本可以常驻在redis内存中,所以在使用的时候,可以直接拿来复用.也减少了代码量

分布式加锁

在应对并发问题时,除了原子操作,Redis客户端可采用加锁的方法,来控制并发写操作对共享数据的修改,从而保证数据的正确性。(其实就是修改同一份数据前,所有的客户端先去抢占锁,抢占到锁的客户端才能去执行Redis命令修改当前的数据(去做(Read-Modify-Write)操作),其他客户端就阻塞等待直到抢占到锁才去做(Read-Modify-Write)操作或者直接放弃)

当有多个客户端需要争抢锁时,需要保证的是这把锁不能是某个客户端本地的锁。否则的话,其它客户端的是无法访问到这把锁的,更不要说是获取锁了。所以在分布式系统中,当有多个客户端需要获取锁时,需要使用分布式锁。此时锁是保存在共享存储系统中的,可以被多个客户端共享访问和获取。

​ 而Redis正好可以被多个客户端共享访问,可以保存分布式锁。

加锁操作和释放锁的操作就是针对锁的键值进行读取、判断、设置的过程。

  • 加锁时根据锁变量值判断是否可以加锁,如果可以则对锁变量值进行修改,表示持有锁;
  • 释放锁时同样需要进行判断,因为需要判断当前加锁的是不是该客户端,如果不判断直接释放锁的话,会被其它客户端将持有的锁给释放掉了;如果可以释放锁,则重置锁变量的值;

这样一来,因为加锁释放锁涉及了多个操作,所以实现分布式锁时需要两个保证:

  • 锁操作的原子性;
  • 分布式锁的可靠性;

锁操作的原子性

锁操作(加锁和释放锁的操作)的原子性可以采用上面提到的单命令操作和Lua脚本操作。

单命令操作和Lua脚本

使用 SETNX 和 DEL命令即可实现加锁和释放锁的操作。SETNX命令表示在执行时会判断键值对是否存在,如果不存在,就设置键值对的值,如果存在,就不做任何设置。

SETNX key value

释放锁时直接将锁删除掉即可。

但进行操作时需要注意两个问题:

  • 一是锁的过期时间设置;在加锁后,如果后面的逻辑发生了异常导致没有释放锁,这时就需要过期时间去保证该客户端不能一直持有锁。
  • 还有一个是需要区别不同客户端的释放锁操作;这可以让每个客户端加锁时设置唯一值;(客户端只能释放自己的加的锁,不能释放别人的锁,这种情况可能发生在当前客户端的锁自动过期后,客户端再去主动释放锁)

锁过期时间的设置和释放锁的操作都需要保证原子性;这里使用SET命令的NX选项 和Lua脚本保证了。

SET KEY VALUE [EX seconds | PX milliseconds] [NX]

即SET lock_key unique_value NX PX 10000 表示给lock_key这个键设置unique_value值,同时设置过期时间为10000ms。

释放锁也包含了读取锁变量值、判断锁变量值和删除锁变量三个操作,不过,我们无法使用单个命令来实现,所以,我们可以采用 Lua 脚本执行释放锁操作,通过 Redis 原子性地执行 Lua 脚本,来保证释放锁操作的原子性。

释放锁时的Lua脚本:(重要)

//释放锁 比较unique_value是否相等,避免误释放,客户端只能释放自己的锁
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

redis-cli  --eval  unlock.script lock_key , unique_value 

分布式锁的可靠性

为了避免锁实例出现故障而导致的锁无法工作的问题,需要按照一定的步骤和规定。Redis 的开发者 Antirez 提出了分布式锁算法 Redlock。

RedLock的基本思路是,是让客户端和多个独立的Redis实例依次请求加锁,如果客户端能够与半数以上的实例成功地完成加锁操作,那么就认为客户端获得了分布式锁。这样一来,即使有某个Redis实例发生故障,那么也有其它实例可以做锁操作的支撑

RedLock算法实现的三个步骤

RedLock算法实现的可以分为3个步骤,假设需要有N个独立的Redis实例:

1 客户端获取当前时间;

2 客户端按顺序依次向N个Redis实例执行加锁操作;

向Redis实例请求加锁,一样是采用SET NX 原子操作的命令,为了保障在加锁过程中Redis故障了,需要给加锁操作设置一个超时时间。如果超时了,那么会去下一个Redis实例继续请求加锁。

加锁操作的超时时间需要远远小于锁的有效时间,一般也是设置几十毫秒。

3一旦客户端完成了和所有Redis实例的加锁操作,客户端计算整个加锁过程的总耗时。

客户端需要满足以下两个条件才能认为是加锁成功:

客户端从超过半数(大于等于N/2 + 1)的Redis实例上成功获取到了锁;

客户端获取锁的总耗时没有超过锁的有效时间。

在满足加锁成功的条件后,需要重新计算锁的有效时间计算结果是锁的最初有效时间减去客户端为获取锁的总耗时。如果锁的有效时间已经来不及完成共享数据的操作了,那么就需要释放锁,以免出现还没完成数据操作,锁就过期的情况(这个有一个阈值,很小)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
数据分析及处理方法全文共3页,当前为第1页。大数据分析及处理方法全文共3页,当前为第1页。大数据分析及处理方法 大数据分析及处理方法全文共3页,当前为第1页。 大数据分析及处理方法全文共3页,当前为第1页。 大数据的处理 周涛博士说:大数据处理数据代理念的三大转变:要全体不要抽样,要效率不要肯定精确,要相关不要因果。 具体的大数据处理方法其实有许多,但是依据长间的实践,笔者总结了一个基本的大数据处理流程,并且这个流程应当能够对大家理顺大数据的处理有所关心。整个处理流程可以概括为四步,分别是采集、导入和预处理、统计和分析,以及挖掘。 采集 大数据的采集是指利用多个数据库来接收发自客户端(Web、App或者传感器形式等)的数据,并且用户可以通过这些数据库来进行简洁的查询和处理工作。比方,电商会使用传统的关系型数据库MySQL和Oracle等来存储每一笔事务数据,除此之外,Redis和MongoDB这样的NoSQL数据库也常用于数据的采集 在大数据的采集过程中,其主要特点和挑战是并发数高,因为同有可能会有成千上万的用户来进行访问操作,比方火车票售票网站和淘宝,它们并发访问量在峰值到达上百万,所以需要在采集端部署大量数据库才能支撑。并且如何在这些数据库之间进行负载均衡和分片确实是需要深入的思索和设计。 导入/预处理 虽然采集端本身会有许多数据库,但是假如要对这些海量数据进行有效的分析,还是应当将这些来自前端的数据导入到一个集中的大型分布式数据库,或者分布式存储集群,并且可以在导入基础上做一些简洁的清洗和预处理工作。也有一些用户会在导入使用来自Twitter的Storm来对数据进行流式计算,来满足部分业务的实计算需求。 大数据分析及处理方法全文共3页,当前为第2页。大数据分析及处理方法全文共3页,当前为第2页。 导入与预处理过程的特点和挑战主要是导入的数据量大,每秒钟的导入量常常会到达百兆,甚至千兆级别。 统计/分析 统计与分析主要利用分布式数据库,或者分布式计算集群来对存储于其内的海量数据进行一般的分析和分类汇总等,以满足大多数常见的分析需求,在这方面,一些实性需求会用到EMC 的GreenPlum、Oracle的Exadata,以及基于MySQL的列式存储Infobright等,而一些批处理,或者基于半结构化数据的需求可以使用Hadoop。 统计与分析这部分的主要特点和挑战是分析涉及的数据量大,其对系统资源,特殊是I/O会有极大的占用。 挖掘 与前面统计和分析过程不同的是,数据挖掘一般没有什么预先设定好的主题,主要是在现有数据上面进行基于各种算法的计算,从而起到预报(Predict)的效果,从而实现一些高级别数据分析的需求。比较典型算法有用于聚类的K-Means、用于统计学习的SVM和用于分类的Naive Bayes,主要使用的工具有Hadoop的Mahout等。 该过程的特点和挑战主要是用于挖掘的算法很冗杂,并且计算涉及的`数据量和计算量都很大,还有,常用数据挖掘算法都以单线程为主。 大数据分析的五个基本方面 1. Analytic Visualizations(可视化分析) 不管是对数据分析专家还是一般用户,数据可视化是数据分析工具最基本的要求。可视化可以直观的展示数据,让数据自己说话,让观众听到结果。 2. Data Mining Algorithms(数据挖掘算法) 可视化是给人看的,数据挖掘就是给机器看的。集群、分割、孤立点分析还有其他的算法让我们深入数据内部,挖掘价值。这些算法不仅要处理大数据的量,也要处理大数据的速度。 大数据分析及处理方法全文共3页,当前为第3页。大数据分析及处理方法全文共3页,当前为第3页。 3. Predictive Analytic Capabilities(预报性分析能力) 数据挖掘可以让分析员更好的理解数据,而预报性分析可以让分析员依据可视化分析和数据挖掘的结果做出一些预报性的推断。 4. Semantic Engines(语义引擎) 我们知道由于非结构化数据的多样性带来了数据分析的新的挑战,我们需要一系列的工具去解析,提取,分析数据。语义引擎需要被设计成能够从"文档"中智能提取信息。 5. Data Quality and Master Data Management(数据质量和数据管理) 数据质量和数据管理是一些管理方面的最正确实践。通过标准化的流程和工具对数据进行处理可以保证一个预先定义好的高质量的分析结果。 假如大数据真的是下一个重要的技术革新的话,我们最好把精力关注在大数据能给我们带来的好处,而不仅仅是挑战。 大数据分析及处理方法全文共3页,当前为第2页。 大数据分析及处理方法全文共3页,当前为第2页。 大数据分析及处理方法全文共3页,当前为第3页。 大数据分析

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值