以下章节讨论了一些常见的经历了瓶颈的应用程序实施和前面所述的场景。所有这些场景都从实施内存中OLTP中受益良多。
以下汇总表提供了每个场景的模式特点,挑战和内存中OLTP带来的主要收益。这将帮助你了解符合你的需求的特定的实施。请注意,某些场景下并不互相排斥,并且一些实施的细节可适用于多种场景。
实施方案 |
模式特点和挑战 |
内存中OLTP的主要收益 |
高速率的数据插入 |
•主要只追加的方式存储 |
•消除争用 |
•写入的工作负荷下无法插入 |
•尽可能减少日志记录的I/O |
|
读取性能和扩展 |
•高性能的读取操作 |
•消除争用 |
•无法满足纵向扩展的需求 |
•高效的数据检索 |
|
|
•尽可能减少代码执行时间 |
|
|
•为了扩展的CPU效率 |
|
繁重的数据处理计算 |
•插入/更新/删除的工作负荷 |
•消除争用 |
•数据库内繁重的计算 |
•尽可能减少代码执行时间 |
|
•读取和写入争用 |
•高效的数据处理 |
|
低延迟 |
•要求典型的数据库解决方案无法实现的低延迟的业务事务 |
•消除争用 |
•高并发会加剧延迟 |
•尽可能减少代码执行时间 |
|
|
•高效的数据检索 |
|
会话状态管理 |
•繁重的插入,更新和点查找 |
•消除争用 |
•在来自于多个无状态的Web服务器的负荷下进行用户扩展 |
•高效的数据检索 |
|
|
•可以选择减少或者去除I/O |
对于每一个场景,将介绍常见业务和应用程序的挑战和瓶颈。我们还将介绍有助于改善这些工作负荷性能的内存中OLTP架构或者设计的注意事项。客户采用的参考方案将在之后的内容中提供。
高速率的数据插入
一些高性能应用程序已经试图解决的关键挑战之一是极高速率的数据加载或插入。接收高度波动的数据流可能会压垮传统型RDBMS的性能。在这种场景中,主要模式是插入。通常,应用程序尝试每秒将一定数量的行或者业务事务输入到系统中,以满足所需的事务吞吐量。智能计量和遥测系统的数据插入是这个场景的两个例子。
典型瓶颈
在数据库中,瓶颈通常出现在“非常热”(频繁访问)的数据集区域中表里的锁和闩锁。大量线程访问在标准B树中的同一数据页可能会导致最新数据页的争用。为了提高查询性能而增加的索引可能也会增加插入执行的额外开销。在许多情况下,需要在写入吞吐量的优化和为了读取添加索引之间进行权衡。在这种场景下,可能包括I/O事务延迟等在内的其他潜在的瓶颈或者性能阻塞者。
应用内存中OLTP — “冲击减缓器”
在这种场景下,“冲击减缓器”是指为了“减缓”输入速率而以内存优化而创建的表。输入速率对于系统是一种“冲击”。
图1 冲击减缓器
这种情况下关于内存优化表的优化包括:
- 消除锁和闩锁争用。
- 如果工作负荷主要由插入组成,这些插入所需的日志记录的数量要小于基于磁盘的对象。这主要是因为系统不会记录内存优化表的索引分配。
- 通过DURABILITY =SCHEMA_ONLY创建内存优化表来消除I/O。只有在内存优化表中的数据能够丢失(比如,服务故障)的情况下可以考虑。在这种故障的情况下,数据将需要被重新创建或者重新加载到表中。
- 通过利用延迟的持续性,在潜在的性能收益和丢失一些数据之间权衡。
这些优化能够使系统实现更大的吞吐量。
内存中OLTP设计的注意事项
在许多情况下,这种场景的实施包括创建内存优化表来存储一部分特定的数据集。作为一个选择,你可以通过Transact-SQL将数据移动到基于磁盘的目标表中。其中需要考虑的一个主要的设计点,是需要驻留在内存优化表中数据集的预期用途和需求。
内存优化表可能只存储一个固定时间周期(几分钟,几小时,几天或更多)的记录,而基于磁盘的SQL Server表则存放“稍微更冷一些”的,不经常访问的数据。由于内存中OLTP集成到SQL Server中,在同一个数据库同时拥有内存优化表和基于磁盘的表是相当普遍的。很典型的是,基于磁盘的表拥有与内存优化表相同的架构。表上的插入(或者“追加”)操作是DML主要的工作负荷。
在内存优化表和基于磁盘的表之间迁移数据可以使用解释型Transact-SQL来完成,选择来自内存优化表数据将其插入到基于磁盘的表里,并(很有可能)从内存优化表中删除该数据。这样的数据迁移通常被计划为一个可以及时执行的“后台”或SQL代理进程。
一个典型的数据迁移实施通常需要一个已知的键值或参照值,以便确定用于迁移的数据。通常已被成功实施的例子包括:
- 日期。
- 可以归于某种类型 “时间戳”的一个序列值。
- 一个范围的值。
内存优化表和基于磁盘的表的这种分区是通常被称为“手动分区”。SQL Server中对于基于磁盘的表没有类似于表和索引分区操作这样的特定系统功能。联机丛书中“用于对内存优化表进行分区的应用程序模式”章节提供了这方面的一个例子。
应用程序部署的其他属性将会影响哪些数据被存储在内存优化表中以及多久将数据迁移到基于磁盘的表中。其中,这些注意事项可能包括:
- 可以被分配给内存优化表的内存数量。输入数据将会持续增长,而分配给SQL Server的内存是有限的。将数据迁移到基于磁盘的表可以释放一些分配的内存。在大多数情况下,由于异步的垃圾收集机制,这不会立即生效。如果应用程序有存储大量数据的需求,它可能不需要将整个数据集都保存在内存优化表中。请注意,基于磁盘的表上的查询可能也需要分配给SQL Server缓冲池中页的内存。
- 对于数据所需的某些属性,内存优化表并不支持。这可能包括数据加密,特殊的索引类型,或者内存优化表不支持的其他选项。
- 考虑查询类型和使用内存中OLTP之外的表和索引得到最好解决的某些查询的性能需求。对于点查找查询,内存优化表提供的哈希索引很有效率。然而,工作负荷模式有可能是OLTP和数据仓库类型的工作负荷的混合。这些数据仓库类型的工作负荷可能会受益于像数据仓库的内存中列存储这样的解决方案。当涉及到内存优化表时,无论是这种索引还是并行执行计划都不能被使用。在这些情况下,在一段时间后将这些数据迁移到基于磁盘的表中或许被证明是最佳的解决方案。
作为内存优化表和基于磁盘的表之间的一个抽象层,也可以用视图来访问这两种表。在这种情况下,需要用解释型Transact-SQL访问视图。
另一种可以实施内存中OLTP并需要考虑和快速数据加载相同注意事项的场景,是暂存表,通常用于将初始数据加载到数据仓库中。内存中OLTP优化的目标场景,OLTP工作负荷在这个场景中也可以实现。系统全面支持使用bcp.exe,BULK INSERT或者SSIS包将数据导入到内存优化表。但是可以为快速数据加载实施的TABLOCK提示或其它的锁行为,与内存中OLTP并不兼容,应该删除。
暂存表通常用作加载数据的中间保存区。一旦数据被处理并迁移到它的最终目的地,暂存表中的数据则不再需要,可能能够被丢弃。此外,如果数据在加载过程中丢失,可以被重新创建并重新加载。因此,在许多情况下,可以使用DURABILITY =SCHEMA_ONLY创建内存优化表,以避免产生所有的日志记录和I/O。
也需要考虑加载,删除或截断暂存表。在使用内存优化表的情况下,你需要删除数据,因为不支持截断。从内存优化表删除数据应该是非常快的。但与从基于磁盘的表中删除数据不同,在垃圾收集进程清理数据行之前,行版本将仍将使用内存。了解了这个机制就能够理解这将暂时增加对内存的需求。当删除大量的数据行时,这可能会有不良的影响。因此,另一种办法是删除并重建暂存表。
最后,需要考虑涉及数据迁移的表存放的位置。内存中OLTP引擎不支持与多个数据库进行交互并涉及内存优化表的事务。因此,暂存表的数据源必须来自于外部的客户端连接,比如SSIS,或者来自同个数据库内的一张表。同样,目标表必须与暂存表在同一个数据库中。
在许多情况下,这样的配置已经证明对于大量的插入和工作负荷的“峰值”是成功的。这个配置还提供了一个成功的整体数据层设计的解决方案来处理这些要求苛刻的工作负荷。采用这种模式客户的成功和测试结果也表明,数据加载并将数据迁移回到基于磁盘的表要更快。也就是说,与直接从基于磁盘的表插入和查询相比要有更少的整体延迟。
客户采用方案
微软内部一个名为事件报告的应用程序,被用于提供准实时的信息,包括来自于大约7500台生成数据的机器的性能数据和事件记录。数据流通过一个Web API。在输入速率的高峰期,数据库无法插入所有数据。通过利用“冲击减缓器”场景中的内存中OLTP,事件报告应用程序增加了六倍的数据插入率。每秒事务数从3500增加到23,000以上。这解决了限制应用程序获得所需吞吐量的数据插入峰值的问题。
读取性能和扩展
在一些情况下,工作负荷的性能关键部分集中在读取的执行。很多时候整体数据集的一小块被大量地查询。它可能是只读的或者只有一些不频繁的更新或者修改。总体而言,在这个模式中,有向大量用户提供服务的需求。通常情况下,他们采用一个明确的(通常是非即席型)查询工作负荷来访问大多为读取的数据中的一个小集合。
有几种类型的架构实施场景可以有利于大量的读取操作。第一种方法是为应用程序使用中间层缓存解决方案来服务于快速读取。第二种方法,在很多情况下,是为了性能,横向扩展读取的工作负荷。这种方法包括拥有查询关键数据的多个副本。多个副本能够尽可能减少客户端之间的数据延迟,并可以成为高可用性架构的一部分。通常,这些解决方案通过使用SQL Server AlwaysOn可用性组的可读辅助副本或者SQL Server的事务复制来实现。横向扩展的解决方案通常运行良好,但要承担高昂的采购和运营成本。
这个工作负荷模式和架构普遍存在于,包括大型零售商,社交网络浏览,应用程序,显示排行榜或者最佳推荐引擎的应用程序等场景中。
典型的瓶颈
在这种场景中,有几种类型的瓶颈可能具有挑战性。即使所有数据页都加载到内存中,并且索引也已经优化, Transact-SQL调用的执行仍然可能很慢。在某些情况下,解析和编译查询的开销也会增加额外的执行时间。在大规模下,并发读写场景的闩锁和锁也会延迟查询执行。
硬件瓶颈,例如CPU利用率,可能会限制扩展,并且需要使用许多只读服务器来分担用户负荷。
中间层缓存的解决方案在管理多个层时会增加额外的复杂度和开销。在层之间数据迁移的性能开销也会增加将数据放到缓存中的总时间。
应用内存中OLTP
内存中OLTP的内存优化表和本地编译的存储过程可以做为一种“缓存”解决方案进行实施。需要对数据的小子集进行极其快速且只读访问的应用程序可以使用这种“缓存”。特别是,本地编译的存储过程可以减少目标查询的执行时间,从而提供了更快的总体查询执行时间。如果工作负荷包含单个数据行的查找,使用非聚集哈希索引将提供额外的性能提升。
对于需要更好读取性能的已有解决方案实施内存中OLTP,通常对于应用程序或数据库代码只需要极少的修改。将数据迁移到内存中OLTP可以尽可能减少与高速缓存管理和分配元数据检索相关的延迟。这将提供更快的数据传输。然而,这种情况的主要收益,通常可以通过将访问这些数据集的数据库代码迁移到本地编译的存储过程来实现。这将通过消除解析,优化,编译,执行计划缓存等方面尽可能减少执行时间。这并不会影响固有延迟的某些其他方面,例如通信协议,网络开销,登录时间等等。
内存中OLTP还提供了对AlwaysOn可用性组和充当事务复制订阅的内存优化表的集成支持。这两种场景能够很好地集成到横向扩展的读取解决方案中。它们可以为可读副本/订阅提供进一步的可扩展性和性能提升。在这些情况下,内存中OLTP的集成也潜在地为服务器整合提供了一种通过提高CPU效率或者只读扩展来实现的机制。这一改进使每台只读服务器能够处理比之前更多的负荷。
图2 读取缓存
内存中OLTP设计的注意事项
在一些情况下,内存优化表可以是读取和写入的主表。如果是这样,主要的收益是在锁和闩锁方面的可伸缩性以及来自于内存优化表潜在的性能收益。由于该表保存了数据的唯一副本,你通常要将内存优化表创建为持久(SCHEMA_AND_DATA)表。这为读取提供了与高速缓存类似的性能,却拥有持续性和“关系型”的特性。
其他情况通常会利用一个中间层的高速缓存。在这种情况下,数据可能驻留在后端的一台数据库服务器中,而数据的一个子集按照一定的时间间隔推送来更新中间层的高速缓存。在这种情况下,应用内存中OLTP也有一些注意事项。首先,在许多情况下,应配置内存优化表为模拟或替换中间层的高速缓存。主要的源表可能拥有持续性的特性(甚至可能不是内存优化表)。然而, “缓存”表可以被创建为SCHEMA_ONLY,如果需要的话,你可以从源中恢复数据集。如果更新不频繁并且I/O没有问题,采用SCHEMA_AND_DATA创建对象在失败时可以通过引擎进行“自动”恢复。
关于使用内存中OLTP替代其他缓存系统的一些更令人信服的理由包括:
- 内存中OLTP提供了一个熟悉的开发和管理的体验。所有对数据的访问都是通过Transact-SQL。不需要其他的编程接口。
- 所有可用性和管理功能都包含在单个源中(数据库和SQL Server)。对于一个跨层和应用程序的高可用性或者灾备策略,这需要的开销更少。在这种情况下,对于每个应用程序的实施和管理是唯一的。
- 创建数据库中的“关系型缓存”需要中间层和服务器数据存储之间更少的网络往返。
- 这将尽可能减少维护环境所需的服务器数量。用户能够在一个环境内,整合和购买更多内存来供内存优化表使用,也可以将其用于数据层的其他资源。
- 性能是非常重要的。因此,内存中OLTP提供本地编译的存储过程和优化的索引,比如适用于点查找的非聚集哈希索引。这些对象能够对关键数据集进行非常快速的访问。
针对内存优化表的查询使用单线程执行,不支持并行计划执行。因此,不需要并行计划就能够高效地运行的目标查询是至关重要的。在许多情况下,用户会考虑通过在基于磁盘的表中执行计算或大的聚合来“预暂存”数据,作为将数据载入内存优化表过程的一部分。因此执行针对性的查找,并不涉及内存优化表上的许多连接或者聚合运算。即使某些查询确实是并行执行的,转换成本地编译的存储过程也有可能将减少执行时间。这些减少的时间足以证明迁移到内存中OLTP是对的。并不是所有针对内存优化表的查询都必须是本地编译的存储过程。只考虑将对性能关键的查询迁移到本地编译的存储过程可以使应用程序继续对内存优化表执行解释型T-SQL。
为了快速读取访问而存储关键部分数据的内存优化表,仍然需要对缓存内的数据进行手动更新。使用一个计划作业或者通过一个用户发起的即席Transact-SQL执行,更新可以重新执行并且是基于时间的。这与处理SQL Server中的两个表对象类似。你可以通过使用本地编译存储过程进一步优化只访问内存优化表的操作。
某些场景需要表数据全部或部分的副本。对此,内存中OLTP集成了横向扩展架构,比如AlwaysOn可用性组的可读辅助副本和事务复制的只读订阅。 AlwaysOn可用性组主要是一个高可用性和灾难恢复的实施方案。每个可用性组允许多达8个辅助副本,并且这些副本提供了读取服务。这种能力也提供了一个支持推动读取工作负荷到副本用于横向扩展的架构。
对于可用性组中的对象,内存中OLTP的配置与基于磁盘的表相同。可用性组使用SQL Server事务日志同步副本。如果需要在辅助副本中读取数据,该表必须被创建为SCHEMA_AND_DATA。
内存中OLTP对象的初始配置和事务复制需要一些特定的初始配置步骤。这些步骤在联机丛书 “复制到内存优化表订阅服务器”一节中可以找到。
客户采用方案
Edgenet®是一家SaaS提供商,开发解决方案将优化的产品数据提供给供应商,零售商和包括Bing和Google®在内的搜索引擎。通过在线和在实体店中使用,Edgenet解决方案确保企业和消费者基于及时准确的产品信息做出决策。通过实施内存中OLTP,Edgenet能够消除他们对应用程序缓存的依赖,并在同一个数据库集成读取的工作负荷,将准实时的库存更新提供给消费者。详情请参阅“Edgenet案例研究”。
繁重数据处理计算
这种场景由插入数据组成,类似于高速率的数据插入。但这种场景需要其他的特性,包括更新,删除和附加数据处理来将数据快速地提供给读取者。通常情况下,数据被直接传送到最终目的地,以便数据能够尽快地供消费者使用。提高吞吐量和扩展并发的读/写性能是这种模式的主要挑战。
在许多情况下,数据以提取-转换-加载(ETL)或流水线的方式移动,传递到数据库中。这种传递可以以持续或者成批的方式进行。然后,将数据写入到最终目标目的地之前,系统对数据进行处理和转换。同时,繁重的读取工作负荷在目标表上执行。有时,查询量可能与OLTP类似。查询量可能是点查找或者是有少数连接来查找一条记录或记录一小部分的特定的变更值。综上所述,这个场景的特有特征包括:
- 涉及一些Transact-SQL代码内的计算密集型行为的一个工作负荷模式。
- 比起大多数追加的插入操作,转换操作有更多的更新和删除DML。
- 直接加载到终端表和扩展并发的写入和读取是重要因素。
可能使用这个解决方案的一些业务场景包括制造供应链或者提供准实时产品目录信息的零售商。
典型瓶颈
这种场景可能存在关于争用的一些问题。插入数据上的闩锁是一个典型的瓶颈。之后,在引擎内处理和转换数据也是耗时的。最后,扩展并发的读写工作负荷导致锁和闩锁争用成为扩展的障碍。
应用内存中OLTP
在这个模式中,最终目标表是内存优化表。通常这是所有基于磁盘的表的替换,而且在工作负荷中没有暂存或进一步的目标表。将对性能敏感的表和Transact-SQL代码移动到内存优化表和本地编译的存储过程,可以为这种工作负荷场景提供更好的性能。
图3 数据转换流水线
内存优化表和多版本乐观并发的实施消除了闩锁和锁。仅这一点就可以用这些特性为许多工作负荷在扩展方面提供显著的性能收益。
在数据被放入目标表之前,大部分工作侧重于对数据的转换和运算。这对于本地编译存储过程是一个理想的情况,可以显著减少转换的总执行时间,并提高了整体吞吐量。一般而言,可以迁移到本地编译的存储过程的工作越多,性能提升的机会就越多。将单个语句执行调用放到一个本地编译的存储过程不太可能产生显著的性能提升。
内存中OLTP设计的注意事项
在许多的这种情况中,用户查询的工作负荷集中在一个或几个数据行上。因此,使用哈希索引优化点查找的查询可能会获益良多。尤其是如果数据的大小和基数定义得当,使得能够实施一个有效的桶计数。
从数据载入和查询的角度考虑内存优化表的整体规模是很重要的。持久表可以拥有的检查点文件数量是有限制的。在多数情况下,数据库的大小在100 GB的最大范围内,这没有问题。如果内存优化表的大小要大得多,应考虑将 “老化”或“更冷”的数据移动到基于磁盘的表中。
当你在考虑迁移到本地编译的存储过程时,请务必正确评估所需的更改。也评估调整代码以遵守支持的语言结构的能力。本地编译的存储过程只支持部分的Transact-SQL功能。
考虑对于关键事务或者在数据库级别设置延迟的持续性。这样做可能会有风险丢失最近的提交事务,但它可能有助于降低事务的执行时间。
客户采用方案
Edgenet是一家SaaS提供商,开发解决方案将优化的产品数据提供给供应商,零售商和包括Bing和Google在内的搜索引擎。通过在线和在实体店中使用,Edgenet解决方案确保企业和消费者基于及时准确的产品信息做出决策。通过实施内存中OLTP,在SQL Server中,Edgenet能够获得8到11倍的事务吞吐量,极大地减少了将更新数据存入数据库中所需的时间。详情请参阅“Edgenet案例研究”。
低延迟执行
许多性能敏感的应用程序的主要焦点是端至端的延迟。在非常低延迟的场景下,为了性能提升,你需要测量和分析在关键执行路径中的每一个组件。在这里,节约几分之一秒的影响对业务都至关重要。
在许多关注延迟的应用程序架构中,在关键执行路径中可能都不存在关系型数据库。系统可以以异步的方式写入到关系型数据库中,因为其对于延迟的影响非常显著。
通常具有低时延需求的行业或解决方案包括了资本市场和投资交易,大规模的在线游戏平台等等。
典型的瓶颈
延迟对于事务经历的所有层都是一个关键的性能指标。所有前面提到的场景都是以某种延迟导致瓶颈为特征,比如锁和闩锁争用。迁移到内存中OLTP是一种行之有效的方法,可以在整体延迟方面提供收益。
你可以通过两种方式查看延迟。首先,在隔离环境中,没有任何其他工作在系统中运行,执行某一工作单元所需的时间。即使在隔离环境中,一些工作单元的执行通常也不能足够快到 满足应用程序的需要。正如前面在“OLTP工作负荷的挑战”一节中所提到的,延迟可能源于:
- 网络和通信协议栈。
- 解析,优化,并编译一个语句。
- 执行一个事务的总体时间,是基于每个操作的CPU指令数,处于较低的水平。
- 将一个持续的事务保存到事务日志的写入时间。
其次,在负荷下的系统行为。在这种场景下,如闩锁和锁或者资源利用率,比如CPU,可能会增加特定工作单元的执行时间。所有这些都增加了处理过程的延迟。
应用内存中OLTP
通过以下方式,内存中OLTP可以极大地减少整体延迟:
- 最小化的代码执行路径,并在代码执行阶段提供了CPU效率。通过使用本地编译的存储过程,它消除了查询在执行时的解析,优化和编译。
- 通过利用非聚集哈希索引,提高点查找查询的性能。
- 如同之前场景所表明的,提高了日志记录的I/O性能。
- 在负荷下,提供了无闩锁和锁的数据访问。这为高并发工作负荷消除了扩展和执行时间的障碍。
内存中OLTP设计的注意事项
在考虑内存中OLTP的延迟优化时,请记住,使用本地编译的存储过程和内存中对象你能够收获大部分优势。因此,将尽可能多的执行路径迁移到这些引擎组件中,这对于总体结果将产生决定性的影响。你有一些不同的I/O性能增强选项,这些选项也会影响一个标准SCHEMA_AND_DATA表的持续性。确保事务持续性的I/O延迟仍然是同步事务执行路径的一部分。
大多数OLTP应用程序发出大量的小查询,这些查询从客户端连接栈增加了额外一层延迟开销。这个栈建立到数据库引擎的连接,检索结果,处理错误等等。内存中OLTP并不提供在网络层的优化。对于底层的客户端服务器通信,比如TDS,或者更高层的,比如客户端网络库,并没有优化。这种网络通信的开销有时会很显著。一个典型的解决办法包括合并或称为“批处理”,多个事务合成单个执行单元。单元执行时只需单次“往返”于数据库引擎。此外,考虑使用SQL Server本机客户端和准备好的执行以减少单次调用的延时。来自客户端和服务器的调用可能不需要在单个线程内同步。如果是这样,可以打开另一个客户端线程采用扩展来提高性能。在这两种情况下,应用程序需要更改以进一步减少这种网络开销。
重要的是要记住,内存中OLTP使用一个乐观的并发控制机制。当两个或更多的事务尝试修改同一行时,这种机制会遭受写冲突。当写冲突发生时,涉及的其中一个事务将被回滚(在冲突发生时和事务提交之前)。回滚将导致一些开销,并增加业务事务的延迟。与此类似,运行在某些隔离级别(可重复读取和序列化)中事务的验证,也会在提交前发生,并且语句会被回滚。失败的事务不会被自动重新提交给引擎。相反,系统会抛出一个应用程序需要进一步处理的错误。我们建议你编写重试逻辑来处理这些冲突。然而,从一开始就尽可能减少潜在的冲突是减少重试次数的理想解决办法。在延迟方面,在客户端实施重试逻辑将有助于将所有SQL Server内执行工作推送到内存中OLTP引擎中。这类似于你将如何处理死锁检测。使用SQL Server本机客户端的准备好的执行模型将尽可能减少反复调用更慢的解释型Transact-SQL相关的开销。详细讨论请参阅“准备好的执行”。
客户采用方案
SBI Liquidity Market提供在线的外汇交易服务。通过实施内存中OLTP,SBI能够提高吞吐量和扩展,并开发其平台以最小化延迟。更多信息请参阅“SBILM案例研究”。
会话状态管理
与先前讨论的更广泛的模式不同,维持会话信息的需求对于许多应用程序来说是一个特殊的功能。会话状态管理包括为无状态的协议维持状态信息,其中最突出的例子是HTTP。当用户与网站交互时,他们的参数选择,行为和内部元数据需要跨多个HTTP请求持续。这种持续性往往在数据库中实现。当使用负载均衡的Web服务器,其中多台服务器可能服务于同一个会话,集中化的状态存储可能会导致增加争用。这种工作负荷的行为将导致对用户会话数据的大量更新,并通常使用集中在单行的查找查询。有些Web平台,比如ASP.NET,提供选项来维持进程内的会话状态或者使用SQL Server关系型数据库来保持这个会话状态的数据。后者通常用于大规模的网站。
会话状态实施的总规模通常是相当小的。但本质上数据是非常动态的。一些线程在积极写入和修改数据的同时,其他线程在读取之前保存的数据。由于尝试访问数据中相对较小区域的线程数的增加,锁和闩锁争用的瓶颈则开始显现。对于使用大量CPU内核的负荷较重的系统,这可能是扩展的一个显著障碍。这些表的更新和查询所需的时间是用户体验的关键。
典型的瓶颈
这些模式通常先开始忍受导致闩锁和锁瓶颈的并行读/写争用的挑战。在许多情况下,这种规模的瓶颈将导致应用程序缓慢或者强制实施逻辑横向扩展。这两种场景对于业务来说都可能有很大的影响。
应用内存中OLTP
内存优化表不用忍受闩锁,因为内存中OLTP引擎的内核采用了无锁的算法和结构,因而没有闩锁。将闩锁热点的表迁移到内存优化表中可以消除这个扩展的瓶颈。值得注意的是,乐观并发模型提供了一个新的应用程序将不得不考虑的范例。通常情况下,会话状态表并不忍受写入进程之间的冲突,因为每个Web会话将只更新自己的行。然而,类似的模式可以具有修改同一行的并发事务,这会导致无锁的冲突。如果发生冲突,其中一个事务将会失败。为冲突做好准备,比如在Transact-SQL或者应用程序重试代码中使用try/catch逻辑,是至关重要的。
图5 会话状态
内存中OLTP设计的注意事项
评估数据的持续性需求。通过将日志内容记录到数据库事务日志,内存中OLTP引擎支持完整的ACID合规性和表的持续性。它还提供了表级别的配置用于将表创建为SCHEMA_ONLY或非持久的。在会话状态管理的应用程序中,丢失这些数据的影响常常是一个用户需要再次登录到Web服务器。因此,在一些情况下,可以实施SCHEMA_ONLY表。在数据是非暂时性的或者I/O的总体减少并不有效的场景中,可以选择将表声明为SCHEMA_AND_DATA从而具备持续性。
在某些情况下,状态管理表包含LOB数据或者有超过8060字节大小的行。内存优化表不支持LOB列,并且只支持小于8060字节大小的行。对于这种情况,应用程序的设计模式包括拆分表和将LOB数据或更大数据类型的列移动到一个基于磁盘的表中。如果瓶颈是在访问LOB数据上,这种方法并不能提供多少好处。因此,如果你频繁访问LOB并且它们是瓶颈的一部分,请仔细考虑整体的利益。另一种方法可以将大数据列(例如,如果数据类型是VARBINARY)拆分成多个行和使用本地编译的存储过程将它们“重新组合”用于检索,这已被证明是一种有效的解决方案。最后,在其他场景中,应用程序应限制数据输入少于8060个字节。有关这些场景的详细信息,请参阅“在内存优化表中实现 LOB 列”。
哈希索引为点查找进行了高度优化。因为几乎所有的数据修改和查询都是对单行数据进行操作,实施内存优化的非聚集哈希索引可以有助于提高查询的性能。
标准的解释型Transact-SQL调用的延迟也会影响终端用户的总体性能。减少每个业务事务的时间可以成为整体性能方面的重要目标。将Transact-SQL代码迁移到本地编译的存储过程并降低事务执行的延迟在改善整体用户体验中都是关键因素。
用于内存中OLTP的ASP.NET会话状态访问接口
一个ASP.NET会话状态访问接口以及Transact-SQL脚本,已经完成开发,使其在使用ASP.NET会话状态时更容易利用SQL Server内存中OLTP的优势。
二进制文件和代码已经被发布在以下位置:
1.包可以从以下网址得到:https://www.nuget.org/packages/Microsoft.Web.SessionState.SqlInMemory/
2.源代码也可通过Codeplex获得:https://msftdbprodsamples.codeplex.com/releases/view/125282
客户采用方案
将内存中OLTP用于状态管理一直是一个许多架构已经利用的热门场景。
对于BWin.party,这个会话状态的实施是其业务最关键任务的一部分。通过迁移到内存OLTP,批处理请求从15,000的限制提高到250,000,吞吐量增长了16倍。这使得BWin.party能够纵向扩展环境,并将18个逻辑分区服务器的SQL Server实例合并成1个。为了做到这一点,BWin.party并没有修改任何SQL Server之外的代码。更多详情,请参阅“Bwin.party案例研究”。
扫码二维码 获取免费视频学习资料
- 本文固定链接: http://phpxs.com/post/2648/
- 转载请注明:转载必须在正文中标注并保留原文链接
- 扫码: 扫上方二维码获取免费视频资料