phpredis中的事务

1 篇文章 0 订阅

背景

事务的定义

事务指逻辑上的一组操作,组成这组操作的各个单元,要不全部成功,要不全部不成功。

redis中的事务

redis自带了乐观锁watch 和 可以实现悲观锁的setnx,watch提供了并发中依赖公共资源被修改的回滚,setnx则原子性的保证锁不会被其他进程或请求占用。具体的api 可以参考 这儿

实验

目的

常规的redis操作都是设置和返回的 单个请求,很少有需要多个api组合起来用的,这个算比较复杂的api,这里要验证 phpredis对事务的支持,和phpredis api的使用。分别对multi() Redis::MULTI or Redis::PIPELINE两种模式,和watch 和 没有watch时multi exec的影响。

工具

  1. wireshark
  2. php+phpredis 拓展
  3. redis-cli 和 一个redis服务端

步骤

一、multi + Redis::PIPELINE

    $conn = new \Redis();
    $conn->connect('192.168.1.9', 6379);
    $conn->multi(Redis::PIPELINE);
    $conn->set('a', 12);
    $conn->set('b', 13);
    $conn->get('a');
    $ret = $conn->exec();
    var_dump($ret);

wireshark抓包
wireshark服务端抓包,指定6379端口
返回

array(3) {
  [0] =>
  bool(true)
  [1] =>
  bool(true)
  [2] =>
  string(2) "12"
 }

结论:var_dump可以看到总共只发起了一次请求(SYN和FIN之间C向S发了一个包得到一个ACK,S向C发了一个结果得到了一个ack,实际只算一次信息交换来回四个包),返回也返回了一个包含所有结果的数组。实际上这个api 和 redis本身的multi没有关系,只是phpredis为了减少网络开销借用而已,redis本身就支持一次发送多个命令。

二、multi + Redis::MULTI

    $conn = new \Redis();
    $conn->connect('192.168.1.9', 6379);
    $conn->multi();
    $conn->set('a', 12);
    $conn->set('b', 13);
    $conn->get('a');
    $ret = $conn->exec();
    var_dump($ret);

wireshark抓包
wireshark服务端抓包,指定6379端口
返回

array(3) {
  [0] =>
  bool(true)
  [1] =>
  bool(true)
  [2] =>
  string(2) "12"
 }

结论:var_dump可以看出两次的返回是一摸一样的,但抓包的结果却大相径庭,总共5次请求(multi,3个get set,一个exec)来回,12个网络包,这个也和redis-cli里用multi和exec使用的效果是一致的。

三、multi + Redis::MULTI + 模拟其他进程修改变量

    $conn = new \Redis();
    $conn->connect('192.168.1.9', 6379);
    $conn->multi();
    $conn->set('a', 12);
    sleep(10); // 给时间偷偷用redis-cli修改 a 的值,这里改成了15
    $conn->set('b', 13);
    $conn->get('a');
    $ret = $conn->exec();
    print_r($ret);

返回

array(3) {
  [0] =>
  bool(true)
  [1] =>
  bool(true)
  [2] =>
  string(2) "12"
 }

结论:中间修改的15没成功,被exec时的覆盖了,说明单纯的multi和exec本身是没有任何事务特性的,只是exec提交的时候会一起执行(原子性的)。

四、watch + multi + Redis::MULTI + 模拟其他进程修改变量(multi之前)

    $conn = new \Redis();
    $conn->connect('192.168.1.9', 6379);
    $conn->watch('a');
    sleep(10); // 给时间偷偷用redis-cli修改 a 的值,这里改成了15
    $conn->multi();
    $conn->set('a', 12);
    $conn->set('b', 13);
    $conn->get('a');
    $ret = $conn->exec();
    print_r($ret);

返回:

array(3) {
  [0] =>
  bool(true)
  [1] =>
  bool(true)
  [2] =>
  string(2) "12"
}

结论:这里返回的是没有事务的结果,因为multi之后才发起了真正的原子性操作,但是因为multi和exec之间的get请求不会实时返回结果,通常multi之前读操作是必不可少的,但是在到multi之前变量被修改了,就算watch了也没有起到事务的作用,比如 抢使用watch秒杀 的例子中,就没有在获得mywatchkey时就watch,而是紧接着multi之前,但也因为这样就算获得了mywatchkey时还有库存,但如果watch之前修改了为没库存也会导致负数库存的出现。

五、watch + multi + Redis::MULTI + 模拟其他进程修改变量(multi之后)

    $conn = new \Redis();
    $conn->connect('192.168.1.9', 6379);
    $conn->watch('a');
    $conn->multi();
    $conn->set('a', 12);
    sleep(10); // 给时间偷偷用redis-cli修改 a 的值,这里改成了15
    $conn->set('b', 13);
    $conn->get('a');
    $ret = $conn->exec();
    print_r($ret);

返回

bool(false)

结论:因为watch 到 unwatch(由exec触发)的mutil和exec过程中watch 的 a被修改了返回了false,触发了事务,这里mutli没有用Redis::PIPELINE 因为PIPELINE模式并没有真正的发送multi 验证实际返回的也是设置成功的数组,没有发生事务。

总结

上面几种情况只有 第五种 才包含完成的事务(有二阶提交,可以回滚和重试),如果单纯用multi 用 PIPELINE 就够了(减少网络开销, 反正都是exec的时候才生效)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值