配置持久化框架diamond简介及高阶应用

配置持久化框架diamond简介及高阶应用

前言

一、简介

二、特点

三、应用场景

四、server搭建

五、发布配置数据

六、简单应用

七、高阶应用

八、spring动态集成diamond

结束语

 

 

前言

 

配置持久化框架diamond 为淘宝内部使用的一个管理持久配置的系统,本次分享除介绍diamond的简单应用外,还将介绍更高级的应用场景。

 

一、简介

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

diamond的源代码:http://code.taobao.org/svn/diamond/trunk

 

二、特点

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

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

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

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

 

三、应用场景

应用场景:

1.分表分库的DB服务器地址.

2.中间件的服务地址.

3.经常变化的开关,配置.

 

四、server搭建

(1)mysql

安装mysql-server的步骤请参考mysql官方文档,安装完毕后,建立数据库,然后建立两张表,建表语句分别如下:

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 ’ ’,

`src_ip` varchar(20) default NULL,

`src_user` varchar(20) default NULL,

`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`)

);

create table group_info (

`id` bigint(64) unsigned NOT NULL auto_increment,

`address` varchar(70) NOT NULL default ’ ’,

`data_id` varchar(255) NOT NULL default ’ ’,

`group_id` varchar(128) NOT NULL default ’ ’,

`src_ip` varchar(20) default NULL,

`src_user` varchar(20) default NULL,

`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_group_address` (`address`,`data_id`,`group_id`)

);

建表完成后,请将数据库的配置信息添加到diamond-server工程的src/resources/jdbc.properties文件中。

(2)tomcat

tomcat是diamond server的运行容器。

tomcat的安装请参考tomcat官方文档,建议使用tomcat7

不需要对tomcat进行任何改动。

(3)diamond server

在diamond-server源代码根目录下,执行mvn clean package -Dmaven.test.skip,成功后会在diamond-server/target目录下生成diamond-server.war(如果没有安装maven,请参考maven官方文档进行安装)。

打包完成后,将diamond-server.war放在tomcat的webapps目录下。

(4)http server

http server用来存放diamond server等地址列表,可以选用任何http server,这里以tomcat为例。

安装tomcat的步骤请参开tomcat官方文档,注意,如果http server和diamond server安装在一台机器上,请修改http server的端口,避免冲突。

修改完端口后,请将diamond-utils工程下的com.taobao.diamond.common.Constants类中的DEFAULT_PORT常量修改成对应的端口号。

安装完成后,请在tomcat的webapps下建立文件夹diamond-server和pushit-server,diamond-server中再建立diamond文件,文件内容是diamond-server的地址列表,一行一个地址,地址为ip;pushit-server中也建立diamond文件,文件内容是pushit-server的地址列表,一行一个地址,地址为ip:port(pushit后文会进行叙述)

(5)pushit

pushit是一个轻量级的消息通知服务组件,用来为diamond做实时通知服务,通知客户端数据的变化,它也是CS的结构,服务端搭建步骤如下:

在pushit源代码根目录下,执行mvn clean package assembly:assembly -Dmaven.test.skip命令,成功后会在pushit/target目录中看到pushit-pushit.tar.gz包。

执行tar -xzvf  pushit-pushit.tar.gz,进行解压。

进入pushit目录,建立logs目录,在logs目录中建立pushit.log文件。

进入pushit/bin目录,执行./pushit-startup.sh ../conf/server.properties命令,启动pushit-server

(6)redis

redis用来存放一些跟统计相关的信息。

redis的安装请参考redis的官方文档。

安装完成后,请在diamond-server的配置文件redis.properties中填写对应的信息。

完成以上6步后,server端的搭建就完成了。

 

五、发布配置数据

diamond发布数据通过手工的方式进行。

修改diamond-server的配置文件user.properties,以k=v的方式添加登录diamond-server的用户名和密码。

在浏览器中输入http://ip:port/diamond-server/,ip和port为server搭建的第(2)步中的地址和端口,登录后进入后台管理界面,然后点击“配置信息管理”—— “添加配置信息”,在输入框中输入dataId、group、内容,最后点击“提交”即可。

成功后,可以在“配置信息管理”中查询到发布的数据。

 

 

六、简单应用

diamond客户端API主要提供了订阅数据的功能.

(1)客户端获取服务端地址

获取服务端地址对客户端是透明的,客户端仅仅需要在本地进行如下域名绑定即可:

domain  ip

其中,domain的值与diamond-utils工程下的com.taobao.diamond.common.Constants类中的DEFAULT_DOMAINNAME和DAILY_DOMAINNAME的值相同,ip为server搭建第(4)步中的http server地址。

