测试软件环境:
1、16G windows7 x64 32core cpu 。
2、jdk 1.7 tomcat 6.x solr 4.8
数据库软件环境:
1、16G windows7 x64 32core cpu 。
2、Oracle 11g
一、Solr默认索引工具DIH。
使用Solr DIH索引数据,一千九百万数据,耗时45分钟左右,每秒钟6500条/s,合计39w条没分钟。
相关jvm最大堆内存为4G,solr index config使用默认参数。
Solr DIH 导入截图:
二、Solrj API 索引数据。
使用Solrj api效率稍差,合计30w每秒,耗时一个多小时。
Solr Server配置参数同上。在客户端机器上,读取数据库数据,使用Solrj api进行索引。代码如下:
import java.io.IOException; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.UUID; import org.apache.solr.client.solrj.SolrServer; import org.apache.solr.client.solrj.SolrServerException; import org.apache.solr.client.solrj.impl.HttpSolrServer; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.util.StringUtils; import com.tianditu.search.v2.POI; public class ImportPOI implements IJobDef{ private SolrServer server; private DatasourceConfig jdbcConfig; private SolrConfig solrConfig; private POIImportConfig poiConfig; public DatasourceConfig getJdbcConfig() { return jdbcConfig; } public void setJdbcConfig(DatasourceConfig jdbcConfig) { this.jdbcConfig = jdbcConfig; } public SolrConfig getSolrConfig() { return solrConfig; } public void setSolrConfig(SolrConfig solrConfig) { this.solrConfig = solrConfig; } public POIImportConfig getPoiConfig() { return poiConfig; } public void setPoiConfig(POIImportConfig poiConfig) { this.poiConfig = poiConfig; } /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub ApplicationContext context = new ClassPathXmlApplicationContext("app-spring.xml"); ImportPOI importTool = (ImportPOI) context.getBean("importPOITool"); importTool.submit(new JobDoneCallBack() { public void onCallback(JobStatus status) { // TODO Auto-generated method stub System.out.println(status.getStatus()); System.out.println(status.getMessage()); } },new JobTimer() { public void onTimeUpdate(long timeCost) { // TODO Auto-generated method stub System.out.println("solr提交一次,距任务开始已耗时:"+timeCost/(1000*60)+"分钟"); } }); } public SolrServer getServer() { return server; } public void setServer(SolrServer server) { this.server = server; } public boolean importPOI(HashMap<String, Object> params){ return false; } private POI getPOI(ResultSet rs) throws SQLException{ POI poi = new POI(); poi.setId((UUID.randomUUID()).toString()); poi.setName(rs.getString("nameforStore")); poi.setAddress(rs.getString("addressforStore")); String lat = rs.getString("lat"); if(lat!=null&&!lat.equalsIgnoreCase("null")&&lat.length()>0){ poi.setLat(Double.valueOf(lat)); } String lon = rs.getString("lon"); //poi.setLon(rs.getDouble("lon")); if(lon!=null&&!lon.equalsIgnoreCase("null")&&lon.length()>0){ poi.setLon(Double.valueOf(lon)); } poi.setNid(rs.getString("DOCID")); String totalCity = rs.getString("totalcity"); if(!StringUtils.isEmpty(totalCity)){//---------citycode String[] cities = totalCity.split(" "); List<String> cs = new ArrayList<String>(); for(String c:cities){ cs.add(c); } poi.setCities(cs); } String types = rs.getString("type"); if(!StringUtils.isEmpty(types)){//type----------------- String[] typea = types.split(" "); List<String> t = new ArrayList<String>(); for(String c:typea){ t.add(c); } //poi.setCities(cs); poi.setTypes(t); } return poi; }; public void submit(JobDoneCallBack callback,JobTimer timer) { if(solrConfig==null){ throw new IllegalArgumentException("SolrJ未正确配置."); } if(jdbcConfig == null){ throw new IllegalArgumentException("JDBC未正确配置."); } if(poiConfig == null){ throw new IllegalArgumentException("POI配置文件未正确配置."); } Connection con = null; Statement pst = null; ResultSet rs = null; SolrServer ss = null; JobStatus status = new JobStatus(); status.setName("ImportPOI"); status.setStatus("failure"); int i = 0; int c = 0; long start = System.currentTimeMillis(); try { Class.forName(jdbcConfig.getDriverClass()).newInstance(); con = DriverManager.getConnection(jdbcConfig.getUrl(), jdbcConfig.getUserName(), jdbcConfig.getPassWord()); int batchSize = Integer.valueOf(poiConfig.getImportRecordSize()); ss = new HttpSolrServer(solrConfig.getSolrUrl()); if(poiConfig.isDeleteOnstartup()){ ss.deleteByQuery("*:*"); ss.commit(); } if(jdbcConfig.getDriverClass().toString().contains("mysql")){//mysql pst = (com.mysql.jdbc.Statement) con.createStatement(ResultSet.FETCH_FORWARD,ResultSet.CONCUR_READ_ONLY); pst.setFetchSize(1); ((com.mysql.jdbc.Statement) pst).enableStreamingResults(); }else{ pst = con.createStatement(); } rs = pst.executeQuery(poiConfig.getImportSQL()); POI p = null; List<POI> pois = new ArrayList<POI>(); while(rs.next()){ p = getPOI(rs); //ss.addBean(p); pois.add(p); if(i>=batchSize){ long commitT = System.currentTimeMillis(); //System.out.println("已耗时:"+(commitT-start)/1000*60+"分钟"); timer.onTimeUpdate((commitT-start)); //System.out.println("提交一次"); ss.addBeans(pois); ss.commit(); pois.clear(); c++; i=0; }else{ i++; } } ss.addBeans(pois); ss.commit(); long end = System.currentTimeMillis(); status.setStatus("success"); status.setMessage("处理成功,总耗时:"+(end-start)/1000*60+"分钟"); status.setTimeCost((end-start)/1000*60); } catch (SQLException e) { // TODO Auto-generated catch block //e.printStackTrace(); status.setMessage(e.toString()); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block //e.printStackTrace(); status.setMessage(e.toString()); } catch (InstantiationException e) { // TODO Auto-generated catch block //e.printStackTrace(); status.setMessage(e.toString()); } catch (IllegalAccessException e) { // TODO Auto-generated catch block //e.printStackTrace(); status.setMessage(e.toString()); } catch (SolrServerException e) { // TODO Auto-generated catch block //e.printStackTrace(); status.setMessage(e.toString()); } catch (IOException e) { // TODO Auto-generated catch block //e.printStackTrace(); status.setMessage(e.toString()); }finally{ try { if(rs!=null){ rs.close(); } } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } try { if(pst!=null)pst.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } try { if(con!=null) con.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } if(callback!=null){ callback.onCallback(status); } } //return false; }; }
整个过程是读取数据库,将数据转成DTO,然后通过SolrServer.addBeans插入solr server,调用SolrServer.commit进行索引提交(就可以查询结果)。
读取转换过程代码如下:
private POI getPOI(ResultSet rs) throws SQLException{ POI poi = new POI(); poi.setId((UUID.randomUUID()).toString()); poi.setName(rs.getString("nameforStore")); poi.setAddress(rs.getString("addressforStore")); String lat = rs.getString("lat"); if(lat!=null&&!lat.equalsIgnoreCase("null")&&lat.length()>0){ poi.setLat(Double.valueOf(lat)); } String lon = rs.getString("lon"); //poi.setLon(rs.getDouble("lon")); if(lon!=null&&!lon.equalsIgnoreCase("null")&&lon.length()>0){ poi.setLon(Double.valueOf(lon)); } poi.setNid(rs.getString("DOCID")); String totalCity = rs.getString("totalcity"); if(!StringUtils.isEmpty(totalCity)){//---------citycode String[] cities = totalCity.split(" "); List<String> cs = new ArrayList<String>(); for(String c:cities){ cs.add(c); } poi.setCities(cs); } String types = rs.getString("type"); if(!StringUtils.isEmpty(types)){//type----------------- String[] typea = types.split(" "); List<String> t = new ArrayList<String>(); for(String c:typea){ t.add(c); } //poi.setCities(cs); poi.setTypes(t); } return poi; };
SolrJ索引过程代码:
List<POI> pois = new ArrayList<POI>(); while(rs.next()){//遍历JDBC ResultSet p = getPOI(rs); //ss.addBean(p); pois.add(p); if(i>=batchSize){//定量批量索引逻辑 long commitT = System.currentTimeMillis(); //System.out.println("已耗时:"+(commitT-start)/1000*60+"分钟"); timer.onTimeUpdate((commitT-start)); //System.out.println("提交一次"); ss.addBeans(pois);//发向SolrServer ss.commit(); pois.clear(); c++; i=0; }else{ i++; } } ss.addBeans(pois);//做最后提交 ss.commit();
分析:
1、性能差别主要在哪里?
答:方案一和方案主要差别在于,方案一访问数据之后直接调用Solr内部UpdateHandler,直接将数据放入索引。而方案二,调用SolrJ索引数据,多了一道网络IO。而且,方案二,在solrj索引之前,先将数据转换为DTO,然后Solrj将DTO转换为SolrInputDocument对象,然后SolrInputDocument对象转换成solr rest 接口所需字符串,中间有多处转换,也存在性能损耗(备注:调用Solrj addBeans批量导入索引的方法是提高性能的方式,如果一个一个的提交,性能会更差,http请求更多)。
2、怎么优化?
答:问题一的分析,就是问题二的答案。主要那么多数据实体转换那块,主要遵守:1、使用调用接口尽量简单,使用ResultSet直接转换成SolrInputDocument对象,少一些数据转换。2、使用数组等数据结构,替换掉目前的List<Bean>。