细说双Buffer缓冲池

前言

缓冲机制是对数据持久化的延迟,减小没必要要的IO,提升数据落盘的效率。本文将会详细探讨拥有双Buffer的缓冲池(下文统称TwinsBufferPool)是如何实现的,读者能够依此推广,获得N-Buffer的实现原理。java

在此篇文章中,缓冲区(Buffer)和缓冲池(BufferPool)是两个重要的概念,很明显,二者构成了一个包含与被包含的关系,一个缓冲池内能够有一个或者多个缓冲区协同工做,缓冲池中的全部缓冲区被组织成了一个环形队列,一前一后的两个缓冲区能够互相替换角色。算法

固然,在整个过程当中,还会有其余辅助工具的出现,在下文都会逐一阐述。编程

1、设计要点

一、可扩展性。毫无疑问,可扩展性是对一个设计良好的软件的一项基本要求,而一个软件的可扩展的地方一般是有不少处的,这在某种程度上会依赖于编程者的经验,若是仅仅局限于产品需求,可能会严重限制了软件的可扩展性。缓冲池是一种相对通用的中间件,扩展点相对比较多,好比:缓冲区数量可指定,线程安全与否,缓冲区阈值调配等等。安全

二、易用性。设计出来的中间件应该是对用户友好的,使用过程当中不会有繁琐的配置,奇形怪状的API,更不能有诸多没必要要的Dependencies,若是能作到代码无侵入性,那就很是完美了。基于这个要求,TwinsBufferPool作成了一个Spring Boot Starter的形式,加入到项目里的dependencies中便可开启使用。微信

三、稳定性。这就是衡量一个中间件好坏的重要KPI之一,从外观上看,一样是一艘船,破了一个洞和无缺完好将会是一个致命的区别,用户指望本身搭上了一艘完整的船,以便能航行万里而无忧。数据结构

四、高效性。说到稳定性,那就不得不说高效了,若是能帮助用户又好又快的解决问题,无疑是最完美的结果。关于TwinsBufferPool的稳定性和高效性两个指标,会在文中附上jemeter的压测结果,并加以说明。架构

2、设计方案

这一小节将会给出TwinsBufferPool完整的设计方案,咱们先从配置提及。并发

每一个参数都会提供默认值,因此不作任何配置也是容许的。以下是目前TwinsBufferPool能提供的配置参数(yml):负载均衡

buffer:
  capacity: 2000
  threshold: 0.5
  allow-duplicate: true
  pool:
    enable-temporary-storage: true
    buffer-time-in-seconds: 120

下面附上参数说明表:工具

图片描述

以上参数比较浅显易懂,这里重点解释enable-temporary-storage和buffer-time-in-seconds这两个参数。

根据参数说明,很明显能够感觉到,这两个参数是为了预防突发状况,致使数据丢失。由于缓冲区都是基于内存的设计的,这就意味着缓冲的数据随时处于一种服务重启,或者服务宕机的高风险环境中,所以,才会有这两个参数的诞生。

由于TwinsBufferPool良好的接口设计,对于以上两个参数的实现机制也是高度可扩展的。TwinsBufferPool默认的是基于Redis的实现,用户也能够用MongoDB,MySQL,FileSystem等方式实现。由此又会衍生出另一个问题,因为各类异常状况,致使临时存储层遗留了必定量的数据,须要在下次启动的时候,恢复这一部分的数据。

总而言之,数据都是经过flush动做最终持久化到磁盘上。

图片描述

由于大多数实际业务场景对于缓冲池的并发量是有必定要求的,因此默认就采用了线程安全的实现策略,受到JDK中ThreadPool的启发,缓冲池也具有了自身状态管理的机制。以下列出了缓冲池全部可能存在的状态,以及各个状态的流转。

/**
 * 缓冲池暂未就绪
 */
private static final int ST_NOT_READY = 1;

/**
 * 缓冲池初始化完毕,处于启动状态
 */
private static final int ST_STARTED = 2;

/**
 * 若是安全关闭缓冲池,会当即进入此状态
 */
private static final int ST_SHUTTING_DOWN = 3;

/**
 * 缓冲池已关闭
 */
private static final int ST_SHUTDOWN = 4;

/**
 * 正在进行数据恢复
 */
private static final int ST_RECOVERING = 5;

图片描述

经过上述的一番分析,设计的方案也呼之欲出了,下面给出主要的接口设计与实现。

图片描述

