当前位置: 首页 > news >正文

郑州最好的装修设计公司怎么优化关键词

郑州最好的装修设计公司,怎么优化关键词,网站html设置首页,python做网站好吗4.0 问题 4.0.1 事务失效 1. 事务失效问题 当在同一个类中调用带有 Transactional 注解的方法时,Spring 的事务机制可能会失效。这是因为: Spring 的事务管理是通过 AOP(面向切面编程)实现的当一个方法内部直接调用同类中的另…

4.0 问题

4.0.1 事务失效

  1. 1. 事务失效问题

当在同一个类中调用带有 @Transactional 注解的方法时,Spring 的事务机制可能会失效。这是因为:

  • Spring 的事务管理是通过 AOP(面向切面编程)实现的
  • 当一个方法内部直接调用同类中的另一个带有 @Transactional 注解的方法时,实际上是在调用原始对象的方法,而不是代理对象的方法
  • 由于事务是由代理对象管理的,所以直接调用原始对象的方法会导致事务注解失效
  1. 2. 代理对象与目标对象

在 Spring AOP 中:

  • 目标对象 :实际的业务类实例(原始对象)
  • 代理对象 :Spring 创建的包装了目标对象的代理,用于实现事务等增强功能

当外部调用一个带有 @Transactional 注解的方法时,实际上是调用的代理对象的方法,代理会在调用目标方法前后添加事务管理的代码。

4.0.2 分布式锁误删

使用 UUID 而不使用 ThreadID 的原因:

  • ThreadID 在每个 JVM 内 ThreadID 是自增的,分布式下会有相同的 ThreadID
  • 所以不能让 ThreadID 作为 value 来做是否为同一把锁的标识

4.1 基本原理

  • 分布式锁:满足分布式系统或集群模式下多进程可见并且互斥的锁。
  • 分布式锁的核心思想就是让大家都使用同一把锁,只要大家使用的是同一把锁,那么我们就能锁住线程,不让线程进行,让程序串行执行,这就是分布式锁的核心思路

4.1.1 满足条件

那么分布式锁他应该满足一些什么样的条件呢?

  • 可见性:多个线程都能看到相同的结果,注意:这个地方说的可见性并不是并发编程中指的内存可见性,只是说多个进程之间都能感知到变化的意思
  • 互斥:互斥是分布式锁的最基本的条件,使得程序串行执行
  • 高可用:程序不易崩溃,时时刻刻都保证较高的可用性
  • 高性能:由于加锁本身就让性能降低,所有对于分布式锁本身需要他就较高的加锁性能和释放锁性能
  • 安全性:安全也是程序中必不可少的一环

4.1.2 三种实现

  • Mysql:mysql 本身就带有锁机制,但是由于 mysql 性能本身一般,所以采用分布式锁的情况下,其实使用 mysql 作为分布式锁比较少见
  • Redis:redis 作为分布式锁是非常常见的一种使用方式,现在企业级开发中基本都使用 redis 或者 zookeeper 作为分布式锁,利用 setnx 这个方法,如果插入 key 成功,则表示获得到了锁,如果有人插入成功,其他人插入失败则表示无法获得到锁,利用这套逻辑来实现分布式锁
  • Zookeeper:zookeeper 也是企业级开发中较好的一个实现分布式锁的方案,由于本套视频并不讲解 zookeeper 的原理和分布式锁的实现,所以不过多阐述

4.2 实现思路

实现分布式锁时需要实现的两个基本方法:

  • 获取锁:
    • 互斥:确保只能有一个线程获取锁
    • 非阻塞:尝试一次,成功返回 true,失败返回 false
  • 释放锁:
    • 手动释放
    • 超时释放:获取锁时添加一个超时时间

核心思路:

  • 我们利用 redis 的 setNx 方法
  • 当有多个线程进入时
  • 我们就利用该方法
  • 第一个线程进入时,redis 中就有这个 key 了,返回了 1
  • 如果结果是 1,则表示他抢到了锁,那么他去执行业务
  • 然后再删除锁,退出锁逻辑,没有抢到锁的哥们,等待一定时间后重试即可

4.3 V1-setnx 分布式锁

4.3.1 代码

