Java 事务策略: 高并发策略

JAVA herman 523浏览 0评论

事务策略是应用于大多数标准业务应用程序的核心策略。它们简单、可靠、相对易于实现,并且提供了最高水平的数据完整性和一致性。但有时,您可能需要减小事务的作用域以获取吞吐量、改善性能并提高数据库的并发性。您如何才能实现这些目的,同时仍然维持高水平的数据完整性和一致性呢?答案是使用 High Concurrency 事务策略。

High Concurrency 策略源自 API 层 策略。API 层策略虽然非常坚固和可靠,但它存在一些缺点。始终在调用栈的最高层(API 层)启动事务有时会效率低下,特别是对于具有高用户吞吐量和高数据库并发性需求的应用程序。限制特定的业务需求,长时间占用事务和长时间锁定都会消耗过多资源。

与 API 层策略类似,High Concurrency 策略释放了客户机层的任何事务责任。但是,这还意味着,您只能通过客户机层调用一次任何特定的逻辑工作单元(LUW)。High Concurrency 策略旨在减小事务的总体作用域,以便资源锁定的时间更短,从而增加应用程序的吞吐量、并发性以及性能。

通过使用此策略所获取的好处在一定程度上将由您所使用的数据库以及它所采用的配置决定。一些数据库(比如说使用 InnoDB 引擎的 Oracle 和 MySQL)不会保留读取锁,而其他数据库(比如没有 Snapshot Isolation Level 的 SQL Server)则与之相反。保留的锁越多,无论它们是共享还是专用的,它们对数据库(以及应用程序)的并发性、性能和吞吐量的影响就越大。
但是,获取并在数据库中保留锁仅仅是高并发性任务的一个部分。并发性和吞吐量还与您释放锁的时间有关。无论您使用何种数据库,不必要地长时间占用事务将更长地保留共享和专用锁。在高并发性下,这可能会造成数据库将锁级别从低级锁提高到页面级锁,并且在一些极端情况下,从页面级锁切换到表级锁。在多数情况下,您无法控制数据引擎用于选择何时升级锁级别的启发方法。一些数据库(比如 SQL Server)允许您禁用页面级锁,以期它不会从行级锁切换到表级锁。有时,这种赌博有用,但大多数情况下,您都不会实现预期中的并发性改善。

底线是,在高数据库并发性的场景中,数据库锁定(共享或专用)的时间越长,则越有可能出现以下问题:

  • 数据库连接耗尽,从而造成应用程序处于等待状态
  • 由共享和专用锁造成的死锁,从而造成性能较差以及事务失败
  • 从页面级锁升级到表级锁

换句话说,应用程序在数据库中所处的时间越长,应用程序能处理的并发性就越低。我所列出的任何问题都会造成您的应用程序运行缓慢,并且将直接减少总体吞吐量和降低性能 — 以及应用程序处理大型并发性用户负载的能力。

igh Concurrency 策略解决了高并发性需求,因为它能将事务在体系结构中的作用域尽可能减小。其结果是,事务会比在 API 层事务策略中更快地完成(提交或回滚)。但是,就像您从 Vasa 中学到的,您不能同时拥有它们。生活中充满了折衷,事务处理也不例外。您不能期望提供与 API 层策略同样可靠的事务处理,同时提供最大的用户并发性和最高的吞吐量。
因此,您在使用 High Concurrency 事务策略时放弃了什么呢?根据您的应用程序的设计,您可能需要在事务作用域外部执行读取操作,即使读取操作用于更新目的。“等一等!”您说:“您不能这样做 — 您可能会更新在最后一次读取之后发生了变化的数据!”这是合理的担忧,并且也是需要开始考虑折衷的地方。通过此策略,由于您未对数据保持读取锁,因此在执行更新操作时遇到失效数据异常的机率会增加。但是,与 Vasa 的情况一样,所有这些都可以归结为一个问题,即哪个特性更加重要:可靠、坚固的事务策略(如 API 层策略),还是高用户并发性和吞吐量。在高并发性情形中,同时实现两者是极为困难的。如果您尝试这样做,则可能会适得其反。
第二个折衷之处是事务可靠性的总体缺乏。此策略难以实现,并且需要更长的时间进行开发和测试,并且比 API 层或 Client Orchestration 策略更易于出错。考虑到这些折衷,您首先应该分析当前的情形以确定使用此策略是否是正确的方法。由于 High Concurrency 策略派生自 API 层策略,因此一种比较好的方法是先使用 API 层策略,并使用较高的用户负载对应用程序执行负载测试(比您预期的峰值负载更高)。如果您发现吞吐量较低、性能较第、等待次数非常多,或者甚至出现死锁,则要准备迁移到 High Concurrency 策略。
在本文的其余部分,我将向您介绍 High Concurrency 事务策略的其他一些特性,以及实现它的两种方法。