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

辽宁网站备案商业空间设计图片

辽宁网站备案,商业空间设计图片,用淘宝域名做网站什么效果,深圳php电商网站开发一、什么是分布式锁 我们在上篇文章中实现了单机模式下的秒杀业务。其中采用了synchronized加锁来解决各种线程安全问题。而synchronized关键字是依赖于单机的JVM,在集群模式下,每个服务器都有独立的JVM,如果此时还采用synchronized关键字加…

 一、什么是分布式锁

我们在上篇文章中实现了单机模式下的秒杀业务。其中采用了synchronized加锁来解决各种线程安全问题。而synchronized关键字是依赖于单机的JVM,在集群模式下,每个服务器都有独立的JVM,如果此时还采用synchronized关键字加锁,就会导致不同服务器间出现线程安全问题:

这时我们就需要一个不依赖JVM的独立的锁,去统一地对多台服务器进行加锁操作:

像这样独立出来的锁就被称之为分布式锁

一个优秀的分布式锁需要满足以下特性:

  • 多进程可见
  • 互斥
  • 高可用
  • 高性能
  • 安全性
  • ……

二、分布式锁的实现

分布式锁的核心是实现多进程之间的互斥,常见的有三种实现方式:

相比之下redis在性能上是最好的,本篇文章就主要来探讨用redis来实现一个分布式锁。

Redis实现简单的分布式锁

这里的锁最重要的一个特点就是需要具备互斥性。redis中的setnx操作,在key不存在的时候可以写入,key如果存在就将无法写入,就很符合互斥性的特点。

这样我们就可以尝试用setnx操作来自己实现一个互斥锁SimpleRedisLock: 

加锁时会执行setnx操作向Redis中添加锁信息。由于setnx的互斥性,只有第一个执行setnx操作的线程才能成功执行写入操作,并返回成功信息,则表示着加锁成功;此时其它线程尝试获取锁去执行setnx操作就会失败,返回错误信息,则表示加锁失败,进入阻塞等待。这样就实现了一个简单的锁的功能。

同时为了避免某个线程在获取锁后执行时间过长导致大量线程长时间处于阻塞态,甚至出现死锁问题。因此我们可以通过Redis的expire操作对锁添加过期时间

代码实现:

public class SimpleRedisLock implements ILock{//锁的名称private String name;//传入redis的方法private StringRedisTemplate stringRedisTemplate;public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {this.name=name;this.stringRedisTemplate=stringRedisTemplate;}//锁的前缀private static final String KEY_PREFIX="lock:";@Overridepublic boolean tryLock(long timeoutSec) {//获取线程标识long threadId = Thread.currentThread().getId();//尝试获取锁Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, threadId + "", timeoutSec, TimeUnit.SECONDS);//判断获取锁是否成功return Boolean.TRUE.equals(success);}@Overridepublic void unlock() {//释放锁,即删除redis中对应的锁对象stringRedisTemplate.delete(KEY_PREFIX+name);}
}

运行流程:

接着就可以用它来代替synchronized为我们上篇文章中的代码进行加锁操作了:

        Long userId = UserHolder.getUser().getId();//创建锁对象SimpleRedisLock lock=new SimpleRedisLock("order:"+userId,new StringRedisTemplate());//尝试获取锁boolean isLock = lock.tryLock(1200);//判断获取锁是否成功if(!isLock){//获取锁失败,返回错误或重试return Result.fail("不许重复下单!");}try{//获取当前对象的代理对象IVoucherOrderService proxy = (IVoucherOrderService)AopContext.currentProxy();return proxy.createVoucherOrder(voucherId);}finally {lock.unlock();}

误删问题

但是上述实现的锁在一些情况下是存在问题的:

由于我们为分布式锁设置了过期时间,很有可能会出现 线程一 业务还未执行完二锁却因为超时而被释放的情况。由于锁被释放,这时其它线程比如 线程二 就会抢到锁,并在Redis中存储上新的锁标识,开始执行自己的业务逻辑。期间 线程一 可能才执行完业务逻辑,并按照流程进行释放锁的操作,此时它并不知道Redis中存储的锁已经不是自己那把了,依旧会删除掉Redis中的锁。而此时 线程二 的业务也没有完成,但锁却被删掉了,此时其它线程如 线程三 又会抢到锁,也有可能会被 线程二 误删掉锁,以此类推。

这种现象就被称之为误删问题。

