Java理论和实践: 理解JTS均衡安全性和性能[Java编程]
本文“Java理论和实践: 理解JTS均衡安全性和性能[Java编程]”是由七道奇为您精心收集,来源于网络转载,文章版权归文章作者所有,本站不对其观点以及内容做任何评价,请读者自行判断,以下是其具体内容:
事件主如果一种非常处理机制.事件在程序中的用处与合理条约在平常业务中的用处类似:假如出了什么问题它们可以帮忙恢复.但由于大大都时间内都没实际 发生什么错误,我们就但愿可以尽大概削减它们的开销以及对别的时间的占用.我们在利用程序中若何利用事件会对利用程序的性能和可伸缩性产生很大的影响.
事件划分
J2EE 容器供应了两种机制用来定义事件的起点和终点:bean 管理的事件和容器管理的事件.在 bean 管理的事件中,用 UserTransaction.begin() 和 UserTransaction.commit() 在 bean 办法中显式开始和完毕一个事件.另一方面,容器管理的事件供应了更多的机动性.通过在装配描写符中为每个 EJB 办法定义事件性属性,您可以指定每个办法的事件性需求并让容器肯定什么时刻开始和完毕一个事件.无论在哪类情形下,构建事件的基本指导方针都是一样的.
进来,出去
事件划分的第一条法则是"尽大概短小".事件供应并发掌握;这普通意味着资源管理器将代表您得到您在事件期间拜候的数据项的锁,并且它必须一向持有这些锁,直到事件完毕.(请回想一下本系列第 1 部份所谈论的 ACID特点,此中"ACID"的"I"代表"断绝"(Isolation).也就是说,一个事件的后果影响不到与该事件并发履行的别的事件.)当您拥有锁时,任何需求拜候您锁定的数据项的别的事件将不得不一向等候,直到您释放锁.假如您的事件很长,那些别的的全部事件都将被锁定,您的利用程序吞吐量将大幅度下降.
法则 1:使事件尽大概短小.
通过使事件尽大概短小,您可以把阻碍别的事件的时间缩到最短,从而提高利用程序的可伸缩性.保持事件尽大概短小的最好办法当然是不在事件中间做任何不必要耗费时间的事,分外是不要在事件中间等候用户输入.
开始一个事件,从数据库检索一些数据,显示数据,然后在仍处于事件中时请用户做出一个挑选大概对比诱人.千万别这么做!即便用户注意力集合,也要耗费数秒来呼应 ― 而在数据库中拥有锁数秒的时间已经是很长的了.假如用户决意脱离计算机,大概是去吃午饭大概乃至回家一天,会发生什么情形?利用程序将只好无奈停机.在事件期间履行 I/O 是招致灾难的诀窍.
法则 2:在事件期间不要等候用户输入.
将相关的操作归在一同
由于每个事件都有不小的开销,您大概认为最好是在单个事件中履行尽大概多的操作以使每个操作的开销到达最小.但法则 1 奉告我们长事件对可伸缩性不利.那么若何实现最小化每个操作的开销和可伸缩性之间的均衡呢?
我们把法则 1 设置为逻辑上的极度 ― 每个事件一个操作 ― 这样不但会招致额外开销,还会危及利用程序状况的一致性.假定事件性资源管理器保护利用程序状况的一致性(请回想一下第 1 部份,此中"ACID"的"C"代表"一致性"(Consistency)),但它们依靠利用程序来定义一致性的意思.实际上,我们在描写事件时利用的一致性的定义有点圆滑:利用程序说一致性是什么意思它就是什么意思.利用程序把几组利用程序状况的改变组织到几个事件中,后果利用程序的状况就成了 定义上的(by definition)一致.然后资源管理器确保假如它必须从弊端恢复的话,就把利用程序状况恢复到近来的一致状况.
在第 1 部份中,我们给出了一个在银行利用程序中将资金从一个帐户转移到另一个帐户的示例.清单 1 展示了这个示例大概的 SQL 实现,它包含 5 个 SQL 操作(一个挑选,两个更新和两个插入操作):
清单 1. 资金转移的样本 SQL 代码
1 SELECT accountBalance INTO aBalance 2 FROM Accounts WHERE accountId=aId; 3 IF (aBalance >= transferAmount) THEN 4 UPDATE Accounts 5 SET accountBalance = accountBalance - transferAmount 6 WHERE accountId = aId; 7 UPDATE Accounts 8 SET accountBalance = accountBalance + transferAmount 9 WHERE accountId = bId; 10 INSERT INTO AccountJournal (accountId, amount) 11 VALUES (aId, -transferAmount); 12 INSERT INTO AccountJournal (accountId, amount) 13 VALUES (bId, transferAmount); 14 ELSE 15 FAIL "Insufficient funds in account"; 16 END IF 17 |
假如我们把这个操作作为五个单独的事件来履行会发生什么情形?这样不但会使履行速度变慢(由于事件开销),还会失去一致性.比方,假如一个人从帐户 A 取了钱,作为履行第一次 SELECT(查抄余额)和随后的记入借方 UPDATE 之间的一个单独事件的一部份,会发生什么情形?这样会违反我们认为这段代码会强迫服从的业务法则 ― 帐户余额应当是非负的.假如在第一次 UPDATE 和第二次 UPDATE 之间系统失利会发生什么情形?目前,当系统恢复时,钱已经脱离了帐户 A 但还没有记入帐户 B 的贷方,并且也无记录阐明缘由.这样,哪个帐户的全部者都不会高兴.
清单 1 中的五个 SQL 操作是单个相关操作 ― 将资金从一个帐户转移到另一个帐户 ― 的一部份.因此,我们但愿要末全部履行它们,要末一个也不履行,倡议在单个事件中全部履行它们.
法则 3:将相关操作归到单个事件中.
抱负化的均衡
法则 1 说事件应尽大概短小.清单 1 中的示例表明有时刻我们必须把一些操作归到一个事件中来保护一致性.当然,它要依靠利用程序来肯定"相关操作"是由什么构成的.我们可以把法则 1 和 3 结合在一同,供应一个描写事件范围的普通指导,我们规定它为法则 4:
法则 4:把相关操作归到单个事件中,但把不相关的操作放到单独的事件中.
容器管理的事件
在利用容器管理的事件时,不是显式声明事件的起点和终点,而是为每个 EJB 办法定义事件性需求.bean 的 assembly-descriptor 的 container-transaction 部份的 trans-attribute 元素中定义了事件情势.(清单 2 中显示了一个 assembly-descriptor 示例.)办法的事件情势以及状况 ― 调用办法能否早已在事件中被征用 ― 决意了当 EJB 办法被调用时容器应当举行下面几个操作中的哪一个:
征用现有事件中的办法.
成立一个新事件,并征用该事件中的办法.
不征用任何事件中的办法.
抛出一个非常.
清单 2. 样本 EJB 装配描写符
1 <assembly-descriptor> 2 ... 3 <container-transaction> 4 <method> 5 <ejb-name>MyBean</ejb-name> 6 <method-name>*</method-name> 7 </method> 8 <trans-attribute>Required</trans-attribute> 9 </container-transaction> 10 <container-transaction> 11 <method> 12 <ejb-name>MyBean</ejb-name> 13 <method-name>logError</method-name> 14 </method> 15 <trans-attribute>RequiresNew</trans-attribute> 16 </container-transaction> 17 ... 18 </assembly-descriptor> 19 |
那么我们应当为自己的 bean 办法挑选哪类情势呢?关于会话 bean 和消息驱动 bean,您普通想利用 Required 来确保每个调用都被作为事件的一部份履行,但仍将答应办法作为一个更大的事件的组件.请当心利用 RequiresNew ;只有在肯定自己的办法的行为应当与调用您的办法的行为脱离提交时,才应当利用这种情势. RequiresNew 普通情形下只和与系统中别的对象关系很少或没什么关系的对象(比方日记对象)一同利用.(把 RequiresNew 与日记对象一同利用对比有意义,因为您大概但愿在不管外围事件能否提交的情形下提交日记消息.)
RequiresNew 利用不当会招致与上面的描写类似的情形,此中,清单 1 中的代码在五个脱离的事件而不是一个事件中履行,这样会使利用程序处于不一致状况.
关于 CMP(容器管理的长期性,container-managed persistence)实体 bean,普通是但愿利用 Required . Mandatory 也是一个公道的选项,分外是在最初开辟时;这将会告诫您实体 bean 办法在事件外被调用这种情形,这时大概会指出一个布置错误.您几近从不但愿把 RequiresNew 和 CMP 实体 bean 一同利用. NotSupported 和 Never 旨在用于非事件性资源,比方 Java 事件 API(Java Transaction API,JTA)事件中无法征用的外部非事件性系统或事件性系统的适配器.
假如 EJB 利用程序计划得当,利用上面的事件情势指导常常会自然地产生法则 4 倡议的事件划分.缘由是 J2EE 体系架构鼓舞把利用程序分化为最小的便利处理的块,并且每个块都作为一个单独的恳求被处理( 不管是以 HTTP 恳求的情势还是作为在 JMS 行列中列队的消息的后果).
重温断绝
在第 1 部份中,我们定义了 断绝(isolation)的意思是:一个事件的影响对与该事件并发履行的别的事件是不可见的;从事件的角度来看,好象事件是持续履行而非并行履行.固然事件性资源管理器常常可以同时处理很多事件并供应断绝的假象,但有时断绝限制实际上要求把新事件耽误到现有事件完成后才开始.由于完成一个事件至少包含一个同步磁盘 I/O(写到事件日记),这就会把每秒的事件数限制到接近每秒的写磁盘次数,这对可伸缩性不利.
实际上,普通是充分放松断绝需求以答应更多的事件并发履行并使系统呼应可以得到改进,使可伸缩性变得更强.几近全部的数据库都支持尺度断绝级别:读未提交的(Read Uncommitted)、读已提交的(Read Committed)、可反复的读(Repeatable Read) 和可串行化的(Serializable).
不幸的是,为容器管理的事件管理断绝目前是在 J2EE 标准的范围之外.但是,很多 J2EE 容器,比方 IBM WebSphere 和 BEA WebLogic,将供应特定于容器的扩大,这些扩大答应您以每办法(per-method)为底子设置事件断绝级别,设置办法与在装配描写符中设置事件情势的办法相同.关于 bean 管理的事件,您可以通过 JDBC 大概别的资源管理器衔接设置断绝级别.
为阐明断绝级别之间的差别,我们首先把几个并发危险分类 ― 这几种危险是当没有适本地断绝时一个事件大概会干与另一个事件的情形.下列的全部这些危险都与这种情形( 第二个事件已经启动后第一个事件变得对第二个事件 可见)的后果有关:
脏读(Dirty Read):当一个事件的中间(未提交的)后果对另一个事件可见时就会发生这种情形.
不可反复的读(Unrepeatable Read):当一个事件读取一个数据项,然后重新读取这个数据项并看到差别的值时就是发生了这种情形.
虚读(Phantom Read):当一个事件履行返回多个行的查询,稍后再次履行同一个查询并看到第一次履行该查询没呈现的额外行时就是发生了这种情形.
四个尺度断绝级别与这三个断绝危险相关,如表 2 所示.最低的断绝级别"读未提交的"并不能保护事件不被别的事件更改,但它的速度最快,因为它不需求争取读锁.最高的断绝级别"可串行化的"与上面给出的断绝的定义相当;每个事件好象都与别的事件的影响完好断绝.
表 2. 事件断绝级别
断绝级别 | 脏读 | 不可反复的读 | 虚读 |
读未提交的 | 是 | 是 | 是 |
读已提交的 | 否 | 是 | 是 |
可反复的读 | 否 | 否 | 是 |
可串行化的 | 否 | 否 | 否 |
关于大大都数据库,缺省的断绝级别为"读已提交的",这是个很好的缺省挑选,因为它禁止事件在事件中的任何给定的点看到利用程序数据的不一致视图."读已提交的"是一个很不错的断绝级别,用于大大都典型的短事件,比方获得报表数据或获得要显示给用户的数据的时刻(大都是作为 Web 恳求的后果),也用于将新数据插入到数据库的情形.
当您需求全部事件间有较高级别的一致性时,利用较高的断绝级别"可反复的读"和"可串行化的"对比符合,比方在清单 1 示例中,您但愿从查抄余额以确保有充足的资金到您实际取钱期间账户余额一向保持不变;这就要求至少要用"可反复的读"断绝级别.在数据一致性绝对重要的情形下,比方考核记帐数据库以确保一个帐户的全部借方金额和贷方金额的总数等于它目前的余额时,大概还需求避免成立新行.这种情形下就需求利用"可串行化的"断绝级别.
最低的断绝级别"读未提交的"很少利用.它实用于您只需求得到近似值,不然查询将招致您不但愿的性能开销这种情形.当您想要预计一个改变很快的数目,如订单数大概本日所下订单的总金额(以美圆为单位)时普通利用""读未提交的".
因为断绝和可伸缩性之间实际是一种此消彼长的关系,所以您在为事件挑选断绝级别时应当当心行事.挑选太低的级别对数据对比危险.挑选太高的级别大概对性能不利,固然负载对比轻时大概不会这样.普通来说,数据一致性问题比性能问题更严重.假如拿不准,应当以当心为主,挑选一个较高的断绝级别.这就引出了法则 5:
法则 5:利用保证数据安全 style="COLOR: #000000" href="http://safe.it168.com/" target=_blank>安全的最低断绝级别,但假如拿不准,请利用"可串行化的".
即便您打算刚开始时以当心为主并但愿后果性能可以承受 ―(被称为"回绝和祷告(denial and prayer)"的性能管理技术 ― 极大概是最常用的性能战略,固然大大都开辟者都不承认这一点),在开辟组件时考虑断绝需求也是有利的.您应当勤奋编写可以容忍级别较低但实用的断绝级别的事件,这样,当稍后性能成为问题时,自己就不会陷入窘境.因为您需求知道办法正在做什么以及这个办法中躲藏了什么一致性假定来精确设置断绝级别,那么在开辟期间细心阐明并发需求和假定,以便在装配利用程序时帮忙作出精确的决意也不失为一个好主张.
完毕语
本文中供应的很多指导大概看起来有点彼此冲突,因为象事件划分和断绝这种问题本来就是此消彼长的.我们正在勤奋均衡安全性(假如我们不关心安全性,那就压根没必要用事件了)和我们用来供应安全限度的工具的性能开销.精确的均衡要依靠很多因素,包含与系统弊端或当机时间相关的代价或侵害以及组织的风险承受本领.
以上是“Java理论和实践: 理解JTS均衡安全性和性能[Java编程]”的内容,如果你对以上该文章内容感兴趣,你可以看看七道奇为您推荐以下文章:
本文地址: | 与您的QQ/BBS好友分享! |