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

深入淘宝Diamond之客户端架构解析

 
阅读更多

 

说明:本文不介绍如何使用Diamond,只介绍Diamond的实现原理

一、什么是Diamond

diamond是淘宝内部使用的一个管理持久配置的系统,它的特点是简单、可靠、易用,目前淘宝内部绝大多数系统的配置,由diamond来进行统一管理。
diamond为应用系统提供了获取配置的服务,应用不仅可以在启动时从diamond获取相关的配置,而且可以在运行中对配置数据的变化进行感知并获取变化后的配置数据。
持久配置是指配置数据会持久化到磁盘和数据库中。

二、Diamond的特点

  • 简单:整体结构非常简单,从而减少了出错的可能性。
  • 可靠:应用方在任何情况下都可以启动,在承载淘宝核心系统并正常运行一年多以来,没有出现过任何重大故障。
  • 易用:客户端使用只需要两行代码,暴露的接口都非常简单,易于理解。

三、Diamond的持久机制


Paste_Image.png

订阅方获取配置数据时,直接读取服务端本地磁盘文件,尽量减少对数据库压力。 这种架构用短暂的延时换取最大的性能和一致性,一些配置不能接受延时的情况下,通过API可以获取数据库中的最新配置

四、Diamond的容灾机制

Diamond作为一个分布式环境下的持久配置系统,有一套完备的容灾机制,数据被存储在:数据库,服务端磁盘,客户端缓存目录,以及可以手工干预的容灾目录。 客户端通过API获取配置数据按照固定的顺序去不同的数据源获取数据:容灾目录,服务端磁盘,客户端缓存。

• 数据库主库不可用,可以切换到备库,Diamond继续提供服务
• 数据库主备库全部不可用,Diamond通过本地缓存可以继续提供读服务
• 数据库主备库全部不可用,Diamond服务端全部不可用,Diamond客户端使用缓存目录继续运行,支持离线启动
• 数据库主备库全部不可用,Diamond服务端全部不可用,Diamond客户端缓存数据被删,可以通过拷贝备份的缓存目录到容灾目录下继续使用

五、Diamond的架构图


图片 1.png

六、Diamond订阅端(客户端)分析

先看一个简单的客户端订阅代码实现:

public class DiamondTestClient {
    public static DiamondManager manager;
    public static void main(String[] str) {
        initDiamondManager();
    }
    private static void initDiamondManager() {
        manager = new DefaultDiamondManager("group_test", "dataId_test", new ManagerListener() {
            public void receiveConfigInfo(String configInfo) {
                System.out.println("configInfo="+ configInfo);
            }   
        });   
    }  
}

参数的说明:
DefaultDiamondManager有三个参数分别是:groupId,dataId和listener。
group和dataId为String类型,二者结合为diamond-server端保存数据的惟一key
ManagerListener 是客户端注册的数据监听器, 它的作用是在运行中接受变化的配置数据,然后回调receiveConfigInfo()方法,执行客户端处理数据的逻辑。如果要在运行中对变化的配置数据进行处理,就一定要注册ManagerListener
我们来看一下DefaultDiamondManager的类图


Paste_Image.png

DefaultDiamondManager的构造方法代码如下:

public DefaultDiamondManager(String group, String dataId, ManagerListener managerListener) {
        this.dataId = dataId;
        this.group = group;

        diamondSubscriber = DiamondClientFactory.getSingletonDiamondSubscriber();

        this.managerListeners.add(managerListener);
        ((DefaultSubscriberListener) diamondSubscriber.getSubscriberListener()).addManagerListeners(this.dataId,
            this.group, this.managerListeners);
        diamondSubscriber.addDataId(this.dataId, this.group);
        diamondSubscriber.start();

    }

说明
1、利用工厂类DiamondClientFactory创建单例订阅者类。
2、将客户端创建的侦听器类添加到侦听器管理list中并注入到新创建的订阅者类中。
3、为订阅者设置dataId和groupId。
4、启动订阅者线程,开始轮询消息。

DiamondSubscriber的类图如下:


Paste_Image.png

执行diamondSubScriber.start()方法直接进入DefaultDiamondSubscriber子类中,先看如下代码:

