spring-master-slave-commondao

阅读更多
互联网的web项目,都有个特点:请求的并发量高,其中请求最耗时的db操作,又是系统优化的重中之重。

为此,往往搭建 db的 一主多从库的 数据库架构。作为web的DAO层,要保证针对主库进行写操作,对多个从库进行读操作。当然在一些请求中,为了避免主从复制的延迟导致的数据不一致性,部分的读操作也要到主库上。(这种需求一般通过业务垂直分开,比如下单业务的代码所部署的机器,读去应该也要从主库读取数据,这块需要定制化)

结合自己在实际项目中的使用的,我分享下一个简单的DAO进行一主多从库的读(取模型负载均衡)写操作的案例:

1、实际项目中的db架构(当然这块的配置交给dba即可)
master库:
server1:common库 以及多个customer库
server2:多个customer库

slave库:
server1机器上的库对于的从库在server3,server4,server5
server2机器上的库对于的从库在server6,server7,server8

2、关于数据库连接和数据源(Connection和DataSource)
Connection :是和服务器上的一个mysql实例的连接,可以指定库,也可以不指定。 如果不指定库,那么在db操作之前,需要先 执行:use dbname; 或者在在表的名字之前加上dbName.dbTable。
DataSource:是一个数据源,允许其实现进行数据库连接的管理和复用,考虑db连接的建立是一个相当耗时的过程,数据源能提升非常大的性能。
确定一个数据源只需要:ip+port,务必清楚这一点。

3、dao的分析和设计。
功能要求:支持一主多从,灵活配置,支持多种数据源提供商
实现思路:根据dbname->ip+port->DataSource->Connection
其中:
主库:dbname->ip+port
从库为:dbname->List,然后负载均衡选择其中一个ip+port

数据源配置的代码:
package com.job.db.dataservice.datasource;

import java.util.List;

import javax.sql.DataSource;

/**
 * 一主多从的配置包装类
 * @author [email protected]
 */
public class MasterSlaveDataSourceMapping {
	
	private List list;
	
	public List getList() {
		return list;
	}

	public void setList(List list) {
		this.list = list;
	}

	/** 主从库配置项
	 */
	public static class MasterSlaveDataSourceMappingItem{
		/** 主库数据源*/
		private DataSource master;
		/** 从库数据源列表*/
		private List slaveList;
		public DataSource getMaster() {
			return master;
		}
		public void setMaster(DataSource master) {
			this.master = master;
		}
		public List getSlaveList() {
			return slaveList;
		}
		public void setSlaveList(List slaveList) {
			this.slaveList = slaveList;
		}
		
	}
}


4、数据源初始化的代码。
package com.job.db.dataservice.datasource;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.sql.DataSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.job.db.dataservice.datasource.MasterSlaveDataSourceMapping.MasterSlaveDataSourceMappingItem;

/**
 * 数据源上下文
 * @author [email protected]
 * @date 2014-7-14下午5:02:14
 */
public abstract class DataSourceContext {
	private static final Logger logger = LoggerFactory.getLogger(DataSourceContext.class);
    /**
     * 主库数据源     (ip+port->IDataSourceProvider)*/
    protected  static final Map masterMap = new HashMap();
    /** 主库数据库名称到ip+port的映射 (dbNmae->ip+port)*/
    protected  static final Map masterDb2IpPortMapping = new HashMap();
    
    /** 从库数据源  (ip+port->IDataSourceProvider)*/
    protected  static  final Map slaveMap = new HashMap();
    /** 从库数据库名称到ip+port的映射*/
    protected  static final Map> slaveDb2IpPortListMapping = new HashMap>();
    
    /** 主从数据源列表配置*/
    private MasterSlaveDataSourceMapping masterSlaveDataSourceMapping;
    
    /**
     * 初始化资源
     */
    public void init(){
    	logger.info("DataSourceContext init begin");
    	List masterSlaveList = masterSlaveDataSourceMapping.getList();
    	for(MasterSlaveDataSourceMappingItem item : masterSlaveList){
    		DataSource masterConfigList = item.getMaster();
    		masterInit(masterConfigList);
    		
    		List  slaveConfigList = item.getSlaveList();
    		slaveInit(slaveConfigList);
    	}
    	logger.info("DataSourceContext init end");
    }
    
