乐观锁是一种不会阻塞其他线程并发的机制,它不会使用数据库的锁进行实现,它的设计里面由于不阻塞其他线程,所以并不会引发线程频繁挂起和恢复,这样便能够提高并发能力,所以也有人把它称为非阻塞锁,那么它的机制是怎么样的呢?

乐观锁使用的是 CAS 原理,所以我们先来讨论 CAS 原理的内容。

CAS 原理概述

在 CAS 原理中,对于多个线程共同的资源,先保存一个旧值(Old Value),比如进入线程后,查询当前存量为 100 个红包,那么先把旧值保存为 100,然后经过一定的逻辑处理。

当需要扣减红包的时候,先比较数据库当前的值和旧值是否一致,如果一致则进行扣减红包的操作,否则就认为它已经被其他线程修改过了,不再进行操作,CAS 原理流程如图 1 所示。

CAS 原理并不排斥并发,也不独占资源,只是在线程开始阶段就读入线程共享数据,保存为旧值。当处理完逻辑,需要更新数据的时候,会进行一次比较,即比较各个线程当前共享的数据是否和旧值保持一致。

如果一致,就开始更新数据;如果不一致,则认为该数据已经被其他线程修改了,那么就不再更新数据,可以考虑重试或者放弃。有时候可以重试,这样就是一个可重入锁,但是 CAS 原理会有一个问题,那就是 ABA 问题,下面先来讨论一下 ABA 问题。

ABA 问题

对于乐观锁而言,我们之前讨论了存在 ABA 的问题,那么什么是 ABA 问题呢?下面看看表 1 的两个线程发生的场景。

在 T3 时刻,由于线程 2 修改了 X=B,此时线程 1 的业务逻辑依旧执行,但是到了 T5 时刻,线程 2 又把 X 还原为 A,那么到了 T6 时刻,使用 CAS 原理的旧值判断,线程 1 就会认为 X 值没有被修改过,于是执行了更新。

我们难以判定的是在 T4 时刻,线程 1 在 X=B 的时候,对于线程 1 的业务逻辑是否正确的问题。由于 X 在线程 2 中的值改变的过程为 A->B->A,才引发这样的问题,因此人们形象地把这类问题称为 ABA 问题。

ABA 问题的发生,是因为业务逻辑存在回退的可能性。如果加入一个非业务逻辑的属性,比如在一个数据中加入版本号(version),对于版本号有一个约定,就是只要修改 X 变量的数据,强制版本号(version)只能递增,而不会回退,即使是其他业务数据回退,它也会递增,那么 ABA 问题就解决了,如表 2 所示。

只是这个 version 变量并不存在什么业务逻辑,只是为了记录更新次数,只能递增,帮助我们克服 ABA 问题罢了,有了这些理论,我们就可以开始使用乐观锁来完成抢红包业务了。

但是这样会导致一个新的问题,就是高并发的情况下失败率比较高。所以目前流行的重入会加入两种限制,一种是按时间戳的重入,也就是在一定时间戳内(比如说 100 毫秒),不成功的会循环到成功为止,直至超过时间戳,不成功才会退出,返回失败。

乐观锁重入机制

因为乐观锁造成大量更新失败的问题,使用时间戳执行乐观锁重入,是一种提高成功率的方法,比如考虑在 100 毫秒内允许重入,把 UserRedPacketServiceImpl 中的方法 grapRedPacketForVersion 修改为以下代码。

@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)

public int grapRedPacketForVersion(Long redPacketId, Long userId) {

// 记录开始时间

long start = System.currentTimeMillis();

// 无限循环,等待成功或者时间满100亳秒退岀

while (true) {

// 获取循环当前时间

long end = System.currentTimeMillis();

// 当前时间己经超过100毫秒,返回失败

if (end - start > 100) {

return FAILED;

}

// 获取红包信息,注意version值

RedPacket redPacket = redPacketDao.getRedPacket(redPacketId);

// 当前小红包库存大于0

if (redPacket.getStock() > 0) {

// 再次传入线程保存的version旧值给SQL判断,是否有其他线程修改过数据

int update = redPacketDao.decreaseRedPacketForVersion(redPacketId, redPacket.getVersion());

// 如果没有数据更新,则说明其他线程已经修改过数据,则重新抢夺

if (update == 0) {

continue;

}

// 生成抢红包信息

UserRedPacket UserRedPacket = new UserRedPacket();

UserRedPacket.setRedPacketId(redPacketId);

UserRedPacket.setUserId(userId);

UserRedPacket.setAmount(redPacket.getUnitAmount());

UserRedPacket.setNote("抢红包" + redPacketId);

// 插入抢红包信息

int result = userRedPacketDao.grapRedPacket(UserRedPacket);

return result;

} else {

// 一旦没有库存,则马上返回

return FAILED;

}

}

}

