Redis进阶 - Redis的原子性操作
Redis进阶 - Redis的原子性操作
一、概述
1.1 原子性
我们在使用 Redis 时,不可避免地会遇到并发访问的问题,比如说如果多个用户同时下单,就会对缓存在 Redis 中的商品库存并发更新。一旦有了并发写操作,数据就会被修改,如果我们没有对并发写请求做好控制,就可能导致数据被改错,影响到业务的正常使用(例如库存数据错误,导致下单异常)。为了保证并发访问的正确性,Redis 提供了两种方法,分别是加锁和原子操作。
加锁是一种常用的方法,在读取数据前,客户端需要先获得锁,否则就无法进行操作。当一个客户端获得锁后,就会一直持有这把锁,直到客户端完成数据更新,才释放这把锁。看上去好像是一种很好的方案,但是,其实这里会有两个问题:一个是如果加锁操作多,会降低系统的并发访问性能;第二个是 Redis 客户端要加锁时,需要用到分布式锁,而分布式锁实现复杂,需要用额外的存储系统来提供加解锁操作。
原子操作是另一种提供并发访问控制的方法,原子操作是指执行过程保持原子性的操作,而且原子操作执行时并不需要再加锁,实现了无锁操作。这样一来,既能保证并发控制,还能减少对系统并发性能的影响。
1.2 Redis 的原子操作
Redis的原子性操作指的是在执行过程中不会被其他操作打断,即操作要么完全执行成功,要么完全不执行,从而保证数据的一致性和可靠性。
二、Redis实现原子操作
Redis通过其内部机制和一些特定的命令来实现原子性操作,以下是Redis中一些常见的原子性操作及其描述。
2.1 单命令操作
Redis 是使用单线程来串行处理客户端的请求操作命令的,所以,当 Redis 执行某个命令操作时,其他命令是无法执行的,这相当于命令操作是互斥执行的。以下这些操作在Redis中是原子性的,因为它们都是由Redis的单个命令执行的,不会被其他操作打断。
命令 | 说明 |
---|---|
set | 设置指定key的值。 |
get | 获取指定key的值。 |
incr | 对指定key的值进行增加操作。 |
decr | 对指定key的值进行减少操作。 |
lpush | 将一个或多个值插入到列表的头部。 |
rpush | 将一个或多个值插入到列表的尾部。 |
lpop | 从列表的头部移除并返回一个元素。 |
rpop | 从列表的尾部移除并返回一个元素。 |
sadd | 向集合中添加一个或多个元素。 |
srem | 从集合中移除一个或多个元素。 |
hset | 设置哈希表中指定字段的值。 |
hdel | 删除哈希表中的一个或多个字段。 |
zadd | 将一个或多个成员元素及其分数值添加到有序集合中。 |
zrem | 从有序集合中移除一个或多个成员。 |
2.2 使用事务
Redis 支持将多个命令组合成一个事务来执行,通过MULTI、EXEC、WATCH和UNWATCH等命令来实现事务。其中,使用 MULTI 命令开始事务,然后依次执行多个命令,接着使用 EXEC 命令来按照顺序依次执行事务中的所有命令,WATCH 用于监视一个或多个键,当有其他客户端对被监视的键进行修改时,事务会被中断,UNWATCH 则用于取消监视。Redis事务的具体的实现步骤如下:
- 客户端向Redis发送MULTI命令,表示事务的开始。
- 服务器收到MULTI命令后,会将客户端的命令请求添加到一个队列中,而不是立即执行。
- 客户端继续发送多个命令请求,这些命令请求都会被添加到同一个队列中。
- 客户端发送EXEC命令,表示事务的执行。
- 服务器收到EXEC命令后,会依次执行队列中的命令。
- 服务器执行完所有命令后,将执行结果返回给客户端。
Redis 保证事务的原子性是通过将事务中的所有命令作为一个整体来执行,即在EXEC命令执行期间,不会处理其他客户端的命令请求。这样可以确保事务中的所有操作都是原子性的,如果事务中的任何一条命令失败,那么整个事务都会被回滚,即所有命令都不会被执行。
2.3 使用Lua脚本
Lua 本身并没有提供对于原子性的直接支持,它只是一种脚本语言,通常是嵌入到其他宿主程序中运行。Redis 2.6及更高版本支持使用Lua脚本来执行多个命令,并且这些命令在Lua脚本中是原子性执行的。这意味着在Lua脚本中执行的所有Redis命令都会作为一个整体来执行,不会被其他操作打断。Redis会在执行脚本期间锁住数据库,确保其他客户端的操作不会干扰。
2.4 特定命令
你可能也注意到了,虽然 Redis 的单个命令操作可以原子性地执行,但是在实际应用中,数据修改时可能包含多个操作,至少包括读数据、数据增减、写回数据三个操作,这显然就不是单个命令操作了,那该怎么办呢?别担心,Redis 提供了几种命令
命令 | 说明 |
---|---|
INCR | 对数据进行增值操作 |
DECR | 对数据进行减值操作 |
SETNX | 用于设置一个键的值,但仅在该键不存在时才执行。这是一个原子性操作,它会检查键是否存在,并在不存在时设置值。 |
GETSET | 用于设置一个键的新值,并返回该键的旧值。这是一个原子性操作,它在设置新值的同时返回旧值,且这个过程不会被其他操作打断。 |
三、结语
总之,Redis通过其内部机制、事务、Lua脚本和特定命令等方式来支持原子性操作,从而确保数据的一致性和可靠性。需要注意的是,虽然Redis提供了一些原子性操作的机制,但并不是所有的操作都是原子的。例如,修改一个字符串类型的值时,如果使用GET和SET命令分别获取和修改,可能会导致中间发生了其他操作,从而导致不是原子性的。因此,在实现原子操作时,需要根据具体情况选择合适的方式。