redis 事务
首先 redis 单个命令是原子性的 因为单线程
Redis客户端对服务端的每次调用都经历了发送命令,执行命令,返回结果三个过程。其中执行命令阶段,由于Redis是单线程来处理命令的,所有到达服务端的命令都不会立刻执行,所有的命令都会进入一个队列中,然后逐个执行,并且多个客户端发送的命令的执行顺序是不确定的,但是可以确定的是不会有两条命令被同时执行,不会产生并发问题,这就是Redis的单线程基本模型。
关系型数据库 比如 mysql存在隔离级别设置 (默认值:repeatable-read)
1、尽可能使用 已有的命令(原子性) 如 INCR、ZPOPMAX、ZPOPMIN
2、Lua脚本
https://redisbook.readthedocs.io/en/latest/feature/scripting.html
redis lua脚本的好处
①减少网络开销。可以将多个请求通过脚本的形式一次发送,减少网络延迟。
②原子操作。redis将整个脚本当做一个整体去执行,中间不会被其他命令插入。因此无需担心脚本执行过程中会出现竞态条件
③复用。客户端发送的脚本会永久保存在redis中,这样,可以复用这一脚本而不用使用代码完成相同的逻辑。
3、watch指令
类似于乐观锁,在事务提交时,如果watch监控的多个KEY中任何KEY的值已经被其他客户端更改,则使用EXEC执行事务时,事务队列将不会被执行,同时返回Nullmulti-bulk应答以通知调用者事务执行失败。
自动的递增一个key
如果在同一时间,只有一个客户端在执行上面的操作,那么上面的操作是可靠的。但如果同时有多个用户执行上面的操作的话,那结果就是不可靠的了
1、INCR命令
2、我们可以使用 WATCH 来很好的解决这个问题:
使用以上的代码,当有另一个用户在我们调用WATCH 和 EXEC之间,修改了mykey的值val,那么这个事务就会失败。这种形式的锁称为乐观锁,是一种非常强大的锁。
WATCH 说明
WATCH 到底是什么意思呢? 这个命令使得 EXEC 命令的执行必须满足一个条件:如果被WATCH的 keys 没有一个被更改(但它们可以在事务中被修改),则执行事务;不然,就不会执行这个事务。(注意,如果你 WATCH了一个有生命周期的key,并且这个key过期了, EXEC 依然会执行)
WATCH 可以被多次调用。所有的WATCH 调用都会在 EXEC 调用之前起作用。WATCH可以接收任意多的key 。
当 EXEC 被调用后, 所有的keys都将UNWATCH,不管这个事务会不会终止。同样,当一个客户端链接关闭后, 该客户端对键的监视也会被取消(UNWATCH)。
可以使用UNWATCH (没有参数)命令来刷新所有被WATCH的keys。有时会这样操作,我们乐观地锁定了几个keys,因为可能我们需要执行一个事务来修改这些keys,但是在读取了keys的当前内容之后,我们不想继续处理了。那么这个时候,我们就可以调用UNWATCH。
https://mp.weixin.qq.com/s/8fdBKAyHZrfHmSajXT_dnA
set(key,1,30,NX)
这样就可以取代setnx指令
这个过期时间
1 不设置 不行 锁可能得不到释放
2 设置了 如果你执行很慢 其实你的锁 已经被释放掉了 如果有人抢到了 就出现竞态 还有就是 复杂 耗时 逻辑在加锁前 处理好 数据准备好 尽量避免 超时锁 关闭
多条命令 尽可能优先使用lua脚本 省得出乱七八糟的问题
出现并发的可能性
还是刚才第二点所描述的场景,虽然我们避免了线程A误删掉key的情况,但是同一时间有A,B两个线程在访问代码块,仍然是不完美的。
怎么办呢?我们可以让获得锁的线程开启一个守护线程,用来给快要过期的锁“续航”
当过去了29秒,线程A还没执行完,这时候守护线程会执行expire指令,为这把锁“续命”20秒。守护线程从第29秒开始执行,每20秒执行一次。
当线程A执行完任务,会显式关掉守护线程。
另一种情况,如果节点1 忽然断电,由于线程A和守护线程在同一个进程,守护线程也会停下。这把锁到了超时的时候,没人给它续命,也就自动释放了。
https://www.runoob.com/redis/redis-transactions.html
http://doc.redisfans.com/topic/transaction.html#watch-zpop
https://baijiahao.baidu.com/s?id=1613631210471699441&wfr=spider&for=pc