百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT技术 > 正文

PHP结合Redis实现乐观锁,防止你正在使用的数据被偷偷篡改

wptr33 2024-12-22 21:12 48 浏览

乐观锁顾名思义就是在读取数据的时候保持乐观,不对数据加锁,写的时候判断数据是否被改变,如果被改变则重新尝试。

悲观锁就是在读取数据的时候就对数据加锁,修改完成释放锁。

乐观锁适用于读多的场景,悲观锁适用于写多的场景。


Redis是单线程的服务,不会有命令并行执行的情况。那为什么还会需要使用乐观锁呢。

Redis为什么要使用乐观锁

在大并发量的情况下,Redis-client有多个,每个 Redis-client 发送过来的命令是有序的,但是多个Redis-client之间命令的执行是无法预料的。如以下的场景。

客户端

clientA

clientB

时间

lock_A = 1


get lock_A = 1



get lock_A = 1


set lock_A = 2 (进行+1操作)

set lock_A = 2 (进行+1操作)

上面的例子,前置条件 lock_A = 1,每个 Redis-client 请求处理将 lock_A 加1,显然两个 Redis-client 执行之后结果应该为 3, 但是上面的例子结果为 2,不符合预期。我们需要如何优化达到我们的预期呢。这时候我们需要基于Redis实现乐观锁。


乐观锁实现

这时候有人可能会想到直接使用 incr 就可以,这个当然没问题,但是我们现在是使用乐观锁的方式解决问题。我们来看下优化后的方案。

客户端

clientA

clientB

时间

(lock_A = 1)


get lock_A = 1


watch lock_A


multi



get lock_A = 1


set lock_A = 2 (进行+1操作)

set lock_A = 2 (进行+1操作)


exec (这里会返回失败)


get lock_A = 2 (重试)


watch lock_A


multi


set lock_A = 3 (进行+1操作)


exec


unwatch

这里主要是通过watch/unwatch、redis事务、循环处理来实现乐观锁。步骤如下:

  1. 在执行操作之前先通过 watch 监控键的变化。
  2. 开启事务。
  3. 执行相关操作进入队列。
  4. 提交事务。
  5. 成功则调用 unwatch 取消监控,失败则重试

模拟以上的操作在两个 Redis-client 的执行过程。

上面截图,按照1 - 6 的命令顺序执行可以复现整个过程。

PHP代码测试

接下来我们使用PHP代码,配合压力测试来复现/解决问题。

设置 lock_A = 1,并发 100,执行1000次请求,预期结果为 1001。分别使用常规方法和乐观锁方法实现并测试。

常规方法

代码

<?php
//连接本地的 Redis 服务
$redis = new Redis();
$redis->connect('redis', 6379);

// 定义键常量
const LOCKKEY = "lock_A";
$lock_A = $redis->get(LOCKKEY);
++$lock_A;
$redis->set(LOCKKEY, $lock_A);

压力测试

ab -n 1000 -c 100 http://localhost:8080/ceshi.php

结果

这里执行结果为:850 ,不符合预期


乐观锁

代码

<?php
//连接本地的 Redis 服务
$redis = new Redis();
$redis->connect('redis', 6379);

// 定义键常量
const LOCKKEY = "lock_A";

while (true) {
    // 监控,一定要放在循环里面,exec之后会取消掉这个 watch
    $redis->watch(LOCKKEY);
    $lock_A = $redis->get(LOCKKEY);
    ++$lock_A;
  	// 开启事务
    $pipline = $redis->multi();
    $pipline = $pipline->set(LOCKKEY, $lock_A);
  	// 执行事务
    $re = $pipline->exec();
    if ($re === false) {
      	// 失败回滚
        $pipline->discard();
    } else {
      	// 成功取消监控
        $redis->unwatch();
        break;
    }
}

压力测试

ab -n 1000 -c 100 http://localhost:8080/ceshi.php

结果

这里执行结果为:1001 ,符合预期

相关推荐

开发者必看的八大Material Design开源项目

MaterialDesign是介于拟物和扁平之间的一种设计风格,自从它发布以来,便引起了很多开发者的关注,在这里小编介绍在Android开发者当中里最受青睐的八个MaterialDesign开源项...

另类插这么可爱,一定是…(另类t恤)

IT之家(www.ithome.com):另类插图:这么可爱,一定是…OSXMavericks和Yosemite打破了苹果对Mac操作系统传统的命名方式,使用加州的某些标志性景点来替换猫...

Android常用ADB命令(安卓adb工具是什么)

杀死应用①根据包名获取APP的PIDadbshellps|grep应用包名②执行kill命令...

微软Mac版PowerPoint测试Reading Order Pane功能

IT之家5月20日消息,微软公司昨日(5月19日)发布博文,邀请Microsoft365Insiders成员,测试macOS新版PowerPoint演示文稿应用,重点引入...

Visual Studio跨平台开发实战(4):Xamarin Android控制项介绍

前言不同于iOS,Xamarin在VisualStudio中针对Android,可以直接设计使用者界面.在本篇教学文章中,笔者会针对Android的专案目录结构以及基本控制项进行介绍,包...

用云存储30分钟快速搭建APP,你信吗?

背景不管你承认与否,移动互联的时代已经到来,这是一个移动互联的时代,手机已经是当今世界上引领潮流的趋势,大型的全球化企业和中小企业都把APP程序开发纳入到他们的企业发展策略当中。但随着手机APP上传的...

谷歌P图神器来了!不用学不用教,输入一句话,分分钟给结果

Pine发自凹非寺量子位|公众号QbitAI当你拍照片时,“模特不好好配合”怎么办?...

iOS文本编辑控件UITextField和UITextVie

记录一个菜鸟的IOS学习之旅,如能帮助正在学习的你,亦枫不胜荣幸;如路过的大神如指教几句,亦枫感激涕淋!细心的朋友可能已经注意到了,IOS学习之旅系列教程在本篇公众号的文章中,封面已经换成美女图片了,...

Android入门图文教程集锦(android 入门教程)

Android入门视频教程集锦AndroidStudio错误gradientandroid:endXattributenotfound...

如何使用Android自定义复合视图(如何使用android自定义复合视图)

在最近的一个客户应用中,我遇到了一个需求,根据选定的值来生成指定数量的编辑框字段,这样用户可以输入人物信息。最初我的想法是把这些逻辑放到Fragment中,只是根据选中值的变化来向线性布局容器中增加编...

原生安卓开发app的框架frida常用关键代码定位

前言有时候可能会对APP进行字符串加密等操作,这样的话你的变量名等一些都被混淆了,看代码就可能无从下手...

教程10 | 三分钟搞定一个智能输入法程序

一案例描述1、考核知识点网格布局线性布局样式和主题Toast2、练习目标掌握网格布局的使用掌握Toast的使用掌握线性布局的使用...

(Android 8.1) 功能与新特性(android的功能)

和你一起终身学习,这里是程序员AndroidAndroid8.1(API级别27)为用户和开发人员引入了各种新特性和功能。本文档重点介绍了开发人员的新功能。通过本章阅读,您将获取到以下内容:Andr...

怎样设置EditText内部文字被锁定不可删除和修改

在做项目的时候,我曾经遇到过这样的要求,就是跟百度贴吧客户端上的一样,在回复帖子的时候,在EditText中显示回复人的名字,而且这个名字不可以修改和删除,说白了就是不可操作,只能在后面输入内容。在E...

如何阻止 Android 活动启动时 EditText 获得焦点

技术背景在Android开发中,当活动启动时,EditText有时会自动获得焦点并弹出虚拟键盘,这可能不是用户期望的行为。为了提升用户体验,我们需要阻止...