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

免费网站建设哪家好企业线上培训课程

免费网站建设哪家好,企业线上培训课程,常州企业网站建设公司,宁波市住房和城乡建设部网站【Redis】Redis实现分布式锁 文章目录【Redis】Redis实现分布式锁1. 分布式锁概念2. 为什么要实现分布式锁2.1 并发安全问题3. 分布式锁的实现方案3.1 Redis实现分布式锁3.1.1 定义分布锁接口和类3.1.2 编写lua脚本3.1.3 使用线程锁3.1.4 总结在实现分布式锁之前,首…

【Redis】Redis实现分布式锁

文章目录

  • 【Redis】Redis实现分布式锁
    • 1. 分布式锁概念
    • 2. 为什么要实现分布式锁
      • 2.1 并发安全问题
    • 3. 分布式锁的实现方案
      • 3.1 Redis实现分布式锁
        • 3.1.1 定义分布锁接口和类
        • 3.1.2 编写lua脚本
        • 3.1.3 使用线程锁
        • 3.1.4 总结

在实现分布式锁之前,首先我们要明白什么是分布式锁?为什么要实现分布式锁?

1. 分布式锁概念

分布式锁:满足分布式系统或集群模式下多进程可见并且互斥的锁。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-II4dqszM-1673770522700)(C:\Users\zhuhuanjie\AppData\Roaming\Typora\typora-user-images\image-20230115153704987.png)]


2. 为什么要实现分布式锁

设定一个场景:美团外卖的特价优惠卷一个用户只能购买一张,不能够重复购买。

在系统单机部署的情况下,通过加锁就能够解决就可以解决一人一单的安全问题,但是当系统的用户量越来越多,并发越来越大,单机部署的项目就越来越感到“捉襟见肘”,所以我们需要在多台服务器上集群部署系统,以满足用户的需求。但是当我们集群部署系统之后,问题也随之而来了,之前的锁无法满足集群部署下系统的并发问题,容易出现一人多单的问题。基于此,我们就需要实现 分布式锁 去解决系统集群部署下的并发问题。


2.1 并发安全问题

首先明白一点,我们单机部署下系统是通过 synchronized (userId.toString().intern()) 对用户id进行加锁。因为在单个jvm中可以确保每次从常量池中得到的用户id都是同一个。当一个线程拿到锁之后,其他线程就无法重复拿到锁了。这样就实现了一人一单。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ChX8HiJM-1673770522701)(C:\Users\zhuhuanjie\AppData\Roaming\Typora\typora-user-images\image-20230115152335502.png)]

当系统集群部署之后,每个单独部署的系统都有了各自的jvm,如果还是像之前通过 synchronized (userId.toString().intern()) 对用户id加锁,那么只能做到在各台单独部署的系统上实现并发安全,但是在整个集群上是并发不安全的。jvm1的第一个线程拿到了锁,jvm2的第一个线程也能拿到锁,因为它们不是拿的不是同一个线程池上的用户id。这样就会出现一人多单的问题。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iwfo45Hy-1673770522701)(C:\Users\zhuhuanjie\AppData\Roaming\Typora\typora-user-images\image-20230115152615007.png)]

所以我们不能使用之前的方案了,我们需要一个外部共享的工具来替代原来的锁监视器。如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mq1GZhA4-1673770522702)(C:\Users\zhuhuanjie\AppData\Roaming\Typora\typora-user-images\image-20230115153157627.png)]

由此,就引出来分布式锁


3. 分布式锁的实现方案

分布式锁的核心是实现多进程之间互斥,满足这一点的方式有很多,常见的方案有以下三种:

  • MYSQL
  • Redis
  • Zookeeper
MSQLRedisZookeeper
互斥利用mysql本身的互斥锁机制利用setnx这样的互斥命令利用节点的唯一性和有序性实现互斥
高可用
高性能一般一般
安全性断开连接,自动释放锁利用锁超时时间,到期释放临时节点,断开连接自动释放

综合考虑,我们使用Redis实现分布式锁。


3.1 Redis实现分布式锁

3.1.1 定义分布锁接口和类

首先我们定义分布式锁的接口:

public interface ILock {/*** 尝试获取锁* @param timeoutSec 锁持有的超时时间,过期后自动释放* @return true 代表获取锁成功 ; false 代表获取锁失败*/boolean tryLock(long timeoutSec);/*** 释放锁*/void unlock();
}

然后定义一个类来实现这个接口:

public class SimpleRedisLock implements ILock {private StringRedisTemplate stringRedisTemplate;private String name;public SimpleRedisLock(StringRedisTemplate stringRedisTemplate, String name) {this.stringRedisTemplate = stringRedisTemplate;this.name = name;}private static final String KEY_PREFIX = "lock:";private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;static {UNLOCK_SCRIPT = new DefaultRedisScript<>();UNLOCK_SCRIPT.setResultType(Long.class);UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));}//获取锁@Overridepublic boolean tryLock(long timeoutSec) {//获取线程标识String threadId = ID_PREFIX + Thread.currentThread().getId();//获取锁Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);return BooleanUtil.isTrue(success);}//版本一 释放锁//@Override//public void unlock() {//    //获取缓存线程标识//    String cacheThreadId = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);//    //获取当前线程标识//    String threadId = ID_PREFIX + Thread.currentThread().getId();//    if (threadId.equals(cacheThreadId)) {//        //判断线程标示是否一致,防止误删//        stringRedisTemplate.delete(KEY_PREFIX + name);//    }//}//版本二 释放锁public void unlock() {//调用lua脚本stringRedisTemplate.execute(UNLOCK_SCRIPT,Collections.singletonList(KEY_PREFIX + name),ID_PREFIX + Thread.currentThread().getId());}
}

