redis事务详解
mysql中也存在事务的概念。其实事务的定义是一样的。一组操作的集合,作为一个整体,要么全执行,要么全不执行。
redis设置事务三步骤:
- 开始事务 :multi
- 操作加入事务队列
- 执行事务 :exec
事务意味着 两个意思
- 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断,这个很重要。
- 事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。
事务的好处就是可以保证 一组操有顺序执行,这个过程中不会插入其他操作。例如抽奖防止抽超过库存这一问题,当抽奖得到某一个奖品时,会先去取库存,看是不是大于0,如果大于0,我们再去把库存减一,然后返回。那么如何保证从取出库存到库存减一的这个过程中不插入其他操作呢。事务这个时候就能解决。(当然decr也能解决,而且更好)
用php模拟实现这个动作
$con->multi(); $a = $con->get('a'); $con->set('a',$a+1); $con->get('a'); $res = $con->exec(); var_dump($res);
这个事务理论上能实现这个操作,但是实际上不会的。由于加入事务队列的操作不会立即执行,所以依赖于上一步的结果而进行的操作会有问题。
a 的值是3,返回如下:
$res = array(3) {
[0]=>
string(1) "3"
[1]=>
bool(true)
[2]=>
string(1) "2"
}
事务中的$a 并没有获得返回,得到了一个object,下次计算当成数字1计算,所以最后永远返回2
所以我们看到,当事务中的操作存在依赖关系时,完全使用事务并不合适。当然,在不存在依赖关系的用这个事务步骤十分可行。如关注和被关注两个操作。
关注发生时,follow(a,b),follow(b,a)这两个 放到一个事务中。
那么如果事务中出现依赖关系怎么办呢?就是上一个问题怎么解决呢?这个请出事务系列的另一个方法watch
watch 的作用是监控一个或者多个键,监控之后,到事务开始这段时间内,若有一个及以上的键值发生了改变或删除(改变可能发生了多次,可能是不同的客户端进行的修改),事务不会执行。
加入事务队列的操作无法立即获取返回结果,所以依赖上一步结果进行下一步操作这样的事务是无法进行的,因为上一步的结果无法在实务中立即获取。
解决方法是 在事务开始前获取结果,然后监控这个key,再开始事务。
如果要想实现整个操作,还是要重新执行整个过程。
对于高并发,这么做并不合适,在watch值之后到multi开始这段时间内,很容易发生变化,导致不执行。
$con->watch('a');
$a = $con->get('a');
$con->multi();
$con->set('a',$a+1);
$con->get('a');
$res = $con->exec();
var_dump($res)
这个能返回想要的结果,最开始服务器中a =>1
array(2) {
[0]=>
bool(true)
[1]=>
string(1) "2"
}
获得了 +1 之后的结果。
$a = $con->get('a');
$con->multi();
这两步之间,若其他的客户端对a进行了改动,那么后面的事务不会执行的。如果想我们的事务执行,上面的那段代码必须重复执行,直到exec动作执行
<?php class RedisTest{ private $redis_host = '192.168.211.129'; private $redis_port = '6379'; private $redis_timeout = '5'; private $redis_auth = 'foobared'; private $con; public function __construct(){ $this->con = new Redis(); $this->con->connect($this->redis_host,$this->redis_port,$this->redis_port); $this->con->auth($this->redis_auth); } /* * 模拟实现初始化某个值的动作。不存在是赋值,存在则不做改变 */ public function setXX($key,$val){ $this->con->watch($key); $res = $this->con->get($key); if($res === false){ $this->con->multi(); $this->con->set($key,$val); $this->con->get($key); $restult = $this->con->exec(); }else{ $restult = $res; $this->con->unwatch();//watch监控的key只有在exec执行完才释放,所以unwatch取消监控 } return $restult; } //模拟实现redis的incr操作 public function incr($key){ $this->con->watch($key); $val = $this->con->get($key); $this->con->multi(); $this->con->set($key,$val+1); $this->con->get($key); $res = $this->con->exec(); if(empty($res)){ $this->incr($key);//如果exec没有执行,则这个动作不停执行,知道执行为止。在并发高的系统中,这个并不合适 } return $res; } public function __destruct(){ $this->con->close(); } } class Client{ public function main(){ $obj = new RedisTest(); $res = $obj->setXX('a', 10); $res = $obj->incr('a'); var_dump($res); } } $obj = new Client(); $obj->main();