로그성 DB INSERT로 인해서 전체적인 DB성능 이슈가 발생하면서 어플리케이션까지 서능에 영향이 미치는 상황이 발생하였다.


처음에는 Logging 라이브러리를 활용하여 Text형태로 쌓아놓고 한가한 시간에 배치로 DB에 INSERT하려고 하다가 공부 겸 JMS(java message service)를 적용해보기로 하였다.


Message Queue 관련 방법은 여러가지가 있던데 선택은 Apache ActiveMQ 를 선택 하였다.

관련 장단점에 대해서는 인터넷 참고!!





우선 Apache ActiveMQ 설치


context-jms.xml


	



	



	



	
    




	
	
	



	
	
	
		
	


MemberMessageConverter.java

package pe.kr.ddakker.jms;

import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.Session;

import org.springframework.jms.support.converter.MessageConversionException;
import org.springframework.jms.support.converter.MessageConverter;

public class MemberMessageConverter implements MessageConverter {

	public MemberMessageConverter(){}
	
	@Override
	public Object fromMessage(Message message) throws JMSException,
			MessageConversionException {
		if(!(message instanceof MapMessage)){
			throw new MessageConversionException("Messaeg isn't a MapMessage");
		}
		
		MapMessage mapMessage = (MapMessage) message;
		Member member = new Member();
		member.setName(mapMessage.getString("name"));
		member.setEmail(mapMessage.getString("email"));
		return member;
	}

	@Override
	public Message toMessage(Object object, Session session) throws JMSException,
			MessageConversionException {
		if(!(object instanceof Member)){
			throw new MessageConversionException("Messaeg isn't a Member");
		}
		
		Member member = (Member) object;
		MapMessage message = session.createMapMessage();
		message.setString("name", member.getName());
		message.setString("email", member.getEmail());
		return message;
	}

}


ReceiverMessageListenerImpl.java

package pe.kr.ddakker.jms;

import javax.annotation.Resource;
import javax.jms.Message;
import javax.jms.MessageListener;

import org.springframework.jms.support.converter.MessageConverter;
import org.springframework.stereotype.Component;

@Component
public class ReceiverMessageListenerImpl implements MessageListener {


	@Resource
	private MessageConverter memberConverter;

	@Override
	public void onMessage(Message message) {
		Member member = null;
		try {
			member = (Member) memberConverter.fromMessage(message);

			if (member != null) {
				System.out.println("member.getEmail(): " + member.getEmail());
			}
		} catch (Exception e) {
			System.err.println(e);
		}

	}

}


JmsTest .java

package pe.kr.ddakker.jms;

import javax.annotation.Resource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath*:config/spring/context-jms.xml"})
public class JmsTest {
	@Resource
	private JmsTemplate jmsTemplate;
	
	@Test
	public void test() {
		
		for (int i = 0; i < 5; i++) {
			Member member = new Member();
			member.setName("ddakker" + i);
			member.setEmail("ddakker" + i + "@gmail.com");
			
			jmsTemplate.convertAndSend(member);
		}
	}
}


SLF4J는 Apache commons-logging과 같이 SLF4J도 Facade 라이브러입니다.

그 말은 인터페이스만 있고, 해당 구현체는 별도의 라이브러리를 사용한다는 말입니다.


이에 대한 잇점은 시스템 전반적으로 사용되어진 로깅시스템의 교체가 수월해진다는 장점이 있습니다.

(사실 한번 적용된 로깅 시스템을 교체하는 경우는 특별한 경우가 아니고서야 별로 없긴 합니다.)


※ 하지만 SLF4J는 별도의 구현체 없이 단독으로도 사용가능하긴 합니다. ㅎ



SLF4J의 탄생 이유는 Aapche Commons-logging 의 비효율적인 문제들이라고 합니다.

여러가지가 있겠지만 잘 모르겠고..;; 개인적으로 편리한 기능 및 셈플 남겨봅니다.


logger.debug("param: " + param var); 


대부분 위와 같이 로그를 남길것이다.

하지만 운영의 DEBUG 하위 모드에서는 로그는 찍히지 않겠지만 로그 문자열에 대한 연산을 이루어져 그에 대한 비용이 발생한다고 합니다.

별거 아닌것 같지만 이에 대한 비용이 찝집하다네요 ㅎㅎ