解释

  • 虽然线程id在每个jvm内都是唯一的,但是不保证在其它jvm中不会出现相同的线程id。所以我们在线程id前面拼接一个uuid,避免线程标识相同而误删锁。
  • 释放锁的过程中,为防止误删锁需要保证校验线程标识是否相同操作和释放锁操作的原子性,这里我们使用lua脚本。

3.1.2 编写lua脚本

在lua脚本中我们要实现两个功能,一个是判断线程标识是否一致,一个是释放锁。

-- 比较线程标识与锁中的标识是否一致
if (redis.call('get', KEYS[1]) == ARGV[1]) then-- 释放锁 del keyreturn redis.call('del', KEYS[1])
end
return 0

3.1.3 使用线程锁

@Override
public Result seckillVoucher(Long voucherId) {//1.查询优惠券idSeckillVoucher voucher = seckillVoucherService.getById(voucherId);//2.判断秒杀是否开始LocalDateTime beginTime = voucher.getBeginTime();LocalDateTime endTime = voucher.getEndTime();if (LocalDateTime.now().isBefore(beginTime)) {//2.1.秒杀未开始return Result.fail("秒杀未开始");}if (LocalDateTime.now().isAfter(endTime)) {//2.2.秒杀已结束return Result.fail("秒杀已结束");}//2.3.秒杀正在进行int stock = voucher.getStock();//3.判断库存是否充足if (stock <= 0) {//3.1库存不足return Result.fail("库存不足");}//3.2.库存充足Long userId = UserHolder.getUser().getId();//确保每个userId的锁是同一把SimpleRedisLock lock = new SimpleRedisLock(stringRedisTemplate, "order:" + userId);if (!lock.tryLock(1200)) {//获取锁失败return Result.fail("不允许重复下单");}try {//获取代理对象(事务)IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();return proxy.createVoucherOrder(voucherId);} finally {//释放锁lock.unlock();}
}@Transactional
public Result createVoucherOrder(Long voucherId) {//4.一人一单Long userId = UserHolder.getUser().getId();//4.1.查询是否已经有订单int count = lambdaQuery().eq(VoucherOrder::getUserId, userId).eq(VoucherOrder::getVoucherId, voucherId).count();if (count > 0) {//已有订单return Result.fail("不能重复购买");}//5.扣减库存//5.1.写法一LambdaUpdateWrapper<SeckillVoucher> updateWrapper = new LambdaUpdateWrapper<>();updateWrapper.setSql("stock=stock-1").eq(SeckillVoucher::getVoucherId, voucherId).gt(SeckillVoucher::getStock, 0);boolean success = seckillVoucherService.update(updateWrapper);//5.2.写法二//boolean success = seckillVoucherService.update()//        .setSql("stock = stock - 1")//        .eq("voucher_id", voucherId).gt("stock", 0).update();if (!success) {//扣减失败return Result.fail("库存不足!");}//6.创建订单VoucherOrder voucherOrder = new VoucherOrder();//6.1.订单idlong orderId = redisIdWorker.nextId("order");voucherOrder.setId(orderId);//6.2.用户idvoucherOrder.setUserId(UserHolder.getUser().getId());//6.3.代金券idvoucherOrder.setVoucherId(voucherId);//6.4.写入数据库save(voucherOrder);//7.返回订单idreturn Result.ok(voucherOrder.getId());
}

解释

在方法中调用另一个加了事务注解的方法,会导致事务注解失效。我们需要让代理对象来调用加了事务注解的方法,这样就不会失效了。


3.1.4 总结

至此,Redis实现的分布式锁已经完成了,但是还有优化的余地。我们也可以使用成熟的分布式锁框架 Redisson

http://www.dtcms.com/wzjs/419352.html

相关文章:

  • 百度一下浏览器seo技术培训岳阳
  • 浙江省住房城乡建设厅官方网站线在成都网站推广公司
  • ps如何做ppt模板下载网站百度移动端排名
  • 门户网站登录入口网站竞价推广托管公司
  • 网站怎么做自响应怎么做网页宣传
  • 南充网站开发什么叫seo
  • 国外网站配色营销系统
  • 1000元做网站硬件工程师培训机构哪家好
  • 网站装修怎么做seo优化资源
  • 做网上购物网站重庆seo网络营销
  • 苹果手机做电影网站百度指数对比
  • 自己做网站是不是需要写代码无锡百度公司代理商
  • 做爰全过程免费的教网站无锡网站关键词推广
  • 大兴模版网站建设哪家好百度关键词收费标准
  • 做外贸怎样上外国网站搜狗关键词排名此会zjkwlgs
  • 网页设计作业制作个人网站保定百度推广联系电话
  • 威海外贸建站清远市发布
  • 如何提升网站速度网站推广途径和推广要点
  • 品牌商城网站制作公司app下载推广
  • 手机微网站 模板人工在线客服
  • 网站开发和网站制作的区别培训班招生方案
  • 网站开发的现状及研究意义网络视频营销
  • 网页设计相关书籍济南seo优化
  • 做ppt的网站有哪些内容优化方法
  • 用vb做网站导航栏网页设计制作网站教程
  • 公司网站如何做水印手机卡顿优化软件
  • 网站设计和美工一样吗专门做推广的公司
  • 广东官方网站建设网络工程师是干什么的
  • 手机网站建设服务商十大搜索引擎神器
  • 做网站建设要学多久网站优化推广教程