入一行,先别惦记着赚钱,先学着让自己值钱。没有哪个行业的钱是好赚的。赚不到钱,赚知识;赚不到知识,赚经历;赚不到经历,赚阅历;以上都赚到了就不可能赚不到钱。让人迷茫的原因只有一个,那就是本该拼搏的年纪,却想太多,做太少。 —— 褚时健
一、概念
- 乐观锁介绍
watch指令在redis事务中提供了CAS(check-and-set)的行为。为了检测被watch的keys在是否有多个clients同时改变引起冲突,这些keys将会被监控。如果至少有一个被监控的key在执行exec命令前被修改,整个事务将会回滚,不执行任何动作,从而保证原子性操作,并且执行exec会得到null的回复。
- 乐观锁工作机制
watch命令会监视给定的每一个key,当exec时如果监视的任一个key自从调用watch后发生过变化,则整个事务会回滚,不执行任何动作。
- watch的key是对整个连接有效的,事务也一样
- exec/discard/unwatch命令及客户端连接关闭都会清除连接中的所有监视
- 如果watch一个不稳定(有生命周期)的key并且此key自然过期,exec仍然会执行事务队列的指令
和MySQL对比
相关命令
- MySQL
- begin:开启一个事务
- commit:提交事务
- rollback:回滚事务
- Redis
- multi:标记事务的开始
- exec:执行事务的commands队列
- discard:结束事务并清除commands队列
- MySQL
默认状态
- MySQL会默认开启一个事务且自动提交(
set autocommit
=0/1来设置),不支持rollback - Redis默认不会开启事务,即command会立即执行而不会排队,不支持rollback
- MySQL会默认开启一个事务且自动提交(
实现原理
- MySQL
- MySQL事务基于undo/redo日志
- undo记录修改前状态,rollback基于undo日志实现
- redo记录修改后的状态,commit基于redo日志实现
- MySQL中无论是否开启事务,sql都会被立即执行并返回执行结果,只是事务开启后执行后的状态只是记录在redo日志,执行commit之后数据才会被写入磁盘
- Redis
- Redis事务基于commands队列,可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。
- 如果没有开启事务,command将会被立即执行并返回执行结果,并且直接写入磁盘
- 如果事务开启,command不会被立即执行而是排入队列,并返回排队状态
- MySQL
二、使用
- 无cas案例
客户端1 | 客户端2 | 说明 |
---|---|---|
>get name “张三” >get age “1” |
>get name “张三” >get age “1” |
连接两个客户端,获取age/name值 |
>multi “OK” >incr age “QUEUED” >set name 李四 “QUEUED” |
\ | 客户端1开启事务,执行命令: 1.age自增+1 2.name值改为李四 |
\ | >incr age “2” >set name 王五 “王五” |
客户端2执行命令,age自增+1,name改为王五 |
>exec 3 “OK” >get name “李四” >get age 3 |
\ | 客户端1提交事务,发现age的值为3(因为客户端2 执行了一次incr命令值变为了2),name为李四 (客户端2修改name的值被客户端1提交覆盖)。 |
- cas案例
客户端1 | 客户端2 | 说明 |
---|---|---|
>get name “张三” >get age “1” |
>get name “张三” >get age “1” |
连接两个客户端,获取age/name值 |
>watch age name “OK” >multi “OK” >incr age “QUEUED” >set name 李四 “QUEUED” |
\ | 客户端1通过watch命令监视age和name,开启事务,执行命令: 1.age自增+1 2.name值改为李四 |
\ | >incr age “2” >set name 王五 “王五” |
客户端2执行命令,age自增1,name改为王五 |
>exec >get name “王五” >get age 2 |
\ | 客户端1提交事务,watch监控发现此期间age和name被修改过,则让事整个务回滚,不执行命令 |
- PHP实现官方demo,跑了一遍个人不太理解
1 | require dirname(___FILE__). '/vendor/autoload.php'; |