在一些客户端多线程高并发的应用场景中,编程中会采用本地文件缓存的方式来保存一些Java类中的信息,如某个Map中的<key,value>缓存为property文件。
如果多个线程同时访问这个property文件,会造成文件版本不一致、读写内容过时等错误情况。
笔者在学习dubbo框架的过程中,发现了dubbo框架中有对于本地URL信息的property缓存机制(为了使得服务消费方能很好的接入服务提供方,减轻注册中心的压力)
1:缓存文件保存机制线程来完成
dubbo中申明为内部类,这样对类使用者来说,是透明的缓存机制。
private class SaveProperties implements Runnable{
private long version;
private SaveProperties(long version){
this.version = version;
}
public void run() {
doSaveProperties(version);
}
}
2:文件保存过程(追加/覆盖属性配置,作为算法流程域,被动调用)
public void doSaveProperties(long version) {
if(version < lastCacheChanged.get()){
return;
}
if (file == null) {
return;
}
Properties newProperties = new Properties();
// 保存之前先读取一遍,防止多个注册中心之间冲突
InputStream in = null;
try {
if (file.exists()) {
in = new FileInputStream(file);
newProperties.load(in);
}
} catch (Throwable e) {
logger.warn("Failed to load registry store file, cause: " + e.getMessage(), e);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
logger.warn(e.getMessage(), e);
}
}
}
// 保存
try {
//批量属性配置转储,类似于HashMap中的addAll机制
newProperties.putAll(properties);
//注意:辅助文件锁机制,创建该文件是为了方便对主文件加锁
File lockfile = new File(file.getAbsolutePath() + ".lock");
if (!lockfile.exists()) {
lockfile.createNewFile();
}
//使用 RandomAccessFile类即可,只需要获得文件管道句柄
RandomAccessFile raf = new RandomAccessFile(lockfile, "rw");
try {
//1:获取文件句柄
FileChannel channel = raf.getChannel();
try {
//2:句柄加锁
FileLock lock = channel.tryLock();
if (lock == null) {
throw new IOException("Can not lock the registry cache file " + file.getAbsolutePath() + ", ignore and retry later, maybe multi java process use the file, please config: dubbo.registry.file=xxx.properties");
}
//3:进入原子操作区,操作为保存文件
//---------------------------------------------------
try {
if (! file.exists()) {
file.createNewFile();
}
FileOutputStream outputFile = new FileOutputStream(file);
try {
newProperties.store(outputFile, "Dubbo Registry Cache");
} finally {
outputFile.close();
}
//4:退出原子操作区
//-----------------------------------------------
} finally {
//5:释放辅助文件锁
lock.release();
}
} finally {
//释放文件句柄
channel.close();
}
} finally {
raf.close();
}
} catch (Throwable e) {
if (version < lastCacheChanged.get()) {
return;
} else {
registryCacheExecutor.execute(new SaveProperties(lastCacheChanged.incrementAndGet()));
}
logger.warn("Failed to save registry store file, cause: " + e.getMessage(), e);
}
}
3:属性文件读取(主动同步调用方法)
private void loadProperties() {
if (file != null && file.exists()) {
InputStream in = null;
try {
in = new FileInputStream(file);
properties.load(in);
if (logger.isInfoEnabled()) {
logger.info("Load registry store file " + file + ", data: " + properties);
}
} catch (Throwable e) {
logger.warn("Failed to load registry store file " + file, e);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
logger.warn(e.getMessage(), e);
}
}
}
}
}
4:属性文件保存(主动调用方法,内嵌缓存机制)
private void saveProperties(URL url) {
if (file == null) {
return;
}
try {
StringBuilder buf = new StringBuilder();
Map<String, List<URL>> categoryNotified = notified.get(url);
if (categoryNotified != null) {
for (List<URL> us : categoryNotified.values()) {
for (URL u : us) {
if (buf.length() > 0) {
buf.append(URL_SEPARATOR);
}
buf.append(u.toFullString());
}
}
}
properties.setProperty(url.getServiceKey(), buf.toString());
long version = lastCacheChanged.incrementAndGet();
if (syncSaveFile) {
doSaveProperties(version);
} else {
registryCacheExecutor.execute(new SaveProperties(version));
}
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
5.文件保存任务异步执行
变量支持
// 文件缓存定时写入
private final ExecutorService registryCacheExecutor = Executors.newFixedThreadPool(1, new NamedThreadFactory("DubboSaveRegistryCache", true));
//是否是同步保存文件
private final boolean syncSaveFile;
//使用原子变量来保证文件版本的一致性
private final AtomicLong lastCacheChanged = new AtomicLong();
算法流程
//使用原子操作来保证文件版本的增量可靠性
long version = lastCacheChanged.incrementAndGet();
if (syncSaveFile) {
doSaveProperties(version);
} else {
//启动异步定时任务执行器,将Runable丢入异步线程池中
registryCacheExecutor.execute(new SaveProperties(version));
}
总结:当A线程进入原子操作区间时,先对辅助文件加锁,然后操纵主文件,操作结束后,释放辅助文件锁。
如此一来,当A线程没有退出原子区域时候,B线程是无法进入进入原子区域的,因为获取不到文件锁。
这样就可以保证对主文件的操作安全性。
也许你会问:“为什么不直接对主文件加锁呢?” 答案是,对主文件加锁了,又如何操作主文件呢,因为文件锁的机制是会对文件的操作屏蔽的。
小小的一个抽象类,尽然引入了这么多的Java高端技术,不得不佩服编码者功力之深厚,优化后的类文件,代码又精简又实用,而且在高并发的应用场景,相信一定很牛的。
难怪Dubbo框架这么成熟,在淘宝内部应用也如此广泛~
分享到:
相关推荐
dobbo源码dubbo-dubbo-2.7.3.rardobbo源码dubbo-dubbo-2.7.3.rardobbo源码dubbo-dubbo-2.7.3.rardobbo源码dubbo-dubbo-2.7.3.rardobbo源码dubbo-dubbo-2.7.3.rardobbo源码dubbo-dubbo-2.7.3.rardobbo源码dubbo-dubbo...
dubbo源码dubbo-dubbo-2.7.3.rardubbo源码dubbo-dubbo-2.7.3.rardubbo源码dubbo-dubbo-2.7.3.rardubbo源码dubbo-dubbo-2.7.3.rardubbo源码dubbo-dubbo-2.7.3.rardubbo源码dubbo-dubbo-2.7.3.rardubbo源码dubbo-dubbo...
本篇将详细讲解基于dubbo-demo-consumer、dubbo-demo-provider和dubbo-simple-monitor的实例服务,带你深入理解Dubbo的核心概念和操作流程。 首先,我们来看`dubbo-demo-consumer`,它是Dubbo服务的消费者。消费者...
《Dubbo监控工具详解——基于dubbo-monitor-simple-2.5.8》 在分布式系统开发中,监控是至关重要的一个环节,它可以帮助开发者实时了解服务的运行状态,及时发现并解决问题。Apache Dubbo,作为一款高性能、轻量级...
jmeter的dubbo插件,jmeter-plugins-dubbo-2.7.8-jar-with-dependencies.jar,适用于JMeter5.4.1版本,将解压后的文件jmeter-plugins-dubbo-2.7.8-jar-with-dependencies放在Jmeter安装目录下的\lib\ext文件夹中,...
本人实际测试过,这两个包可用。...2.修改dubbo-monitor中的conf目录中的dubbo.properties dubbo.registry.address 与 dubbo-admin中的配置一样 3.到dubbo-monitor中的bin目录下运行 start.sh脚本 ok
dubbo-admin-2.5.4提供支持JDK1.7及JDK1.8的War包 Dubbo是Alibaba开源的分布式服务框架,它最大的特点是按照分层的方式来架构,使用这种方式可以使各个层之间解耦合(或者最大限度地松耦合)。从服务模型的角度来看...
《Dubbo Admin 2.5.4:高效管理与监控的利器》 在分布式系统中,服务治理扮演着至关重要的角色。Dubbo,作为阿里巴巴开源的一款高性能、轻量级的Java RPC框架,提供了丰富的服务治理功能。其中,`dubbo-admin`是...
《JMeter Plugins for Dubbo压力测试详解》 在IT行业中,系统压力测试是评估软件性能不可或缺的一环。本文将深入探讨“jmeter-plugins-dubbo-2.7.1-jar-with-dependencies”这一系统压测工具包,它专门针对基于Java...
标签 "springboot-2" 和 "dubbo-2.6.x" 明确了项目所使用的具体技术版本。Spring Boot 2.0.x 引入了许多改进和新特性,包括对 Spring Framework 5 和 Java 9 的支持,以及更好的性能和稳定性。而 Dubbo 2.6.x 版本则...
dubbo官方自带了dubbo-admin及dubbo-simple/dubbo-monitor-simple二个子项目用于服务治理及服务监控。 dubbo-monitor-simple是Alibaba的开源项目,用于监控在dubbo框架下接口暴露,注册情况,也可以看接口的调用...
【Dubbo Monitor Simple】是Dubbo框架中的一个关键组件,主要功能是提供服务监控与管理。Dubbo是一款高性能、轻量级的开源Java RPC框架,它由阿里巴巴开发并维护,旨在提高服务治理的效率和质量。Monitor Simple是...
dubbo 最新dubbo-admin-2.8.4.war 菜单报错已修改。
jmeter-plugins-dubbo-2.7.1-jar-with-dependencies 2.jar jmeter本身并不支持dubbo接口的测试,需要下载第三方插件,然后将jar包放入${JMETER_HOME}\lib\ext路径下,重启即可。
该包为dubbo-monitor,使用方法请参见博文 《Dubbo进阶(五)—— dubbo-monitor-simple使用》 https://blog.csdn.net/sunhuaqiang1/article/details/80141478
《JMeter Plugins for Dubbo详解》 在性能测试领域,Apache JMeter 是一款广泛应用的开源工具,它能够对各种类型的应用进行压力和负载测试。针对分布式服务框架Dubbo的测试需求,社区开发了JMeter Plugins for ...