This commit is contained in:
by931
2022-09-06 22:30:37 +08:00
parent 66970f3e38
commit 3d6528675a
796 changed files with 3382 additions and 3382 deletions

View File

@@ -181,7 +181,7 @@ function hide_canvas() {
<p>上一讲,我讲了分布系统的事务一致性,今天这一讲,我想带你了解分布式系统中与锁有关的面试问题。</p>
<h3>案例背景</h3>
<p>分布式锁是解决协调分布式系统之间,同步访问共享资源的一种方式。详细来讲:在分布式环境下,多个系统在同时操作共享资源(如写数据)时,发起操作的系统通常会通过一种方式去协调其他系统,然后获取访问权限,得到访问权限后才可以写入数据,其他系统必须等待权限释放。
<img src="assets/CgqCHl_-esuAcq7vAAFRjfl0DJE878.png" alt="22.png" /></p>
<img src="assets/CgqCHl_-esuAcq7vAAFRjfl0DJE878.png" alt="png" /></p>
<p>分布式锁</p>
<p>我和其他的面试官交流后发现,很多面试官都会问候选人与分布式锁相关的问题,在一些细节上挖得还比较细。比如在分布式系统中涉及共享资源的访问,一些面试官会深挖如何控制并发访问共享资源;如何解决资源争抢等技术细节,这些问题在下单场景、优惠券场景都会被考察到,足以证明“分布式锁”考点的重要性。</p>
<p>那么假设你正在面试,面试官模拟了系统秒杀的场景:为了防止商品库存超售,在并发场景下用到了分布式锁的机制,做商品扣减库存的串行化操作。然后问你:“你如何实现分布式锁?”你该怎么回答呢?</p>
@@ -216,7 +216,7 @@ function hide_canvas() {
<pre><code>select id from order where order_id = xxx for update
</code></pre>
<p>基于关系型数据库实现分布式锁比较简单,不过你要注意,基于 MySQL 行锁的方式会出现交叉死锁,比如事务 1 和事务 2 分别取得了记录 1 和记录 2 的排它锁,然后事务 1 又要取得记录 2 的排它锁,事务 2 也要获取记录 1 的排它锁,那这两个事务就会因为相互锁等待,产生死锁。</p>
<p><img src="assets/Ciqc1F_-euqAGDKSAAMBFlwOlu0123.png" alt="23.png" /></p>
<p><img src="assets/Ciqc1F_-euqAGDKSAAMBFlwOlu0123.png" alt="png" /></p>
<p>数据库交叉死锁</p>
<p>当然,你可以通过“超时控制”解决交叉死锁的问题,但在高并发情况下,出现的大部分请求都会排队等待,所以“基于关系型数据库实现分布式锁”的方式在性能上存在缺陷,所以如果你回答“基于关系型数据库 MySQL 实现分布式锁”,通常会延伸出下面两个问题。</p>
<ul>
@@ -279,7 +279,7 @@ end
<li><strong>如何合理设置超时时间</strong></li>
</ul>
<p>通过超时时间来控制锁的失效时间,不太靠谱,比如在有些场景中,一个线程 A 获取到了锁之后,由于业务代码执行时间可能比较长,导致超过了锁的超时时间,自动失效,后续线程 B 又意外的持有了锁,当线程 A 再次恢复后,通过 del 命令释放锁,就错误的将线程 B 中同样 key 的锁误删除了。</p>
<p><img src="assets/Cip5yF_-ewGAUSb5AAFrhs6QnWo499.png" alt="24.png" /></p>
<p><img src="assets/Cip5yF_-ewGAUSb5AAFrhs6QnWo499.png" alt="png" /></p>
<p>锁超时导致的误操作</p>
<p>所以,如果锁的超时时间设置过长,会影响性能,如果设置的超时时间过短,有可能业务阻塞没有处理完成,<strong>能否合理设置超时时间,是基于缓存实现分布式锁很难解决的一个问题。</strong></p>
<p><strong>那么如何合理设置超时时间呢?</strong> 你可以基于续约的方式设置超时时间:先给锁设置一个超时时间,然后启动一个守护线程,让守护线程在一段时间后,重新设置这个锁的超时时间。实现方式就是:写一个守护线程,然后去判断锁的情况,当锁快失效的时候,再次进行续约加锁,当主线程执行完成后,销毁续约锁即可。</p>