简介

diamond是淘宝内部使用的一个管理持久配置的系统,它的特点是简单、可靠、易用,目前淘宝内部绝大多数系统的配置,由diamond来进行统一管理。

diamond为应用系统提供了获取配置的服务,应用不仅可以在启动时从diamond获取相关的配置,而且可以在运行中对配置数据的变化进行感知并获取变化后的配置数据。

持久配置是指配置数据会持久化到磁盘和数据库中。

diamond的特点是简单、可靠、易用:

简单:整体结构非常简单,从而减少了出错的可能性。

可靠:应用方在任何情况下都可以启动,在承载淘宝核心系统并正常运行一年多以来,没有出现过任何重大故障。

易用:客户端使用只需要两行代码,暴露的接口都非常简单,易于理解。

、作为一个配置中心,diamond的功能分为发布和订阅两部分。因为diamond存放的是持久数据,这些数据的变化频率不会很高,甚至很低,所以发布采用手工的形式,通过diamond后台管理界面发布;订阅是diamond的核心功能,订阅通过diamond-client的API进行。
、diamond服务端采用mysql加本地文件的形式存放配置数据。发布数据时,数据先写到mysql,再写到本地文件;订阅数据时,直接获取本地文件,不查询数据库,这样可以最大程度减少对数据库的压力。
、diamond服务端是一个集群,集群中的每台机器连接同一个mysql,集群之间的数据同步通过两种方式进行,一是每台server定时去mysqldump数据到本地文件,二是某一台server接收发布数据请求,在更新完mysql和本机的本地文件后,发送一个HTTP请求(通知)到集群中的其他几台server,其他server收到通知,去mysql中将刚刚更新的数据dump到本地文件。
、每一台server前端都有一个nginx,用来做流量控制。
、图中没有将地址服务器画出,地址服务器是一台有域名的机器,上面运行有一个HTTPserver,其中有一个静态文件,存放着diamond服务器的地址列表。客户端启动时,根据自身的域名绑定,连接到地址服务器,取回diamond服务器的地址列表,从中随机选择一台diamond服务器进行连接。
可以看到,整个diamond的架构非常简单,使用的都是最常用的一些技术以及产品,它之所以表现得非常稳定,跟其架构简单是分不开的,当然,稳定的另一个主要原因是它具备一套比较完善的容灾机制,容灾机制将在下一篇文章中讲述。

源码地址

https://github.com/takeseem/diamond.git

