如果被问到分布式锁,应该怎样回答?

cc277ee092477f76debbf2eef89f45ec.gif

作者 | tech-bus.七十一

来源 | 程序员巴士

说到锁,在平时的工作中,主要是使用synchronized关键字,或者相关的一些类库来实现同步,但这都是基于单机应用而言的,当我们的应用多实例部署时,这时候就需要用到分布式锁了,常用的分布式锁主要是基于redis的分布式锁和基于zookeeper的分布式锁及基数据库的分布式锁,前俩个主要基于中间件的特性来实现,今天介绍一下基于数据库的分布式锁的实现,在一些并发不高的场景下比较适用。

首先需要在数据库中建立好数据表,相关的字段如下所示:

CREATE TABLE IF NOT EXISTS `lock_tbl`(`lock_id` INT NOT NULL, -- 主键且主要字段不可少`des_one` VARCHAR(20), -- 可有可无`des_two` VARCHAR(20), -- 可有可无PRIMARY KEY ( `lock_id` )
)ENGINE=InnoDB DEFAULT CHARSET=utf8;

接着我们使用单实例应用,编写一个接口,去买一个表里的商品,大致思路就是:读取库存,库存减一,回写数据库,返回成功,其核心代码如下:

public class StockServiceImpl implements StockService{@AutowiredStockMapper stockMapper;@Overridepublic Stock selectByPrimaryKey(Integer goodsId) {return stockMapper.selectByPrimaryKey(goodsId);}// 加锁也只能保证单个实例线程安全性public synchronized void byGoods() throws InterruptedException {// 这里写死,数据库里就一条记录且ID为1,拿到数据Stock stock = selectByPrimaryKey(1);// 获取到商品的库存Long goodsStock = stock.getGoodsStock();// 减库存goodsStock -= 1;stock.setGoodsStock(goodsStock);// 为了将问题放大这里睡上几秒 拉长查库存和更新库存的之间的时间间隔Thread.sleep(3000);// 更新updateByPrimaryKeySelective(stock);// 输出System.out.println("更新后库存为:" + goodsStock);}@Overridepublic int updateByPrimaryKeySelective(Stock record) {return stockMapper.updateByPrimaryKeySelective(record);}
}

在单个实例里面加个synchronized后完全正常的减库存,然后我们启动两个实例后使用postman对接口进行压测,出现如下情况:

eac64d81b4f6a4efdb448fa616a34c92.png
实例1打印日志
3dec93d55e6b4e195791a114ff623c59.png
实例2打印日志

通过截图可知上述程序已经出现超卖现象,接下来进行改造,使用数据库层面的锁,我们知道向一张表中插入俩条相同主键的数据,只可能成功一条,因为主键具有约束性,所以利用这个特点,当我们向数据库插入成功时,即代表获取到锁,从而去运行我们的业务代码,当我们的业务代码运行完时,我们把数据库的该条记录进行删除,即代表释放锁,从而其他线程即有机会获取到锁,再去跑业务代码,这样即使运行的是俩个实例,同一时间也只能一个线程去运行业务代码,也就不会出现超卖这种情况了。下面给出加锁和解锁的代码:

// 上锁。由于上锁失败的话会直接返回失败,并不会再次获取
// 是非阻塞的,这里利用循环实现阻塞。@Overridepublic boolean tryLock() {// 这里的Lock就是简单的一个POJO对象映射到数据库中一张表的字段Lock lock = new Lock();lock.setLockId(1);// 通过while循环来实现阻塞while (true) {try {// 首先查询一下主键为1的数据是否存在,如果存在则说明锁已经被占用了if (lockMapper.selectByPrimaryKey(1) == null) {// 不存在则尝试加锁即向数据库中插入数据int i = lockMapper.insert(lock);if (i == 1) {return true;}}Thread.sleep(1000);} catch (InterruptedException e) {}}}// 解锁代码@Overridepublic void unLock() {deleteByPrimaryKey(1);}

对service层的购买商品的代码就进行加锁

// 买商品public void byWithLock() throws InterruptedException {// 上锁lockService.tryLock();// 业务代码byGoods();// 释放锁并跳出循环lockService.unLock();}

对于controller层的代码

@RestController
public class LoadBalance {@AutowiredStockServiceImpl stockService;@RequestMapping("/balance")public String balance() {try {stockService.byWithLock();} catch (InterruptedException e) {e.printStackTrace();}return "success";}
}

再次将程序启动,使用postman简单做下压测,发现已经正常进行减库存了。结果如下图所示

2e4b9d753e8a5f2f17f57e44440b6c9d.png
实例1日志
70e5059a260c94486266c9b748af4c17.png
实例2日志

‍‍

存在的问题

  • 如果有一台实例拿到锁后宕机了,锁未能及时释放,那么其他实例将永远无法获取到锁。

  • 不可重入,一台实例拿到锁后,想再次获取该锁时会失败

