|
|
51CTO旗下网站
|
|
移动端

必收藏技能!为Java多线程应用程序优化数据存储库

数据存储库通常是超高要求系统的瓶颈。在这些系统中,正在执行的查询数量非常大。DelayedBatchExecutor是一个用于减少所需查询数量的组件,通过在Java多线程应用程序中对所需查询进行批处理。

作者:读芯术来源:读芯术|2019-11-25 10:46

数据存储库通常是超高要求系统的瓶颈。在这些系统中,正在执行的查询数量非常大。DelayedBatchExecutor是一个用于减少所需查询数量的组件,通过在Java多线程应用程序中对所需查询进行批处理。

1个参数的n个查询Vs. n个参数的1个查询

假设有一个对关系数据库执行查询的Java应用程序,以便在给定其唯一标识符(id)的情况下检索Product实体(row)。

查询如下所示:

SELECT * FROM PRODUCT WHERE ID =

现在,检索n个Products,有如下两种方法:

  • 执行1个参数的n个独立查询:
  1. SELECT * FROM PRODUCT WHERE ID = 
  2.  
  3. SELECT * FROM PRODUCT WHERE ID = 
  4.  
  5. ... 
  6.  
  7. SELECT * FROM PRODUCT WHERE ID = 

  • 使用IN运算符或ORs的串联,对n个参数执行1个查询以便同时检索n个 Products
  1. -- Example using IN OPERATOR 
  2.  
  3. SELECT * FROM PRODUCT WHERE ID IN (, , ..., ) 

后者在网络流量和数据库服务器资源(CPU和磁盘)方面更为有效,因为:

  • 往返数据库的次数为1,而不是n。
  • 数据库引擎优化了n个参数的数据遍历过程,即每个表格可能只需要扫描1次,而不是n次。

这不仅适用于SELECT操作,而且适用于其他操作,例如 INSERTs,UPDATEs和DELETEs。实际上,JDBC API包括上述操作的批量处理操作。

同样的情况也适用于NoSQL存储库,其中大多都明确提供BULK操作。

DelayedBatchExecutor

需要从数据库中检索数据的Java应用程序,如REST微服务或异步消息处理器,通常以多线程应用程序(*1)实现,其中:

  • 每个线程在其执行的某个时刻执行相同的查询(每个查询具有不同的参数)。
  • 并发线程数很高(每秒数十或数百)。

在这种场景下,数据库很可能在较短的时间间隔内多次执行相同的查询。

如前所述,如果将1个参数的n个查询替换为具有n个参数的单个等效查询,那么则应用程序将使用较少的数据库服务器和网络资源。

好消息是它可以通过timewindows(时间窗口)的机制来实现,如下所示:

第一个尝试执行查询的线程会打开一个时间窗口,因此其参数被存储在一个列表中,同时该线程被暂停。在时间窗口内执行相同查询的其余线程会将其参数添加到列表中,并且也会被暂停。此时,数据库上未执行任何查询。

时间窗口结束或列表已满(先前已定义最大容量限制)后,将使用列表中存储的所有参数执行单个查询。最后,一旦数据库提供了该查询的结果,每个线程将接收相应的结果,同时所有线程将自动恢复。

笔者构建了一个简单而轻量级的应用机制(DelayedBatchExecutor),很容易在新的或现有的应用程序中使用。它基于Reactor库,并且为参数列表使用超时的Flux缓冲发布器。

运用DelayedBatchExecutor的吞吐量和延迟分析

假设针对Products的REST微服务公开了一个端点,用于检索数据库中给定的 productId的Product数据。在没有DelayedBatchExecutor的情况下,如果每秒对端点命中200次,则数据库每秒执行200个查询。如果端点使用的DelayedBatchExecutor 配置了50毫秒的时间窗口且最大容量=10个参数,数据库每秒钟将只执行10个参数的20个查询,代价是每执行一个线程,最多在50毫秒内增加延时(*2)。

换句话说,为了将延时增加50毫秒(* 2),数据库每秒接收的查询减少了10倍,然而保持了系统的整体吞吐量。还不错!!

其他有趣的配置:

  • 窗口时间= 100毫秒,最大容量= 20个参数→20个参数的10个查询(查询减少20倍)
  • 窗口时间= 500毫秒,最大容量= 100个参数→2个查询100个参数(查询减少100倍)

执行中的DelayedBatchExecutor

深入研究Product微服务示例。假设对于每个传入的HTTP请求,微服务的控制器都要求检索已有id的Product(Java Bean),因此将调用以下方法:

DAO组件(ProductDAO)的public Product getProductById(IntegerproductId) .

以下分别是有和没有 DelayedBatchExecutor的DAO执行。