if( logger.isDebugEnabled() ){

    logger.debug("param: " + param var);

}


그래서 대부분 위와 같이 특정 로그레벨일 때만 처리 되도록 하긴 하지만 소스양이 늘어나고 귀찮아지죠.

그에 따라 위와 같이 하지 않고 첫번째 방법으로만 하는 경우가 파다합니다.


SLF4J는 이러한 비용을 절약되도록 설계되었다고 합니다.



 logger.debug("param: {}", param var);


위와 같이 문자열을 연산하지 않고, 해당 함수 내부에서 상황에 따라 연산이 이루어지게 하는것 같습니다.


[build.gradle]

apply plugin: 'java'
apply plugin: 'eclipse'

sourceCompatibility = 1.6

repositories {
    mavenCentral()
}

// SLF4J 단독
List loggerSlf4j = [
    "org.slf4j:slf4j-api:1.7.5",
    "org.slf4j:slf4j-simple:1.7.5",
    "org.slf4j:jcl-over-slf4j:1.7.5"
]

// SLF4J + Log4j
List loggerSlf4jAndLog4j = [
    "org.slf4j:slf4j-log4j12:1.7.5",
    "org.slf4j:jcl-over-slf4j:1.7.5"
]

dependencies {
    //compile loggerSlf4j
    compile loggerSlf4jAndLog4j
}

// commons-logging 의존성 제거
configurations {
    all.collect { configuration ->
        configuration.exclude group: 'commons-logging', module: 'commons-logging'
    }
}


[log4j.xml]




	
		
			
		
	

	
		
		
	


[Foo.java]

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

public class Foo {
	static final Logger logger = LoggerFactory.getLogger(Foo.class);

	public void doIt() {
		logger.debug("안녕 {}", "하세요.");
	}
}


예제 소스에서 볼수 있듯이, SLF4J단독으로 사용하든, Log4j를 구현체로 사용하든 소스상에 변화는 없습니다.


설정 중 한가지 눈여겨볼 수항은 "jcl-over-slf4j" 입니다.

"jcl-over-slf4j' 추가 목적은 기존 Springfrawork 와 같이 Apache commons-logging를 사용하는 라이브러리를 사용하고 있을 경우 소스상은 commons-logging이지만 실제 동작은 SLF4J쪽을 호출 하도록 하는 역할을 하도록 합니다.



SLF4J + Log4j 에 대한 예제만 작성하였지만 "http://www.slf4j.org/manual.html" 사이트를 참고하시면 아래와 같이 각 구현체 마다 사용해야 하는 라이브러리 정보가 있습니다.


slf4j-log4j12-1.7.5.jar
Binding for log4j version 1.2, a widely used logging framework. You also need to place log4j.jar on your class path.

slf4j-jdk14-1.7.5.jar
Binding for java.util.logging, also referred to as JDK 1.4 logging

slf4j-nop-1.7.5.jar
Binding for NOP, silently discarding all logging.

slf4j-simple-1.7.5.jar
Binding for Simple implementation, which outputs all events to System.err. Only messages of level INFO and higher are printed. This binding may be useful in the context of small applications.

slf4j-jcl-1.7.5.jar
Binding for Jakarta Commons Logging. This binding will delegate all SLF4J logging to JCL.

logback-classic-1.0.13.jar (requires logback-core-1.0.13.jar)
NATIVE IMPLEMENTATION There are also SLF4J bindings external to the SLF4J project, e.g. logback which implements SLF4J natively. Logback'sch.qos.logback.classic.Logger class is a direct implementation of SLF4J's org.slf4j.Logger interface. Thus, using SLF4J in conjunction with logback involves strictly zero memory and computational overhead.



	Order deny,allow
	Deny from all
	Allow from 1.1.1.  # 1~255 활성


import java.io.File;
import java.io.FileInputStream;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.FormulaEvaluator;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.apache.poi.xssf.usermodel.XSSFCell;

/**
 * Excel Read 한다. xls, xlsx 가능
 * ex) 	ExcelGenerator.excelToList(new File("path..xls,xlsx");
 * 		ExcelGenerator.excelToList(new File("path..xls,xlsx", new Integer[]{x, y, width, height});
 * ExcelGenerator.excelToList(new File("path..xls,xlsx", x, y, width, height);
 * 		java.awt.Rectangle(int x, int y, int width, int height) 와 같은 형태로 왼쪽 위 모서리 좌표와, 오른쪽 아래 모서리 좌표 정보 
 * @author 	ddakker
 * @version 1.0
 * @date	2012.05.04
 *
 */
