万字长文!不为人所知的分布式锁实现全都在这里了

1、引入业务场景

2、分布式锁家族成员介绍

3、分布式锁成员实现原理剖析

4、最后的总结

2019 已经过去!

2020 已经到站!

----

1、引入业务场景

首先来由一个场景引入:

最近老板接了一个大单子,允许在某终端设备安装我们的APP,终端设备厂商日活起码得几十万到百万级别,这个APP也是近期产品根据市场竞品分析设计出来的,几个小码农通宵达旦开发出来的,主要功能是在线购物一站式服务,后台可以给各个商家分配权限,来维护需要售卖的商品信息。

老板大O:谈下来不容易,接下来就是考虑如何吸引终端设备上更多的用户注册上来,如何引导用户购买,这块就交给小P去负责了,需求尽快做,我明天出差!

产品小P:嘿嘿~,眼珠一转儿,很容易就想到了,心里想:“这还不简单,起码在首页搞个活动页... ”。

技术小T:很快了解了产品的需求,目前小J主要负责这块,找了前端和后端同学一起将活动页搞的快差不多了。

业务场景一出现

因为小T刚接手项目,正在吭哧吭哧对熟悉着代码、部署架构。在看代码过程中发现,下单这块代码可能会出现问题,这可是分布式部署的,如果多个用户同时购买同一个商品,就可能导致商品出现 库存超卖 (数据不一致) 现象,对于这种情况代码中并没有做任何控制。

原来一问才知道,以前他们都是售卖的虚拟商品,没啥库存一说,所以当时没有考虑那么多...

这次不一样啊,这次是售卖的实体商品,那就有库存这么一说了,起码要保证不能超过库存设定的数量吧。

小T大眼对着屏幕,屏住呼吸,还好提前发现了这个问题,赶紧想办法修复,不赚钱还赔钱,老板不得疯了,还想不想干了~

业务场景二出现

小T下面的一位兄弟正在压测,发现个小问题,因为在终端设备上跟鹅厂有紧密合作,调用他们的接口时需要获取到accesstoken,但是这个accesstoken过期时间是2小时,过期后需要重新获取。

压测时发现当到达过期时间时,日志看刷出来好几个不一样的access_token,因为这个服务也是分布式部署的,多个节点同时发起了第三方接口请求导致。

虽然以最后一次获取的accesstoken为准,也没什么不良副作用,但是会导致多次不必要的对第三方接口的调用,也会短时间内造成accesstoken的 重复无效获取(重复工作)

业务场景三出现

下单完成后,还要通知仓储物流,待用户支付完成,支付回调有可能会将多条订单消息发送到MQ,仓储服务会从MQ消费订单消息,此时就要 保证幂等性,对订单消息做 去重 处理。

以上便于大家理解为什么要用分布式锁才能解决,勾勒出的几个业务场景。

上面的问题无一例外,都是针对共享资源要求串行化处理,才能保证安全且合理的操作。

用一张图来体验一下:

分布式系统

此时,使用Java提供的Synchronized、ReentrantLock、ReentrantReadWriteLock...,仅能在单个JVM进程内对多线程对共享资源保证线程安全,在分布式系统环境下统统都不好使,心情是不是拔凉呀。

心情是这样的

这个问题得请教 分布式锁 家族来支持一下,听说他们家族内有很多成员,每个成员都有这个分布式锁功能,接下来就开始探索一下。

2、分布式锁家族成员介绍

为什么需要分布式锁才能解决?

听听 Martin 大佬们给出的说法:

Martin kleppmann 是英国剑桥大学的分布式系统的研究员,曾经跟 Redis 之父 Antirez 进行过关于 RedLock (Redis里分布式锁的实现算法)是否安全的激烈讨论。

他们讨论了啥,整急眼了?

都能单独写篇文章了

请你自己看 Maritin 博客文章:

https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html

效率:

使用分布式锁可以避免多个客户端重复相同的工作,这些工作会浪费资源。比如用户支付完成后,可能会收到多次短信或邮件提醒。

比如业务场景二,重复获取access_token。

对共享资源的操作是幂等性操作,无论你操作多少次都不会出现不同结果。

