处理竞争条件:一个实际示例
来源:dev.to
时间:2024-07-15 10:28:08 500浏览 收藏
偷偷努力,悄无声息地变强,然后惊艳所有人!哈哈,小伙伴们又来学习啦~今天我将给大家介绍《处理竞争条件:一个实际示例》,这篇文章主要会讲到等等知识点,不知道大家对其都有多少了解,下面我们就一起来看一吧!当然,非常希望大家能多多评论,给出合理的建议,我们一起学习,一起进步!
在你的职业生涯中,你会遇到薛定谔的猫问题,这种情况有时有效,有时无效。竞争条件是这些挑战之一(是的,只是其中之一!)。
在这篇博文中,我将展示一个真实的示例,演示如何重现问题并讨论使用 postgresql 的可序列化事务隔离和咨询锁来处理竞争条件的策略。
受到“设计数据密集型应用程序”第 7 章 - 事务“弱隔离级别”的启发
带有实际示例的 github 存储库
应用程序
此应用程序管理医院医生的值班轮班。为了关注竞争条件问题,让我们简化我们的场景。我们的应用程序围绕这个表进行解析:
create table shifts ( id serial primary key, doctor_name text not null, shift_id integer not null, on_call boolean not null default false );
我们有一条关键的业务规则:
- 每个班次必须始终有至少一名医生待命。
正如您可能已经猜到的,实现简单的 api 可能会导致竞争条件场景。考虑这个假设的情况:
杰克和约翰在同一班次期间都在医院待命。几乎同时,他们决定请假。一个成功了,但另一个依赖于有关有多少医生轮班的过时信息。结果,两人最终都下班了,这违反了业务规则,并在没有值班医生的情况下离开了特定的班次:
john --begin------doctors on call: 2-------leave on call-----commit--------> (t) \ \ \ \ \ \ \ \ database ------------------------------------------------------------------> (t) / / / / / / / / jack ------begin------doctors on call: 2-----leave on call----commit-------> (t)
重现问题
该应用程序是一个用 golang 实现的简单 api。查看 github 存储库,了解有关如何运行和执行脚本以重现此竞争条件场景的说明。总之,您需要:
- 启动服务器:yarn nxserve hospital-shifts
- 运行 k6 测试来重现竞争条件场景:yarn nx test hospital-shifts
测试尝试同时取消两名医生,使用不同的方法访问端点:shiftid=1使用咨询锁,shiftid=2使用可序列化事务隔离,shiftid=3是天真没有并发控制的实现。
k6结果会输出自定义指标来指示哪个shiftid违反了业务规则:
✓ at least one doctor on call for shiftid=1 ✓ at least one doctor on call for shiftid=2 ✗ at least one doctor on call for shiftid=3 ↳ 36% — ✓ 123 / ✗ 217
您将需要 yarn、go、k6 和 docker 等工具,或者您可以使用 devbox 来更轻松地设置存储库依赖项。解决竞争条件
当我们的应用程序根据陈旧数据做出决策时,就会出现问题。如果两项交易几乎同时运行并且两者都试图叫停医生轮班,则可能会发生这种情况。一笔交易按预期成功,但另一笔交易由于依赖过时的信息,也错误地成功。我们怎样才能防止这种不良行为呢?有几种方法可以实现这一点,我将探索 postgresql 支持的两个选项,尽管在其他数据库管理系统中也可以找到类似的解决方案。
可串行化事务隔离
可序列化快照隔离自动检测并防止异常情况,例如我们的应用程序演示的写入偏差。
我不会深入探讨事务隔离背后的理论,但它是许多流行数据库管理系统中的常见主题。您可以通过搜索快照隔离来找到很好的材料,例如 postgresql 官方文档中有关事务隔离的内容。此外,这是多年前提出此解决方案的论文。空谈很便宜,所以让我们看一下代码:
首先启动事务并设置隔离级别为serialized:
// init transaction with serializable isolation level tx, err := db.begintxx(c.request().context(), &sql.txoptions{ isolation: sql.levelserializable, })然后,继续执行操作。在我们的例子中它执行这个函数:
create or replace function update_on_call_status_with_serializable_isolation(shift_id_to_update int, doctor_name_to_update text, on_call_to_update boolean) returns void as $$ declare on_call_count int; begin -- check the current number of doctors on call for this shift select count(*) into on_call_count from shifts s where s.shift_id = shift_id_to_update and s.on_call = true; if on_call_to_update = false and on_call_count = 1 then raise exception '[serializableisolation] cannot set on_call to false. at least one doctor must be on call for this shiftid: %', shift_id_to_update; else update shifts s set on_call = on_call_to_update where s.shift_id = shift_id_to_update and s.doctor_name = doctor_name_to_update; end if; end; $$ language plpgsql;每当由于并发执行而出现不一致的情况时,可序列化隔离级别将允许一个事务成功,并会自动回滚其他事务并显示此消息,因此您可以安全地重试:
error: could not serialize access due to read/write dependencies among transactions
- 您可以在 updatewithserializedisolation 函数中找到完整的示例。
确保执行业务规则的另一种方法是显式锁定特定班次的资源。我们可以在
交易级别使用咨询锁来实现这一点。这种类型的锁完全由应用程序控制。您可以在这里找到更多相关信息。
需要注意的是,锁可以在会话级别和事务级别应用。您可以探索此处提供的各种功能。在我们的例子中,我们将使用 pg_try_advisory_xact_lock(key bigint) → boolean,它在提交或回滚后自动释放锁:
begin; -- attempt to acquire advisory lock and handle failure with exception if not pg_try_advisory_xact_lock(shift_id_to_update) then raise exception '[advisorylock] could not acquire advisory lock for shift_id: %', shift_id_to_update; end if; -- perform necessary operations -- commit will automatically release the lock commit;这是我们应用程序中使用的完整函数:
-- Function to Manage On Call Status with Advisory Locks, automatic release when the trx commits CREATE OR REPLACE FUNCTION update_on_call_status_with_advisory_lock(shift_id_to_update INT, doctor_name_to_update TEXT, on_call_to_update BOOLEAN) RETURNS VOID AS $$ DECLARE on_call_count INT; BEGIN -- Attempt to acquire advisory lock and handle failure with NOTICE IF NOT pg_try_advisory_xact_lock(shift_id_to_update) THEN RAISE EXCEPTION '[AdvisoryLock] Could not acquire advisory lock for shift_id: %', shift_id_to_update; END IF; -- Check the current number of doctors on call for this shift SELECT COUNT(*) INTO on_call_count FROM shifts s WHERE s.shift_id = shift_id_to_update AND s.on_call = TRUE; IF on_call_to_update = FALSE AND on_call_count = 1 THEN RAISE EXCEPTION '[AdvisoryLock] Cannot set on_call to FALSE. At least one doctor must be on call for this shiftId: %', shift_id_to_update; ELSE UPDATE shifts s SET on_call = on_call_to_update WHERE s.shift_id = shift_id_to_update AND s.doctor_name = doctor_name_to_update; END IF; END; $$ LANGUAGE plpgsql;
- 您可以在 updatewithadvisorylock 函数中找到完整的示例。
处理竞争条件,例如我们讨论的
写入倾斜场景,可能非常棘手。有大量的研究和不同的方法来解决这些问题,所以如果您好奇的话,一定要查看一些论文和文章。
这些问题可能会在现实生活中出现,例如当多人尝试在活动中预订同一个座位或在剧院购买同一个座位时。它们往往随机出现,并且很难弄清楚,特别是如果这是您第一次与它们打交道。当您遇到竞争条件时,重要的是要研究哪种解决方案最适合您的具体情况。我将来可能会做一个基准测试来比较不同的方法并为您提供更多见解。
我希望这篇文章对您有所帮助。请记住,有一些工具可以帮助解决这些问题,而且您并不是唯一面临这些问题的人!
亚姆塞基 / 开发者
dev.to 博客文章的实现终于介绍完啦!小伙伴们,这篇关于《处理竞争条件:一个实际示例》的介绍应该让你收获多多了吧!欢迎大家收藏或分享给更多需要学习的朋友吧~golang学习网公众号也会发布Golang相关知识,快来关注吧!
-
505 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
188 收藏
-
317 收藏
-
430 收藏
-
451 收藏
-
311 收藏
-
408 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 507次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习