public class ExcelGenerator {
	private static final Log logger = LogFactory.getLog(ExcelGenerator.class);
	
	public static Object[][] excelToList(File file ) throws Exception {
		return excelToList(file, 0, null);
	}
	
	/**
	 * 엑셀 데이터를 Object[colunm][row] 형태로 변환한다.
	 * @param file			엑셀 파일(xls, xlsx)
	 * @param x				왼쪽 위 모서리 x
	 * @param y				왼쪽 위 모서리 y
	 * @param width			오른쪽 아래 모서리 x
	 * @param height		오른쪽 아래 모서리 y
	 * @return
	 * @throws Exception
	 */
	public static Object[][] excelToList(File file, int x, int y, int width, int height) throws Exception {
		Integer [] colRow = {x, y, width, height};
		return excelToList(file, 0, colRow);
	}
	
	/**
	 * 엑셀 데이터를 Object[colunm][row] 형태로 변환한다.
	 * @param file			엑셀 파일(xls, xlsx)
	 * @param colRow		범위 지정 new Integer[]{왼쪽 위 모서리 x, 왼쪽 위 모서리 y, 오른쪽 아래 모서리 x, 오른쪽 아래 모서리 y}
	 * @return
	 * @throws Exception
	 */
	public static Object[][] excelToList(File file, Integer [] colRow) throws Exception {
		return excelToList(file, 0, colRow);
	}
	
	/**
	 * 엑셀 데이터를 Object[colunm][row] 형태로 변환한다.
	 * @param file			엑셀 파일(xls, xlsx)
	 * @param sheetIndex	시트 번호
	 * @return
	 * @throws Exception
	 */
	public static Object[][] excelToList(File file, int sheetIndex) throws Exception {
		return excelToList(file, sheetIndex, null);
	}
	
	/**
	 * 엑셀 데이터를 Object[colunm][row] 형태로 변환한다.
	 * @param file				엑셀 파일(xls, xlsx)
	 * @param sheetIndex		시트 번호
	 * @param colRow			범위 지정 new Integer[]{왼쪽 위 모서리 x, 왼쪽 위 모서리 y, 오른쪽 아래 모서리 x, 오른쪽 아래 모서리 y}
	 * @return
	 */
	public static Object[][] excelToList(File file, int sheetIndex, Integer [] colRow) throws Exception {
		Object [][] contentData = null;
		
		FileInputStream fis			= null;
		Workbook 		workBook	= null;
        Sheet			sheet		= null;
        
        try {
        	if( colRow != null ){
	        	if( colRow.length != 4 )					throw new Exception("colRow[] 무조껀 셋팅 해야 합니다. ex) new Integer[]{0,0,6,2}");
	        	if( !(colRow[0] >= 0 && colRow[1] >= 0) )	throw new Exception("colRow[] 0 이상 입력 하시요. ex) new Integer[]{1,1,6,2}");
	        	if( !(colRow[2] >= 0 && colRow[3] >= 0) )	throw new Exception("colRow[] 0 이상 입력 하시요. ex) new Integer[]{1,1,6,2}");
        	}
        	fis			= new FileInputStream(file);
        	workBook	= WorkbookFactory.create(fis);
    		sheet		= workBook.getSheetAt(sheetIndex);
            
            int minCol = colRow==null?0:colRow[0];
            int maxCol = colRow==null?-1:colRow[2]+1;
            
            int minRow = colRow==null?0:colRow[1];
            int maxRow = colRow==null?sheet.getPhysicalNumberOfRows():colRow[3]+1;
            
            FormulaEvaluator evaluator = workBook.getCreationHelper().createFormulaEvaluator();
            
            for( int i=minRow; i<maxRow; i++ ) {
            	Row row = sheet.getRow(i);
            	if( i-minRow == 0 ){
	            	if( maxCol == -1 ){
	            		maxCol = row.getPhysicalNumberOfCells();
	            	}
	            	contentData = new Object[maxRow-minRow][maxCol-minCol];
            	}
                for( int j=minCol; j<maxCol; j++ ) {
                	Cell cell = row.getCell(j)==null?row.createCell(j):row.getCell(j);
                	
                	Object value = null;
                    switch( cell.getCellType() ) {
                         case XSSFCell.CELL_TYPE_FORMULA :
                        	 if( !(cell.toString() == "") ){
	                             if(evaluator.evaluateFormulaCell(cell)==XSSFCell.CELL_TYPE_NUMERIC){
	                            	 //value = cell.getNumericCellValue();         
	                            	 value = String.valueOf(new Double(cell.getNumericCellValue()).intValue());
	                             }else if(evaluator.evaluateFormulaCell(cell)==XSSFCell.CELL_TYPE_STRING){
	                            	 value = cell.getStringCellValue();
	                             }else if(evaluator.evaluateFormulaCell(cell)==XSSFCell.CELL_TYPE_BOOLEAN){
	                                  value = String.valueOf(cell.getBooleanCellValue());         
	                             }
                        	 }
                        	 break;
                         case XSSFCell.CELL_TYPE_NUMERIC :
                        	 //value = cell.getNumericCellValue();
                        	 value = String.valueOf(new Double(cell.getNumericCellValue()).intValue());
                             break;
                         case XSSFCell.CELL_TYPE_STRING :
                        	 value = cell.getStringCellValue()==null?new String(""):cell.getStringCellValue();
                        	 break;
                         case XSSFCell.CELL_TYPE_BOOLEAN :
                        	 value = Boolean.toString(cell.getBooleanCellValue()); //boolean
                        	 break;
                         default :
                    }
                    
                    logger.debug("xlsx ["+i+"]["+j+"]=" + ((cell==null || value==null)?"":value));
                    contentData[i-minRow][j-minCol] = (value==null)?new String(""):value;
                }
            }
        } catch (Exception e) {
        	logger.error(e);
            throw new Exception("POI Excel Read 중 오류가 발생하였습니다.");
        } finally {
        	if( fis != null ) fis.close();
        }
        return contentData;
    }

