learn.lianglianglee.com/专栏/分布式技术原理与实战45讲-完/11 分布式锁有哪些应用场景和实现?.md.html
2022-05-11 18:52:13 +08:00

1137 lines
28 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>11 分布式锁有哪些应用场景和实现?.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/00 开篇词:搭建分布式知识体系,挑战高薪 Offer.md">00 开篇词:搭建分布式知识体系,挑战高薪 Offer.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/01 如何证明分布式系统的 CAP 理论?.md">01 如何证明分布式系统的 CAP 理论?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/02 不同数据一致性模型有哪些应用?.md">02 不同数据一致性模型有哪些应用?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/03 如何透彻理解 Paxos 算法?.md">03 如何透彻理解 Paxos 算法?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/04 ZooKeeper 如何保证数据一致性?.md">04 ZooKeeper 如何保证数据一致性?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/05 共识问题:区块链如何确认记账权?.md">05 共识问题:区块链如何确认记账权?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/06 如何准备一线互联网公司面试?.md">06 如何准备一线互联网公司面试?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/07 分布式事务有哪些解决方案?.md">07 分布式事务有哪些解决方案?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/08 对比两阶段提交,三阶段协议有哪些改进?.md">08 对比两阶段提交,三阶段协议有哪些改进?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/09 MySQL 数据库如何实现 XA 规范?.md">09 MySQL 数据库如何实现 XA 规范?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/10 如何在业务中体现 TCC 事务模型?.md">10 如何在业务中体现 TCC 事务模型?.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/分布式技术原理与实战45讲-完/11 分布式锁有哪些应用场景和实现?.md">11 分布式锁有哪些应用场景和实现?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/12 如何使用 Redis 快速实现分布式锁?.md">12 如何使用 Redis 快速实现分布式锁?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/13 分布式事务考点梳理 + 高频面试题.md">13 分布式事务考点梳理 + 高频面试题.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/14 如何理解 RPC 远程服务调用?.md">14 如何理解 RPC 远程服务调用?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/15 为什么微服务需要 API 网关?.md">15 为什么微服务需要 API 网关?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/16 如何实现服务注册与发现?.md">16 如何实现服务注册与发现?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/17 如何实现分布式调用跟踪?.md">17 如何实现分布式调用跟踪?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/18 分布式下如何实现配置管理?.md">18 分布式下如何实现配置管理?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/19 容器化升级对服务有哪些影响?.md">19 容器化升级对服务有哪些影响?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/20 ServiceMesh服务网格有哪些应用.md">20 ServiceMesh服务网格有哪些应用.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/21 Dubbo vs Spring Cloud两大技术栈如何选型.md">21 Dubbo vs Spring Cloud两大技术栈如何选型.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/22 分布式服务考点梳理 + 高频面试题.md">22 分布式服务考点梳理 + 高频面试题.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/23 读写分离如何在业务中落地?.md">23 读写分离如何在业务中落地?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/24 为什么需要分库分表,如何实现?.md">24 为什么需要分库分表,如何实现?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/25 存储拆分后,如何解决唯一主键问题?.md">25 存储拆分后,如何解决唯一主键问题?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/26 分库分表以后,如何实现扩容?.md">26 分库分表以后,如何实现扩容?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/27 NoSQL 数据库有哪些典型应用?.md">27 NoSQL 数据库有哪些典型应用?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/28 ElasticSearch 是如何建立索引的?.md">28 ElasticSearch 是如何建立索引的?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/29 分布式存储考点梳理 + 高频面试题.md">29 分布式存储考点梳理 + 高频面试题.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/30 消息队列有哪些应用场景?.md">30 消息队列有哪些应用场景?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/31 集群消费和广播消费有什么区别?.md">31 集群消费和广播消费有什么区别?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/32 业务上需要顺序消费,怎么保证时序性?.md">32 业务上需要顺序消费,怎么保证时序性?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/33 消息幂等:如何保证消息不被重复消费?.md">33 消息幂等:如何保证消息不被重复消费?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/34 高可用:如何实现消息队列的 HA.md">34 高可用:如何实现消息队列的 HA.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/35 消息队列选型Kafka 如何实现高性能?.md">35 消息队列选型Kafka 如何实现高性能?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/36 消息队列选型RocketMQ 适用哪些场景?.md">36 消息队列选型RocketMQ 适用哪些场景?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/37 消息队列考点梳理 + 高频面试题.md">37 消息队列考点梳理 + 高频面试题.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/38 不止业务缓存,分布式系统中还有哪些缓存?.md">38 不止业务缓存,分布式系统中还有哪些缓存?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/39 如何避免缓存穿透、缓存击穿、缓存雪崩?.md">39 如何避免缓存穿透、缓存击穿、缓存雪崩?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/40 经典问题:先更新数据库,还是先更新缓存?.md">40 经典问题:先更新数据库,还是先更新缓存?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/41 失效策略:缓存过期都有哪些策略?.md">41 失效策略:缓存过期都有哪些策略?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/42 负载均衡:一致性哈希解决了哪些问题?.md">42 负载均衡:一致性哈希解决了哪些问题?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/43 缓存高可用:缓存如何保证高可用?.md">43 缓存高可用:缓存如何保证高可用?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/44 分布式缓存考点梳理 + 高频面试题.md">44 分布式缓存考点梳理 + 高频面试题.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/45 从双十一看高可用的保障方式.md">45 从双十一看高可用的保障方式.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/46 高并发场景下如何实现系统限流?.md">46 高并发场景下如何实现系统限流?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/47 降级和熔断:如何增强服务稳定性?.md">47 降级和熔断:如何增强服务稳定性?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/48 如何选择适合业务的负载均衡策略?.md">48 如何选择适合业务的负载均衡策略?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/49 线上服务有哪些稳定性指标?.md">49 线上服务有哪些稳定性指标?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/50 分布式下有哪些好用的监控组件?.md">50 分布式下有哪些好用的监控组件?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/51 分布式下如何实现统一日志系统?.md">51 分布式下如何实现统一日志系统?.md.html</a>
</li>
<li>
<a href="/专栏/分布式技术原理与实战45讲-完/52 分布式路漫漫,厚积薄发才是王道.md">52 分布式路漫漫,厚积薄发才是王道.md.html</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>11 分布式锁有哪些应用场景和实现?</h1>
<p>电商网站都会遇到秒杀、特价之类的活动,大促活动有一个共同特点就是访问量激增,在高并发下会出现成千上万人抢购一个商品的场景。虽然在系统设计时会通过限流、异步、排队等方式优化,但整体的并发还是平时的数倍以上,参加活动的商品一般都是限量库存,如何防止库存超卖,避免并发问题呢?分布式锁就是一个解决方案。</p>
<h3>如何理解分布式锁</h3>
<p>我们都知道,在业务开发中,为了保证在多线程下处理共享数据的安全性,需要保证同一时刻只有一个线程能处理共享数据。</p>
<p>Java 语言给我们提供了线程锁,开放了处理锁机制的 API比如 Synchronized、Lock 等。当一个锁被某个线程持有的时候,另一个线程尝试去获取这个锁会失败或者阻塞,直到持有锁的线程释放了该锁。</p>
<p>在单台服务器内部,可以通过线程加锁的方式来同步,避免并发问题,那么在分布式场景下呢?</p>
<p><img src="assets/CgqCHl6z34CAWUxoAAE1hnZz5gE051.png" alt="图片1.png" /></p>
<p>分布式场景下解决并发问题,需要应用分布式锁技术。如上图所示,分布式锁的目的是保证在分布式部署的应用集群中,多个服务在请求同一个方法或者同一个业务操作的情况下,对应业务逻辑只能被一台机器上的一个线程执行,避免出现并发问题。</p>
<h3>分布式锁的常用实现</h3>
<p>实现分布式锁目前有三种流行方案即基于数据库、Redis、ZooKeeper 的方案。</p>
<h4>基于关系型数据库</h4>
<p>基于关系型数据库实现分布式锁,是依赖数据库的唯一性来实现资源锁定,比如主键和唯一索引等。</p>
<p>以唯一索引为例,创建一张锁表,定义方法或者资源名、失效时间等字段,同时针对加锁的信息添加唯一索引,比如方法名,当要锁住某个方法或资源时,就在该表中插入对应方法的一条记录,插入成功表示获取了锁,想要释放锁的时候就删除这条记录。</p>
<p>下面创建一张基于数据库的分布式锁表:</p>
<pre><code class="language-java">CREATE TABLE `methodLock` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`method_name` varchar(64) NOT NULL DEFAULT '' COMMENT '锁定的方法或者资源',
PRIMARY KEY (`id`),
UNIQUE KEY `uidx_method_name` (`method_name `) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='对方法加锁';
</code></pre>
<p>当希望对某个方法加锁时,执行以下 SQL 语句:</p>
<pre><code class="language-java">insert into methodLock(method_name) values ('method_name');
</code></pre>
<p>在数据表定义中,我们对 method_name 做了唯一性约束,如果有多个请求同时提交到数据库的话,数据库会保证只有一个操作可以成功,那么就可以认为操作成功的那个线程获得了该方法的锁,可以执行后面的业务逻辑。</p>
<p>当方法执行完毕之后,想要释放锁的话,在数据库中删除对应的记录即可。</p>
<p>基于数据库实现分布式锁操作简单,但是并不是一个可以落地的方案,有很多地方需要优化。</p>
<p><strong>存在单点故障风险</strong></p>
<p>数据库实现方式强依赖数据库的可用性,一旦数据库挂掉,则会导致业务系统不可用,为了解决这个问题,需要配置数据库主从机器,防止单点故障。</p>
<p><strong>超时无法失效</strong></p>
<p>如果一旦解锁操作失败,则会导致锁记录一直在数据库中,其他线程无法再获得锁,解决这个问题,可以添加独立的定时任务,通过时间戳对比等方式,删除超时数据。</p>
<p><strong>不可重入</strong></p>
<p>可重入性是锁的一个重要特性,以 Java 语言为例,常见的 Synchronize、Lock 等都支持可重入。在数据库实现方式中,同一个线程在没有释放锁之前无法再次获得该锁,因为数据已经存在,再次插入会失败。实现可重入,需要改造加锁方法,额外存储和判断线程信息,不阻塞获得锁的线程再次请求加锁。</p>
<p><strong>无法实现阻塞</strong></p>
<p>其他线程在请求对应方法时,插入数据失败会直接返回,不会阻塞线程,如果需要阻塞其他线程,需要不断的重试 insert 操作,直到数据插入成功,这个操作是服务器和数据库资源的极大浪费。</p>
<p>可以看到,借助数据库实现一个完备的分布式锁,存在很多问题,并且读写数据库需要一定的性能,可能会影响业务执行的耗时。</p>
<p>下面我们来看下应用缓存如何实现。</p>
<h4>应用 Redis 缓存</h4>
<p>相比基于数据库实现分布式锁,缓存的性能更好,并且各种缓存组件也提供了多种集群方案,可以解决单点问题。</p>
<p>常见的开源缓存组件都支持分布式锁,包括 Redis、Memcached 及 Tair。以常见的 Redis 为例,应用 Redis 实现分布式锁,最直接的想法是利用 setnx 和 expire 命令实现加锁。</p>
<p>在 Redis 中setnx 是「set if not exists」如果不存在则 SET 的意思,当一个线程执行 setnx 返回 1说明 key 不存在,该线程获得锁;当一个线程执行 setnx 返回 0说明 key 已经存在那么获取锁失败expire 就是给锁加一个过期时间。</p>
<p>伪代码如下:</p>
<pre><code class="language-java">if(setnx(key,value)==1){
expire(key,expireTime)
try{
//业务处理
}finally{
//释放锁
del(key)
}
}
</code></pre>
<p>使用 setnx 和 expire 有一个问题,这两条命令可能不会同时失败,不具备原子性,如果一个线程在执行完 setnx 之后突然崩溃,导致锁没有设置过期时间,那么这个锁就会一直存在,无法被其他线程获取。</p>
<p>为了解决这个问题,在 Redis 2.8 版本中,添加了 SETEX 命令SETEX 支持 setnx 和 expire 指令组合的原子操作,解决了加锁过程中失败的问题。</p>
<p>添加 SETEX 命令, 就是一个完善的分布式锁吗?在下一课时的内容中我会详细分享。</p>
<h4>基于 ZooKeeper 实现</h4>
<p>ZooKeeper 有四种节点类型,包括持久节点、持久顺序节点、临时节点和临时顺序节点,利用 ZooKeeper 支持临时顺序节点的特性,可以实现分布式锁。</p>
<p>当客户端对某个方法加锁时,在 ZooKeeper 中该方法对应的指定节点目录下,生成一个唯一的临时有序节点。</p>
<p><img src="assets/CgqCHl6z34-ALXWpAAEiUjbLEMc631.png" alt="图片2.png" /></p>
<p>判断是否获取锁,只需要判断持有的节点是否是有序节点中序号最小的一个,当释放锁的时候,将这个临时节点删除即可,这种方式可以避免服务宕机导致的锁无法释放而产生的死锁问题。</p>
<p>下面描述使用 ZooKeeper 实现分布式锁的算法流程,根节点为 /lock</p>
<ul>
<li>客户端连接 ZooKeeper并在 /lock 下创建临时有序子节点,第一个客户端对应的子节点为 /lock/lock01/00000001第二个为 /lock/lock01/00000002</li>
<li>其他客户端获取 /lock01 下的子节点列表,判断自己创建的子节点是否为当前列表中序号最小的子节点;</li>
<li>如果是则认为获得锁,执行业务代码,否则通过 watch 事件监听 /lock01 的子节点变更消息,获得变更通知后重复此步骤直至获得锁;</li>
<li>完成业务流程后,删除对应的子节点,释放分布式锁。</li>
</ul>
<p>在实际开发中,可以应用 Apache Curator 来快速实现分布式锁Curator 是 Netflix 公司开源的一个 ZooKeeper 客户端,对 ZooKeeper 原生 API 做了抽象和封装,若感兴趣可自行查询资料了解。</p>
<h3>总结</h3>
<p>这一课时分享了分布式锁的应用场景和几种实现,包括分布式锁的概念,使用数据库方式、缓存和 ZooKeeper 实现分布式锁等。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/分布式技术原理与实战45讲-完/10 如何在业务中体现 TCC 事务模型?.md">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/分布式技术原理与实战45讲-完/12 如何使用 Redis 快速实现分布式锁?.md">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"709976a8e9103cfa","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>