本质上就是为了避免对共享资源重复操作,从而提高效率。

正确性:

使用分布式锁同样可以避免锁失效的发生,一旦发生会引起正确性的破坏,可能会导致数据不一致,数据缺失或者其他严重的问题。

比如业务场景一,商品库存超卖问题。

对共享资源的操作是非幂等性操作,多个客户端操作共享资源会导致数据不一致。

分布式锁有哪些特点呢?

以下是分布式锁的一些特点,分布式锁家族成员并不一定都满足这个要求,实现机制不大一样。

互斥性: 分布式锁要保证在多个客户端之间的互斥。

可重入性:同一客户端的相同线程,允许重复多次加锁。

锁超时:和本地锁一样支持锁超时,防止死锁。

非阻塞: 能与 ReentrantLock 一样支持 trylock() 非阻塞方式获得锁。

支持公平锁和非公平锁:公平锁是指按照请求加锁的顺序获得锁,非公平锁真好相反请求加锁是无序的。

分布式锁家族实现者介绍

分布式锁家族实现者一览:

分布式锁家族实现者

思维导图做了一个简单分类,不一定特别准确,几乎包含了分布式锁各个组件实现者。

下面让他们分别来做下自我介绍:

1、数据库

排它锁(悲观锁):基于 select * from table where xx=yy for update SQL语句来实现,有很多缺陷,一般不推荐使用,后文介绍。

乐观锁:表中添加一个时间戳或者版本号的字段来实现,update xx set version = new... where id = y and version = old 当更新不成功,客户端重试,重新读取最新的版本号或时间戳,再次尝试更新,类似 CAS 机制,推荐使用。

2、Redis

特点:CAP模型属于AP | 无一致性算法 | 性能好

开发常用,如果你的项目中正好使用了redis,不想引入额外的分布式锁组件,推荐使用。

业界也提供了多个现成好用的框架予以支持分布式锁,比如Redissonspring-integration-redis、redis自带的setnx命令,推荐直接使用。

另外,可基于redis命令和redis lua支持的原子特性,自行实现分布式锁。

3、Zookeeper

特点:CAP模型属于CP | ZAB一致性算法实现 | 稳定性好

开发常用,如果你的项目中正好使用了zk集群,推荐使用。

业界有Apache Curator框架提供了现成的分布式锁功能,现成的,推荐直接使用。

另外,可基于Zookeeper自身的特性和原生Zookeeper API自行实现分布式锁。

4、其他

Chubby,Google开发的粗粒度分布锁的服务,但是并没有开源,开放出了论文和一些相关文档可以进一步了解,出门百度一下获取文档,不做过多讨论。

Tair,是阿里开源的一个分布式KV存储方案,没有用过,不做过多讨论。

Etcd,CAP模型中属于CPRaft一致性算法实现,没有用过,不做过多讨论。

Hazelcast,是基于内存的数据网格开源项目,提供弹性可扩展的分布式内存计算,并且被公认是提高应用程序性能和扩展性最好的方案,听上去很牛逼,但是没用过,不做过多讨论。

当然了,上面推荐的常用分布式锁Zookeeper和Redis,使用时还需要根据具体的业务场景,做下权衡,实现功能上都能达到你要的效果,原理上有很大的不同。

*画外音:* 你对哪个熟悉,原理也都了解,hold住,你就用哪个。

3、分布式锁成员实现原理剖析

数据库悲观锁实现

以「悲观的心态」操作资源,无法获得锁成功,就一直阻塞着等待。

1、有一张资源锁表

CREATE TABLE `resource_lock` (
  `id` int(4) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `resource_name` varchar(64) NOT NULL DEFAULT '' COMMENT '锁定的资源名',
  `owner` varchar(64) NOT NULL DEFAULT '' COMMENT '锁拥有者',
  `desc` varchar(1024) NOT NULL DEFAULT '备注信息',
  `update_time` timestamp NOT NULL DEFAULT '' COMMENT '保存数据时间,自动生成',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uidx_resource_name` (`resource_name `) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='锁定中的资源';

resource_name 锁资源名称必须有唯一索引。

2、使用姿势

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值