    /**
	 * List 데이터를 엑셀 형식으로 변환한다.
	 * 		* String만 테스트 해봄.. 다른 데이터형은 직접 테스트 해보실것!!![2013.08.22]
	 * @param list
	 * @return
	 * @throws Exception
	 * @auther ddakker 2013. 8. 22.
	 */
	public static HSSFWorkbook listToExcel(List> list) throws Exception {
		HSSFWorkbook wb = null;
		try{

			wb = new HSSFWorkbook();
			HSSFRow row = null;
			HSSFCell cell = null;

			HSSFSheet sheet = wb.createSheet();

			wb.setSheetName(0, "Sheet1");
			sheet.setColumnWidth(0, sheet.getDefaultColumnWidth() * 700);
			sheet.setColumnWidth(1, sheet.getDefaultColumnWidth() * 4300);

			HSSFCellStyle titleCellStyle = wb.createCellStyle();
			titleCellStyle.setBorderBottom(HSSFCellStyle.BORDER_THICK);
			titleCellStyle.setBorderLeft(HSSFCellStyle.BORDER_THICK);
			titleCellStyle.setBorderRight(HSSFCellStyle.BORDER_THICK);
			titleCellStyle.setBorderTop(HSSFCellStyle.BORDER_THICK);
			titleCellStyle.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);
			titleCellStyle.setFillForegroundColor(HSSFColor.GREY_25_PERCENT.index);

			Iterator it = list.get(0).keySet().iterator();
			int haderIdx = 0;
			HSSFRow titleRow = sheet.createRow(0);
			while (it.hasNext()) {
				String key = it.next();

				cell = titleRow.createCell(haderIdx++);
				cell.setCellValue(key);
				cell.setCellStyle(titleCellStyle);
			}

			HSSFCellStyle contentCellStyle = wb.createCellStyle();
			contentCellStyle.setBorderBottom(HSSFCellStyle.BORDER_THIN);
			contentCellStyle.setBorderLeft(HSSFCellStyle.BORDER_THIN);
			contentCellStyle.setBorderRight(HSSFCellStyle.BORDER_THIN);
			contentCellStyle.setBorderTop(HSSFCellStyle.BORDER_THIN);
			contentCellStyle.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);
			contentCellStyle.setFillForegroundColor(HSSFColor.WHITE.index);

