一、序言
在并发场景中,当热点缓存 Key 生效时,流量霎时打到数据库中,此所谓缓存击穿景象;当大范畴的缓存 Key 生效时,流量也会打到数据库中,此所谓缓存雪崩景象。
当应用分布式行锁时,可能无效解决 缓存击穿
问题;当应用分布式表锁时,可能解决 缓存雪崩
问题。实际操作中,分布式表锁不在思考范畴,理由是升高并发量。
本文将从另一个角度登程,将申请流量合并和拆分,以进步零碎的并发量。
二、实践根底
流量的合并与拆分原理是将多条申请合并成一条申请,执行后再将后果拆分。在数据库与缓存架构中,缓存 Key 生效的霎时,大量反复申请打到数据库中。实际上除了第一条申请为无效申请,随后的申请为有效申请,节约数据库连贯资源。
流量的合并与拆分实际是额定唤醒一个线程,每隔固定工夫(比方 200 毫秒)发送合并后的申请,执行实现后将查问后果进行拆分,散发到原始申请中,原始申请响应用户申请。
从利用到数据库之间连贯资源需要显著降落,从而进步数据库连贯资源利用率。
三、利用实际
(一)编码与应用
基于 MybatisPlus 提供一个内置封装的服务类QueueServiceImpl
,通明的实现查问详情流量的合并与拆分,使用者可屏蔽外部实现。
<dependency>
<groupId>xin.altitude.cms</groupId>
<artifactId>ucode-cms-common</artifactId>
<version>1.4.4</version>
</dependency>
对于肯定工夫区间内的所有申请,合并成一条申请解决。
@Override
public BuOrder getOrderById(Long orderId) {return getById(orderId);
}
举例说明,如果特定工夫区间内会集了雷同的主键申请,那么合并后的申请查问一次数据库便可能响应所有的申请。
子类重写父类办法,可批改合并与拆分的行为。
@Override
protected RequstConfig createRequstConfig() {RequstConfig config = new RequstConfig();
/* 单次最大合并申请数量 */
config.setMaxRequestSize(100);
/* 外围线程池大小 */
config.setCorePoolSize(1);
/* 申请距离(毫秒)*/
config.setRequestInterval(200);
return config;
}
(二)实现细节
1、ConcurrentLinkedQueue
应用 ConcurrentLinkedQueue
并发平安队列用于缓冲和接管申请,定时工作以固定频率从队列中生产数据,将多条申请条件合并后汇总查问。
2、CompletableFuture
CompletableFuture
类是合并与拆分的要害类,原始申请将查问条件封装成 CompletableFuture
对象,提交到队列中后陷入阻塞,定时工作分批次组装查问条件,失去后果后将后果拆分并存入 CompletableFuture
对象中,原始申请线程被唤醒,持续响应用户申请。
3、ScheduledExecutorService
以肯定的工夫距离发送合并后的申请。
(二)其它利用场景
利用于数据库间流量的合并申请与拆分,首先进步数据库连贯资源(稀缺资源)利用率,其次进步网络间数据传输效率。100 条数据收发 100 次与 100 条数据收发 1 次的效率差异。
1、服务间接口调用
服务间 API 接口调用同样实用于流量的合并与拆分:比方向订单服务发送 Http API 申请,同一时刻有 100 个用户发动查问申请,应用流量合并与拆分的思维可将多个订单查问申请转换成批查问申请,失去后果后散发到不同的申请线程,响应用户申请。
四、小结
在本文中,选用的队列是本地并发平安的队列,在分布式系统中,本地队列是否适合?此处选用本地队列基于两点思考:一是无严格的分布式的需要;二是 CompletableFuture
类不反对序列化。思考应用 Redis 做分布式队列的想法无奈实现,你用本地队列,只管会有大量查问条件数据冗余(不影响后果),回避了分布式队列的网络 IO 提早,反而有更优的查问效率。
本计划仅在高并发场景受害,属于针对并发场景进行架构的优化,一般我的项目应用惯例操作即可。
演示我的项目代码地址
喜爱本文点个♥️赞♥️反对一下,如有须要,可通过微信
dream4s
与我分割。相干源码在 GitHub,视频解说在 B 站,本文珍藏在博客天地。