服务端安装

  1. 检出源码,修改配置文件 jdbc.properties 中的数据库连接信息,完成之后maven打包
  2. 数据库执行初始化sql
    create database diamond;
    grant all on diamond.* to CK@'%' identified by 'abc';
    use diamond
    create table config_info (
    `id` bigint(64) unsigned NOT NULL auto_increment,
    `data_id` varchar(255) NOT NULL default ' ',
    `group_id` varchar(128) NOT NULL default ' ',
    `content` longtext NOT NULL,
    `md5` varchar(32) NOT NULL default ' ',
    `gmt_create` datetime NOT NULL default '2010-05-05 00:00:00',
    `gmt_modified` datetime NOT NULL default '2010-05-05 00:00:00',
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_config_datagroup` (`data_id`,`group_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;
  3. 将打好的包 diamond-server.war 放到tomcat工作目录,启动。启动成功之后,访问  http://localhost:8090/diamond-server/
  4. 发布数据,账号密码是user.properties中配置的,默认是 abc=123。登录后进入后台管理界面,然后点击“配置信息管理”—— “添加配置信息”,在输入框中输入dataId、group、内容,最后点击“提交”即可。
    成功后,可以在“配置信息管理”中查询到发布的数据。
  5. 集群安装。修改node.properties,格式为   ip\:port  ,这里面的冒号,一定要通过\转义一下,要不然获取地址不对。当存在node节点的配置,发布修改数据后会通知其他节点更新。
  6. 每台diamond-server 前建议增加nginx转发,方便限流,而且客户端默认请求80端口
  7. 其他配置: system.properties中的dump_config_interval 是多久去更新一次本地缓存的数据 默认是 600秒

客户端安装

客户端获取数据方法:

DiamondManager manager = new DefaultDiamondManager(group, dataId, new ManagerListener() {
public Executor getExecutor() {
return null;
} public void receiveConfigInfo(String configInfo) {
// 客户端处理数据的逻辑 }
});

集成思路:重写PropertyPlaceholderConfigurer,将diamond管理的配置交个spring,spring的xml可以直接使用${}来查询数据,增加工具类PropertiesUtils.java 方便查询diamond管理的数据。具体代码

<!-- 引入依赖diamond -->
<dependency>
<groupId>com.taobao.diamond</groupId>
<artifactId>diamond-client</artifactId>
<version>2.0.5.4.taocode-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.taobao.diamond</groupId>
<artifactId>diamond-utils</artifactId>
<version>2.0.5.4.taocode-SNAPSHOT</version>
</dependency>

重写PropertyPlaceholderConfigurer

package com.zyx.demo.common.spring;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer; import java.util.Iterator;
import java.util.List;
import java.util.Properties; /**
* <p>重写PropertyPlaceholderConfigurer,将diamond配置信息交给spring</p>
*/ public class SpringPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer { private List<String> diamondList; public List<String> getDiamondList() {
return diamondList;
} public void setDiamondList(List<String> diamondList) {
this.diamondList = diamondList;
} @Override
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props) throws BeansException {
Properties properties = PropertiesUtils.getProperties(diamondList);
if (properties == null) {
String diamondFilePath = PropertiesUtils.DIAMOND_FILEPATH;//System.getProperty("user.home") + System.getProperty("file.separator") + ".diamond.domain";
throw new RuntimeException("从diamond获取配置为空(dataId和group是" + diamondList + "),请检查diamond要连接的环境:" + diamondFilePath);
}
this.setProperties(properties);
for (Iterator<Object> iterator = properties.keySet().iterator(); iterator.hasNext();) {
String key = (String) iterator.next();
String value = (String) properties.get(key);
props.setProperty(key, value);
}
super.processProperties(beanFactoryToProcess, properties);
} }

PropertiesUtils.java工具类

package com.zyx.demo.common.spring;

import com.taobao.diamond.manager.ManagerListener;
import com.taobao.diamond.manager.ManagerListenerAdapter;
import com.taobao.diamond.manager.impl.DefaultDiamondManager;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger; import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Executor; /**
* <p>工具类,获取diamond配置</p>
*/ public class PropertiesUtils { public static Properties properties; private static Logger logger = Logger.getLogger(PropertiesUtils.class);
private static final long TIME_OUT = 5000L;
private static String diamondIpList;
private static List<String> diamondIdgroupList;
protected static final String DIAMOND_FILEPATH="diamond.data"; public static Properties getProperties(List<String> diamondList) {
diamondIdgroupList = diamondList;
if (null == properties) {
init();
}
return properties;
} public static Properties getProperties() {
if (null == properties) {
init();
}
return properties;
} /**
* 根据key从map中取值
*/
public static Object getValueByKey(String key) {
if (null == properties) {
init();
}
return properties.get(key);
} public static String getStringValueByKey(String key) {
return (String) getValueByKey(key);
} public static int getIntValueByKey(String key) {
return Integer.parseInt((String) getValueByKey(key));
} public static double getDoubleValueByKey(String key) {
return Double.parseDouble((String) getValueByKey(key));
} public static boolean getBooleanValueByKey(String key) {
return Boolean.parseBoolean((String) (getValueByKey(key)));
} public static String getStringValueByKey(String key, String defaultV) {
Object value = getValueByKey(key);
if (value == null) {
return defaultV;
}
return (String) value;
} public static int getIntValueByKey(String key, int defaultV) {
Object value = getValueByKey(key);
if (value == null) {
return defaultV;
}
return Integer.parseInt((String) value);
} public static double getDoubleValueByKey(String key, double defaultV) {
Object value = getValueByKey(key);
if (value == null) {
return defaultV;
}
return Double.parseDouble((String) value);
} public static boolean getBooleanValueByKey(String key, boolean defaultV) {
Object value = getValueByKey(key);
if (value == null) {
return defaultV;
}
return Boolean.parseBoolean((String) (value));
} /**
* init(读取多个dataId 与 groupId )*/
private static void init() { String diamondFilePath = PropertiesUtils.class.getClassLoader().getResource(DIAMOND_FILEPATH).getPath() ;//System.getProperty("user.home") + "/.diamond.domain";
try { List<String> contentList = FileUtils.readLines(new File(diamondFilePath), "UTF-8");
for (String ipList : contentList) {
if (!ipList.contains("#")) {
diamondIpList = ipList.trim();
break;
}
}
} catch (Exception e) {
logger.error("获取diamond文件内容失败:" + e.getMessage(), e);
}
logger.info("diaond-->filePath:" + diamondFilePath + " change diamondIpList:" + diamondIpList);
if (diamondIdgroupList != null && diamondIpList != null) {
for (String str : diamondIdgroupList) {
// dataid
String dataId = "";
String groupId = "";
if (str.indexOf(":") > -1) {
dataId = str.substring(0, str.indexOf(":"));
}
if (str.lastIndexOf(":") > -1) {
groupId = str.substring(str.indexOf(":") + 1,str.length());
}
if (!StringUtils.isEmpty(dataId) && !StringUtils.isEmpty(groupId)) {
DefaultDiamondManager manager = new DefaultDiamondManager(dataId, groupId, new ManagerListenerAdapter() {
public void receiveConfigInfo(String configInfo) {
//数据发生变更时,更新数据
putAndUpdateProperties(configInfo);
}
}, diamondIpList);
String configInfo = manager.getAvailableConfigureInfomation(TIME_OUT);
logger.debug("从diamond取到的数据是:" + configInfo);
putAndUpdateProperties(configInfo);
} else {
logger.error("diamond数据配置properties异常: DataId:" + dataId + ",Group:" + groupId);
}
}
} else {
logger.error("diamond数据配置properties异常: diamondBeanList is null or diamondIpList is null");
}
} /**
* 更新properties中数据*/
public static void putAndUpdateProperties(String configInfo) {
if (StringUtils.isNotEmpty(configInfo)) {
if (properties == null) {
properties = new Properties();
}
try {
properties.load(new ByteArrayInputStream(configInfo.getBytes()));
} catch (IOException e) {
logger.error("根据diamond数据流转成properties异常" + e.getMessage(), e);
}
} else {
logger.error("从diamond取出的数据为空,请检查配置");
}
} public static List<String> getDiamondIdgroupList() {
return diamondIdgroupList;
} public static void setDiamondIdgroupList(List<String> diamondIdgroupList) {
PropertiesUtils.diamondIdgroupList = diamondIdgroupList;
} public String getDiamondIpList() {
return diamondIpList;
} }

spring配置

    <!-- diamond管理配置文件 -->
<bean id = "propertyConfigurer" class="com.zyx.demo.common.spring.SpringPropertyPlaceholderConfigurer">
<property name="diamondList">
<list>
<value>com-zyx-demo:com-zyx-demo</value>
</list>
</property>
</bean>

容灾机制

是diamond具有一套完备的容灾机制,容灾机制涉及到client和server两部分,主要包括以下几个方面:
1、server存储数据的方式。
server存储数据是“数据库+本地文件”的方式,集群间的数据同步我们在之前的文章中讲过(请参考专题二的原理部分),client订阅数据时,访问的是本地文件,不查询数据库,这样即使数据库出问题了,仍然不影响client的订阅。
2、server是一个集群。
这是一个基本的容灾机制,集群中的一台server不可用了,client发现后可以自动切换到其他server上进行访问,自动切换在client内部实现。
3、client保存snapshot
client每次从server获取到数据后,都会将数据保存在本地文件系统,diamond称之为snapshot,即数据快照。当client下次启动发现在超时时间内所有server均不可用(可能是网络故障),它会使用snapshot中的数据快照进行启动。
4、client校验MD5
client每次从server获取到数据后,都会进行MD5校验(数据保存在responsebody,MD5保存在responseheader),以防止因网络故障造成的数据不完整,MD5校验不通过直接抛出异常。
5、client与server分离
client可以和server完全分离,单独使用,diamond定义了一个“容灾目录”的概念,client在启动时会创建这个目录,每次主动获取数据(即调用getAvailableConfigInfomation()方法),都会优先从“容灾目录”获取数据,如果client按照一个固定的规则,在“容灾目录”下配置了需要的数据,那么client直接获取到数据返回,不再通过网络从diamond-server获取数据。同样的,在每次轮询时,都会优先轮询“容灾目录”,如果发现配置还存在于其中,则不再向server发出轮询请求。以上的情形,会持续到“容灾目录”的配置数据被删除为止。
根据以上的容灾机制,我们可以总结一下diamond整个系统完全不可用的条件:
1、数据库不可用。
2、所有server均不可用。
3、client主动删除了snapshot
4、client没有备份配置数据,导致其不能配置“容灾目录”。
同时满足以上4个条件的概率,在生产环境中是极小的。
以上就是diamond的容灾机制

其他相关

Xdiamond
1、基于数据库做配置存储
2、相对于diamond增加了权限设计,结合Secret key,保证配置的安全
3、配置缓存在本地,防止应用因为网络问题而不能启动

disconf是来自百度的分布式配置管理平台,包括百度、滴滴出行、银联、网易、拉勾网、苏宁易购、顺丰科技 等知名互联网公司正在使用!  https://github.com/knightliao/disconf

diamond简介和使用的更多相关文章

  1. 阿里中间件——diamond

    一.前言 最近工作不忙闲来无事,仔细分析了公司整个项目架构,发现用到了很多阿里巴巴集团开源的框架,今天要介绍的是中间件diamond. 二.diamond学习笔记 1.diamond简介 diamon ...

  2. 【转】分布式数据层 TDDL 来自:阿里巴巴

    淘宝根据自己的业务特点开发了TDDL(Taobao Distributed Data Layer 外号:头都大了 ©_Ob)框架,主要解决了分库分表对应用的透明化以及异构数据库之间的数据复制,它是一个 ...

  3. 笔者带你剖析淘宝TDDL(TAOBAO DISTRIBUTE DATA LAYER)

    注:本文部分内容引用本人博客http://gao-xianglong.iteye.com/blog/1973591   前言 在开始讲解淘宝的TDDL(Taobao Distribute Data L ...

  4. mysql中间件研究(Atlas,cobar,TDDL)

    mysql-proxy是官方提供的mysql中间件产品可以实现负载平衡,读写分离,failover等,但其不支持大数据量的分库分表且性能较差.下面介绍几款能代替其的mysql开源中间件产品,Atlas ...

  5. 淘宝分布式数据层:TDDL[转]

    淘宝根据自己的业务特点开发了TDDL(Taobao Distributed Data Layer 外号:头都大了 ©_Ob)框架,主要解决了分库分表对应用的透明化以及异构数据库之间的数据复制,它是一个 ...

  6. cobar和tddl分享

    Cobar是阿里巴巴(B2B)部门开发的一种关系型数据的分布式处理系统,它可以在分布式的环境下看上去像传统数据库一样为您提供海量数据服务.那么具体说说我们为什么要用它,或说cobar--能干什么?以下 ...

  7. mysql中间件研究(Atlas,cobar,TDDL)[转载]

    mysql中间件研究(Atlas,cobar,TDDL) mysql-proxy是官方提供的mysql中间件产品可以实现负载平衡,读写分离,failover等,但其不支持大数据量的分库分表且性能较差. ...

  8. 淘宝分布式数据层TDDL

    剖析淘宝 TDDL ( TAOBAO DISTRIBUTE DATA LAYER ) 注:原文:http://gao-xianglong.iteye.com/blog/1973591   前言 在开始 ...

  9. [转帖]剖析淘宝TDDL(TAOBAO DISTRIBUTE DATA LAYER)

    剖析淘宝TDDL(TAOBAO DISTRIBUTE DATA LAYER) 博客分类: 原博客地址: http://qq85609655.iteye.com/blog/2035176 distrib ...

随机推荐

  1. 九度oj 题目1108:堆栈的使用

    题目描述: 堆栈是一种基本的数据结构.堆栈具有两种基本操作方式,push 和 pop.Push一个值会将其压入栈顶,而 pop 则会将栈顶的值弹出.现在我们就来验证一下堆栈的使用. 输入: 对于每组测 ...

  2. 九度oj 题目1367:二叉搜索树的后序遍历序列

    题目描述: 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果.如果是则输出Yes,否则输出No.假设输入的数组的任意两个数字都互不相同. 输入: 每个测试案例包括2行: 第一行为1个整数 ...

  3. [LOJ#2270][BZOJ4912][SDOI2017]天才黑客

    [LOJ#2270][BZOJ4912][SDOI2017]天才黑客 试题描述 SD0062 号选手小 Q 同学为了偷到 SDOI7012 的试题,利用高超的黑客技术潜入了 SDOI 出题组的内联网的 ...

  4. P1410 子序列 (动态规划)

    题目描述 给定一个长度为N(N为偶数)的序列,问能否将其划分为两个长度为N/2的严格递增子序列. 输入输出格式 输入格式: 若干行,每行表示一组数据.对于每组数据,首先输入一个整数N,表示序列的长度. ...

  5. 【二叉搜索树】poj 1577 Falling Leaves

    http://poj.org/problem?id=1577 [题意] 有一颗二叉搜索树,每次操作都把二叉搜索树的叶子从左到右揪掉(露出来的父节点就变成了新的叶子结点) 先给出了揪掉的叶子序列(多个字 ...

  6. cf725F Family Photos

    Alice and Bonnie are sisters, but they don't like each other very much. So when some old family phot ...

  7. 洛谷 [P3224] 永无乡

    Treap 的合并 首先感谢 @Capella 的DeBug 其次,这是由一个 & 号引发的血案 注意对于所有修改操作都要 & Treap的合并, 启发式合并,对于每一个节点都 ins ...

  8. ADO:防止更新的数据含有单引号而出错

    原文发布时间为:2008-08-01 -- 来源于本人的百度文章 [由搬家工具导入] public void Update( string au_lname, string zip,string au ...

  9. Valentine's Day Round hdu 5176 The Experience of Love [好题 带权并查集 unsigned long long]

    传送门 The Experience of Love Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 65536/65536 K (Ja ...

  10. Python入门--4--分之和循环

    1.用ELIF比较省CPU: 第一种方法,使用if score = int(input('请输入你的分数:')) if (score <= 100) and (score >= 90): ...