`
xiangkui
  • 浏览: 24976 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

dubbo源码分析-2(注册表AbstractRegistry设计技巧讲解)

 
阅读更多

 

在一些客户端多线程高并发的应用场景中,编程中会采用本地文件缓存的方式来保存一些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框架这么成熟,在淘宝内部应用也如此广泛~

 

分享到:
评论
3 楼 xiangkui 2014-02-13  
引用

文件锁定以整个 Java 虚拟机来保持。但它们不适用于控制同一虚拟机内多个线程对文件的访问。

多个并发线程可安全地使用文件锁定对象。


也就是说,
1:JVM内部线程之间可以获取重复的共享锁定,操作系统进程之间不允许获取重复的独占锁(在一些特殊的操作系统中,jvm内部共享锁会转换为独占锁)

1.1 可通过调用某个锁定的 isShared 方法来确定它是独占的还是共享的

2:不适合在JVM内对文件进行访问,因为在JVM内是共享锁,尽管如此,FileLock是并发安全的

3: 判断是否锁定重叠,可使用 overlaps 方法。
2 楼 xiangkui 2014-02-13  
hengyunabc 写道
貌似LZ有个地方理解不对,FileLock是进程级的锁,而且
// 文件缓存定时写入
    private final ExecutorService registryCacheExecutor = Executors.newFixedThreadPool(1, new NamedThreadFactory("DubboSaveRegistryCache", true));

这个executor是只有一个线程的。
FileLock的作用是防止其它线程来修改这个cache文件,而不是同进程内的线程。

刚查了一下,确实是进程级别的加锁,参考地址为
http://dlc.sun.com.edgesuite.net/jdk/jdk-api-localizations/jdk-api-zh-cn/publish/1.6.0/html/zh_CN/api/java/nio/channels/FileLock.html
感谢指正,
1 楼 hengyunabc 2014-02-13  
貌似LZ有个地方理解不对,FileLock是进程级的锁,而且
// 文件缓存定时写入
    private final ExecutorService registryCacheExecutor = Executors.newFixedThreadPool(1, new NamedThreadFactory("DubboSaveRegistryCache", true));

这个executor是只有一个线程的。
FileLock的作用是防止其它线程来修改这个cache文件,而不是同进程内的线程。

相关推荐

    dobbo源码dubbo-dubbo-2.7.3.rar

    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-2.7.3.rar

    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-demo-consumer、dubbo-demo-provider和dubbo-simple-monitor的实例服务,带你深入理解Dubbo的核心概念和操作流程。 首先,我们来看`dubbo-demo-consumer`,它是Dubbo服务的消费者。消费者...

    dubbo-monitor-simple-2.5.8-assembly.tar.gz

    《Dubbo监控工具详解——基于dubbo-monitor-simple-2.5.8》 在分布式系统开发中,监控是至关重要的一个环节,它可以帮助开发者实时了解服务的运行状态,及时发现并解决问题。Apache Dubbo,作为一款高性能、轻量级...

    jmeter-plugins-dubbo-2.7.8-jar-with-dependencies.jar

    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文件夹中,...

    dubbo-admin-2.5.4及dubbo-monitor-2.5.3 安装及配置

    本人实际测试过,这两个包可用。...2.修改dubbo-monitor中的conf目录中的dubbo.properties dubbo.registry.address 与 dubbo-admin中的配置一样 3.到dubbo-monitor中的bin目录下运行 start.sh脚本 ok

    dubbo-admin-2.5.4.war

    dubbo-admin-2.5.4提供支持JDK1.7及JDK1.8的War包 Dubbo是Alibaba开源的分布式服务框架,它最大的特点是按照分层的方式来架构,使用这种方式可以使各个层之间解耦合(或者最大限度地松耦合)。从服务模型的角度来看...

    dubbo-admin-2.5.4.war后台管理

    《Dubbo Admin 2.5.4:高效管理与监控的利器》 在分布式系统中,服务治理扮演着至关重要的角色。Dubbo,作为阿里巴巴开源的一款高性能、轻量级的Java RPC框架,提供了丰富的服务治理功能。其中,`dubbo-admin`是...

    jmeter-plugins-dubbo-2.7.1-jar-with-dependencies

    《JMeter Plugins for Dubbo压力测试详解》 在IT行业中,系统压力测试是评估软件性能不可或缺的一环。本文将深入探讨“jmeter-plugins-dubbo-2.7.1-jar-with-dependencies”这一系统压测工具包,它专门针对基于Java...

    springboot2.0.x+dubbo-spring-boot-starter

    标签 "springboot-2" 和 "dubbo-2.6.x" 明确了项目所使用的具体技术版本。Spring Boot 2.0.x 引入了许多改进和新特性,包括对 Spring Framework 5 和 Java 9 的支持,以及更好的性能和稳定性。而 Dubbo 2.6.x 版本则...

    dubbo-monitor-simple-2.6.1.tgz

    dubbo官方自带了dubbo-admin及dubbo-simple/dubbo-monitor-simple二个子项目用于服务治理及服务监控。 dubbo-monitor-simple是Alibaba的开源项目,用于监控在dubbo框架下接口暴露,注册情况,也可以看接口的调用...

    dubbo-monitor-simple

    【Dubbo Monitor Simple】是Dubbo框架中的一个关键组件,主要功能是提供服务监控与管理。Dubbo是一款高性能、轻量级的开源Java RPC框架,它由阿里巴巴开发并维护,旨在提高服务治理的效率和质量。Monitor Simple是...

    dubbo-admin-2.8.4.war

    dubbo 最新dubbo-admin-2.8.4.war 菜单报错已修改。

    jmeter-plugins-dubbo-2.7.1-jar-with-dependencies.jar.zip

    jmeter-plugins-dubbo-2.7.1-jar-with-dependencies 2.jar jmeter本身并不支持dubbo接口的测试,需要下载第三方插件,然后将jar包放入${JMETER_HOME}\lib\ext路径下,重启即可。

    dubbo监控中心dubbo-monitor-simple

    该包为dubbo-monitor,使用方法请参见博文 《Dubbo进阶(五)—— dubbo-monitor-simple使用》 https://blog.csdn.net/sunhuaqiang1/article/details/80141478

    jmeter-plugins-dubbo-2.7.1-jar-with-dependencies (1)

    《JMeter Plugins for Dubbo详解》 在性能测试领域,Apache JMeter 是一款广泛应用的开源工具,它能够对各种类型的应用进行压力和负载测试。针对分布式服务框架Dubbo的测试需求,社区开发了JMeter Plugins for ...

Global site tag (gtag.js) - Google Analytics