private static final String KEY_PREFIX="lock:"
@Override
public boolean tryLock(long timeoutSec) {// 获取线程标示String threadId = Thread.currentThread().getId()// 获取锁Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, threadId + "", timeoutSec, TimeUnit.SECONDS);return Boolean.TRUE.equals(success);
}
public void unlock() {//通过del删除锁stringRedisTemplate.delete(KEY_PREFIX + name);
}
  @Overridepublic Result seckillVoucher(Long voucherId) {// 1.查询优惠券SeckillVoucher voucher = seckillVoucherService.getById(voucherId);// 2.判断秒杀是否开始if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {// 尚未开始return Result.fail("秒杀尚未开始!");}// 3.判断秒杀是否已经结束if (voucher.getEndTime().isBefore(LocalDateTime.now())) {// 尚未开始return Result.fail("秒杀已经结束!");}// 4.判断库存是否充足if (voucher.getStock() < 1) {// 库存不足return Result.fail("库存不足!");}Long userId = UserHolder.getUser().getId();//创建锁对象(新增代码)SimpleRedisLock lock = new SimpleRedisLock("order:" + userId, stringRedisTemplate);//获取锁对象boolean isLock = lock.tryLock(1200);//加锁失败if (!isLock) {return Result.fail("不允许重复下单");}try {//获取代理对象(事务)IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();return proxy.createVoucherOrder(voucherId);} finally {//释放锁lock.unlock();}}

4.3.2 问题

逻辑说明:

  • 持有锁的线程在锁的内部出现了阻塞,导致他的锁自动释放
  • 这时其他线程,线程 2 来尝试获得锁,就拿到了这把锁
  • 然后线程 2 在持有锁执行过程中,线程 1 反应过来,继续执行
  • 而线程 1 执行过程中,走到了删除锁逻辑
  • 此时就会把本应该属于线程 2 的锁进行删除,这就是误删别人锁的情况说明

解决方案:

(线程 1,2 都是该用户的请求)

  • 解决方案就是在每个线程释放锁的时候,去判断一下当前这把锁是否属于自己
  • 如果属于自己,则不进行锁的删除
  • 假设还是上边的情况,线程 1 卡顿,锁自动释放
  • 线程 2 进入到锁的内部执行逻辑,此时线程 1 反应过来,然后删除锁
  • 但是线程 1,一看当前这把锁不是属于自己,于是不进行删除锁逻辑
  • 当线程 2 走到删除锁逻辑时,如果没有卡过自动释放锁的时间点
  • 则判断当前这把锁是属于自己的,于是删除这把锁。

4.4 V2-解决误删问题

需求:修改之前的分布式锁实现,满足:在获取锁时存入线程标示(可以用 UUID 表示)

在释放锁时先获取锁中的线程标示,判断是否与当前线程标示一致

  • 如果一致则释放锁
  • 如果不一致则不释放锁

核心逻辑:在存入锁时,放入自己线程的标识,在删除锁时,判断当前这把锁的标识是不是自己存入的,如果是,则进行删除,如果不是,则不进行删除。

4.4.1 代码

private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";
@Override
public boolean tryLock(long timeoutSec) {// 获取线程标示String threadId = ID_PREFIX + Thread.currentThread().getId();// 获取锁Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);return Boolean.TRUE.equals(success);
}
public void unlock() {// 获取线程标示String threadId = ID_PREFIX + Thread.currentThread().getId();// 获取锁中的标示String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);// 判断标示是否一致if(threadId.equals(id)) {// 释放锁stringRedisTemplate.delete(KEY_PREFIX + name);}
}

有关代码实操说明:

  • 在我们修改完此处代码后,我们重启工程
  • 然后启动两个线程
  • 第一个线程持有锁后,手动释放锁
  • 第二个线程 此时进入到锁内部,再放行第一个线程
  • 此时第一个线程由于锁的 value 值并非是自己
  • 所以不能释放锁,也就无法删除别人的锁
  • 此时第二个线程能够正确释放锁
  • 通过这个案例初步说明我们解决了锁误删的问题。

4.4.2 问题

更为极端的误删逻辑说明:

  • 线程 1 现在持有锁之后,在执行业务逻辑过程中
  • 他正准备删除锁,而且已经走到了条件判断的过程中
  • 比如他已经拿到了当前这把锁确实是属于他自己的
  • 正准备删除锁,但是此时他的锁到期了
  • 那么此时线程 2 进来,但是线程 1 他会接着往后执行
  • 当他卡顿结束后,他直接就会执行删除锁那行代码
  • 相当于条件判断并没有起到作用,这就是删锁时的原子性问题
  • 之所以有这个问题,是因为线程 1 的拿锁,比锁,删锁,实际上并不是原子性的
  • 我们要防止刚才的情况发生

4.5 V3-Lua 解决删锁原子性问题

我们来回想一下我们释放锁的逻辑:

  1. 1. 获取锁中的线程标示
  2. 2. 判断是否与指定的标示(当前线程标示)一致
  3. 3. 如果一致则释放锁(删除)
  4. 4. 如果不一致则什么都不做
-- 这里的 KEYS[1] 就是锁的key,这里的ARGV[1] 就是当前线程标示
-- 获取锁中的标示,判断是否与当前线程标示一致
if (redis.call('GET', KEYS[1]) == ARGV[1]) then-- 一致,则删除锁return redis.call('DEL', KEYS[1])
end
-- 不一致,则直接返回
return 0

Java 代码改造 Lua 脚本

我们的 RedisTemplate 中,可以利用 execute 方法去执行 lua 脚本,参数对应关系就如下图股

private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;static {UNLOCK_SCRIPT = new DefaultRedisScript<>();UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));UNLOCK_SCRIPT.setResultType(Long.class);}public void unlock() {// 调用lua脚本stringRedisTemplate.execute(UNLOCK_SCRIPT,Collections.singletonList(KEY_PREFIX + name),ID_PREFIX + Thread.currentThread().getId());
}
经过以上代码改造后,我们就能够实现 拿锁比锁删锁的原子性动作了~

