在许多大型项目中,通常有如下需求,业务扩展的时候,比如说人员管理,按各个地区进行分库处理。
打个比方:查询一个人的信息,输入身份证号(举例而已,代表唯一),北京有一套用户表,上海有一套一样的用户表,江西也有一套一样的用户表等等,多数据源实例。
需求:根据身份证号获取数据源信息,即我要查询哪个数据库信息才能查到。
新手思路:
遍历各个数据库嘛,这么简单的事。
但是有没有想过,挨个挨个去连接数据源,执行select * from userInfo等类似查询,100个地区数据库,你就要连接100次,运气差一点,可能最后一个查询才是你要的库。而且还要考虑连接数据库等耗时,消耗资源的事。
你的项目经理,马上给你回复:你明天不用来上班了哈哈。
并发实现
由于数据源较多,我们考虑采用多线程并发实现,让各个线程并发执行,而不是单一的一个线程等待结果,一旦哪个线程找到,马上停止其他线程搜索查询。
public static DbResource getUserRegionResByPmCode(String PM_CODE){
DbResource dbrs = null;
List<DbResource> lres = getAllRemoteDbResouce();
dbrs = new SearchResourceTask(lres).find(0, PM_CODE);
return dbrs;
}
PM_CODE:你可以理解为身份证编码
DbResource:为封装的数据源对象,
getAllRemoteDbResource()方法为获取所有数据源信息,一般项目扩大业务会存储各个地区的数据库连接配置信息。
然后把所有的数据库连接信息交给SearchResourceTask搜索任务。
package com.cloud.munici.zhdw.service.dao.systemcenter;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.cloud.munici.service.frame.util.ExceptionStackTrace;
import com.cloud.munici.zhdw.service.dbase.model.DbResource;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.jfinal.kit.HttpKit;
/**
* 多线程并发到不同地方数据库搜索匹配的数据
* 有线程有数据返回立马停止所有线程继续搜索
*
*/
public class SearchResourceTask {
private static final Logger logger = LoggerFactory.getLogger(SearchResourceTask.class);
private static Gson gson = new Gson();
private ExecutorService executor = null;
private Semaphore semaphore = null;
private Object lock = new Object();
// 默认并发线程数
private int thredNum = 3;
private BlockingQueue<DbResource> bq = null;
private boolean quit = false;
// 最终返回的数据源结果
private DbResource result = null;
/**
*
* @param lres 区域数据源列表
*/
public SearchResourceTask(List<DbResource>lres){
this.bq = new ArrayBlockingQueue<DbResource>(lres.size());
this.bq.addAll(lres);
// 根据多数据源的大小,更改默认并发线程数
if( this.thredNum < lres.size() ){
this.thredNum = lres.size();
}
this.executor = Executors.newCachedThreadPool();
this.semaphore = new Semaphore(this.thredNum);
}
/**
*
* @param type 数据类型
* @param param 要搜索的数据
* @return
*/
public DbResource find( final int type, final String param){
for (int i = 0; i < thredNum; i++) {
Runnable runnable = new Runnable() {
public void run() {
try {
while( true ){
// 执行前判断是否需要break,找到之后会给quit赋值true
if(SearchResourceTask.this.quit){break;}
semaphore.acquire();
if( bq.isEmpty() ){
// 队列空的时候,释放信号量
semaphore.release();
break;
}
// 从队列里取一个去搜索
DbResource dbrs = bq.poll();
String url = dbrs.getDB_SERVICE_URL();
String data = null;
// 查询过程,业务需求而已,在本例type为0
switch(type){
case 0:
data = String.format("{'api': 'PmManager','action': 'getPmBriefInfoByPmCode', 'region_source':{'REGION_CODE':'%s','DB_SERVICE_URL':'%s','DB_URL':'%s','UNAME':'%s','UPWD':'%s'}, 'ident_code':'%s','data':{'PM_CODE':'%s'}}", dbrs.getREGION_CODE(),dbrs.getDB_SERVICE_URL(),dbrs.getDB_URL(),dbrs.getUNAME(),dbrs.getUPWD(), param,param);
break;
case 1:
data = String.format("{'api': 'MgrUserAPI','action': 'getUserByUserId', 'region_source':{'REGION_CODE':'%s','DB_SERVICE_URL':'%s','DB_URL':'%s','UNAME':'%s','UPWD':'%s'}, 'ident_code':'%s','data':{'USER_ID':'%s'}}",dbrs.getREGION_CODE(),dbrs.getDB_SERVICE_URL(),dbrs.getDB_URL(),dbrs.getUNAME(),dbrs.getUPWD(), param,param);
break;
case 2:
data = String.format("{'api': 'uac','action': 'findAccount', 'region_source':{'REGION_CODE':'%s','DB_SERVICE_URL':'%s','DB_URL':'%s','UNAME':'%s','UPWD':'%s'}, 'ident_code':'%s', 'data':{'MOBILE_NO':'%s'}}",dbrs.getREGION_CODE(),dbrs.getDB_SERVICE_URL(),dbrs.getDB_URL(),dbrs.getUNAME(),dbrs.getUPWD(), param,param);
default:
break;
}
JsonObject jo = null;
try{
// 搜索远程区域数据 自己封装的方法,data为协议参数,使用的是Jfinal 框架,即发起一个网络post请求,实际可使用自己喜欢的网络框架
String r = HttpKit.post(url, data);
jo = gson.fromJson(r, JsonObject.class);
}catch(Exception e){
logger.error(dbrs.getREGION_NAME()+"的DBAS访问地址异常");
//logger.error(ExceptionStackTrace.getStackTrace(e));
}
// 判断是否搜索到符合条件的数据
if(jo != null && jo.has("code") && jo.get("code").getAsInt() == 0){
SearchResourceTask.this.result = dbrs;
SearchResourceTask.this.quit = true; // 通知所有线程退出
synchronized(lock){
SearchResourceTask.this.lock.notify(); // 搜索到符合条件的数据, 通知调用线程继续
}
semaphore.release();
break;
}else{
semaphore.release(); // 释放信号量,让线程可再次继续执行
}
}//end loop while
} catch (Exception e) {
logger.error(ExceptionStackTrace.getStackTrace(e));
}
}
};
executor.execute(runnable); // 执行线程
} // for end
executor.shutdown();
synchronized(lock){
try {
lock.wait(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return result;
}
}
获取到数据源信息后可以把身份证跟找到的正确数据源缓存到内存中,或写入到一张数据库表中,或redis缓存。
表字段内容大致是(身份证号->正确的数据源)等字段。
在查询的时候先查询有没有对应记录,没有才执行并发搜索任务,提高效率。
经过单元测试,觉得效率还好,觉得博文不错,记得浏览下本人店铺哦!!!
专业墙纸贴纸厨房用具装饰出售,本人网店经营,访问即是爱
博客对你有用记得访问下哦,增加下访问量,如有需要可以下单购买哦^_^。店铺地址https://item.taobao.com/item.htm?id=570637716145