/**
     * 启动DiamondSubscriber:<br>
     * 1.阻塞主动获取所有的DataId配置信息<br>
     * 2.启动定时线程定时获取所有的DataId配置信息<br>
     */
    public synchronized void start() {
        if (isRun) {
            return;
        }

        if (null == scheduledExecutor || scheduledExecutor.isTerminated()) {
            scheduledExecutor = Executors.newSingleThreadScheduledExecutor();
        }

        localConfigInfoProcessor.start(this.diamondConfigure.getFilePath() + "/" + DATA_DIR);
        serverAddressProcessor = new ServerAddressProcessor(this.diamondConfigure, this.scheduledExecutor);
        serverAddressProcessor.start();

        this.snapshotConfigInfoProcessor =
                new SnapshotConfigInfoProcessor(this.diamondConfigure.getFilePath() + "/" + SNAPSHOT_DIR);
        // 设置domainNamePos值
        randomDomainNamePos();
        initHttpClient();

        // 初始化完毕
        isRun = true;

        if (log.isInfoEnabled()) {
            log.info("当前使用的域名有:" + this.diamondConfigure.getDomainNameList());
        }

        if (MockServer.isTestMode()) {
            bFirstCheck = false;
        }
        else {
            // 设置轮询间隔时间
            this.diamondConfigure.setPollingIntervalTime(Constants.POLLING_INTERVAL_TIME);
        }
        // 轮询
        rotateCheckConfigInfo();

        addShutdownHook();
    }

说明:
1、ServerAddressProcessor类从服务端获取提供服务的地址列表(可能会多个)。
2、randomDomainNamePos这个方法是随机从服务地址列表中选取一个地址。
3、初始化httpClient客户端,使用initHttpClient方法。
4、设置读取配置文件的轮询时间默认为15秒。
5、rotateCheckConfigInfo这个方法是真正与服务端交互的轮询方法。

rotateCheckConfigInfo方法的代码如下:

/**
     * 循环探测配置信息是否变化,如果变化,则再次向DiamondServer请求获取对应的配置信息
     */
    private void rotateCheckConfigInfo() {
        scheduledExecutor.schedule(new Runnable() {
            public void run() {
                if (!isRun) {
                    log.warn("DiamondSubscriber不在运行状态中,退出查询循环");
                    return;
                }
                try {
                    checkLocalConfigInfo();
                    checkDiamondServerConfigInfo();
                    checkSnapshot();
                }
                catch (Exception e) {
                    e.printStackTrace();
                    log.error("循环探测发生异常", e);
                }
                finally {
                    rotateCheckConfigInfo();
                }
            }

        }, bFirstCheck ? 60 : diamondConfigure.getPollingIntervalTime(), TimeUnit.SECONDS);
        bFirstCheck = false;
    }

说明
1、方法内部启动一个定时线程,默认每隔60秒执行一次。
2、方法内部实际上三个主方法分别是:

  • checkLocalConfigInfo:主要是检查本地数据是否有更新,如果没有则返回,有则返回最新数据,并通知客户端配置的listener。
  • checkDiamondServerConfigInfo:远程调用服务端,获取最新修改的配置数据并通知客户端listener。
  • checkSnapshot:主要是持久化数据信息用的方法。

6.1 checkLocalConfigInfo代码分析

