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

一、什么是Diamond

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

二、Diamond的特点

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

三、Diamond的持久机制

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

四、Diamond的容灾机制

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

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

五、Diamond的架构图

六、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);}   });   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

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

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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

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

DiamondSubscriber的类图如下:

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

/*** 启动DiamondSubscriber:<br>* 1.阻塞主动获取所有的DataId配置信息<br>* 2.启动定时线程定时获取所有的DataId配置信息<br>*/public synchronized void start() {if (isRun) {return;}if ( == 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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

说明:
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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

说明
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 ( == cacheDatas) {continue;}for (Entry<String, CacheData> cacheDataEntry : cacheDatas.entrySet()) {final CacheData cacheData = cacheDataEntry.getValue();try {String configInfo = getLocalConfigureInfomation(cacheData);if ( != 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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

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

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

// 判断是否变更,没有变更,返回nullif (!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;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

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

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 ( != 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);}}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

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

6.2 checkDiamondServerConfigInfo代码分析

private void checkDiamondServerConfigInfo() {Set<String> updateDataIdGroupPairs = checkUpdateDataIds(diamondConfigure.getReceiveWaitTime());if ( == 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 ( == cacheDatas) {continue;}CacheData cacheData = cacheDatas.get(freshGroup);if ( == cacheData) {continue;}receiveConfigInfo(cacheData);}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

说明:
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>>();
  • 1
  • 2

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

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

[文章转载出处于此链接→](https://blog.csdn.net/u013970991)

深入解析淘宝Diamond之客户端架构相关推荐

  1. python爬取淘宝数据魔方_淘宝数据魔方技术架构解析

    淘宝网拥有国内最具商业价值的海量数据.截至当前,每天有超过30亿的店铺.商品浏览记录,10亿在线商品数,上千万的成交.收藏和评价数据.如何 从这些数据中挖掘出真正的商业价值,进而帮助淘宝.商家进行企业 ...

  2. 淘宝数据魔方技术架构解析读后感

    本次阅读文章为:淘宝数据魔方技术架构解析 文章地址:https://mp.weixin.qq.com/s?__biz=MzAxNjAzMTQyMA==&mid=2648476063&i ...

  3. 分布式系统 淘宝数据魔方技术架构解析

    淘宝网拥有国内最具商业价值的海量数据.每天有超过30亿的店铺.商品浏览记录,10亿在线商品数,上千万的成交.收藏和评价数据.如何从这些数据中挖掘出真正的商业价值,进而帮助淘宝.商家进行企业的数据化运营 ...

  4. 阅读心得3:《淘宝数据魔方技术架构解析 》

    本周阅读了老师推荐阅读的公众号:架构师中的推文<淘宝数据魔方技术架构解析>,感想如下: 淘宝,已成为一个国民级别的应用,每个新手机在推荐应用里的购物这一选项,排在第一个的就是淘宝.淘宝,更 ...

  5. 读《淘宝数据魔方技术架构解析》有感

    淘宝网拥有国内最具商业价值的海量数据.截至当前,每天有超过30亿的店铺.商品浏览记录,10亿在线商品数,上千万的成交.收藏和评价数据.如何从这些数据中挖掘出真正的商业价值,进而帮助淘宝.商家进行企业的 ...

  6. python爬取淘宝数据魔方_《淘宝数据魔方技术架构解析》阅读笔记

    淘宝网拥有国内最具商业价值的海量数据.截至当前,每天有超过30亿的店铺.商品浏览记录,10亿在线商品数,上千万的成交.收藏和评价数据.如何从这些数据中挖掘出真正的商业价值,进而帮助淘宝.商家进行企业的 ...

  7. 阿里千亿级购物节背后,淘宝智能客服架构演进之路

    " 淘宝上每天都有上百万的客服在线为上亿的买家提供服务,客服服务平台也从一个简单的分流系统逐步演进到覆盖买家.客服和客服主管三位一体的平台解决方案. 作者简介:淘宝技术部-媒体技术与消费连接 ...

  8. [转]浅析淘宝数据魔方技术架构

    为什么80%的码农都做不了架构师?>>>    为此,我们进行了一系列数据产品的研发,比如为大家所熟知的量子统计.数据魔方和淘宝指数等.尽管从业务层面来讲,数据产品的研发难度并不高; ...

  9. 淘宝网手机客户端开发(一)目录篇

    内容简介:         淘宝网手机客户端是一个基于Android进行应用的项目,通过该客户端实现了注册功能.登陆.分页展示商品列表.能够对商品按名称进行模糊查询.能够对商品进行管理.能够对商品按价 ...

  10. 淘宝十年资深架构师吐血总结淘宝的数据库架构设计和采用的技术手段。

    淘宝十年资深架构师吐血总结淘宝的数据库架构设计和采用的技术手段. 文章目录 淘宝十年资深架构师吐血总结淘宝的数据库架构设计和采用的技术手段. 本文导读 1.分库分表 2.数据冗余 3.异步复制 4.读 ...

最新文章

  1. python处理excel大数据-当Excel遇到大数据问题,是时候用Python来拯救了
  2. Ext2.2系列(50篇)
  3. bbmail-小巧的邮件提示程序
  4. 2通过程序获得环境变量,getenv(),setenv()函数和unsetenv()函数,env查看环境变量,echo输出指定的环境变量
  5. linux下Vim和Terminal配色
  6. java成员方法的一般格式为_Java基本知识(四)
  7. 2021抖音私域经营白皮书
  8. leetcode —— 200. 岛屿数量
  9. 关于telnet的问题
  10. EasyUI Datagrid 自定义列、Foolter及单元格编辑
  11. 【codevs1282】约瑟夫问题
  12. php facebook授权登录获取头像_IdentityServer4从数据库获取User登录并对Claims授权验证(五)...
  13. 不透明度百分比 16进制值对照表;rgb色值16进制转化原理
  14. java activity_java中的Activity
  15. np.random用法
  16. envoy做集中式egress sidecar
  17. 关于bootstrap4 以下 与bootstrap5 的区别
  18. js(76-108)
  19. Excel 数据爬取
  20. java实现计算机界面

热门文章

  1. stm32c6t6硬件iic接口的使用--以0.96寸oled为例
  2. U盘启动制作 + xp镜像
  3. 【EXLIBRIS】#小词旮旯# 001 Lock
  4. php 批量下载网页文件,批量下载文件(以xxx网站为例)
  5. 实现从oss(阿里云)服务器批量下载文件
  6. 360与腾讯之争之厚黑学分析
  7. 对 Go2 错误处理提案的批判
  8. IIS本地FTP服务器搭建
  9. 十个 Python 自动化常用操作
  10. Pytorch 学习笔记--to(device)的用法