本文介绍一种构建缓存的新方式,这种缓存能有效地扩展,甚至在缓存
大小超过 10,000 行时仍比">访问数据库
更快。新的缓存结构对应用程序透明,所以您可安全地转换现
有的缓存,并且显著减少响应时间和 CPU 使用量。ESQL 是
WebSphere® Message Broker 和 IBM® Integration Bus 定义的一种 SQL 扩展,用于定义和处理消息流中的数据。ESQL 共享变量常常用于缓存数据库表的内容,共享变量缓存实现为一个包含 SELECT 操作结果的数组,而搜索通过向该数组发出 SELECT 命令来执行。此方法的问题是,ESQL SELECT 操作执行顺序搜索,所以随着缓存增长,搜索会变慢。大多数用户发现,在缓存大小超出几千行时,访问数据库比使用缓存更快。本文介绍一种构建缓存的新方式,这种缓存能有效扩展,而且甚至在大小超过 10,000 行时仍比访问数据库更快。
假如有一个消息流接收包含机场编码(比如 LHR)的消息,并访问一个数据库表来获取相应的城市名称 (London)。每条消息访问一次数据库可能代价很高,所以一种常见的解决方案是在第一次访问时将这个表加载到一个 ESQL ROW 共享变量中,然后搜索该共享变量。 本文首先介绍称为标准缓存 的最常用缓存结构,然后介绍一种称为 ESQL 缓存 的更高效且可扩展的变体,这种缓存利用了 ESQL 访问其变量的方式。
标准缓存
使用 ESQL 共享变量的最常见的缓存实现包含一个共享的 ROW,其中包含对缓存的数据库表执行 SELECT 的结果:
DECLARE CACHE SHARED ROW;
假设您有一个名为 AIRPORTS 的数据库表,它包含 CODE 和 CITY 两列。此代码加载该缓存:
SET CACHE.AIRPORT[] = SELECT A.CODE, A.CITY FROM Database.AIRPORTS AS A;
CACHE 变量的内容如下所示:
CACHE.AIRPORT[1].CODE = AAACACHE.AIRPORT[1].CITY = AnaaCACHE.AIRPORT[2].CODE = AABCACHE.AIRPORT[2].CITY = Arrabury...CACHE.AIRPORT[4225].CODE = LHRCACHE.AIRPORT[4225].CITY = London...CACHE.AIRPORT[9185].CODE = ZZVCACHE.AIRPORT[9185].CITY = Zanesville
此函数实现该缓存:
DECLARE CACHE SHARED ROW;CREATE PROCEDURE getCity_v01 (IN airportCode CHARACTER) RETURNS CHARACTERBEGIN -- v01: "standard" cache -- PERFORMANCE TEST ONLY! No ATOMIC blocks. -- Do not use if Additional Instances > 0. IF CACHE.AIRPORT.CODE IS NULL THEN -- load the cache SET CACHE.AIRPORT[] = SELECT A.CODE, A.CITY FROM Database.AIRPORTS AS A; END IF; RETURN THE(SELECT ITEM A.CITY FROM CACHE.AIRPORT[] AS A WHERE A.CODE = airportCode);END;
为了保证可读性,显示了没有 ATOMIC 块的缓存加载,此方法仅适用于只有一个线程运行该消息流时。还有其他搜索缓存的方法,比如使用 FOR或 WHILE 循环,但使用 SELECT 是最快的。
使用标准缓存时的性能
此缓存结构的问题在于它无法扩展。一次用户跟踪显示,SELECT 按顺序扫描该表,直至找到满足 WHERE 子句的一行。随着该表不断扩大,搜索会变慢。最终,丢弃缓存而每次都访问数据库的速度更快。
在 Windows 7 64 位版本上使用 IBM Integration Server V9 和一个本地 DB2 V10.1 数据库的度量结果,显示了不断增长的缓存的影响:
图 1. 不同缓存大小每条消息需要的毫秒数(无缓存与标准缓存对比)
该测试包括发出 10,000 条消息来随机访问缓存中的前 1000、2000、3000(依此类推)行,进而模拟不同的缓存大小。缓存的总大小为 9185 行。该图表显示了处理一条消息所花的时间(以毫秒为单位)。数据库响应时间(大约 0.8 毫秒)是不变的(标签 NO_CACHE)。
在此配置中,至不超过 3000 行时,缓存比数据库更快。对于更大的缓存,缓存比数据库访问更慢。(其他配置的结果将不同,比如远程访问数据库,或者使用内存型数据库。)
ESQL Cache
ESQL Cache 将每个键和值(在我们的示例中为机场编码和城市名称)存储为名称/值对:
CACHE.AAA = AnaaCACHE.AAB = Arrabury...CACHE.LHR = London...CACHE.ZZV = Zanesville
没有数组。要返回给定机场编码对应的城市名称,缓存搜索函数只需引用适当的变量:
RETURN CACHE.{airportCode};
以下是实现新缓存结构的函数:
CREATE PROCEDURE getCity_v02 (IN airportCode CHARACTER) RETURNS CHARACTERBEGIN -- v02: "ESQL" cache -- PERFORMANCE TEST ONLY! No ATOMIC blocks. -- Do not use if Additional Instances > 0. IF FIELDNAME(CACHE.*[1]) IS NULL THEN -- load the cache DECLARE TEMPCACHE ROW; SET TEMPCACHE.AIRPORT[] = SELECT A.CODE, A.CITY FROM Database.AIRPORTS AS A; FOR cacheline AS TEMPCACHE.AIRPORT[] DO CREATE LASTCHILD OF CACHE NAME cacheline.CODE VALUE cacheline.CITY; END FOR; END IF; RETURN CACHE.{airportCode};END;
要按新结构组织缓存,该函数会导航数据库 SELECT 返回的每一行。在此示例中,每行包含一个机场编码(比如 JFK)和城市 (New York)。这个名称/值对用于向缓存添加一个变量,其中变量名称为编码,它的值为城市 (CACHE.JFK='New York')。
为了保证可读性,显示的缓存加载不包含 ATOMIC 块。上述代码仅适用于没有额外的线程时。以下是填充一个缓存行的语句:
SET CACHE.{cacheline.CODE} = cacheline.CITY;
当且仅当该键(在本例中为 CODE)只有一个实例时,SET 和 CREATE 才等效。在该键存在多个实例时使用 SET,缓存中将只有一个实例(最后一个)。
ESQL Cache 的性能
因为搜索功能直接访问该变量,所以它快得多且能更好地扩展。下面的图表比较了 ESQL Cache 与标准缓存的响应时间。图 2 显示了最多 9000 行的缓存大小的每条消息的毫秒数:
图 2:不同缓存大小每条消息需要的毫秒数(ESQL Cache 与无缓存对比)
对于所有缓存大小,ESQL Cache(每条消息的平均花时为 0.21 毫秒)都比访问数据库快得多。它也比标准缓存快得多,如图 3 中所示:
图 3. 不同缓存大小每条消息需要的毫秒数(ESQL Cache 与标准缓存对比)
对于所有缓存大小,ESQL Cache 都比标准缓存更快。该图表隐藏了每条消息的 ESQL Cache 用时并非真正不变、而在非常缓慢地增长的事实。这一增长在图 4 中的图表中很明显,其中仅显示了 ESQL Cache:
图 4:不同缓存大小每条消息需要的毫秒数(ESQL Cache)
响应时间会从缓存大小为 1000 个条目时的每条消息 0.2 毫秒,增长到缓存大小为 9000 个条目时的每条消息 0.23 毫秒,增幅约为 15%。ESQL Cache 何时变得比数据库访问更慢?尽管作者没有通过实验验证这一点,但线性投影表明,对于所测试的配置,直到 80,000 个条目,ESQL Cache 都仍然比数据库访问更快。
使用多个键
ESQL Cache 可轻松地支持多个键,以满足 SELECT ...WHERE KEY1=value1 AND KEY2=value2 的等效需求。以下是它的结构:
CACHE.aa.aa = xxxCACHE.aa.bb = yyyCACHE.xx.yy = zzz
返回一个值:
RETURN CACHE.{key1}.{key2};
ESQL Cache 与 Global Cache 之对比
WebSphere Message Broker V8 引入了 Global Cache。它使用执行组的 JVM 堆来存储数据,提供 Java API 来向缓存存储和从中获取数据。它很容易实现,而且在众多缓存大小上提供了一致的性能。Global Cache 相较 ESQL 共享变量的一个优势是,缓存可在消息流、集成服务器、执行组和集成总线或消息代理之间共享,而 ESQL 共享变量的范围仅限于消息流。图 5 比较了 Global Cache 和 ESQL Cache:
图 5. 不同缓存大小每条消息需要的毫秒数(ESQL Cache 与 Global Cache 对比)
对于所测试的所有缓存大小,ESQL Cache(每条消息 0.21 毫秒)比 Global Cache(平均每条消息 0.43 毫秒)更快。但是,对于更大的大小,Global Cache 将更快。之前使用的同样的线性投影表明,达到大约 40,000 个条目时,ESQL Cache 就会变得比 Global Cache 更慢。
结束语
我们提出的缓存结构会为带 ESQL 共享变量的缓存带来显著的性能改进。实现这种新缓存的逻辑非常简单,所以转换现有的标准缓存结构也很简单。
我们还未度量超过 9000 行的缓存大小,但该趋势表明,包含 80,000 个条目的缓存仍比访问数据库更快。准确的分界点将依赖于您具体的硬件和软件配置,但在任何情况下,ESQL Cache 都在当前几千个条目的分界点基础上带来了显著的改进。
对于更大的缓存大小,可考虑使用 IBM Integration Bus Global Cache,它提供了始终不错的性能且容易实现。如果需要跨消息流、集成服务器、执行组或集成总线/消息代理而共享缓存,那么 Global Cache 是惟一的选择,因为 ESQL 共享变量的范围仅限于消息流。
致谢
感谢来自南非开普敦的 Amanda Erlank 和来自南非 Standard Bank 的 Johannes Wagener 审阅本文的初步草案。