简介

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题目1008:最短路径问题

    题目描述: 给你n个点,m条无向边,每条边都有长度d和花费p,给你起点s终点t,要求输出起点到终点的最短距离及其花费,如果最短距离有多条路线,则输出花费最少的. 输入:                 ...

  2. 九度oj 题目1035:找出直系亲属

    题目描述:     如果A,B是C的父母亲,则A,B是C的parent,C是A,B的child,如果A,B是C的(外)祖父,祖母,则A,B是C的grandparent,C是A,B的grandchild ...

  3. redis介绍和安装(一)

    Redis介绍:redis是一个key-value存储系统. 和Memcached类似,它支持存储的value类型相对更多,包括 string(字符串). list(链表).set(集合).zset( ...

  4. BZOJ 4004 [JLOI2015]装备购买 ——线性基

    [题目分析] 题目很简单,就是要维护一个实数域上的线性基. 仿照异或空间的线性基的方法,排序之后每次加入一个数即可. 卡精度,开long double 和 1e-6就轻松水过了. [代码] #incl ...

  5. [BZOJ4776] [Usaco2017 Open]Modern Art(差分 + 思维?)

    传送门 可以预处理出每种颜色的上下左右的位置,这样就框出来了一个个矩形,代表每种颜色分别涂了哪里. 然后用二维的差分. 就可以求出来每个位置至少涂了几次,如果 > 1 的话,就肯定不是先涂的, ...

  6. 刷题总结——过河(NOIP2015)

    题目: 题目背景 NOIP2005提高组试题2. 题目描述 在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧.在桥上有一些石子,青蛙很讨厌踩在这些石子上.由于桥的长度和青蛙一次跳过的距离都 ...

  7. P2014 选课 (树形动规)

    题目描述 在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习,在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习.现在有N门功课,每门课有个学分,每门课有一 ...

  8. Lumia 1020 诞生:诺基亚拍照技术的一次狂欢

    它在手机发展史上留下一长串坚实的脚印,拥趸遍及世界.它从巅峰滑落,但从未放弃向过去致敬的机会. 2002 年,作为世界上第一款内置摄像头拍照手机,诺基亚 7650 的横空出世将手机行业硬生生推上一个新 ...

  9. nosql整理

    Nosql: Redis,Memcache,MongoDB,Hbase,Couchbase  LevelDB https://www.cnblogs.com/lina520/p/7919551.htm ...

  10. 【转】UITableViewCell自适应高度 UILabel自适应高度和自动换行

    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {     ...