    /** 初始化主库资源*/
    private final void masterInit(DataSource dataSource) {
    	doMasterInit(dataSource);
    }
    
    /** 初始化从库资源*/
    private final void slaveInit(List dataSourceList) {
    	for(DataSource dataSource : dataSourceList){
    		doSlaveInit(dataSource);
    	}
    }
    
    protected abstract void doMasterInit(DataSource dataSource);
    
    protected abstract void doSlaveInit(DataSource dataSource);
    
    /**
     * 关闭资源
     */
    public void shutDown() {
    	logger.info("DataSourceContext shutDown begin ");
        for (IDataSourceProvider provider : masterMap.values()) {
            provider.shutdown();
        }
        Collection listCollection = slaveMap.values();
        for (IDataSourceProvider provider: listCollection) {
                provider.shutdown(); 
        }
        logger.info("DataSourceContext shutDown end ");
    }

    /** 根据dbname获取主库数据源*/
    public static IDataSourceProvider getMasterDataSourceProvider(String dbName){
    	String ipPortKey = masterDb2IpPortMapping.get(dbName);
    	if(ipPortKey == null){
    		return null;
    	} else {
    		return masterMap.get(ipPortKey);
    	}
    }
    
    /** 根据数据库的名字获取多个从库数据源列表*/
    public static  List getSlaveDataSourceProvider(String dbName){
    	List ipPortKeyList = slaveDb2IpPortListMapping.get(dbName);
    	if(ipPortKeyList == null){
    		return null;
    	} else {
    		List retList = new ArrayList();
    		for(String item : ipPortKeyList){
    			IDataSourceProvider provider = slaveMap.get(item);
    			retList.add(provider);
    		}
    		return retList;
    	}
    }

	public void setMasterSlaveDataSourceMapping(
			MasterSlaveDataSourceMapping masterSlaveDataSourceMapping) {
		this.masterSlaveDataSourceMapping = masterSlaveDataSourceMapping;
	}
    
}


数据初始化的一个具体实现:
package com.job.db.dataservice.datasource.impl;

import java.util.ArrayList;

import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSource;
import org.apache.commons.lang.StringUtils;

import com.job.db.dataservice.datasource.DataSourceContext;

/**
 *  简单实现(TODO BasicDataSource 很粗糙的哈)
 * @author wangxinchun
 */
public class DefaultDataSourceContext extends DataSourceContext {

	protected void doMasterInit(DataSource dataSource) {
		URLInfo urlInfo = getDataSourceURLInfo(dataSource);
		String key = urlInfo.getDataSourceKey();
		if(StringUtils.isNotEmpty(urlInfo.getDb())){
			masterDb2IpPortMapping.put(urlInfo.getDb(), key);
		}
		if(masterMap.get(key) != null){
			return;
		}
		masterMap.put(key, new DefaultDatasourceProvider(dataSource));
	}

	protected void doSlaveInit(DataSource dataSource) {
		URLInfo urlInfo = getDataSourceURLInfo(dataSource);
		String key = urlInfo.getDataSourceKey();
		if(StringUtils.isNotEmpty(urlInfo.getDb())) {
			if(slaveDb2IpPortListMapping.get(urlInfo.getDb()) == null){
				slaveDb2IpPortListMapping.put(urlInfo.getDb(), new ArrayList());
			}
			slaveDb2IpPortListMapping.get(urlInfo.getDb()).add(key);
		}
		if(slaveMap.get(key) != null){
			return;
		}
		slaveMap.put(key, new DefaultDatasourceProvider(dataSource));
	}

	private URLInfo getDataSourceURLInfo(DataSource dataSource) {
		URLInfo urlInfo = new URLInfo();
		if (dataSource instanceof BasicDataSource) {
			// jdbc:mysql://192.168.229.37:3309/tts?useUnicode=true&characterEncoding=utf8
			BasicDataSource basicDataSource = (BasicDataSource) dataSource;
			String url = basicDataSource.getUrl();
			String temp = url.substring(url.indexOf("//")+2);
			if(temp.indexOf("?") >0 ){
				temp = temp.substring(0,temp.indexOf("?"));
			}
			String[] tempArr = temp.split(":|/");
			urlInfo.setIp(tempArr[0]);
			urlInfo.setPort(tempArr[1]);
			urlInfo.setDb(tempArr[2]);
		}
		return urlInfo;
	}
	