(2)创建订阅者

DiamondManager manager = new DefaultDiamondManager(group, dataId, new ManagerListener() {

public void receiveConfigInfo(String configInfo) {

// TODO:客户端处理数据的逻辑

}

});

参数的说明:

group和dataId为String类型,二者结合为diamond-server端保存数据的惟一key

ManagerListener 是客户端注册的数据监听器, 它的作用是在运行中接受变化的配置数据,然后回调receiveConfigInfo()方法,执行客户端处理数据的逻辑。如果要在运行中对变化的配置数据进行处理,就一定要注册ManagerListener

(3)获取配置数据

String configInfo = manager.getAvailableConfigInfomation(timeout);

diamond-server端保存的配置全都为文本类型,返回给客户端的配置数据为java.lang.String类型,timeout为从网络获取配置数据的超时时间。客户端调用每次调用该方法,都能够保证获取一份最新的可用的配置数据。

 

七、高阶应用

 通过 AbstractDynamicConfig 实例化时注册 RemoteConfigManagerListener 来绑定diamond监听,来实现实例对象通过感知远程数据配置变更而动态变更自身的目的.

核心代码: 

DynamicConfig

import org.oschina.config.dynamic.source.ConfigSource;

/**
 * 动态配置组件
 *
 */
public interface DynamicConfig extends IProperties{
	/**
	 * 分组
	 * @return
	 */
	public String getGroup();
	/**
	 * 配置名称(默认取类全名)
	 * @return
	 */
	public String getName();
	/**
	 * 配置文件properties路径
	 * @return
	 */
	public String getConfigPath();
	
	/**
	 * 加载配置文件列表
	 * @return
	 */
	public ConfigSource[] getSources();
	
	/**
	 * 初始化 配置(由实例外面来调用 , 或由容器实例化对象后调用)
	 * @return
	 */
	public boolean init();
	
	/**
	 * 退出系统之前的所做的操作(由容器来调用)
	 */
	public void destroy();
	
	/**
	 * 根据 文本内容 初始化配置组件属性
	 * @param content
	 * @return
	 */
	public boolean dynamicChangeConfig(String content);
	/**
	 * 动态变更配置组件属性值
	 * @param field
	 * @param value
	 * @return
	 */
	public boolean dynamicChangeConfig(String field ,String value);
}

  AbstractDynamicConfig


import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.oschina.config.dynamic.enums.MethodTypeEnum;
import org.oschina.config.dynamic.listener.RemoteConfigManagerListener;
import org.oschina.config.dynamic.source.ConfigSource;
import org.oschina.config.dynamic.source.SourceTypeEnum;
import org.oschina.config.dynamic.utils.DynamicConfigUtils;
import org.oschina.config.dynamic.utils.IOUtils;