当因为版本号原因更新失败后,会重新尝试抢夺红包,但是会实现判断时间戳,如果时间戳在 100 毫秒内,就继续,否则就不再重新尝试,而判定失败,这样可以避免过多的 SQL 执行,维持系统稳定。乐观锁按时间戳重入 。

但是有时候时间戳并不是那么稳定,也会随着系统的空闲或者繁忙导致重试次数不一。

有时候我们也会考虑限制重试次数

通过 for 循环限定重试 3 次,3 次过后无论成败都会判定为失败而退出,这样就能避免过多的重试导致过多 SQL 被执行的问题

Redis乐观锁详解及应用

在Redis的事务中,WATCH命令可用于提供CAS(check-and-set)功能。假设通过WATCH命令在事务执行之前监控了某个key,倘若在WATCH之后Key的值发生了变化,EXEC命令执行的事务将被放弃,同时返回nil以通知调用者事务执行失败:

redis> SET key 1

OK

redis> WATCH key

OK

redis> SET key 2

OK

redis> MULTI

OK

redis> SET key 3

QUEUED

redis> EXEC

(nil)

redis> GET key

"2"

因此,借用redis使用watch可以完成秒杀抢购功能,使用redis中两个key完成秒杀抢购功能,mywatchkey用于存储抢购数量和mywatchlist用户存储抢购列表。

php示例代码:

$redis = new redis();

$result = $redis->connect('127.0.0.1', 6379);

$mywatchkey = $redis->get("mywatchkey");

$rob_total = 100; //抢购数量

if($mywatchkey

$redis->watch("mywatchkey");

$redis->multi();

//设置延迟,方便测试效果。

sleep(5);

//插入抢购数据

$redis->hSet("mywatchlist","user_id_".mt_rand(1, 9999),time());

$redis->set("mywatchkey",$mywatchkey+1);

$rob_result = $redis->exec();

if($rob_result){

$mywatchlist = $redis->hGetAll("mywatchlist");

echo "抢购成功!
";

echo "剩余数量:".($rob_total-$mywatchkey-1)."
";

echo "用户列表:

";

var_dump($mywatchlist); //打印抢购成功用户

}else{

echo "手气不好,再抢购!";exit;

}

}

