分布式锁的实现方式有3种主流方法,你知道吗?

一、背景

并发问题是电商系统中最常见的问题之一,比如库存超卖、抽奖多、优惠券多、积分多、积分少;出现上述问题的原因是多台机器、多个请求同时修改同一个共享资源,如果不加以限制,会导致数据混乱和数据不一致;解决并发问题的方法有很多,比如:队列、异步、反应式、锁;因为现在的互联网是分布式的,所以本文只介绍如何使用更广泛的分布式锁的方式来保证质量。

二、分布式锁介绍

1.什么是分布式锁

首先了解什么是锁。在单机系统中,当多个线程同时更改一个变量时,需要对变量或代码块进行同步,以保证变量的串行修改。同步本质上是通过锁来实现的。为了实现同一段代码多个线程同时串行执行,需要在某处做一个标记,并且该标记必须对每个线程可见。当标记不存在时,可以设置标记,其余的当线程发现已经有标记时,等待有标记的线程结束同步代码块,取消标记然后尝试设置标记。这个标记可以理解为一把锁。分布式锁是多机系统的标志。

2.实现分布式锁的主流方式

目前实现分布式锁的主流方法有3种,分别是:

基于MySQL的锁表

数据库版本号乐观锁

关于锁的具体实现的文章已经太多了,本文不再赘述。

三、质量保证

一旦并发问题涉及到钱同步代码块的锁是什么,通常会导致不同程度的资金损失,在我们的功能测试中很难发现。因此,并发的质量保证就显得尤为重要,可以抽象为三层来保证: 三个步骤:pre-event、in-event、post-event;事前保障通过Review提前规避技术风险,事中保障验证技术实施过程是否存在漏洞,事后保障验证数据是否符合预期,对于有并发风险的项目,以上三者的保障步骤是必不可少的。

1.质量前保证

预保证阶段发生在技术审查阶段。在这个阶段,我们需要评估当前业务场景是否存在并发风险;如果是,请确定我们的技术选择。

评估并发风险

评估并发风险的关键是是否有多个进程同时访问共享资源,简而言之,是否有多个进程同时更新相同的数据;比如:电商库存,多人同时购买同一种产品,意味着同一种产品的库存会同时更新,这里存在并发风险。

技术选择

要做出正确的技术选择,我们需要了解以上三种方式实现的锁的优缺点以及应用场景。

实现方法

MySQL数据库表的乐观锁适用于多读少写,共享资源为数据库单行数据的场景; MySQL表锁实现的锁一般不推荐; ZooKeeper分布式锁虽然适用于大部分分布式场景,但由于其实现复杂度相对较高,需要引入额外的中间件,在大部分业务场景中很少使用,而基于Redis的缓存分布式锁被广泛使用;但是具体的业务实现使用哪种类型的分发锁,还是需要根据当前的业务特点来决定;

在技术审查阶段,一方面,我们需要评估是否存在并发风险。另一方面,我们需要识别开发人员在实施该技术时可能存在的漏洞。分布式锁的实现漏洞请参考下面的CodeReview。的焦点。

2.保护

代码审查

1)Redis 缓存分布式锁

Redis 通常可以使用 setnx(key,value) 函数来实现分布式锁。 key和value是基于缓存的分布式锁的两个属性,其中key代表锁id。 setnx函数返回1表示获得了锁,返回0表示其他服务器已经获得了锁;

1)) Redis 密钥

例如:商品库存,我们的key应该是针对某个商品的,而不是所有商品,锁定A商品不会影响B商品。

2)) 锁释放

针对以上问题,在释放锁时,需要先读取当前key的值,然后与传入的值进行比较;以上两步必须保证原子性,如果原生Redis可以使用lua脚本保证原子性;如果是tair,可以采用TairString的cad方法; value 必须是唯一值,唯一标记就是当前对象加的锁。

3)) 锁定超时

你可以启动另一个线程来继续当前的超时,但这增加了系统的复杂度;