	 private class URLInfo{
		 private String ip;
		 private String port;
		 private String db;
		 
		public String getIp() {
			return ip;
		}
		public void setIp(String ip) {
			this.ip = ip;
		}
		public String getPort() {
			return port;
		}
		public void setPort(String port) {
			this.port = port;
		}
		public String getDb() {
			return db;
		}
		public void setDb(String db) {
			this.db = db;
		}
		public String getDataSourceKey(){
			return ip+":"+port;
		}
	 }
}

package com.job.db.dataservice.datasource.impl;

import javax.sql.DataSource;

import org.apache.log4j.Logger;

import com.job.db.dataservice.datasource.IDataSourceProvider;

/**
 * Proxool 连接池包装类
 * @author [email protected]
 * @date 2014-7-15下午2:14:08
 */
public class DefaultDatasourceProvider implements IDataSourceProvider {

    private static Logger log = Logger.getLogger(DefaultDatasourceProvider.class);

    private DataSource dataSource;

    
    public DefaultDatasourceProvider( DataSource dataSource){
    	this.dataSource = dataSource;
    }
    
    public DataSource getDataSource() {
        return dataSource;
    }

    public void shutdown() {
    	log.info("shutdown");
    }
    
}


5、spring 关于数据的配置。
 
		
		 
		 
		 
		  
		 
		 
	

 
    
		
		
		
	
	
	 
		
		
		
	
	
	
		
		
		
	

 
    
       
    
    
        
           
           
        
    
 
 
     
		
		
		
	
	
	 
		
		
		
	
	
	
		
		
		
	

 
    
       
    
    
        
           
           
        
    
 
    
 
      
		
		
		
	
	
	 
		
		
		
	
	
	
		
		
		
	

 
    
       
    
    
        
           
           
        
    
 
    
 
   
      
         
             
             
             
         
      
   
   

	
	   
	

6、数据源的使用。
package com.job.db.dataservice.connection;

import java.sql.Connection;

/**
 * 提供Connection连接
 * @author wangxinchun
 */
public interface ConnectionGetter {

	/**
	 * 根据dbName获取数据库连接
	 * @param dbName 数据库的名字
	 */
    public Connection getConnection(String dbName);

}

通过数据源获取数据库连接
package com.job.db.dataservice.connection.impl;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

import com.job.db.dao.AppDBContext;
import com.job.db.dataservice.connection.ConnectionGetter;
import com.job.db.dataservice.datasource.DataSourceContext;
import com.job.db.dataservice.datasource.IDataSourceProvider;
import com.job.db.dataservice.exception.DaoException;

/**
 * 提供根据dbName 获取读或写连接
 * @author [email protected]
 * @date 2014-7-15下午2:26:27
 */
public abstract class AbstractConnectionGetter implements ConnectionGetter {
	
	private static Integer concount =Integer.MIN_VALUE;
	
    public Connection getDefaultConnection(String dbName, boolean isWrite) throws SQLException {
        if (dbName == null) {
            throw new DaoException("get connection error cause dbName id null");
        }
		Integer id = AppDBContext.getLastReadWriteFlag();
		// 写操作直接获得connection ,如果上一次操作是写连接,那么优先返回写连接
		if ( isWrite || (id != null && id < 0)) {
			AppDBContext.setLastReadWriteFlag(-1);
            IDataSourceProvider provider = DataSourceContext.getMasterDataSourceProvider(dbName);
            if (provider == null) {
                throw new DaoException("no write database dbName is " + dbName);
            }
            return provider.getDataSource().getConnection();
        } else {
			// 读操作取模做balance 负载均衡
            List providers = DataSourceContext.getSlaveDataSourceProvider(dbName);
            if (providers == null || providers.size() == 0) {
                throw new DaoException("no slave database for clientId " + dbName);
            }
            int psize = providers.size();
			if (id == null) {
				id = Math.abs(concount++ % psize);
				AppDBContext.setLastReadWriteFlag(id);
			}
			IDataSourceProvider sdp = providers.get(id % psize);
            return sdp.getDataSource().getConnection();
        }
    }
}

  • master-slave-commondao-trunk.rar (1.7 MB)
  • 下载次数: 24

你可能感兴趣的:(spring,dao,master,slave,dataSource)