			for( int i=0,size=list.size(); i<size; i++ ){
				row = sheet.createRow((i+1));

				it = list.get(0).keySet().iterator();
				haderIdx = 0;
				while (it.hasNext()) {
					String key = it.next();

					cell = row.createCell(haderIdx++);
					cell.setCellStyle(contentCellStyle);

					if( list.get(i).get(key) instanceof String ) 			cell.setCellValue( (String) list.get(i).get(key) );
					if( list.get(i).get(key) instanceof Integer ) 			cell.setCellValue( (Integer) list.get(i).get(key) );
					if( list.get(i).get(key) instanceof Long ) 				cell.setCellValue( (Long) list.get(i).get(key) );
					if( list.get(i).get(key) instanceof Float ) 			cell.setCellValue( (Float) list.get(i).get(key) );
					if( list.get(i).get(key) instanceof Double ) 			cell.setCellValue( (Double) list.get(i).get(key) );
					if( list.get(i).get(key) instanceof Date ) 				cell.setCellValue( (Date) list.get(i).get(key) );
					if( list.get(i).get(key) instanceof java.sql.Date ) 	cell.setCellValue( (java.sql.Date) list.get(i).get(key) );
				}
			}
		}catch(Exception e){
			logger.error("POI List에서 Excel로 변환 중 오류가 발생하였습니다.");
			logger.error(e);
		}
		return wb;
	}
}

--------------


dom4j-1.6.1.jar

poi-3.6-20091214.jar

poi-ooxml-3.6-20091214.jar

poi-ooxml-schemas-3.6-20091214.jar

xmlbeans-2.3.0.jar


메뉴얼(http://wiki.apache.org/solr/DataImportHandler)이 있기는 하지만 직관적이지 않아 이리저리 검색도 해보고 문의해서 정리한다.
 
Window 7
Tomcat 5.5.35,
Apache Solr 3.5.0
Oracle 10g

톰켓 설치 경로
D:\apache-solr\tomcat

/conf/server.xml
<Connector port="8983" ..... URIEncoding="UTF-8" />
포트는 편하신것으로 하면 될듯하다.
Encoding 는 검색할때 Request 호출 시 한글 깨짐을 방지하기 위함이다.(해당 WAS에 따라서 또는 소스레벨에서 적절하게 하면 될듯하다.)

/bin/catalina.bat
set CATALINA_HOME=D:\apache-solr\tomcat
set JAVA_OPTS=%JAVA_OPTS% -Dsolr.solr.home=D:\apache-solr\solr ... 기존 옵션...

/common/lib/ojdbc14.jar 복사하자.(Oracle)

Solr 작동 소스 경로
D:\apache-solr\tomcat\webapps\solr
Apache-solr-3.5.0.zip 파일 하위 dist/apache-solr-3.5.0.war 파일을 solr.war 로 변경하여 복사한다.
Apache-solr-3.5.0.zip 파일 하위 dist/apache-solr-dataimporthandler-3.5.0.jar, apache-solr-dataimporthandler-extras-3.5.0.jar 두 파일을 WEB-INF/lib 하위에 복사한다.
(Solr 설정파일 colrconfig.xml <lib> 설정으로도 할 수 있지만 간편히 복사하자.

Solr 설정파일 경로
D:\apache-solr\solr 
Apache-solr-3.5.0.zip 파일 하위 example/solr 폴더를 복사한다.
/conf/solrconfig.xml
<requestHandler name="/dataimport" class="org.apache.solr.handler.dataimport.DataImportHandler">
    <lst name="defaults">
      <str name="config">data-config.xml</str>
    </lst>
</requestHandler>
추가 하자 data-config.xml 파일은 같은경로에 두자.
/conf/data-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<dataConfig>
<dataSource type="JdbcDataSource" driver="oracle.jdbc.OracleDriver" url="jdbc:oracle:thin:@127.0.0.1:1521:test" user="test" password="test"/>

<document name="products">
        <entity name="item" query="SELECT ID, TITLE FROM TB_DATA">
<field name="id" column="ID" />
<field name="title" column="TITLE" />
        </entity>
    </document>
</dataConfig> 

한글형태소분석기는 http://cafe.naver.com/korlucene/462 를 참조하자.

이제 Tomcat 시작하고
http://localhost:8983/solr/dataimport?command=full-import 하면 인덱싱 된다.
http://localhost:8983/solr/admin 에서 *:* 모두 검색해보자.

 
 

+ Recent posts