/**
 * 动态配置组件(实现了基本的功能方法)
 * 
  • initConfig(sources); -- 由外部来调用
  • *
  • setRemoteConfigManagerListener(remoteConfigManagerListener); -- 由外部来调用
  • * */ public abstract class AbstractDynamicConfig implements DynamicConfig { protected Log logger = LogFactory.getLog(this.getClass()); protected Set supportedSourceTypes;//支持的资源类型(默认为 properties) protected ConfigSource[] sources;//资源列表 protected Properties resultProp = new Properties(); protected boolean initFlag = false;//初始化标识 protected Map initParams = null;//初始化时参数配置 protected String defaultGroup = "defaultGroup"; protected RemoteConfigManagerListener remoteConfigManagerListener;//监听器 public RemoteConfigManagerListener getRemoteConfigManagerListener() { return remoteConfigManagerListener; } public void setRemoteConfigManagerListener(RemoteConfigManagerListener remoteConfigManagerListener) { logger.info("setRemoteConfigManagerListener(" + remoteConfigManagerListener + ")..."); if(!initFlag){ logger.warn("setRemoteConfigManagerListener(" + remoteConfigManagerListener + ")...init()"); this.init(); } if(null != this.remoteConfigManagerListener){ this.remoteConfigManagerListener.unregister(this,getInitParams()); logger.info("[" + this.remoteConfigManagerListener + "] unregister(" + this + ")"); } //重设监听器 this.remoteConfigManagerListener = remoteConfigManagerListener; if(null != this.remoteConfigManagerListener){ this.remoteConfigManagerListener.register(this,getInitParams()); logger.info("[" + this.remoteConfigManagerListener + "] register(" + this + ")"); } } /** * 支持的资源类型(默认为 properties) * @return */ public Set getSupportedSourceTypes() { if(null == supportedSourceTypes){ supportedSourceTypes = new HashSet (); } if(supportedSourceTypes.isEmpty()){ supportedSourceTypes.add(SourceTypeEnum.properties); } return supportedSourceTypes; } public void setSupportedSourceTypes(Set supportedSourceTypes) { this.supportedSourceTypes = supportedSourceTypes; } public Map getInitParams() { return initParams; } public void setInitParams(Map initParams) { this.initParams = initParams; } /** * 构造(子类必须隐式或显示调用) */ public AbstractDynamicConfig(){ super(); if(logger.isDebugEnabled()){ logger.debug("AbstractDynamicConfig[" + this.getConfigPath() + "] instanced."); } } /* * (non-Javadoc) */ public boolean init() { boolean flag = false; if(initFlag){ logger.warn("initConfig repeat -- sources=[" + sources + "]"); return false; } ConfigSource[] sources = getSources(); if(null != sources){ for(ConfigSource source : sources){ try { Properties localProp = new Properties(); localProp.load(IOUtils.getInputStream(source.getPath())); updateProperties(localProp); } catch (Exception e) { logger.error("dynamicChangeConfig loadProperties and updateProperties["+ e.toString() + "]"); } } } logger.info("initConfig[" + flag + "] -- sources=[" + sources + "]"); initFlag = true; return flag; } public void updateProperties(Properties prop){ Enumeration propEnum=prop.keys(); while(propEnum.hasMoreElements()){ String key=propEnum.nextElement().toString(); String value = prop.get(key)!=null?prop.getProperty(key).toString():""; boolean flag = dynamicChangeConfig(key,value); if(flag){ this.resultProp.put(key, value); } } } /* * (non-Javadoc) */ public ConfigSource[] getSources() { if(null != sources){ return sources; } if(StringUtils.isBlank(this.getConfigPath())){ sources = new ConfigSource[]{}; return sources; } SourceTypeEnum sourceTypeEnum=this.getSupportedSourceTypes().iterator().next(); String sourcePath = this.getConfigPath() + "." + sourceTypeEnum; List sourceList = new ArrayList(); URLClassLoader uc=((URLClassLoader)Thread.currentThread().getContextClassLoader()); Enumeration cfgfileUrl; try { cfgfileUrl = uc.findResources(sourcePath); while(cfgfileUrl.hasMoreElements()){ ConfigSource cs=new ConfigSource(); cs.setPath(cfgfileUrl.nextElement().toString());//可用sping的相关类清理此路径 cs.setBaseSource(true); cs.setSourceType(sourceTypeEnum); sourceList.add(cs); } } catch (IOException e) { logger.error("dynamicChangeConfig findResources["+ e.toString() + "]"); } sources = sourceList.toArray(new ConfigSource[]{}); return sources; } /* * (non-Javadoc) */ public String getConfigPath() { String path = this.getClass().getName(); if(null != path){ path = path.replace(".", "/"); } if(logger.isTraceEnabled()){ logger.trace("getConfigPath -- " + path); } return path; } /* * (non-Javadoc) */ public boolean dynamicChangeConfig(String field, String value) { boolean flag = true; if(StringUtils.isBlank(field)){ return false; } field = field.trim(); Method getMethod = null; Method setMethod = null; try { getMethod = DynamicConfigUtils.getInnerMethod(MethodTypeEnum.GET,field,this.getClass()); setMethod = DynamicConfigUtils.getInnerMethod(MethodTypeEnum.SET,field,this.getClass()); } catch (Exception e) { logger.error("dynamicChangeConfig getMethod and setMethod["+ e.toString() + "] -- field=[" + field + "],value=[" + value + "]"); } if(getMethod == null){ logger.info("BasicComponentProp getMethod is null , key = " + field + " , value = " + value + " , component = " + this.getClass().getName()); return false; } Class rtype = getMethod.getReturnType(); try { if(String.class.isAssignableFrom(rtype)) { setMethod.invoke(this, new Object[] {value}); } else if(Map.class.isAssignableFrom(rtype)) { Map map = DynamicConfigUtils.analyzeString2Map(rtype, getMethod.getGenericReturnType(), value); setMethod.invoke(this, new Object[] {map}); } else if(rtype.isArray()) { Object object = DynamicConfigUtils.analyzeString2Array(getMethod.getReturnType().getComponentType(), value); setMethod.invoke(this, new Object[] {object}); } else if(Collection.class.isAssignableFrom(rtype)) { Collection collection = DynamicConfigUtils.analyzeString2Collection(getMethod, value); setMethod.invoke(this, new Object[] {collection}); } else { Object object = DynamicConfigUtils.transBasicValue(rtype, value); if(null != object) { setMethod.invoke(this, new Object[] {object}); } else { logger.info("Abstract setmethod failure , field = " + field + " , value = " + value + " , bean = " + this.getClass().getName()); } } } catch (Exception e) { flag = false; logger.error("dynamicChangeConfig change and func["+ e.toString() + "] -- field=[" + field + "],value=[" + value + "]"); } logger.info("dynamicChangeConfig[" + flag + "] -- field=[" + field + "],value=[" + value + "]"); return flag; } /* * (non-Javadoc) */ public boolean dynamicChangeConfig(String configContent) { boolean flag = false; if(StringUtils.isBlank(configContent)){ return flag; } Properties dynProp; try { dynProp = new Properties(); dynProp.load(new StringReader(configContent)); updateProperties(dynProp); } catch (IOException e) { logger.error("dynamicChangeConfig loadProperties and updateProperties["+ e.toString() + "] -- configContent=[" + configContent + "]"); } logger.info("dynamicChangeConfig[" + flag + "] -- configContent=[" + configContent + "]"); return flag; } /* * (non-Javadoc) */ public void destroy(){ // TODO Auto-generated method stub logger.info("destroy over."); } /* * (non-Javadoc) */ public String getName() { return getClass().getName(); } public String getGroup() { return defaultGroup; } public Properties toProperties() { return this.resultProp; } }

    RemoteConfigManagerListener
    
    import java.util.Map;
    
    import org.oschina.config.dynamic.DynamicConfig;
    
    
    /**
     * 远程配置变更监听器
     *
     */
    public interface RemoteConfigManagerListener {
    	/**
    	 * 将 config 注册到监听器
    	 * @param config 
    	 * @param extraParams 可为空
    	 * @return
    	 */
    	public boolean register(DynamicConfig config ,Map
                     
                     
                      
                       extraParams);
    	
    	/**
    	 * 将 config 从监听器中注销
    	 * @param config
    	 * @param extraParams 可为空
    	 * @return
    	 */
    	public boolean unregister(DynamicConfig config ,Map
                      
                      
                       
                        extraParams);
    	
    	/**
    	 * 退出系统之前的所做的操作(由容器来调用)
    	 */
    	public void destroy();
    }
    
                      
                      
                     
                     


    八、spring动态集成diamond

    核心代码: 
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.Properties;
    import java.util.Set;
    
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.springframework.core.io.Resource;
    
    /**
     * 扩展 spring PropertyPlaceholderConfigurer
     * 
    • 支持 Set<IProperties> customProperties
    • * */ public class CustomPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer { private Log log = LogFactory.getLog(this.getClass()); private static final String XML_FILE_EXTENSION = ".xml"; private Set customProperties; public Set getCustomProperties() { return customProperties; } public void setCustomProperties(Set extendsProperties) { this.customProperties = extendsProperties; this.mergerConfig(extendsProperties); } protected void mergerConfig(Set extendsProperties) { if (null != extendsProperties && !extendsProperties.isEmpty()) { Properties result = new Properties(); try { for (IProperties p : extendsProperties) { fillProperties(result, p); } this.setProperties(result); if(log.isInfoEnabled()){ log.info("CustomPropertyPlaceholderConfigurer: mergerConfig(" + customProperties + ") OK!"); } } catch (IOException e) { log.error("CustomPropertyPlaceholderConfigurer: mergerConfig(" + customProperties + ") error: " + e.getMessage() , e); } } } private void fillProperties(Properties props, IProperties p) throws IOException { try { props.putAll(p.toProperties()); } catch(Exception e) { log.error("CustomPropertyPlaceholderConfigurer: fillProperties(" + p + ") error: " + e.getMessage() , e); } } private void fillProperties(Properties props, Resource resource) { InputStream is = null; try { is = resource.getInputStream(); String filename = resource.getFilename(); if ((filename != null) && (filename.endsWith(XML_FILE_EXTENSION))) props.loadFromXML(is); else props.load(is); } catch(Exception e) { log.error("CustomPropertyPlaceholderConfigurer: fillProperties(" + resource + ") error: " + e.getMessage() , e); } finally { if(null != is){ try { is.close(); } catch (IOException e) { e.printStackTrace(); } } } } }

    xml配置示例: 
    
                    
                    
    
                    
                    
    
        
                     
                     
    	
                     
                     
    		
                      
                      
    	
                     
                     
    	
    	
                     
                     
    	
                     
                     
    	
                     
                     
    	
    	
                     
                     
    	
                     
                     
    		
                      
                      
    		
                      
                      
    			
                       
                       
                    
                        
                        
                    
                        
                        
    			
                       
                       
    		
                      
                      
    	
                     
                     
    	
    
                    
                    

     

    结束语

    钻石恒久远 一颗永流传

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值