Redis中的事务

70 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第17天,点击查看活动详情

事务

事务也是一个老生常谈的话题了,这里我们就不说事务的概念了,直接来看看redis中的事务。

\

比如这样的一个现象:

127.0.0.1:6380> set name zhangsan
OK
127.0.0.1:6380> get name
"lisi"

该客户端设置了一个数据,key为name,值为zhangsan,而在获取的时候却得到了lisi,这是为什么呢?

原来,在该客户端获取数据之前,又有别的客户端抢在了它前面修改了数据,由此导致了这样的问题发生,为此,我们需要使用事务控制来避免这一问题:

127.0.0.1:6380> multi
OK
127.0.0.1:6380> set name zhangsan
QUEUED
127.0.0.1:6380> get name
QUEUED
127.0.0.1:6380> exec
1) OK
2) "zhangsan"

首先通过 multi 指令开启事务,然后添加数据,在获取数据之前同样另一个客户端修改了数据,最后执行 exec 指令提交事务,redis便会输出在这次事务中所有指令的结果,可以看到数据并没有被别的客户端修改。

\

当在事务过程中执行了错误的指令时,我们可以使用 discard 指令来取消此时事务,取消之后事务中的所有指令操作都将失效。

而且在事务过程中,执行了语法错误的指令,比如 set 打成了 sat ,redis会自动帮助我们取消事务,又比如对string类型的数据执行 incr操作,redis会自动执行事务中正确的操作指令,并取消执行那些错误的指令。

假设有这样一个需求,天猫双11热卖过程中,需要对已经售罄的商品进行补货,有4个业务员都拥有补货的权限,补货这一过程也涉及到多个操作,那么如何保证这些操作不会重复进行呢?

\

使用 watch指令可以监控某个数据,当数据发生变化时取消所有操作,比如:

set name zs
watch name
multi
set age 20
exec

在这组指令中,首先添加了一个数据,然后使用watch监视了name,随之开启事务,并在事务中添加了另一个数据,但是在提交事务之前,别的客户端修改了name数据,此时再提交事务便会输出nil:

127.0.0.1:6380> set name zs
OK
127.0.0.1:6380> watch name
OK
127.0.0.1:6380> multi
OK
127.0.0.1:6380> get name
QUEUED
127.0.0.1:6380> set age 20
QUEUED
127.0.0.1:6380> exec
(nil)
127.0.0.1:6380> keys *
1) "name"

而且添加的数据age也不存在了,需要注意的是watch操作必须在事务开启之前执行,若在事务中执行则会报错。

通过 unwatch 指令可以取消所有数据的监控。

分布式锁

继续看一个场景,双11的网站流量是非常巨大的,一些商家也会在双11推出一些秒杀活动,当然了,秒杀的商品是有数量限制的,比如说,某个手机厂商设置了100个苹果手机用于秒杀活动,而事实上,参与此次秒杀的人是非常多的,它将远远大于手机数量,那么怎么保证商品不会出现超卖的现象,即:最后一件商品不会被多个人同时购买,此时用刚才的watch指令已经无法解决这个问题了,我们需要使用——分布式锁。

\

使用 setnx指令设置一个分布式锁:

setnx lock-key value

比如这样的一组指令:

127.0.0.1:6380> setnx lock-num true
(integer) 1
127.0.0.1:6380> incrby num -1
(integer) 9
127.0.0.1:6380> del lock-num
(integer) 1

这是将商品库存减1的正常流程,首先设置一个lock-num锁,值是无所谓的,然后将库存减1,完成操作后删除锁,假如该客户端在减库存操作完成之前别的客户端也进行减库存操作,则会出现:

127.0.0.1:6380> setnx lock-num true
(integer) 0

这是setnx指令的特性所导致的,setnx指令会判断当前数据库中是否存在lock-num键,若存在则返回0,此时说明别的客户端正在修改库存,那么当前客户端就应该进入等待状态或者做别的操作,只有别的客户端执行完减库存操作并删除了锁之后,setnx就返回了1,这时候就可以正常进行减库存操作了。

死锁

在分布式锁的基础上可能会出现死锁的问题,当客户端添加了分布式锁进行操作后,在删除锁之前出现了突发情况,比如断电、宕机等问题,此时就无法释放该锁,导致别的客户端一直在等待锁的释放。

\

我们可以使用 expire 指令为分布式锁设置一个有效时间,当有效时间过后redis便会自动删除该锁,这样就解决了死锁问题:

expire lock-num 60

当60秒时间过后,锁会被自动删除。