Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

聊聊并发控制锁 #44

Open
wsafight opened this issue May 8, 2022 · 0 comments
Open

聊聊并发控制锁 #44

wsafight opened this issue May 8, 2022 · 0 comments

Comments

@wsafight
Copy link
Owner

wsafight commented May 8, 2022

对于企业应用来说,完全不涉及到并发的问题,基本是不可能的。因为对于一个应用中很多的事情都是同时进行的。并发可能发生在数据获取,服务调用乃至于用户交互中。并发问题有两个重要的解决方案,一个是隔离,另一个是不变性。

并发问题会发生在多个执行单元同时访问同一资源的时候,此时,一个好的方法就是分好“蛋糕”,让每一个执行单元都能访问到各自的资源。好的并发设计就是:找到创建好隔离区的办法,然后通过分析工作流让隔离区能够完成尽可能多的任务。

在共享数据可以改变的情况下,并发问题就有可能发生。从实际的场景出发,同时有两个客户询问两位服务员是否还有某一货品时,两位服务员各自去查看了一下系统并回复客户还有一份,两位客户中一定有一位会失望。那么这件事情的解决方案就是添加隔离区(购物车),服务员把当前货品放入客户的购物车成功后告知用户,然后失败的一方就可以告知用户货品已经销售一空。虽然存在已购用户退货的可能,但无疑比前一结果要好太多。这也就是下文中所说的悲观锁。

下面我们开始介绍两种并发控制策略:

乐观和悲观并发控制

在某个系统中,同时有两个企业员工 A 和 B 想要编辑同一个用户信息。此时 A 和 B 都获取到了用户的信息数据。然后他们两个进行了修改,A 员工先完成了操作并且进行了提交。然后 B 员工完成了操作也进行了提交。此时系统中的这个用户信息只保留了 B 提供的数据,而丢弃了 A 员工的数据。这可能会造成一些难以预料的问题,甚至有可能导致他们丢掉工作。虽然可以通过操作日志来追溯到是哪个员工操作了数据,但这个信息没有任何意义,因为系统并没有让任何员工得知修改这一情况。

当一些可变数据无法隔离时候,我们可以用两种不同的控制策略:乐观锁策略和悲观锁策略。乐观锁用于冲突检测,悲观锁用于冲突避免。

悲观者策略非常简单,当 A 用户获取到用户信息时系统把当前用户信息给锁定,然后 B 用户在获取用户信息时就会被告知别人正在编辑。等到 A 员工进行了提交,系统才允许 B 员工获取数据。此时 B 获取的是 A 更新后的数据。

乐观者策略则不对获取进行任何限制,这时候我们可以在用户信息中添加版本号来告知用户信息已被修改。乐观锁要求每条数据都有一个版本号,同时在更新数据时候就会更新版本号,如 A 员工在更新用户信息时候提交了当前的版本号。系统判断 A 提交的时候的版本号和该条信息版本号一致,允许更新。然后系统就会把版本号修改掉,B 员工来进行提交时携带的是之前版本号,此时系统判定失败,要求 B 重新获取数据和版本号,然后再一次进行提交。

乐观锁和悲观锁进行选择的标准是: 冲突的频率和严重性。如果冲突的结果对于用户是难以接受的,我们只能采用悲观锁策略。如果冲突的结果不会很严重,或者频率也较低,我们就可以选择乐观锁,它更容易实现,也具有更好的并发性。

当然,我们也可以对乐观锁进行一些优化,把更新时间(作为版本号)和更新用户添加到信息中,如此以来,系统就可以告知 B 员工该条信息被修改过,以及在何时何人操作。系统还可以提供给 B 新的更新时间以及是否强制更新的选择。当然,甚至可以基于业务需求以及日志信息等来告知 B 员工之前具体修改的信息。

死锁

使用悲观锁技术有一个特别的问题就是死锁,即用户在已经获取锁的情况下还想要获取更多的锁。以最早的两个客户的问题来说,就是水果蛋糕需要获取水果和蛋糕,两个用户各有其中一种,并期望获取对方东西。

解决死锁的方法是检测处理和超时控制。

检查处理会检测出死锁发生并且会选择一个“牺牲者”,让他放弃他所拥有的已保证另外一个客户可以获取水果蛋糕。而超时控制则是给每个锁添加一个超时时间,一旦达到了超时时间,当前的购物车里面的物品就被清掉。

超时控制和检测机制用于已经发生了死锁的情况,而另外的方法则是避免死锁的发生。防止死锁的方法就是在用户获取锁的时候就获取所有可能需要的锁,粗力度锁(这很保守,但很有效),即水果蛋糕不是由两个货品组合而成的。

粗力度锁是覆盖多个资源的单个锁,这样会简化多个锁带来的复杂性。这其实也会发生在乐观锁的过程中,例如用户和用户相关地址信息,如果用户地址信息修改后也会更改用户信息,这样如何获取和设置乐观锁呢?我们需要寻找到一组资源的核心。

同时,找到一组资源的核心也会使得开发的代码逻辑更加清晰。大家不妨想一下,在数据库层面的操作中,是选择先更新子表然后再去更新主表这样的逻辑顺序更好,还是以主表为入口进行更新修改更好呢?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant