검색팀 송원석
YSM 사내 교육자료
실제 검색을 하나하나 날려봐야 검색 카운트를 파악 가능
20만 키워드 검색시 예상 소요시간 : 4~5시간
1. 키워드 목록을 쭉 가져와서
2. 키워드마다 다시 서버 호출
3. 받은 result값으로 화면 갱신
//키워드마다 다시 ajax call
$.ajax("searchCount.do", {
data : {},
type : 'POST',
tryCount: 0,
retryLimit : 5,
success : function(data) {
if(data) {
// 화면갱신
}
}
});
//검색시작 버튼 클릭시
$("#start").click(function(){
$.ajax("getKeywordList.do",{
data : {
"hello" : "world"
},
success : function(result) {
totalCount = result.totalCount;
list = result.list;
//키워드 하나씩 계속 루프 돌면서 처리
search();
}
});
});
$("#search0").text(searchSummary + requestCnt);
$("#keyword0").text(++keywordSummary);
$("#click0").text(clickSummary + clickCnt);
$("#searchClick0").text(searchClickSummary.toFixed(3) );
참쉽죠?
서버에서 백그라운드로 돌아가게 바꿔볼까?
AS-IS | TO-BE | |
---|---|---|
서버 호출 | 키워드 건바이건 | 딱 한번만 호출 |
키워드목록 | 클라이언트에서 루프처리 | 서버에서 루프처리 |
진행 progress 정보 | 건바이건 자바스크립트 연산 | static하게 서버에 정보를 가지고있음 |
화면 갱신 | 건바이건 자바스크립트 화면갱신 | 주기적으로 서버를 호출해서 진행 정보 가지고옴 |
실행 종료시 | X | 엑셀로 export해서 다운로드 가능하게 처리 |
<task:executor id="asyncExecutor" pool-size="10-100" queue-capacity="10" rejection-policy="ABORT" />
<task:annotation-driven executor="asyncExecutor" />
XML 기반
@Async
public void existsCheckList(SearchRequestBean reqBean, HashMap<String, String> param) throws InterruptedException {
logger.info("exists shop job start");
// 잡 초기화
Job job = JobTaskUtil.startJob(CommonUtil.EXISTS_SHOP_JOB_ID);
// 키워드 목록을 가지고온다.
List<KeywordLogBean> keywordList = searchDAO.getKeywordList(param);
job.setTotalCount(keywordList.size());
reqBean.setSiteid("1");
reqBean.setMode("simple");
reqBean.setPageSize(1);
if (StringUtils.isEmpty(reqBean.getServer())) {
reqBean.setServer(Const.TEST_SERVER);
}
String opt = CommonUtil.getLastQueryType();
int progressCount = 0;
// 키워드 마다 루프를 돔
for (KeywordLogBean keyword : keywordList) {
if (!job.getIsRunning()) {
break;
}
if (StringUtils.isEmpty(opt)) {
opt = CommonUtil.getLastQueryType();
}
reqBean.setKwd(keyword.getKeyword());
String[] kql_str = kqlGenerator.makeKQLString2(opt, reqBean, true);
// 키워드별로 코난 검색엔진 호출
SearchResultBean resultList = KonanModule.searchTestList(kql_str, reqBean);
if (resultList.getTotalCount() <= 0 && StringUtils.isEmpty(reqBean.getSid())) {
resultList = KonanModule.searchTestList2(kql_str, reqBean);
}
// 카운트를 받아와서 체크
int count = resultList.getTotalCount() + resultList.getSecondTotalCount();
if (count > 0) {
HashMap<String, String> map = new HashMap<>();
map.put("keyword", keyword.getKeyword());
map.put("count", Integer.toString(count));
job.getList().add(map);
}
progressCount++;
job.setProgressCount(progressCount);
}
// 엑셀 생성 로직 이관
JobTaskUtil.completeJob(job.getJobId());
logger.info("exists shop job done");
}
method에 @Async 만 쓰면 OK
@Async사용시 http thread가 아닌 별도의 thread를 내부적으로 만들어서 사용
화면갱신이 예전보다 매끄럽지 못함
건by건 -> 가끔으로 다운그레이드됨
websocket을 써볼까?
nodejs에서는 내가 금방 세팅 했었는데...
<web-app
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<absolute-ordering>
<name>spring_web</name>
</absolute-ordering>
</web-app>
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/myHandler").withSockJS();
}
@Bean
public WebSocketHandler myHandler() {
return new MyHandler();
}
}
SocketJs에서 알 수 없는 404에러 발생
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/portfolio").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.setApplicationDestinationPrefixes("/app");
config.enableSimpleBroker("/topic", "/queue");
}
}
이번엔 stomp에서 알 수 없는 404에러 발생
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<absolute-ordering>
<name>spring</name>
</absolute-ordering>
servlet-mapping의 url-pattern이 특정 확장자로만 지정
되어 있으면 오류 발생.
url-pattern을 / 로 설정해서
모든 request를 spring에서 받게 설정
모든 스크립트 & 이미지파일 404에러 발생
spring 설정에서 js와 image경로를 resource handler로 설정
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/common/**").addResourceLocations("/common/");
registry.addResourceHandler("/images/**").addResourceLocations("/images/").setCachePeriod(31556926);
}
<script src="<c:url value='/common/js/sockjs-0.3.4.js'/>"></script>
<script src="<c:url value='/common/js/stomp.js'/>"></script>
var sock = new SockJS('/RankPilot/websocket');
stompClient = Stomp.over(sock);
stompClient.connect({}, function(frame) {
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/jobStatus_checkSearchCount', function(obj){
if (obj) {
var jobStatus = JSON.parse(obj.body);
console.info(jobStatus);
}
});
});
@Configuration
@EnableWebSocketMessageBroker
public class AppWebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/websocket", "notify", "/jobStatus*").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic");
registry.setApplicationDestinationPrefixes("/RankPilot");
}
}
stompClient.send('/topic/notify', {}, 'hello world');
@Controller
public class WebsocketController {
@MessageMapping("/notify")
@SendTo("/topic/notify")
public String notify(String message) {
return message;
}
}
@Autowired
private SimpMessagingTemplate template;
public void sendWebsocketJobStatus(String jobId, Job job) {
HashMap<String, Object> returnMap = new HashMap<>();
returnMap.put("totalCount", job.getTotalCount());
returnMap.put("progressCount", job.getProgressCount());
returnMap.put("isRunning", job.getIsRunning());
returnMap.put("reportStatus", job.getReportStatus());
returnMap.put("errorMsg", job.getErrorMsg());
returnMap.put("totalTimeMsg", job.getTotalTimeMsg());
template.convertAndSend("/topic/jobStatus_" + jobId, returnMap);
}
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
<version>${spring-version}</version>
</dependency>
var sock = new SockJS('/RankPilot/websocket');
stompClient = Stomp.over(sock);
stompClient.connect({}, function(frame) {
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/jobStatus_checkSearchCount', function(obj){
if (obj) {
var jobStatus = JSON.parse(obj.body);
$(".endCount").html(jobStatus.progressCount);
$("#endProgress").html(jobStatus.totalCount);
var percent = jobStatus.progressCount / jobStatus.totalCount * 100;
$("#progressBar").css({width: (percent + "%")});
$("#jobPercent").html(Math.floor(percent));
$("#totalTimeMsg").html("[약 " +jobStatus.totalTimeMsg + "]");
}
});
});
특정 사용자에게만 메시지 전송의 경우
@SendToUser or
template.convertAndSendToUser 사용
stompClient.subscribe(destination, callback, { id: mysubid });