Redis学习十
Redis应对并发访问
Redis为了保证并发的正确性,提供的俩种方法,分别是加锁和原子操作。
加锁操作和服务器的锁一样,修改和获取数据的时候先获得锁然后进行加锁直到数据更新完才会释放锁
原子操作是指执行过程保持原子性的操作。
Redis的俩种原子操作
- 把多个操作在 Redis 中实现成一个操作,也就是单命令操作;
- 把多个操作写到一个 Lua 脚本中,以原子性方式执行单个 Lua 脚本。使用 Redis 的 EVAL 命令来执行脚本。
单命令
Redis提供了INCR/DECR 命令可以对数据进行增值 / 减值操作;
Redis实现分布式锁
在分布式系统中,当有多个客户端需要获取锁时,我们需要分布式锁。此时,锁是保存在一个共享存储系统中的,可以被多个客户端共享访问和获取。相应的,加锁和释放锁的操作就变成了读取、判断和设置共享存储系统中的锁变量值。
锁的要求
要求一:分布式锁的加锁和释放锁的过程,涉及多个操作。所以,在实现分布式锁时,我们需要保证这些锁操作的原子性;
要求二:共享存储系统保存了锁变量,如果共享存储系统发生故障或宕机,那么客户端也就无法进行锁操作了。在实现分布式锁时,我们需要考虑保证共享存储系统的可靠性,进而保证锁的可靠性。
基于单个 Redis 节点实现分布式锁
单个实例时,可以设置一个key值来保存锁变量,不同的客户端同时对这个key值进行判断。
首先是 SETNX 命令,它用于设置键值对的值。具体来说,就是这个命令在执行时会判断键值对是否存在,如果不存在,就设置键值对的值,如果存在,就不做任何设置。(加锁的时候记得保存客服端的唯一标识符【unique_value】,避免其他客户端释放锁)
记得给锁变量设置一个过期时间(避免此客户端忘记释放锁)
释放锁的时候记得判断唯一标识符
// 加锁, unique_value作为客户端唯一性的标识
// Redis 给 SET 命令提供了类似的选项 NX,用来实现“不存在即设置”。
SET lock_key unique_value NX PX 10000
基于多个 Redis 节点实现高可靠的分布式锁
Redlock 算法的基本思路,是让客户端和多个独立的 Redis 实例依次请求加锁,如果客户端能够和半数以上的实例成功地完成加锁操作,那么我们就认为,客户端成功地获得分布式锁了,否则加锁失败。
Redlock 算法的执行步骤
- 第一步是,客户端获取当前时间。
- 第二步是,客户端按顺序依次向 N 个 Redis 实例执行加锁操作。
- 第三步是,一旦客户端完成了和所有 Redis 实例的加锁操作,客户端就要计算整个加锁过程的总耗时。
客户端只有在满足下面的这两个条件时,才能认为是加锁成功。
条件一:客户端从超过半数(大于等于 N/2+1)的 Redis 实例上成功获取到了锁;
条件二:客户端获取锁的总耗时没有超过锁的有效时间。
在满足了这两个条件后,我们需要重新计算这把锁的有效时间,计算的结果是锁的最初有效时间减去客户端为获取锁的总耗时。如果锁的有效时间已经来不及完成共享数据的操作了,我们可以释放锁,以免出现还没完成数据操作,锁就过期了的情况。
事务机制
事物机制
原子性(Atomicity)就是一个事务中的多个操作必须都完成,或者都不完成
一致性(Consistency)指数据库中的数据在事务执行前后是一致的。
隔离性(Isolation)数据库在执行一个事务时,其它操作无法存取到正在执行事务访问的数据。
持久性(Durability)。数据库执行事务后,数据的修改要被持久化保存下来。当数据库重启后,数据的值需要是被修改后的值。
Redis的事物
Redis 提供了 MULTI、EXEC 两个命令
#开启事务
127.0.0.1:6379> MULTI
OK
#将a:stock减1,
127.0.0.1:6379> DECR a:stock
QUEUED
#将b:stock减1
127.0.0.1:6379> DECR b:stock
QUEUED
#实际执行事务
127.0.0.1:6379> EXEC
1) (integer) 4
2) (integer) 9
原子性
- 在执行 EXEC 命令前,客户端发送的操作命令本身就有错误(比如语法错误,使用了不存在的命令),在命令入队时就被 Redis 实例判断出来了。对于这种情况,在命令入队时,Redis 就会报错并且记录下这个错误。此时,我们还能继续提交命令操作。等到执行了 EXEC 命令之后,Redis 就会拒绝执行所有提交的命令操作,返回事务失败的结果。
- 务操作入队时,命令和操作的数据类型不匹配,但 Redis 实例没有检查出错误。但是,在执行完 EXEC 命令以后,Redis 实际执行这些事务操作时,就会报错。不过,需要注意的是,虽然 Redis 会对错误命令报错,但还是会把正确的命令执行完。在这种情况下,事务的原子性就无法得到保证了。
- 在执行事务的 EXEC 命令时,Redis 实例发生了故障,导致事务执行失败。
一致性
- 一致性 ,事务本身就会被放弃执行,所以可以保证数据库的一致性。(DISCARD命令)
- 命令入队时没报错,实际执行时报错。在这种情况下,有错误的命令不会被执行,正确的命令可以正常执行,也不会改变数据库的一致性。
- EXEC 命令执行时实例发生故障。由于可以使用redis-check-aof 清除事务 可以保证一致性
隔离性
- 并发操作在 EXEC 命令前执行,此时,隔离性的保证要使用 WATCH 机制来实现,否则隔离性无法保证;
- 并发操作在 EXEC 命令后执行,此时,隔离性可以保证。
WATCH 机制会先检查监控的键是否被其它客户端修改了。如果修改了,就放弃事务执行,避免事务的隔离性被破坏。
持久性
Redis 是内存数据库,所以,数据是否持久化保存完全取决于 Redis 的持久化配置模式。
如果 Redis 没有使用 RDB 或 AOF,那么事务的持久化属性肯定得不到保证。如果 Redis 使用了 RDB 模式,那么,在一个事务执行后,而下一次的 RDB 快照还未执行前,如果发生了实例宕机,这种情况下,事务修改的数据也是不能保证持久化的。如果 Redis 采用了 AOF 模式,因为 AOF 模式的三种配置选项 no、everysec 和 always 都会存在数据丢失的情况,所以,事务的持久性属性也还是得不到保证。
所以,不管 Redis 采用什么持久化模式,事务的持久性属性是得不到保证的。
建议
- 在使用事务时,建议配合 Pipeline 使用。因为事务的命令像客服端和服务器一来一回的发送信息大大的降低了效率。而PopeLine 是把所有命令打包好全部发送到服务端(并不是一次性全部发送过去,涉及到了网络窗口),服务端全部处理完成后返回。(减少了IO交换次数)