缓冲
缓冲(Buffer)通过对数据进行暂存,然后批量进行传输或者操作,多采用顺序方式,来缓解不同设备之间次数频繁但速度缓慢的随机读写。
从宏观上来说,JVM 的堆就是一个大的缓冲区,代码不停地在堆空间中生产对象,而垃圾回收器进程则在背后默默地进行垃圾回收。
优点在于:
-
缓冲双方能各自保持自己的操作节奏,操作处理顺序也不会打乱,可以 one by one 顺序进行;
-
以批量的方式处理,减少网络交互和繁重的 I/O 操作,从而减少性能损耗;
-
优化用户体验,比如常见的音频/视频缓冲加载,通过提前缓冲数据,达到流畅的播放效果
缓冲区优化思路
缓冲通常配合异步编程。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jaf4vKVq-1605251095378)(https://s0.lgstatic.com/i/image/M00/3A/1A/Ciqc1F8hIuqATvhSAAB9F5pMiOE699.png)]
- 同步
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YJlpmaYV-1605251095379)(https://s0.lgstatic.com/i/image/M00/3A/25/CgqCHl8hIvuAILAKAABaDCSPRRw546.png)]
- 异步
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CS7xVGph-1605251095381)(https://s0.lgstatic.com/i/image/M00/3A/25/CgqCHl8hIwaAQl3SAACaljNt5Fs553.png)]
缓存
缓存的优化效果是非常好的,它既可以让原本载入非常缓慢的页面,瞬间秒开,也能让本是压力山大的数据库,瞬间清闲下来。缓存,本质上是为了协调两个速度差异非常大的组件。
缓存一致性
对于一个缓存项来说,常用的操作有四个:写入、更新、读取、删除。
-
写入:缓存和数据库是两个不同的组件,只要涉及双写,就存在只有一个写成功的可能性,造成数据不一致。
-
更新:更新的情况类似,需要更新两个不同的组件。
-
读取:读取要保证从缓存中读到的信息是最新的,是和数据库中的是一致的。
-
删除:当删除数据库记录的时候,如何把缓存中的数据也删掉?
推荐使用触发式的缓存一致性方式,使用懒加载的方式,可以让缓存的同步变得非常简单:
-
当读取缓存的时候,如果缓存里没有相关数据,则执行相关的业务逻辑,构造缓存数据存入到缓存系统;
-
当与缓存项相关的资源有变动,则先删除相应的缓存项,然后再对资源进行更新,这个时候,即使是资源更新失败,也是没有问题的。
但这样还是有问题。
上面提到的缓存删除动作,和数据库的更新动作,明显是不在一个事务里的。如果一个请求删除了缓存,同时有另外一个请求到来,此时发现没有相关的缓存项,就从数据库里加载了一份到缓存系统。接下来,数据库的更新操作也完成了,此时数据库的内容和缓存里的内容,就产生了不一致。
下面这张图,直观地解释了这种不一致的情况,此时,缓存读取 B 操作以及之后的读取操作,都会读到错误的缓存值。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B2PJ0nrO-1605251095383)(https://s0.lgstatic.com/i/image/M00/3D/B0/CgqCHl8qa5-AWDbqAACK1Itu_Wc954.png)]
可以使用分布式锁来解决这个问题,将缓存操作和数据库删除操作,与其他的缓存读操作,使用锁进行资源隔离即可。一般来说,读操作是不需要加锁的,它会在遇到锁的时候,重试等待,直到超时。
池化对象
池化技术——使用一个虚拟的池子,将这些资源保存起来,当使用的时候,我们就从池子里快速获取一个即可。
对象池在进行初始化时,要指定三个主要的参数:
-
maxTotal对象池中管理的对象上限
-
maxIdle最大空闲数
-
minIdle最小空闲数
其中maxTotal和业务线程有关,当业务线程想要获取对象时,会首先检测是否有空闲的对象。如果有,则返回一个;否则进入创建逻辑。此时,如果池中个数已经达到了最大值,就会创建失败,返回空对象。
对象在获取的时候,有一个非常重要的参数,那就是最大等待时间(maxWaitMillis),这个参数对应用方的性能影响是比较大的。该参数默认为 -1,表示永不超时,直到有对象空闲。
下面的场景,就可以考虑使用池化来增加系统性能:
-
对象的创建或者销毁,需要耗费较多的系统资源;
-
对象的创建或者销毁,耗时长,需要繁杂的操作和较长时间的等待;
-
对象创建后,通过一些状态重置,可被反复使用。
枚举模式单例实现
public class EnumSingleton {
private EnumSingleton() {
}
// static块,jvm会保证instance只被调用一次
public static EnumSingleton getInstance() {
return Holder.HOLDER.instance;
}
private enum Holder {
HOLDER;
private final EnumSingleton instance;
Holder() {
instance = new EnumSingleton();
}
}
}
static块,jvm会保证instance只被调用一次。Java只有在Enum类第一次被引用时才会去加载,也能保证懒加载。