private void checkLocalConfigInfo() {
        for (Entry<String/* dataId */, ConcurrentHashMap<String/* group */, CacheData>> cacheDatasEntry : cache
            .entrySet()) {
            ConcurrentHashMap<String, CacheData> cacheDatas = cacheDatasEntry.getValue();
            if (null == cacheDatas) {
                continue;
            }
            for (Entry<String, CacheData> cacheDataEntry : cacheDatas.entrySet()) {
                final CacheData cacheData = cacheDataEntry.getValue();
                try {
                    String configInfo = getLocalConfigureInfomation(cacheData);
                    if (null != configInfo) {
                        if (log.isInfoEnabled()) {
                            log.info("本地配置信息被读取, dataId:" + cacheData.getDataId() + ", group:" + cacheData.getGroup());
                        }
                        popConfigInfo(cacheData, configInfo);
                        continue;
                    }
                    if (cacheData.isUseLocalConfigInfo()) {
                        continue;
                    }
                }
                catch (Exception e) {
                    log.error("向本地索要配置信息的过程抛异常", e);
                }
            }
        }

说明:
1、循环本地缓存数据,比较数据是否更新变化,重点看getLocalConfigureInfomation方法。
2、如果有更新数据则调用popConfigInfo方法通知客户端listener。

再深入看getLocalConfigureInfomation方法,代码如下:

// 判断是否变更,没有变更,返回null
        if (!filePath.equals(cacheData.getLocalConfigInfoFile())
                || existFiles.get(filePath) != cacheData.getLocalConfigInfoVersion()) {
            String content = FileUtils.getFileContent(filePath);
            cacheData.setLocalConfigInfoFile(filePath);
            cacheData.setLocalConfigInfoVersion(existFiles.get(filePath));
            cacheData.setUseLocalConfigInfo(true);

            if (log.isInfoEnabled()) {
                log.info("本地配置数据发生变化, dataId:" + cacheData.getDataId() + ", group:" + cacheData.getGroup());
            }

            return content;
        }
        else {
            cacheData.setUseLocalConfigInfo(true);

            if (log.isInfoEnabled()) {
                log.debug("本地配置数据没有发生变化, dataId:" + cacheData.getDataId() + ", group:" + cacheData.getGroup());
            }

            return null;
        }

说明:
这段代码很关键,判断当前缓存的数据是否持久化的文件数据是否一致,包括版本号,文件路径等信息,如果服务器端有配置数据更新,客户端则拿到最新的数据后更新本地文件内容。

popConfigInfo方法的代码如下:

void popConfigInfo(final CacheData cacheData, final String configInfo) {
        final ConfigureInfomation configureInfomation = new ConfigureInfomation();
        configureInfomation.setConfigureInfomation(configInfo);
        final String dataId = cacheData.getDataId();
        final String group = cacheData.getGroup();
        configureInfomation.setDataId(dataId);
        configureInfomation.setGroup(group);
        cacheData.incrementFetchCountAndGet();
        if (null != this.subscriberListener.getExecutor()) {
            this.subscriberListener.getExecutor().execute(new Runnable() {
                public void run() {
                    try {
                        subscriberListener.receiveConfigInfo(configureInfomation);
                        saveSnapshot(dataId, group, configInfo);
                    }
                    catch (Throwable t) {
                        log.error("配置信息监听器中有异常,group为:" + group + ", dataId为:" + dataId, t);
                    }
                }
            });
        }
        else {
            try {
                subscriberListener.receiveConfigInfo(configureInfomation);
                saveSnapshot(dataId, group, configInfo);
            }
            catch (Throwable t) {
                log.error("配置信息监听器中有异常,group为:" + group + ", dataId为:" + dataId, t);
            }
        }
    }

说明:
这段代码主要是将已经更新的数据通知给客户端织入的listener程序,使能够达到最新数据通知给客户端。

6.2 checkDiamondServerConfigInfo代码分析

private void checkDiamondServerConfigInfo() {
        Set<String> updateDataIdGroupPairs = checkUpdateDataIds(diamondConfigure.getReceiveWaitTime());
        if (null == updateDataIdGroupPairs || updateDataIdGroupPairs.size() == 0) {
            log.debug("没有被修改的DataID");
            return;
        }
        // 对于每个发生变化的DataID,都请求一次对应的配置信息
        for (String freshDataIdGroupPair : updateDataIdGroupPairs) {
            int middleIndex = freshDataIdGroupPair.indexOf(WORD_SEPARATOR);
            if (middleIndex == -1)
                continue;
            String freshDataId = freshDataIdGroupPair.substring(0, middleIndex);
            String freshGroup = freshDataIdGroupPair.substring(middleIndex + 1);

            ConcurrentHashMap<String, CacheData> cacheDatas = cache.get(freshDataId);
            if (null == cacheDatas) {
                continue;
            }
            CacheData cacheData = cacheDatas.get(freshGroup);
            if (null == cacheData) {
                continue;
            }
            receiveConfigInfo(cacheData);
        }
    }

说明:
1、通过HttpClient方式从服务端获取更新过的dataId和groupId集合。
2、根据dataId和groupId再从服务端将相应变化的数据获取下来。
3、通知客户端注册的listener程序。

上面二种方式通知客户端的listener程序,都是通过allListeners这个属性获取的

private final ConcurrentMap<String/* dataId + group */, CopyOnWriteArrayList<ManagerListener>/* listeners */> allListeners =
            new ConcurrentHashMap<String, CopyOnWriteArrayList<ManagerListener>>();

这行代码就是在最开始的那个客户端使用的例子中注册在allListeners中的。

七、Diamond客户端与服务端交互时序图


图片 1.png
  • 大小: 85.1 KB
  • 大小: 24.7 KB
分享到:
评论
1 楼 worldcbf 2016-04-13  
这个开源项目已经不更新了,你们在生产环境有使用吗?

相关推荐

    diamond客户端使用方法.docx

    - **Diamond服务端**:提供配置存储、查询等功能的服务端应用,是Diamond架构的核心组成部分。 - **配置中心**:通常指Diamond服务端,用于集中存储和管理所有应用程序的配置信息。 - **配置刷新**:当配置发生变化...

    来自淘宝diamond:http:__code.taobao.org_p_diamond_src_.zip

    淘宝Diamond项目是一个专门为淘宝平台开发的分布式配置管理系统。它旨在解决大规模分布式系统中配置管理的难题,提供了一...对于学习和研究分布式系统、微服务架构的开发者来说,深入研究淘宝Diamond的源码将大有裨益。

    淘宝diamond源码

    通过分析淘宝Diamond的源代码,开发者可以深入理解其内部工作原理,学习如何实现一个高可用、高性能的配置中心,这对于构建大规模分布式系统具有很高的参考价值。同时,这也为其他开源项目提供了一个优秀的学习案例...

    diamond3_读取_micaps_diamond3_气象_3类数据_

    这里,我们将深入探讨Diamond3数据格式、Micaps系统以及如何读取这类数据。 Diamond3是Micaps系统中的一种特定数据格式,用于存储和传输气象预报和观测数据。这种格式通常包含多种气象参数,如温度、湿度、风速、...

    diamond使用说明

    ##### 客户端特性解析 1. **数据快照**:客户端每次成功获取数据后,会将其保存至本地文件系统中作为快照(snapshot)。这种机制确保了在网络中断或服务器故障的情况下,客户端仍能利用最新的快照数据正常工作。 2. *...

    taobao Diamond 部分maven jar包

    **淘宝Diamond Maven Jar包详解** 淘宝Diamond是一款由阿里巴巴开源的分布式配置中心,它主要用于解决大规模分布式系统中的配置管理问题。在大型互联网企业中,随着系统的复杂度增加,配置的管理和更新变得越来越...

    diamond源代码

    《深入解析"Diamond":淘宝内部配置管理系统》 在当今的互联网行业中,系统配置的管理和更新是一项至关重要的任务。为了确保系统的稳定运行和快速响应业务变化,淘宝内部开发了一款名为"Diamond"的配置管理系统。...

    Diamond 配置生成

    2. 客户端组件:在各个服务器节点上运行的Diamond客户端,会定期或按需向配置中心拉取最新的配置信息。客户端通过监听配置中心的变更事件,实现实时更新。 3. 配置推送:当配置发生改变时,配置中心会主动推送给...

    Diamond.java

    Diamond.java

    lattice Diamond多种解密方法

    lattice diamond 的license 多种解密方法,让你在lattice diamond芯片开发上,大展身手!

    Diamond 3.9 lisence万能版.zip

    《Lattice Diamond 3.9 License 万能版解析与应用》 在电子设计自动化(EDA)领域,Lattice Diamond 是一款广泛使用的 FPGA 设计工具,由 Lattice Semiconductor 公司提供。它集成了逻辑综合、布局布线、仿真、硬件...

    diamond 中文版 lattice软件

    《Lattice Diamond中文版软件详解》 在计算机科学与电子工程领域,电路设计是一项至关重要的工作,而Lattice公司的Diamond软件则是其中的一款杰出工具。它为用户提供了丰富的硬件描述语言(HDL)编译、仿真、综合...

    diamond软件

    《钻石(Diamond)软件——揭示原子世界的微观之美》 在信息技术与科学研究的交汇处,有一款名为"Diamond"的强大工具,它专为在原子水平上进行晶体结构可视化而设计。这款软件是科学家们探索物质本质,理解晶体结构...

    Lattice Diamond 3.1 User Guide

    Lattice Diamond 3.1是一款由Lattice Semiconductor Corporation开发的FPGA设计软件工具。Lattice Semiconductor是专门从事可编程逻辑器件的半导体制造商,它提供从低功耗移动设备到高性能通信系统的多种产品线。在...

    Diamond软件

    它以其直观的界面、强大的功能以及易上手的操作,成为了晶体学研究者的首选软件之一。 Diamond软件的核心功能在于其能够帮助用户轻松创建、编辑和可视化各种晶体结构。无论你是新手还是经验丰富的研究人员,都能...

    Black_Diamond_EA (1)_forexearobot_forex_Diamond_DiamondEA下载_blac

    《黑钻EA(1):外汇交易的钻石策略解析》 在外汇交易的世界里,一款优秀的自动交易系统能够为投资者带来稳定且高效的投资回报。"Black_Diamond_EA (1)_forexearobot_forex_Diamond_DiamondEA下载_blac" 提到的...

    晶体结构模拟软件专用的Diamond

    《晶体结构模拟软件Diamond深度解析》 在现代科学研究中,晶体结构模拟扮演着至关重要的角色。它能够帮助科学家们理解材料的基本性质,预测新材料的性能,并优化现有材料的设计。Diamond是一款专为此目的设计的高级...

    Diamond 软件.rar

    首先,让我们深入了解Diamond软件的核心功能。这款软件主要分为两个主要部分:数据采集和数据分析。在数据采集阶段,Diamond能够与各种XRD仪器无缝对接,实时获取并记录实验数据。而在数据分析阶段,它提供了丰富的...

    Diamond梳理

    2. **源码分析**:由于标签中包含“源码”,这部分内容可能涉及对Diamond的源代码进行解析,帮助读者理解其内部机制,比如它是如何管理依赖关系的,以及如何提高应用的灵活性和可扩展性。 3. **工具使用**:Diamond...

    Lattice厂家开发软件Diamond安装文件 License

    尽管没有直接提供详细的安装步骤,但推荐参考《FPGA开发之Diamond安装使用(一)》这篇教程,其中应该详细介绍了如何正确安装软件、加载 License 以及验证其有效性。通常,安装过程会涉及以下步骤: 1. 下载并解压 ...

Global site tag (gtag.js) - Google Analytics