我们可以通过在对锁进行删除操作前加上判断,判断此时Redis中存储的锁标识是否与自己的锁标识一致。若一致,则正常执行删除操作;若不一致,则不进行删除。

这样就可以对代码进行改进:

在加锁时存入线程标识。由于不同服务器的线程id可能会发生重复,所以我们可以生成随机的UUID作为前缀,保证线程标识的唯一性

    private static final String ID_PREFIX= UUID.randomUUID().toString(true) +"-";@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 Boolean.TRUE.equals(success);}

在释放锁前,先获取锁中线程标识,判断与当前线程标识是否一致,若一致,才执行释放锁操作

    @Overridepublic void unlock() {// 获取当前线程标识String threadId = ID_PREFIX + Thread.currentThread().getId();// 获取Redis中记录的锁标识String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);// 判断线程标识与锁标识是否一致if(threadId.equals(id)){// 一致,成功释放锁stringRedisTemplate.delete(KEY_PREFIX+name);}}

原子性问题

由于判断锁标识与释放锁并不是原子的,可能在判断完锁标识后由于JVM的垃圾回收机制等原因陷入阻塞,期间就可能会被其他线程获取锁,而后就会导致误删:

一看到原子性可能就会想到事务,与数据库的事务不同,redis的事务可以保证原子性,但无法保证数据的一致性。事务中的多个操作其实是在做批处理,是在最终一次性去执行的。没有办法先查询,然后判断再去释放。因为做查询操作时是拿不到结果的,它是最后一次性执行的,因此无法将它们放在同一个事务中,只能用redis中的一些乐观锁进行一些判断,确保在释放时没有人做过修改

我们可以用Lua编写脚本,在脚本中编写多条命令,确保多条命令执行时的原子性。

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

接下来就可以在释放锁的方法中直接调用写好的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);}@Overridepublic void unlock() {// 调用Lua脚本stringRedisTemplate.execute(UNLOCK_SCRIPT,Collections.singletonList(KEY_PREFIX + name),ID_PREFIX+Thread.currentThread().getId());}

这种方式就是利用了lua脚本的原子性,在里面依次执行多个redis的命令,这就能保证原子性


那么本篇文章就到此为止了,如果觉得这篇文章对你有帮助的话,可以点一下关注和点赞来支持作者哦。如果有什么讲的不对的地方欢迎在评论区指出,希望能够和你们一起进步✊

http://www.yayakq.cn/news/854839/

相关文章:

  • 诸城营销型网站建设wordpress 0day
  • 自己做一个网站难不难广州安全教育平台官网
  • 电商网站设计图片素材wordpress python采集
  • 网站备案填写网站名称如何建立公司网站推广
  • 科大讯飞哪些做教学资源的网站沈阳网站开发培训
  • 健身会所网站模板深圳龙华网站建设公司
  • 网站建设丽水怎么进入广告联盟看广告赚钱
  • 自己怎么建个网站赚钱吗最新军事新闻事件报道
  • 公司网站建设好处营销型网站制作培训
  • 国外免费推广网站营销型企业网站分
  • 好网站建设安阳市住房和城乡建设厅网站
  • 迪庆州住房和城乡建设局网站怎么做一个电商网站
  • 网站模块图成都少儿编程培训机构排名前十
  • 珠海工商年检到哪个网站做婚庆一条龙价目表
  • 怎样健建设一个有利于优化的网站专业手机app开发公司
  • 天津营销网站建设联系方式网络服务主要包括哪些服务
  • 突唯阿网站seo电子商务网站建设与管理期末考试题
  • 宠物寄养网站毕业设计利用百度搜索自己的网站
  • 手机设计培训网站建设seo网站建设微
  • 济南网站优化公司电话wordpress做注册登陆界面
  • 建立自己的购物网站世界军事新闻最新消息
  • 深圳京圳建设监理有限公司网站网站开发后端框架什么意思
  • 外贸网站好做吗wordpress上不去
  • 企业如何做好网站的seo优化html网页模板网站
  • 广州市筑正工程建设有限公司网站福州网站建设liedns
  • 如何设计网站的首页专门做画册封面的网站
  • 建设部网站公示公告站长工具介绍
  • 浙江省国有建设用地出让网站优设网页设计网站
  • 国美在线网站建设费用做一家视频网站
  • 陕西省城乡和住房建设厅网站wordpress 主题文件夹