如何解决

  • 对于存在实例宕机导致锁无法释放的问题,可以在插入数据的时候将当前的一个时间戳也插入数据库中,然后启一个定时任务,定期去扫表,同时设定一个锁的超时时间(该超时时间一定要大于正常的接口调用时间),将超时的记录进行删除。

  • 对于不可重入,可以在表中插入数据的时候增加实例和线程相关的信息,当获取锁时进行判断,如果相符则直接获取锁。

悲观锁

悲观锁简单理解就是在任何情况下都是悲观的认为请求临界资源的时候都会与其他线程发生冲突,因此每次都是加悲观锁,这种锁具有强烈的侵占性和排他性。上述的例子中所加的锁就是悲观锁即先取锁再访问,MySql自带的悲观锁是For Update,使用For Update可以显示的增加行锁,但悲观锁会让数据库额外的开销,同时增加死锁的风险。

乐观锁

乐观锁简单理解就是每次线程请求临界资源时都认为不会有其他线程与其竞争,只有在数据进行提交的时候才进行竞争,在检测数据冲突时并不依赖数据库本身的锁机制,不影响请求的性能。上述例子我们可以在数据库表中增加一个Version版本号,对于要进行修改的数据,先从数据库中将改Version的版本号查出来,然后修改的时候带上该版本号一起修改‍

SELECT VERSION FROM TABLE_A  -- 假设这里查出来version的值是OldVersion
UPDATE TABLE_A SET COUNT = COUNT -1, VERSION = VERSION + 1 WHERE VERSION = OldVersion

总结

并发不是特别高的情况下可以考虑使用基于数据库的分布式锁,尽量采用乐观锁的方式以提高应用的吞吐量。

2aedf86348bae0ed66d87a46416168b3.gif

a60e1303ec7fa6be798f98cf99bf98bf.png

往期推荐

为什么大家都在抵制用定时任务实现「关闭超时订单」功能?

Gartner 发布 2022 年汽车行业五大技术趋势

别再用 Redis List 实现消息队列了,Stream 专为队列而生

OpenStack 如何跨版本升级

37ecf3916b6ff4c1cb9122550b15f57a.gif

点分享

cf83e09c915e7119245f7ace6ccda35c.gif

点收藏

1809ffb83eda92140f13998ce626b34e.gif

点点赞

42d68d3eff1cb19c28d99a2fa3329abf.gif

点在看

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/512226.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

工业视觉智能实战经验之IVI算法框架2.0

简介: 工业视觉智能团队在交付了多个工业视觉智能质检项目后,发现了工业视觉智能的共性问题和解法,打造了工业视觉智能平台,通过平台的方式积累和提升工业视觉的通用能力。在平台建设上最核心的能力是算法能力。算法能力包括不断增…

技术干货 | jsAPI 方式下的导航栏的动态化修改

简介: 操作指导:通过 jsAPI 实现导航栏的动态修改。 很多开发同学在接入 H5 容器后都会对容器的导航栏进行深度定制,除了 Native 的定制化之外,还有很多场景是使用到 jsAPI 的方式,通过 jsAPI 实现导航栏的动态修改。 …

Gartner:企业机构需重新定义网络安全领导者角色

编辑 | 宋慧 供稿 | Gartner 根据Gartner的最新调查,由于网络风险责任已被转移到IT以外,并且日益分散的生态系统导致网络安全领导者正在失去对决策的直接控制权,企业机构需要重新定义网络安全领导者的角色。 如今,安全和风险管理…

成本直降50%,下一代网关震撼发布

简介: 在容器和K8s主导的云原生时代,网关的新形态变得逐渐清晰,阿里内部也孵化出了下一代的网关产品 - 云原生网关,已在支付宝、淘宝、优酷、口碑等业务成功上线,并且经历了2020双11大促海量请求的考验,目前…

备战“双11”,阿里云为企业提供一站式资源保障服务

简介: 阿里云弹性计算将上线资源保障服务,通过智能化资源诊断、推荐、资源预定及授权候补为用户提供一站式自助化资源保障服务,兼顾灵活,经济的同时还能获得时刻的确定性保障,为业务顺畅前行保驾护航。 报名体验资源保…

快速上手 Serverless | 入门第一课

简介: 本文从云计算抛砖引玉,详解 Serverless 的典型应用场景和一些产品介绍。 一、 从云计算到 Serverless 自世界上第一台通用计算机 ENIAC (图左)诞生以来,计算机科学与技术的发展就从未停止过前进的脚步。2003年-2006年,谷歌…

钉钉宜搭邵磊:钉钉宜搭低代码加速业务互联 让改变发生