java抢红包并发问题,Redis乐观锁解决高并发抢红包的问题【redis】相关推荐

  1. mysql乐观锁 秒杀_使用数据库乐观锁解决高并发秒杀问题,以及如何模拟高并发的场景,CyclicBarrier和CountDownLatch类的用法...

    数据库:mysql 数据库的乐观锁:一般通过数据表加version来实现,相对于悲观锁的话,更能省数据库性能,废话不多说,直接看代码 第一步: 建立数据库表: CREATE TABLE `skill_ ...

  2. 利用Redis锁解决高并发问题

    利用Redis锁解决高并发问题 参考文章: (1)利用Redis锁解决高并发问题 (2)https://www.cnblogs.com/yszr/p/11698696.html 备忘一下.

  3. Redis锁解决高并发问题

    Redis锁解决高并发问题 redis真的是一个很好的技术,它可以很好的在一定程度上解决网站一瞬间的并发量,例如商品抢购秒杀等活动. redis之所以能解决高并发的原因是它可以直接访问内存,而以往我们 ...

  4. 猿创征文 | 微服务 Spring Boot 整合Redis 实战开发解决高并发数据缓存

    文章目录 一.什么是 缓存? ⛅为什么用缓存? ⚡如何使用缓存 二.实现一个商家缓存 ⌛环境搭建 ♨️核心源码 ✅测试接口 三.采用 微服务 Spring Boot 注解开启缓存 ✂️@CacheEn ...

  5. Redis(十)redis使用list解决高并发问题,如商品秒杀

    redis真的是一个很好的技术,它可以很好的在一定程度上解决网站一瞬间的并发量,例如商品抢购秒杀等活动. redis之所以能解决高并发的原因是它可以直接访问内存,而以往我们用的是数据库(硬盘),提高了 ...

  6. PHP利用Mysql锁解决高并发

    前面写过利用文件锁来处理高并发的问题的,现在我们说另外一个处理方式,利用Mysql的锁来解决高并发的问题 先看没有利用事务的时候并发的后果 创建库存管理表 CREATE TABLE `storage` ...

  7. (13) 悲观锁和乐观锁解决hibernate并发(转)

    前言:  做项目时由于业务逻辑的需要,必须对数据表的一行或多行加入行锁,举个最简单的例子,图书借阅系统.假设 id=1 的这本书库存为 1 ,但是有 2 个人同时来借这本书,此处的逻辑为 Select ...

  8. 数据库并发抢红包_Redis悲观锁解决高并发抢红包的问题

    悲观锁是一种利用数据库内部机制提供的锁的方法,也就是对更新的数据加锁,这样在并发期间一旦有一个事务持有了数据库记录的锁,其他的线程将不能再对数据进行更新了,这就是悲观锁的实现方式. 首先在 RedPa ...

  9. mysql 高并发写入锁表_使用mysql中的锁解决高并发问题

    阿里云产品通用代金券,最高可领1888分享一波阿里云红包. 阿里云的购买入口 为什么要加锁 多核计算机的出现,计算机实现真正并行计算,可以在同一时刻,执行多个任务.在多线程编程中,因为线程执行顺序不可 ...

  10. Zookeeper锁实现分布式锁解决高并发高可用秒杀系统

    抢购前数据 库存表 订单表 zookeeper配置类 /*** @Author yqq* @Date 2022/05/30 23:24* @Version 1.0*/ @Configuration p ...

最新文章

  1. Flask 数据迁移 报错 Table 'xxx' is already defined for this MetaData instance
  2. python打包加版本信息_使用pyi-set_version为PyInstaller打包出来的程序附加版本信息...
  3. css3自适应布局单位vw,vh你知道多少?
  4. Runtime error 216 at xxx 故障解决一例
  5. ML《集成学习(四)Boosting之回归树、提升树和GBDT》
  6. Android性能优化系列---管理你的app内存(一)
  7. VTD的官方help翻译-ROD部分(10~15章)
  8. 微软应用商店应用无法联网_微软,诺基亚应用商店-即将开业!
  9. Hash表素数大集合
  10. 权力来自于他人的服从
  11. 汽车自动驾驶级别分类
  12. 服务器虚拟化解锁方案,RX 5700乐成刷入RX 5700 XT BIOS:频率、功耗双双解锁
  13. vin码识别(车架号识别)SDK的应用
  14. 【Linux C】进程、线程和进程间通信
  15. 基于springboot开发的停车场管理系统-计算机毕业设计
  16. python筛选包含特定值的行_使用pandas筛选出指定列值所对应的行
  17. 以汽车制造业为例,细说制造企业如何实施PLM系统项目?
  18. 核磁共振、顺磁共振、磁共振成像这些原理你都了解吗
  19. 用C/C++实现输入IQ数据,计算时差(IQ数据互相关),完成TDOA定位(Chan算法)
  20. Failed to start LSB: Bring up/down networking

热门文章

  1. c语言maxval函数,fortran语言常用函数
  2. 利用Python从数据分析的角度告诉你NBA2018-2019常规赛季为什么字母哥比哈登强?
  3. Maven中dependencyManagement标签的作用
  4. word2003流程图变成图片_怎样将word文档的部分内容(流程图)制作成图片 详细始末...
  5. 国培计算机网络技术培训心得,2017国培学习心得体会及感受
  6. 2022软件测试好学吗,大概要学多久?(附学习路线图)
  7. C1041: 无法打开程序数据库“xxx.pdb”;如果要将多个 CL.EXE 写入同一个 .PDB 文件,请使用 /FS
  8. ecap捕捉epwm波形的占空比及频率(总结)
  9. TMS320C6748_ECAP_APWM
  10. connect: cannot assign requested address