经过以上的讲解,也不难理解BufferPool定义的接口。缓冲池的整个生命周期,以及内部的一些运做机制都得以体现。值得注意的是,在设计上,将缓冲池和存储层作了逻辑分离,使得扩展性进一步获得加强。

存储相关的接口包含了一些简单的CURD,目前默认是用Redis做为临时存储层,MongoDB做为永久存储层,用户能够根据须要实现其余的存储方式。

下图展示的是TwinsBufferPool的实现方式,DataBuffer是缓冲区,必须依赖的基础元素。由于设计的是环形队列,因此依赖了CycleQueue,这个环形队列的interface也是自定义的,在JDK中没有找到比较合适的实现。

图片描述

值得注意的是,BufferPool接口定义是灵活可扩展的,TwinsBufferPool只是提供了一种基于环形队列的实现方式,用户也能够自行设计,使用另一种数据结构来支撑缓冲池的运做。

3、压测报告

使用的是我的的PC电脑,机器的配置以下:

处理器:i5-7400 CPU 3.00GHZ 四核

内存:8.00GB

操做系统:Windows10 64位 基于x64的处理器

运行环境以下:

jdk 1.8.0_144

SpringBoot_2.1.0,内置Tomcat9.0

Redis_v4.0.1

MongoDB_v3.4.7

测试工具:

jemeter_v5.1

总共测试了四组参数,每组参数主要是针对最大容量,阈值和最大缓冲时间三个参数来作调整。

第一组:

buffer:
  capacity: 1000
  threshold: 0.8
  pool:
    buffer-time-in-seconds: 60

第二组:

buffer:
  capacity: 5000
  threshold: 0.8
  pool:
    buffer-time-in-seconds: 60

第三组

buffer:
  capacity: 5000
  threshold: 0.8
  pool:
    buffer-time-in-seconds: 300

第四组

buffer:
  capacity: 10000
  threshold: 0.8
  pool:
    buffer-time-in-seconds: 300

总共采集了9个指标:CPU占用率,堆内存/M,线程数,错误率,吞吐量/sec,最长响应时间/ms,最短响应时间/ms,平均响应时间/ms,数据丢失量。

限于篇幅,只展现4个指标:堆内存,数据丢失量,平均响应时间,吞吐量。
图片描述
图片描述
图片描述
图片描述

整体来看,随着每秒并发量的增长,各项指标呈现了不太乐观的趋势,其中最不稳定的是第四组参数,波动较为明显,综合表现最佳的是第二组参数,其次是第三组。

数据丢失量是一个比较让人关心的指标,从图中能够得知,在并发量达到4000的时候,开始有数据丢失的现象,而形成这一现象的缘由并不是是TwinsBufferPool实现代码的Bug,而是请求超时致使的“Connection refused”,由于每一个Servlet运行容器都会有超时机制,若是排队请求时间过长,就是直接被拒绝了。所以,看数据丢失量和错误率曲线,这二者是一致的。若是设置成不超时,那么将是零丢失量,零错误率,所带来的代价就是平均响应时间会拉长。

由于受限于我的的测试环境,整个测试过程显得不是很严谨,得出来的数据也并非很完美,不过,我这里提供了一些优化调整的建议:

一、硬件环境。正所谓“巧妇难为无米之炊”,若是提供的硬件性能自己就是有限的话,那么,在上面运行的软件也难以获得正常的发挥。

二、软件架构。这个想象的空间很大,其中有一种方案我认为将来能够归入到RoadMap中:多缓冲池的负载均衡。咱们能够尝试在一个应用中启用多个缓冲池,经过调度算法,将缓冲数据均匀的分配给各个缓冲池,不至于出现只有一个缓冲池“疲于奔命”的情况,最起码系统的吞吐量会有所提高。

三、其余中间件或者工具的辅助,好比加上消息中间件能够起到削峰的做用,各项指标也将会有所改善。

四、参数调优。这里的参数指代的不只仅是缓冲池的参数,还有包括最大链接数,最大线程数,超时时间等诸多外部参数。

4、总结

本文详细阐述了双Buffer缓冲池的设计原理,以及实现方式,并对TwinsBufferPool实施了压测,也对测试结果进行了一番分析。


欢迎关注个人微信订阅号:技术汇

若是想查看完整的测试报告,可在订阅号内回复关键词:测试报告,便可获取到下载连接。

若是想深刻研究TwinsBufferPool源码的读者,可在订阅号内回复关键词:缓冲池
图片描述

相关文章
相关标签/搜索