知识卡片
悲观离线锁两个实践坑:死锁与会话丢失
内容
跨系统事务实现悲观离线锁时,有两个容易踩的实践坑。死锁:像数据库”SELECT FOR UPDATE”或实体EJB那样,锁机制一直死等到锁可用为止,很容易导致经典死锁(两个用户各自持有对方需要的资源,互相永久等待);跨系统的业务事务动辄持续几分钟甚至二十分钟,让锁请求傻等下去毫无意义(没人愿意为这样的锁等那么久),如果要实现”等待”还得引入超时控制,反而把问题搞得更复杂。作者给出的解法很干脆:不等待——让锁管理对象在锁不可用时直接抛出异常,业务事务当场失败、由调用方决定是否重试,这样就完全绕开了”要不要等、等多久、怎么判断死锁”这一整套麻烦。会话丢失:如果客户端在事务进行到一半时崩溃,这个事务永远不会正常结束,它占有的锁自然也就永远不会被释放——这在Web应用里是个大问题,因为用户经常直接抛弃会话、不打招呼就走人。作者建议不要自己造轮子去处理这个问题,而是交给应用服务器/Web容器自带的会话超时机制:可以注册一个监听对象,在HTTP会话失效时自动触发释放该会话持有的所有锁;退而求其次的办法是给每个锁打上时间戳,定期清理超过一定时限的锁。可迁移启发:处理并发系统里”等待共享资源”和”持有者意外消失”这两类经典难题时,与其自己动手实现一套等待/超时/清理机制,不如优先考虑现成基础设施(容器的会话生命周期、数据库自身的锁机制)能不能直接顶上——很多此类问题已经在更底层被反复验证过,重新发明轮子的代价通常高于直接借用。
参考来源
- 位置:《企业应用架构模式》第二部分"模式"之"第16章 离线并发模式"之"16.2.1 运行机制"(源文件:_epub-src/OEBPS/Text/000193.html)
- 结论依据:原文说明"不等待也可以,因为编写等待方法要涉及超时控制,使问题一下就复杂化了。只需让锁管理对象在锁不可用时抛出异常就行了,这样就没有和死锁打交道的麻烦了……理想情况是让用应用服务器的超时机制来处理,而不是应用自己来处理……超时可以通过注册一个监听对象在HTTP会话无效时释放它所占有的锁",直接支撑本卡结论。
- 原始内容:不等待也可以,因为编写等待方法要涉及超时控制,使问题一下就复杂化了。只需让锁管理对象在锁不可用时抛出异常就行了,这样就没有和死锁打交道的麻烦了。