redis分布式锁 mysql事务_redis事务机制、分布式锁、数据一致性

redis中的事务是一组命令的集合。使用MULTI命令来开启事务,这期间的所有命令都会被放到一个命令队列里,使用EXEC命令来触发事务,将队列中的所有命令执行。

127.0.0.1:6379> multi

OK

127.0.0.1:6379> lpush nums 1

QUEUED

127.0.0.1:6379> lpush nums 3

QUEUED

127.0.0.1:6379> blpop num 0

QUEUED

127.0.0.1:6379> exec

1) (integer) 1

2) (integer) 2

3) (nil)

127.0.0.1:6379> lrange nums 0 -1

1) "3"

2) "1"

几个需要注意的地方:

redis的事务没有像mysql一样提供回滚(rollback),比较简洁,开发者需要自行做事务执行失败后处理;

如果在一个事务中的命令执行出现错误,那么所有命令都不会执行;

如果在一个事务中运行出现错误,那么正确的命令会被执行!!

MULTI和EXEC命令是一起执行的,所以并不能将一个命令的执行结果作为另一个命令的参数

WATCH、UNWATCH、DISCARD命令

redis提供了WATCH命令来保证原子性的操作,WATCH可以监控一个或多个键,如果在一个事务开启之前,一旦监控的键被修改或删除,之后的事务就不会再执行,监控会持续到EXEC命令执行完,被监控的键会自动UNWATCH。

127.0.0.1:6379> watch nums

OK

127.0.0.1:6379> lpush nums 6

(integer) 5

127.0.0.1:6379> multi

OK

127.0.0.1:6379> lpush nums 7

QUEUED

127.0.0.1:6379> exec

(nil) //执行事务失败

需要注意的是,WATCH监控开启后,监控的键没被修改或删除,在事务中是可以对监控的键进行修改的。

127.0.0.1:6379> watch mykey

OK

127.0.0.1:6379> multi

OK

127.0.0.1:6379> set mykey 4

QUEUED

127.0.0.1:6379> exec

1) OK

UNWATCH命令可以在WATCH命令执行之后、MULTI命令执行之前取消对某个键的监控

DISCARD命令可以在MULTI命令执行之后,EXEC命令执行之前,取消WATCH监控的键和情况命令队列,然后从事务状态中退出

分布式锁

redis的事务只会在数据被其他客户端抢先修改的情况,通知执行MULTI、EXEC这些命令的客户端,让它撤销对数据的修改操作,但是,并不能阻止其他客户端对数据进行修改,只能称之为乐观锁(optimistic locking)。所以其他客户端因为事务执行失败而进行不断的重试,当负载变大时,乐观锁就变得不那么完美了,这时需要使用redis实现分布式锁。

可以基于zookeeper来实现分布式锁,每个系统都通过zookeeper来获取分布式锁,保证同一时间只有一个系统实例在操作某个key,本文不详细介绍。

使用redis来实现分布式锁

使用setnx命令来争抢锁,使用del可以释放锁

127.0.0.1:6379> setnx lock-key 123456

(integer) 1 // 返回1上锁成功

127.0.0.1:6379> setnx lock-key 1234567

(integer) 0 // 返回0上锁失败,说明已被上锁

127.0.0.1:6379> get lock-key

"123456"

127.0.0.1:6379> del lock-key

(integer) 1

死锁

如果一个持有锁的客户端或进程崩溃或重启了,那么锁就成了死锁,解决办法是使用expire设置锁的超时时间,让redis自动去删除锁

127.0.0.1:6379> expire lock-key 3

(integer) 1

或者直接使用set命令来完成上锁和超时设置

127.0.0.1:6379> set lock-key 123456 EX 3

OK

语言层面的代码demo就不写了,原理一样,按照实际业务来写

使用到缓存,数据库双读双写,就有数据一致性的问题,如何解决呢?

对于大部分系统来说,是允许一小段时间出现缓存和数据库数据不一致的情况。如果系统是严格要求缓存和数据库数据必须一致,那么可以采用:读请求和写请求串行化,放到同一个队列中执行。

串行化可以保证数据一致性,但是同样系统的吞吐量就会降低,串行的队列并发量高,必然会有堵塞,反而会成为整个系统的瓶颈。所以不是必须,一般不推荐这么做。

经典的缓存和数据库读写模式

读的时候,先读缓存,未命中缓存,再读数据库,取出数据,更新缓存,并返回响应

更新缓存时,先更新数据库,再删除缓存

为什么是删除缓存,而不是更新缓存?

因为在很多复杂的缓存场景下,缓存不仅仅是直接从数据库里取出来的值,而是结合多个表的数据进行复杂计算后得到的值。所以只要这几个表相关字段只要有更新,难道就要更新一次缓存吗?而这个缓存还不一定会被用到,直接更新的意义不大,还会浪费大量开销。

实际的做法是,在用到这个缓存时,再去更新。而这个时候,缓存可能已经被删除了,从删除到实际用到这个缓存,这期间不需要更新,所以只要用到缓存计算一次更新就好。这也是一种懒加载的思想。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值