4.6 小总结

基于 Redis 的分布式锁实现思路:

  • 利用 set nx ex 获取锁,并设置过期时间,保存线程标示
  • 释放锁时先判断线程标示是否与自己一致,一致则删除锁
    • 特性:

    - 利用 set nx 满足互斥性

    - 利用 set ex 保证故障时锁依然能释放,避免死锁,提高安全性

    - 利用 Redis 集群保证高可用和高并发特性

笔者总结:我们一路走来,利用添加过期时间,防止死锁问题的发生,但是有了过期时间之后,可能出现误删别人锁的问题,这个问题我们开始是利用删之前 通过拿锁,比锁,删锁这个逻辑来解决的,也就是删之前判断一下当前这把锁是否是属于自己的,但是现在还有原子性问题,也就是我们没法保证拿锁比锁删锁是一个原子性的动作,最后通过 lua 表达式来解决这个问题

但是目前还剩下一个问题锁不住,什么是锁不住呢,你想一想,如果当过期时间到了之后,我们可以给他续期一下,比如续个 30s,就好像是网吧上网,网费到了之后,然后说,来,网管,再给我来 10 块的,是不是后边的问题都不会发生了,那么续期问题怎么解决呢,可以依赖于我们接下来要学习 redission 啦

测试逻辑:

第一个线程进来,得到了锁,手动删除锁,模拟锁超时了,其他线程会执行 lua 来抢锁,当第一条线程利用 lua 删除锁时,lua 能保证他不能删除其他的锁,第二个线程删除锁时,利用 lua 同样可以保证不会删除别人的锁,同时还能保证原子性。

http://www.cadmedia.cn/news/15312.html

相关文章:

  • 重庆建设工程信息网官网入口网址北京百度seo工作室
  • 中国会议营销网站seo策略
  • 网络推广软件技巧杭州龙席网络seo
  • 模版营销型网站怎么做湖南靠谱seo优化公司
  • 吉林省建设信息网站关于网络营销的方法
  • 安徽省建设工程八大员报名网站关键词优化外包
  • 东营网红餐厅seo搜索引擎优化介绍
  • 网站建设合同纠纷站长网站大全
  • 马云是做网站的软文营销的宗旨是什么
  • 上海阿里巴巴网站建设网站外贸推广
  • 深圳高端展位设计公司seo推广seo技术培训
  • 网站建设违约产品推广介绍怎么写
  • 早期经典网页游戏南宁网站运营优化平台
  • 南昌网站建设公司机构黄页网站推广公司
  • 苹果电脑做网站好用吗免费发布广告信息平台
  • 北京网站建设w亿玛酷1专注百度指数快刷软件
  • wordpress reddit主题站长工具seo综合查询广告
  • 茶颜悦色vi设计手册独立站seo实操
  • 网站建设 回本北京网络营销推广外包
  • 整形医院网站源码下载网站优化主要优化哪些地方
  • 武汉光谷空轨百中搜优化
  • 东莞市企业网站制作企业网页制作图片
  • 新手如何做网站维护成人技术培训班有哪些种类
  • 建设大型网站建设网络口碑营销名词解释
  • 孝感市网站建设培训学校资质办理条件
  • seo诊断分析哪个杭州seo好
  • 安徽房地产网站建设百度风云榜电视剧排行榜
  • 网站开发简历免费seo排名网站
  • 南宁太阳能网站建设服装品牌策划方案
  • 深圳市企业网站建设哪家好长沙网站seo收费