将过期时间设置为很长的时间,保证在锁释放之前可以执行逻辑;这个解决方案简单但有缺陷。遇到突发的系统异常,无法释放锁,只能等待redis key超时。并且超时时间设置的比较长,所以在当前时间没有人可以获取到锁,阻塞了业务执行,很可能导致失败;

4)) 锁定粒度

如果对一个共享资源的写入是根据另一个共享资源的值计算的,那么锁的范围必须包括读取的共享资源;如果作用域不包含读共享资源,就会导致脏读,最终导致数据错误。如下图,Client B最终计算出的B的结果是错误的。

5)) 获取锁失败

由于其他线程已经获取了锁,对于当前线程获取锁失败的处理方式有3种:抛出异常让用户重试;通过旋转再次抓住锁;发布和订阅,订阅锁释放消息;在低场景下,异常抛出和自旋抓取都是可以接受的。在高并发场景下,不建议同时抛出异常和自旋抓取。

2)MySQL数据库锁CR点

数据库的表需要包含数字字段version,读取数据时读取version字段,更新数据时判断当前版本是否等于读取版本,当前版本加1;如果等于 更新成功,并不代表数据已经过期,更新失败。比如以积分系统为例,增加积分的场景多种多样,采用乐观锁保证数据的正确性。

乐观锁CR注意点:

where条件必须命中索引(最好是主键或唯一索引),否则表会被锁定; where条件必须命中索引(最好是主键或唯一索引),否则表会被锁定;

更新表集必须包含version = version + 1;

update返回结果为0时,必须根据业务场景进行处理,独立重试或抛出异常;

实现原理是:创建锁表,对关键资源做唯一约束,通过增加记录锁定资源同步代码块的锁是什么,释放锁时删除记录;一般不推荐这种用法。

并发测试

并发测试一般可以分为三类:

可以通过接口压力测试进行测试,检查最终数据是否与预期不一致;

压测工具:jmeter可以进行压测(群里可以直接使用pas-server进行压测,方便快捷);

可以使用JVM的并发函数CountDownLatch、CyclicBarrier等,CountDownLatch片段代码:

public void invokeAllTask​​(ConcurrencyRequest request, Runnable task) {

final CountDownLatch startCountDownLatch = new CountDownLatch(1);

final CountDownLatch endCountDownLatch = new CountDownLatch(request.getConcurrency());

for (int i = 0; i

线程 t = new Thread(() -> {

试试{

startCountDownLatch.await();

试试{

task.run();

} 最后{

endCountDownLatch.countDown();

}

} 捕捉(异常前){

log.error(“异常”, ex);

}

});

t.start();

}

startCountDownLatch.countDown();

试试{

endCountDownLatch.await();

} 捕捉(InterruptedException ex){

log.error(“线程异常中断”, ex);

}

}1.2.3.4.5.6.7.8.9.1 0.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.

使用jmeter的定时器Synchronizing Timer也可以实现这个功能。

3.事后

数据对账

数据对账(数据一致性验证)是我们系统上线后并发问题的最后一道防线。我们使用对账来识别我们数据中的不一致之处;压力测试有成本并且受技能熟练程度和压力测试设计的影响,不一定能揭示问题;如果在被测场景的评估中出现并发问题的概率极低,即使发生影响,影响也比较小,那么review+reconciliation的方式也是一个不错的选择;

如何进行对账,不同的业务场景有不同的对账方式,例如:

选择count(*)作为task_count,

场景代码,

来自 task_record 的 order_id,其中 unique_id 不为空

按场景代码分组,

order_idhaving count(*)> 11.2.3.4.5.6.7.8.

四、总结

作为一个质量保证的学生,你必须经常拉伸一个字符串,看看当前场景下是否会出现并发问题;并发问题的识别简单来说就是有没有同时更新相同的数据,如果有的话就要注意开发同学是不是在处理并发之后,上面主要介绍了并发的实现,然后就可以根据场景进行分析;关于并发场景的质量保证,一般原则可以总结如下:

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论