diamond简介和使用
簡介
diamond是淘寶內(nèi)部使用的一個管理持久配置的系統(tǒng),它的特點是簡單、可靠、易用,目前淘寶內(nèi)部絕大多數(shù)系統(tǒng)的配置,由diamond來進(jìn)行統(tǒng)一管理。
diamond為應(yīng)用系統(tǒng)提供了獲取配置的服務(wù),應(yīng)用不僅可以在啟動時從diamond獲取相關(guān)的配置,而且可以在運(yùn)行中對配置數(shù)據(jù)的變化進(jìn)行感知并獲取變化后的配置數(shù)據(jù)。
持久配置是指配置數(shù)據(jù)會持久化到磁盤和數(shù)據(jù)庫中。
diamond的特點是簡單、可靠、易用:
簡單:整體結(jié)構(gòu)非常簡單,從而減少了出錯的可能性。
可靠:應(yīng)用方在任何情況下都可以啟動,在承載淘寶核心系統(tǒng)并正常運(yùn)行一年多以來,沒有出現(xiàn)過任何重大故障。
易用:客戶端使用只需要兩行代碼,暴露的接口都非常簡單,易于理解。
1、作為一個配置中心,diamond的功能分為發(fā)布和訂閱兩部分。因為diamond存放的是持久數(shù)據(jù),這些數(shù)據(jù)的變化頻率不會很高,甚至很低,所以發(fā)布采用手工的形式,通過diamond后臺管理界面發(fā)布;訂閱是diamond的核心功能,訂閱通過diamond-client的API進(jìn)行。 2、diamond服務(wù)端采用mysql加本地文件的形式存放配置數(shù)據(jù)。發(fā)布數(shù)據(jù)時,數(shù)據(jù)先寫到mysql,再寫到本地文件;訂閱數(shù)據(jù)時,直接獲取本地文件,不查詢數(shù)據(jù)庫,這樣可以最大程度減少對數(shù)據(jù)庫的壓力。 3、diamond服務(wù)端是一個集群,集群中的每臺機(jī)器連接同一個mysql,集群之間的數(shù)據(jù)同步通過兩種方式進(jìn)行,一是每臺server定時去mysqldump數(shù)據(jù)到本地文件,二是某一臺server接收發(fā)布數(shù)據(jù)請求,在更新完mysql和本機(jī)的本地文件后,發(fā)送一個HTTP請求(通知)到集群中的其他幾臺server,其他server收到通知,去mysql中將剛剛更新的數(shù)據(jù)dump到本地文件。 4、每一臺server前端都有一個nginx,用來做流量控制。 5、圖中沒有將地址服務(wù)器畫出,地址服務(wù)器是一臺有域名的機(jī)器,上面運(yùn)行有一個HTTPserver,其中有一個靜態(tài)文件,存放著diamond服務(wù)器的地址列表。客戶端啟動時,根據(jù)自身的域名綁定,連接到地址服務(wù)器,取回diamond服務(wù)器的地址列表,從中隨機(jī)選擇一臺diamond服務(wù)器進(jìn)行連接。 可以看到,整個diamond的架構(gòu)非常簡單,使用的都是最常用的一些技術(shù)以及產(chǎn)品,它之所以表現(xiàn)得非常穩(wěn)定,跟其架構(gòu)簡單是分不開的,當(dāng)然,穩(wěn)定的另一個主要原因是它具備一套比較完善的容災(zāi)機(jī)制,容災(zāi)機(jī)制將在下一篇文章中講述。
源碼地址
https://github.com/takeseem/diamond.git
服務(wù)端安裝
檢出源碼,修改配置文件 jdbc.properties 中的數(shù)據(jù)庫連接信息,完成之后maven打包
數(shù)據(jù)庫執(zhí)行初始化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 ;
將打好的包diamond-server.war 放到tomcat工作目錄,啟動。啟動成功之后,訪問 http://localhost:8090/diamond-server/
發(fā)布數(shù)據(jù),賬號密碼是user.properties中配置的,默認(rèn)是 abc=123。登錄后進(jìn)入后臺管理界面,然后點擊“配置信息管理”—— “添加配置信息”,在輸入框中輸入dataId、group、內(nèi)容,最后點擊“提交”即可。
成功后,可以在“配置信息管理”中查詢到發(fā)布的數(shù)據(jù)。
集群安裝。修改node.properties,格式為 ip:port ,這里面的冒號,一定要通過轉(zhuǎn)義一下,要不然獲取地址不對。當(dāng)存在node節(jié)點的配置,發(fā)布修改數(shù)據(jù)后會通知其他節(jié)點更新。
每臺diamond-server 前建議增加nginx轉(zhuǎn)發(fā),方便限流,而且客戶端默認(rèn)請求80端口
其他配置: system.properties中的dump_config_interval 是多久去更新一次本地緩存的數(shù)據(jù) 默認(rèn)是 600秒
客戶端安裝
客戶端獲取數(shù)據(jù)方法:
DiamondManager manager = new DefaultDiamondManager(group, dataId, new ManagerListener() {
public Executor getExecutor() {
return null;
}
public void receiveConfigInfo(String configInfo) {
// 客戶端處理數(shù)據(jù)的邏輯
}
});
集成思路:重寫PropertyPlaceholderConfigurer,將diamond管理的配置交個spring,spring的xml可以直接使用${}來查詢數(shù)據(jù),增加工具類PropertiesUtils.java 方便查詢diamond管理的數(shù)據(jù)。具體代碼
<!-- 引入依賴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要連接的環(huán)境:" + 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;
}
/**
* 根據(jù)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文件內(nèi)容失敗:" + 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) {
//數(shù)據(jù)發(fā)生變更時,更新數(shù)據(jù)
putAndUpdateProperties(configInfo);
}
}, diamondIpList);
String configInfo = manager.getAvailableConfigureInfomation(TIME_OUT);
logger.debug("從diamond取到的數(shù)據(jù)是:" + configInfo);
putAndUpdateProperties(configInfo);
} else {
logger.error("diamond數(shù)據(jù)配置properties異常: DataId:" + dataId + ",Group:" + groupId);
}
}
} else {
logger.error("diamond數(shù)據(jù)配置properties異常: diamondBeanList is null or diamondIpList is null");
}
}
/**
* 更新properties中數(shù)據(jù)*/
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("根據(jù)diamond數(shù)據(jù)流轉(zhuǎn)成properties異常" + e.getMessage(), e);
}
} else {
logger.error("從diamond取出的數(shù)據(jù)為空,請檢查配置");
}
}
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>
容災(zāi)機(jī)制
是diamond具有一套完備的容災(zāi)機(jī)制,容災(zāi)機(jī)制涉及到client和server兩部分,主要包括以下幾個方面:
1、server存儲數(shù)據(jù)的方式。
server存儲數(shù)據(jù)是“數(shù)據(jù)庫+本地文件”的方式,集群間的數(shù)據(jù)同步我們在之前的文章中講過(請參考專題二的原理部分),client訂閱數(shù)據(jù)時,訪問的是本地文件,不查詢數(shù)據(jù)庫,這樣即使數(shù)據(jù)庫出問題了,仍然不影響client的訂閱。
2、server是一個集群。
這是一個基本的容災(zāi)機(jī)制,集群中的一臺server不可用了,client發(fā)現(xiàn)后可以自動切換到其他server上進(jìn)行訪問,自動切換在client內(nèi)部實現(xiàn)。
3、client保存snapshot
client每次從server獲取到數(shù)據(jù)后,都會將數(shù)據(jù)保存在本地文件系統(tǒng),diamond稱之為snapshot,即數(shù)據(jù)快照。當(dāng)client下次啟動發(fā)現(xiàn)在超時時間內(nèi)所有server均不可用(可能是網(wǎng)絡(luò)故障),它會使用snapshot中的數(shù)據(jù)快照進(jìn)行啟動。
4、client校驗MD5
client每次從server獲取到數(shù)據(jù)后,都會進(jìn)行MD5校驗(數(shù)據(jù)保存在responsebody,MD5保存在responseheader),以防止因網(wǎng)絡(luò)故障造成的數(shù)據(jù)不完整,MD5校驗不通過直接拋出異常。
5、client與server分離
client可以和server完全分離,單獨使用,diamond定義了一個“容災(zāi)目錄”的概念,client在啟動時會創(chuàng)建這個目錄,每次主動獲取數(shù)據(jù)(即調(diào)用getAvailableConfigInfomation()方法),都會優(yōu)先從“容災(zāi)目錄”獲取數(shù)據(jù),如果client按照一個固定的規(guī)則,在“容災(zāi)目錄”下配置了需要的數(shù)據(jù),那么client直接獲取到數(shù)據(jù)返回,不再通過網(wǎng)絡(luò)從diamond-server獲取數(shù)據(jù)。同樣的,在每次輪詢時,都會優(yōu)先輪詢“容災(zāi)目錄”,如果發(fā)現(xiàn)配置還存在于其中,則不再向server發(fā)出輪詢請求。以上的情形,會持續(xù)到“容災(zāi)目錄”的配置數(shù)據(jù)被刪除為止。
根據(jù)以上的容災(zāi)機(jī)制,我們可以總結(jié)一下diamond整個系統(tǒng)完全不可用的條件:
1、數(shù)據(jù)庫不可用。
2、所有server均不可用。
3、client主動刪除了snapshot
4、client沒有備份配置數(shù)據(jù),導(dǎo)致其不能配置“容災(zāi)目錄”。
同時滿足以上4個條件的概率,在生產(chǎn)環(huán)境中是極小的。
以上就是diamond的容災(zāi)機(jī)制
其他相關(guān)
Xdiamond
1、基于數(shù)據(jù)庫做配置存儲
2、相對于diamond增加了權(quán)限設(shè)計,結(jié)合Secret key,保證配置的安全
3、配置緩存在本地,防止應(yīng)用因為網(wǎng)絡(luò)問題而不能啟動
disconf是來自百度的分布式配置管理平臺,包括百度、滴滴出行、銀聯(lián)、網(wǎng)易、拉勾網(wǎng)、蘇寧易購、順豐科技 等知名互聯(lián)網(wǎng)公司正在使用! https://github.com/knightliao/disconf
總結(jié)
以上是生活随笔為你收集整理的diamond简介和使用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: modelsim将vcd文件转换成wlf
- 下一篇: ARM PSCI在ATF和Linux k