简介: 近日,在2021“低代码技术发展与应用线上研讨会”上,钉钉宜搭产品总监邵磊带来了“钉钉宜搭低代码加速业务互联 让改变发生”的主题演讲,详细介绍了钉钉宜搭低代码产品的六大互联能力。 宜搭是今年1月份正式上线到钉钉&…

Cloudera发布全球企业数据成熟度报告,混合云趋势中有效数据战略是关键

编辑 | 宋慧 出品 | CSDN云计算 2022年3月初,企业数据云公司Cloudera近日发布与技术市场研究公司Vanson Bourne联合编写的全球企业数据战略研究报告,报告分别洞察了数据的使用和价值、企业数据战略、企业数据发展趋势、企业业务计划四大部分的内容&…

基于海量日志和时序数据的质量建设最佳实践

简介: 在云原生和DevOps研发模式的挑战下,一个系统从开发、测试、到上线的整个过程中,会产生大量的日志、指标、事件以及告警等数据,这也给企业质量平台建设带来了很大的挑战。本议题主要通过可观测性的角度来讨论基于海量日志和时…

阿里云RDS深度定制-XA Crash Safe

简介: 近几年,随着分布式数据库系统的兴起,特别是基于MySQL分布式数据库系统,会用到XA来保证全局事务的一致性。众所周知,MySQL对XA事务的支持是比较弱的,存在很多问题。为了满足分布式数据库系统对XA事务的…

java集合表_java集合类散列表

哈希表是种数据结构,它可以提供快速的插入操作和查找操作。第一次接触哈希表时,它的优点多得让人难以置信。不论哈希表中有多少数据,插入和删除(有时包括侧除)只需要接近常量的时间即0(1)的时间级。实际上,这只需要几条机器指令。…

庖丁解牛|图解 MySQL 8.0 优化器查询转换篇

简介: 本篇介绍子查询、分析表和JOIN的复杂转换过程 一 背景和架构 在《庖丁解牛-图解MySQL 8.0优化器查询解析篇》一文中我们重点介绍了MySQL最新版本8.0.25关于SQL基本元素表、列、函数、聚合、分组、排序等元素的解析、设置和转换过程,本篇我们继续…

Java 底层知识:什么是 “桥接方法” ?

作者 | 小志来源 | 程序员小灰导语笔者在最近的日常工作中,因业务需要,研究 Java 字节码层面的知识。具体是,需要根据类字节码,获取特定方法名的方法入参,此方法名在源码中只有一个。但是在实际使用中发现:…

ACMMM2021|在多模态训练中融入“知识+图谱”:方法及电商应用实践

简介: 随着人工智能技术的不断发展,知识图谱作为人工智能领域的知识支柱,以其强大的知识表示和推理能力受到学术界和产业界的广泛关注。近年来,知识图谱在语义搜索、问答、知识管理等领域得到了广泛的应用。 作者 | 朱渝珊 来源 |…

带你体验云原生场景下 Serverless 应用编程模型

简介: 阿里云 Knative 基于 ASK 之上,在完全兼容社区 Knaitve 的同时对 FC、ECI 工作负载进行统一应用编排,支持事件驱动、自动弹性,为您提供统一的 Serverless 应用编程模型。 背景 阿里云 Serverless Kubernetes(A…

CSO全球网络安全大会来了,权威奖项征集中

全球网络安全顶级峰会——IDC 2022 全球CSO网络安全大会(以下简称大会)将于2022年6月首次落地中国。本届大会以“聚力数据安全 赋能企业现代化”为主题,由Foundry(IDG)/IDC联合上海市信息安全行业协会共同举办&#xf…

stream of java_Java 8 新特性-Stream更优雅的处理集合入门

Java 8 新特性之——Stream一. 简单介绍Stream是Java 8提出了的一种新的对集合对象功能的增强。它集合Lambda表达式,对集合提供了一些非常便利,高效的操作,使得代码具有非常高的可读性,优雅性!!举个例子来说…

MySQL深潜|剖析Performance Schema内存管理

简介: 本文主要是通过对PFS引擎的内存管理源码的阅读,解读PFS内存分配及释放原理,深入剖析其中存在的一些问题,以及一些改进思路。 一 引言 MySQL Performance schema(PFS)是MySQL提供的强大的性能监控诊断工具,提供…

敲地鼠java_Java实现的打地鼠小游戏完整示例【附源码下载】

本文实例讲述了Java实现的打地鼠小游戏。分享给大家供大家参考,具体如下:这里涉及到java线程和GUI的相关知识,一个简单的java小游戏打地鼠,有兴趣的朋友可以优化一下。先来看看运行效果:具体代码:Mouse.jav…

深入理解 Docker 网络原理

作者 | 渡、来源 | CSDN博客Docker网络原理容器是相对独立的环境,相当于一个小型的Linux系统,外界无法直接访问,那他是怎么做的呢,这里我们先了解下Linux veth pair。1. Linux veth pairveth pair是成对出现的一种虚拟网络设备接口…