没有 DelayedBatchExecutor

  1. public classProductDAO { 
  2.  
  3. public Product getProductById(Integer id) { 
  4.  
  5. Product product= ...// execute the query SELECT * FROM PRODUCT WHERE ID= 
  6.  
  7. // using your favourite API: JDBC, JPA, Hibernate... 
  8.  
  9. return product; 
  10.  
  11.  
  12. ... 
  13.  

有DelayedBatchExecutor

  1. // Singleton 
  2.  
  3. publicclass ProductDAO { 
  4.  
  5. DelayedBatchExecutor2 delayedBatchExecutorProductById = 
  6.  
  7. DelayedBatchExecutor.define(Duration.ofMillis(50), 10, this::retrieveProductsByIds); 
  8.  
  9. public Product getProductById(Integer id) { 
  10.  
  11. Product product = delayedBatchExecutorProductById.execute(id); 
  12.  
  13. return product; 
  14.  
  15.  
  16. private List retrieveProductsByIds(List idList) { 
  17.  
  18. List productList = ...// execute query:SELECT * FROM PRODUCT WHERE ID IN (idList.get(0), ..., idList.get(n)); 
  19.  
  20. // using your favourite API: JDBC, JPA, Hibernate... 
  21.  
  22. // The positions of the elements of the list to return must match the ones in the parameters list. 
  23.  
  24. // For instance, the first Product of the list to be returned must be the one with 
  25.  
  26. // the Id in the first position of productIdsList and so on... 
  27.  
  28. // NOTE: null could be used as value, meaning that no Product exist for the given productId 
  29.  
  30. return productList; 
  31.  
  32.  
  33. ... 
  34.  

首先,必须在DAO中创建一个DelayedBatchExecutor实例,在本例中为 delayedBatchExecutorProductById。需要以下三个参数:

  • 时间窗口(在此示例中为50毫秒)
  • 参数列表的最大容量(在此示例中为10个参数)
  • 将使用参数列表调用的方法(详细信息见后文)。在此示例中,方法为retrieveProductsByIds

其次,已经重构了DAO方法 publicProduct getProductById(Integer productId),以简单调用delayedBatchExecutorProductById 实例的execute 方法。所有的“magic”都是由 DelayedBatchExecutor完成的。

之所以delayedBatchExecutorProductById是DelayedBatchExecutor2

如果execute方法需要接收两个参数(例如,一个 Integer和一个String)并返回Product实例,则定义为 DelayedBatchExecutor3

最终,retrieveProductsByIds方法必须返回List 并接收List作为参数。

如果使用的是 DelayedBatchExecutor3

就是这样。

一旦运行,执行控制器逻辑的并发线程会在某时刻调用方法 getProductById(Integerid) ,并且此方法将返回对应的Product。并发线程不知自己已经被 DelayedBatchExecutor暂停并恢复了。

由数据存储库延伸的“题外话”

尽管本文与数据存储库有关,但 DelayedBatchExecutor也可以用在其他地方,例如:对REST进行微服务请求。再说,用1个参数启动n个GET请求要比使用n个参数启动1个GET昂贵得多。

DelayedBatchExecutor的优化

笔者创建了 DelayedBatchExecutor并使用了一段时间,有效地解决了个人项目中并发线程启动的多个查询的执行问题。因此相信它对其他人可能也有用处,所以决定将其公开。

话虽如此,DelayedBatchExecutor改进和功能扩展的空间还很大。最有趣的是能够根据执行的特定条件动态更改DelayedBatchExecutor参数(窗口时间和最大容量)的功能,以便在利用带有n个参数的查询时很大程度地减少延时。

【编辑推荐】

  1. 3分钟学会MySQL数据库的逻辑架构原理
  2. 分享两个Shell脚本,一键统计数据库临时表空间和阻塞lock信息
  3. 华为GaussDB数据库推出双分布式架构,打造金融核心智能生产交易解决方案
  4. 记一次生产数据库数据文件进行分区转移
  5. 详解MySQL数据库default设置的三种不同表现
【责任编辑:华轩 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢

订阅专栏+更多

骨干网与数据中心建设案例

骨干网与数据中心建设案例

高级网工必会
共20章 | 捷哥CCIE

396人订阅学习

中间件安全防护攻略

中间件安全防护攻略

4类安全防护
共4章 | hack_man

144人订阅学习

CentOS 8 全新学习术

CentOS 8 全新学习术

CentOS 8 正式发布
共16章 | UbuntuServer

288人订阅学习

视频课程+更多

kali linux 安全测试

kali linux 安全测试

讲师:艾海涛405人学习过

OCP培训 Oracle 12c/18c/19c OCP认证实战培训视频【会员2折秒杀】

OCP培训 Oracle 12c/18c/19c OCP认证实战培训

讲师:风哥25988人学习过

强哥带你精通tomcat

强哥带你精通tomcat

讲师:周玉强4590人学习过

读 书 +更多

网管员成长手记——网络组建、配置与应用

本书主要以“网管员的成长经历”为线索展开,虚拟出一个“新手”网管员的工作和学习环境,将网管员的成长分为4个阶段,以“网管入职充电→...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊

51CTO服务号

51CTO官微