简介

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. 【Luogu】P1967货车运输(最大生成森林+倍增LCA)

    题目链接 倍增LCA是个什么蛇皮原理啊,循环完了还得再往上跳一次才能到最近公共祖先 合着我昨天WA两次就是因为这个 建最大生成森林,因为图不一定是联通的,所以不一定是一棵树.这个地方用克鲁斯卡尔就好了 ...

  2. 洛谷P3758 - [TJOI2017]可乐

    Portal Description 给出一张\(n(n\leq30)\)个点\(m(m\leq100)\)条边的无向图.初始时有一个可乐机器人在点\(1\),这个机器人每秒会做出以下三种行为之一:原 ...

  3. bzoj3211 花神游历各国 线段树,势能分析

    [bzoj3211]花神游历各国 2014年3月17日2,7230 Description   Input   Output 每次x=1时,每行一个整数,表示这次旅行的开心度 Sample Input ...

  4. cf493E Vasya and Polynomial

    Vasya is studying in the last class of school and soon he will take exams. He decided to study polyn ...

  5. Codevs 1021 玛丽卡==洛谷 P1186

    时间限制: 2 s 空间限制: 128000 KB  题目等级 : 大师 Master  题目描述 Description 麦克找了个新女朋友,玛丽卡对他非常恼火并伺机报复. 因为她和他们不住在同一个 ...

  6. git多人协作--分支

    分支: 创建分支: git checkout -b 新分支 切换分支: git checkout 目标分支 删除分支: git branch -d 待删除分支 推送到远程分支: git checkou ...

  7. 【docker】启动docker连接数据库 出现FATAL: password authentucation failed for user "homestatead"问题

    docker可以成功启动,启动命令如下: docker run -d -p : -v `pwd`/pgdata:/var/lib/postgresql/data -e POSTGRES_USER=ho ...

  8. Spring MVC入门实例

    1.web.xml配置 <?xml version="1.0" encoding="UTF-8"? > <web-app xmlns:xsi= ...

  9. 步步为营(十六)搜索(二)BFS 广度优先搜索

    上一篇讲了DFS,那么与之相应的就是BFS.也就是 宽度优先遍历,又称广度优先搜索算法. 首先,让我们回顾一下什么是"深度": 更学术点的说法,能够看做"单位距离下,离起 ...

  10. evaluate-reverse-polish-notation——栈

    Evaluate the value of an arithmetic expression in Reverse Polish Notation. Valid operators are+,-,*, ...