빅데이터를 접하기 시작하면서 자주듣게 되는 용어가 있습니다. 맵/리듀스 라는 용어인데요, MR이라고도 많이 쓰구요, 빅데이터 처리에는 늘 맵리듀스 개념이 들어가죠.

그럼, 빅데이터 처리의 기본이되는 맵리듀스란 무엇인지 자세히 알아볼께요.

일단 맵(Map) 이라는 것은 지도? 아니구요, :) 데이터를 담아두는 자료 구조 중의 하나입니다.
맵은 키와 밸류라는 두개의 값을 쌍으로 가지고 있는 형태입니다.
수학시간에 좌표를 표시할때 순서쌍이라고 하죠, (x,y) 이렇게 하던 바로 그 개념입니다.
여기서 x가 키이고, y가 밸류 즉 값인거죠.
그리고 함수 f(x) => y 도  생각나시죠? x를 알면 y를 알 수 있는 구조로 관리 됩니다.   



리듀스(Reduce)는 이 맵을 정리해 나가는(줄여나가는) 방법이라고 할 수 있습니다.
키를 기준으로 (같은 키 값을 가진 맵들의) 개수를 센다든지, 같은 키를 기준으로 밸류를 모두 더하거나,
평균을 내거나 하는 것들이 있습니다.
글 마지막 부분에 더 많은 예제가 있으니까 끝까지 꼭 읽어보세요. :)

그럼... 맵리듀스의 가장 쉬운 예제를 살펴 볼께요.

말로만은 이해가 어려우니 예를 들어볼께요.
맵리듀스 예제로 가장많이 등장하는 wordcount를 가지고 설명할께요.
wordcount는 어떤 글에 등장하는 단어들의 개수를 세는 작업이구요,
java 프로그래밍을 처음 배울때 "hello world"를 찍어보듯 맵리듀스를 처음 배울때 짜보는 프로그램 입니다.

단어 수를 세기 위한 맵은 일단 대상이 되는 글이 담긴 파일을 한 줄 한 줄 읽어가면서 단어를 잘라 만듭니다.
그리고 밸류는 무조건 1로 해서 차곡차곡 나열만 하는거죠.

그리고 나면 하둡 같은 빅데이터 처리 프레임워크에서 suffling이라는 작업을 통해서 비슷한것끼리 모아가면서 정리를 해줍니다.

이어서 리듀스 프로그램이 돌아가게 되는데요. 이때 키가 같은 경우(단어가 같은)의 밸류(1로 입력한)를 더하라고 프로그래밍 하면 단어별로 출현 빈도가 집계되는 것입니다.
 


그런데,,, 도대체 왜 빅데이터에서 맵리듀스의 개념을 사용하는 걸까요?

빅데이터는 양이 워낙 많기 때문에 처리 프로세스는 최대한 단순하게 만들어야 합니다.
그래야 수많은 서버에 흩어서 병렬로 처리할 수 있거든요.
처리의 순서가 중요하다든지 실패했을때 다시하려면 몇단계 전으로 돌아가야 한 다든지 하면 대략난감해지니까요.
이를 위해서는 기준이 되는 값은 하나!이어야 프로세스가 단순합니다.
그래서 기준이되는 값인 키가 하나인 맵 구조를 선택한거구요.
키에 대한 실제 처리는 밸류(값)를 가지고 하게 되는데 여기에 수행 하는 연산도 역시 단순해야 합니다.
어떤식이냐면 교환법칙과 결합법칙이 성립해야 하죠.
임의의 서버에서 임의의 순서로 두 개의 맵을 선택해서 연산을 수행하더라도
최종결과는 늘 같아야 병렬처리가 가능 하니까요.

 
이런 조건을 만족하기 위해서 모든 사용자들의 처리를 하나의 틀 안에서 단순화 하는거죠.

맵 만들고, 리듀스 하고... 다시 맵 만들고 리듀스하고... 하는 식으로요.

이 틀을 어기지만 않는다면 수백대의 서버가 일을 나누어 해도 오류없이 처리가 가능하게 됩니다.
그리고 또 신기한것이 이런 틀 안에서도 맵리듀스를 잘 이용하거나, 여러단계를 반복 시키면, 참 다양하고 꽤 복잡한 작업도 처리가 가능하게 되니, 충분히 분할만 해나간다면 본질적으로 복잡한 작업이란 없는건지도 모르겠습니다. :)

개념을 이해하기 위해서, 맵리듀스를 사용하는 패턴을 조금 더 자세하게 살펴볼께요.

예를 들어 쇼핑몰 사용자의 사용 로그가 아래와 같이 남는다고 가정 해볼께요.


사용일자:사용자아이디:행동유형:관련금액
-----------------------------------------
20150305 0930:chulsoo:addToCart:0
20150305 1002:chulsoo:buy:33000
...

쇼핑몰을 방문한 사용자를 집계하고 싶을때는
로그를 한 줄씩 읽으면서 (사용자아이디, 1) 형태로 맵을 만듭니다.
리듀스 할때 밸류를 서로 더해 주셔도 되고,

key의 개수를 세어주는 함수가 제공된다면 그걸 그냥 호출하면 됩니다.

대략 적으로 코드 형태를 보여드리자면 이런 식입니다.

일종의 pseudo code랄 수 있겠네요. :)


map(사용자아이디, 1)
reduceByKey((a,b) => a+b)


맵을 만들때는 로그의 한 줄을 읽어서 적당한 부분을 잘라내는 파싱 작업이 먼저 이루어져야 하죠. 그래야 map에 저렇게 사용자 아이디를 넣어줄 수 있으니까요.
그리고 reduceByKey에 나오는 a,b는 두 개의 맵을 들고 연산을 수행할때, 각각의 밸류값 입니다.

키는 reduceByKey라는 이름이 알려주듯 같은 키 값을 가진 맵들끼리 계속해서 연산을 해서 맵의 숫자를 줄이는 거죠.

최종적으로는 그 키를 가지는 맵이 한 개 남을때 까지요.

그러니까 키는 정해줄 필요가 없고 첫번째 맵의 밸류 a와 두번째 맵의 밸류 b를 가지고

어떤 연산을 수행할 것인지 (위 예제에서는 더하기(+) 네요) 만 정해 주면 됩니다.

•    특정행동이 일어난 건수를 행동 유형별로 알고 싶을때는
로그를 한 줄씩 읽으면서 (행동유형, 1) 형태로 맵을 만들구요,
리듀스처리에서는 키가 같은 두개의 맵이 만날때마다 밸류 값을 서로 더해주는 함수를 선언하면 됩니다.
map(행동유형, 1)
reduceByKey((a,b) => a+b)

•    당일에 특정행동에 관련된 금액의 총계를 뽑고 싶을때는
사용일시가 당일인 건을 한 줄씩 읽으면서 (행동유형, 관련금액) 형태로 맵을 만들고,
리듀스 처리에서는 키가 같은 두 개의 맵에 만날때마나 밸류값을 서로 더해주는 함수를 선언하면 됩니다.
map(행동유형, 관련금액)
reduceByKey((a,b) => a+b)

•    사용자별 행동별 건수와 같이 두가지 이상의 조건을 조합하고 싶은 경우에는요,
그 조건을 적절하게 이어붙여서 키 값을 만드세요.
한 줄씩 읽으면서 (사용자아이디_행동유형, 1) 형태로 맵을 만드는 것 처럼요.
그리고 나서 리듀스 처리를 하고,
이후에 이걸 다시 파싱해서 DB에 넣어서 조회하거나 하는 방식으로 사용할 수 있습니다.
map (사용자아이디_행동유형, 1)
reduceByKey((a,b) => a+b)

패턴이 보이시나요? :)
로그에서 원하는 조건 부분을 잘라서 맵에 넣으시구요, 밸류에는 개수를 셀때는 1을 원하는 값이 있을때는 그 값을 넣어줍니다. 그리고 나서 키를 기준으로 리듀스를 하시면 됩니다.


출처: http://cskstory.tistory.com/entry/맵리듀스-MapReduce-이해하기 [아는 만큼]



'Hadoop ecosystem > MapReduce' 카테고리의 다른 글

Indexing  (0) 2018.04.14
InputType  (0) 2018.04.14
MapReduce프로그래밍을 위한 HL  (0) 2017.05.04
MapReduce 2  (0) 2017.05.03
Map Reduce 1  (0) 2017.05.02
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@Controller
public class PostController {
    
    private static final Logger logger = LoggerFactory.getLogger(PostController.class);
    
    // ...
     
     @RequestMapping(value="/boardDetail/write", method = RequestMethod.GET)
     public ModelAndView writeDetailBoard() throws Exception{
         
         ModelAndView mv = new ModelAndView("/Sample/boardWrite");
         return mv;
     }
     
     @RequestMapping(value = "/boardDetail/insert", method = RequestMethod.POST)
     public ModelAndView insertBoard(@RequestParam Map<String,Object> commandMap, @RequestPart("file") MultipartFile[] files) throws Exception{
            ModelAndView mv = new ModelAndView("redirect:/");
            sampleservice.insertBoard(commandMap, files);
            return mv;
        }
     
 
     @RequestMapping(value = "/boardDetail/modify/update", method=RequestMethod.PUT)
     public ModelAndView updateBoard(@ModelAttribute Post post) throws Exception{
         sampleservice.updateBoard(post);
         return new ModelAndView("redirect:/");
     }
     
      @RequestMapping(value= "/boardDetail/modify/delete/{id}", method=RequestMethod.DELETE)
     public ModelAndView DeleteBoard(@ModelAttribute Post post) throws Exception {
         sampleservice.DeleteBoard(id);
         return new ModelAndView("redirect:/");
     }
 
    //...
 
}
     
cs


'Programming > Spring' 카테고리의 다른 글

IOC / DL / DI  (0) 2018.05.13
Spring Framework  (0) 2018.05.12
CRUD 구현하기 - front end  (0) 2018.02.16
Ajax  (0) 2018.02.15
ViewResolver  (0) 2018.02.14

HTML FORM에서는 기본적으로 GET과 POST밖에 지원을 해주지않는다.


그래서 request를 보낼때, INPUT 타입으로 _method를 주어서 거기에 원하는 CRUD를 넣어 전송할 수 있다.

그러면, web.xml에 이 필터를 추가해 놓는다면, _method에 해당하는 RequestMetod에 매핑이 된다.


<filter>

       <filter-name>httpMethodFilter</filter-name>

       <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>

</filter>

<filter-mapping>

      <filter-name>httpMethodFilter</filter-name>

       <url-pattern>/*</url-pattern>

</filter-mapping> 


그리고 jsp단에서는 이에 따라서 값을 변경해주는 로직이 필요하다.


document.getElementById()으로 받아온 form 아이디와 form을 연결해준다. 그리고 넘어온 아이디가 없다면document.createElement()으로 form을 새로 만들어 주고,

document.setAttribute(key, value)을 이용하여 input이나 hidden값을 담아주고 appendChild()과 document.body.appendChild()으로 부모에 연결시켜준다. 이를 이용해서 위에서 말한 로직을 만들어준다.


아래는 그 코드이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
function Func(_formId) {
    
    this.formId = (if(_formId == "")) == true ? "commonForm" : _formId;
    this.url = "";
    
    if(this.formId == "commonForm"){
        
        formId = document.createElement("form");
    }
    else{
        formId =  document.getElementById(_formId);
    }
    
    this.setUrl = function setUrl(url){
        this.url = url;
    };
    
    this.addParam = function addParam(key, value){
        
        var hidden = document.createElement("input");
        
        hidden.setAttribute("type""hidden");
        hidden.setAttribute("name", key);
        hidden.setAttribute("id", key);
        hidden.setAttribute("value", value);
        
        formId.appendChild(hidden);
    };
    
    this.submit = function submit(method){
        
        if(method == "PUT"){
            
            var hidden = document.createElement("input");
            
            hidden.setAttribute("type""hidden");
            hidden.setAttribute("name""_method");
            hidden.setAttribute("value""PUT");
            
            formId.appendChild(hidden);
            
            method="POST";
        }
        else if(method == "DELETE"){
            
            var hidden = document.createElement("input");
            
            hidden.setAttribute("type""hidden");
            hidden.setAttribute("name""_method");
            hidden.setAttribute("value""DELETE");
            
            formId.appendChild(hidden);
            
            method="POST";
        }
        
        formId.setAttribute("action", this.url);
        formId.setAttribute("method", method);
        
        document.body.appendChild(formId);
        
        formId.submit();    
    };
    
}
cs



이렇게 준비가 모두 완료되었으면 jsp에서 CRUD 를 구현할수 있다. 


GET

1
2
3
var Func = new Func();
Func.setUrl(URL);
Func.submit("GET");  
cs



POST

1
2
3
var Func = new Func("frm");
Func.setUrl(URL);
Func.submit("POST");
cs


PUT

1
2
3
var Func = new Func("frm");
Func.setUrl(URL);
Func.submit("PUT");
cs


DELETE

1
2
3
var Func = new Func("frm");
Func.setUrl("URL"+ {id});
Func.submit("DELETE");
cs



해당하는 곳에서 이렇게 메서드만 구현해주면 간단하게 CRUD를 요청할수가 있다.


'Programming > Spring' 카테고리의 다른 글

Spring Framework  (0) 2018.05.12
CRUD구현하기 - Back end  (0) 2018.02.16
Ajax  (0) 2018.02.15
ViewResolver  (0) 2018.02.14
Spring 진행과정  (0) 2018.02.14
1. AJAX(Asynchronous Javascript and XML)란

웹의 흐름에서는 새로운 URL로 호출 되면서 페이지가 새로 뿌려지지만, 그래서 화면이 깜빡거린다.
AJAX를 사용하면 일부만을 로드해올 수 있다.

2. 원리

브라우저 위에 주소창에 URL을 쓰고 들어간다. 그러면 브라우저가 그 URL(서버)로 연결시켜준다.
서버에서 받아온 데이터들을 브라우저가 받아서 파싱해서 뿌려준다.
이게 보통 웹의 흐름이라면, AJAX는 AJAX 자체가 브라우저라고 생각하면 된다. 

3. 예제

jQuery를 사용하면

스크립트 단에서 다음과 같이 ajax를 만들어준다. 여기서 주의할 점은 contentType을 "application/json; charset=UTF-8"으로 설정해주지 않고 json데이터를 던지게 되면 인코딩이 서로 맞지않아서 Controller단에서 데이터를 제대로 받을수가 없어서 415에러가 뜨게 된다. 아래 예제에서는 Controller단에서 데이터를 Object형식으로 던지게 되는데, 이를 ajax Success는 json형식으로 받아오게 된다.
이를 위해서 jackson을 사용하였다.

pom.xml에 아래와 같이 jackson을 추가해서 Controller단에서 던지는 객체를 json형태로 변환해주도록 하자.

1
2
3
4
5
6
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>{Setting Version}</version>
</dependency>
 
cs



json data를 만드는 방법은 다음과 같다

var data = new Object();
data.A = "a";
data.B = "b";

결과값 >> {"A":"a", "B":"b"}

1
2
3
4
5
6
7
8
9
10
11
12
$.ajax({
   url  : "...",
   type : "POST",
   contentType : "application/json; charset=UTF-8",
   data : JSON.stringify(data),
   success : function(data){
       A(data);
   },
   error : function(request, status, error){
       alert(error);
   }
});
cs

JSON.stringify(data)는 JSON객체를 String으로 변환해주는 메소드이다.


1
2
3
4
5
6
7
8
9
10
private static final Logger logger = LoggerFactory.getLogger(PostController.class);
 
@ResponseBody
@RequestMapping(value = "...", method = RequestMethod.POST)
public List<Comment> InsertComment(@RequestBody HashMap<String,Object> commandMap) throws Exception{
 
 logger.info(commandMap.toString());
 
 return commentService.insertComment(commandMap);
}
cs

정리

ajax가 json을 String형식으로 던지게 되면 Controller는 @RequestBody으로 연결해준곳에 HashMap으로 받아와서 로직을 실행하고, 그 다음 결과값을 return해줄때, 객체 형태로 던지는데 이때, jackson덕분에 이를 json형태로 자동으로 변환해 주고, 이를 ajax에서 success로 받아서 원하는 형태의 html로 변환해주면 된다.

여기서 주의할 점은 @ResponseBody인데 이는 결과값을 return할때 DispatcherServlet이 ModelAndView와 같은 객체를 가지고 ViewResolver에게 넘겨준다음 리턴되는 View를 찾는 것이 아니라 return값그대로 HttpResponse Body에 직접 쓰여지게 되는 어노테이션이다.


'Programming > Spring' 카테고리의 다른 글

CRUD구현하기 - Back end  (0) 2018.02.16
CRUD 구현하기 - front end  (0) 2018.02.16
ViewResolver  (0) 2018.02.14
Spring 진행과정  (0) 2018.02.14
root-context & servlet-context  (0) 2018.02.14

InternalResourceViewResolver

  1. Jsp나 Html 파일과 같이 웹 어플리케이션의 내부 자원을 이용하여 뷰를 생성하는 AbstractUrlBasedView 타입의 뷰 객체를 리턴합니다.
  2. local ViewName을 physical ViewName으로 리턴하는 역할을 합니다.
  3. 기본적으로 사용하는 View 클래스입니다.
  4. prefix, suffix 프로퍼티를 사용합니다.

<bean id="internalResource" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"></property>
    <property name="prefix" value="jsp/"></property>
    <property name="suffix" value=".jsp"></property>
</bean>


BeanNameViewResolver

  1. 뷰 이름과 동일한 이름을 갖는 빈을 뷰 객체로 사용합니다.
  2. 주로 커스텀 View 클래스를 뷰로 사용해야 하는 경우에 사용합니다.
  3. 파일다운로드할때 주로 사용합니다.


 <bean id="fileviewResolver" class="org.springframework.web.servlet.view.BeanNameViewResolver"/>
     <property name="order" value="0"/>
<bean id="download" class="sp.mvc.file.download.ExcelDown"/> 


XmlViewResolver

뷰 이름과 동일한 이름을 갖는 빈을 뷰 객체로 사용합니다.

별도의 XML 설정 파일로부터 빈 객체를 검색합니다.

Location 프로퍼티의 값을 지정하지 않을 경우 기본값은 "/WEB-INF/views.xml"입니다.


<bean id="viewResolver" class="org.springframework.web.servlet.view.XmlViewResolver">
    <property name="location" value="/WEB-INF/xml_views.xml"></property>
</bean>



이 외에도 커스텀 뷰를 이용하여 엑셀, PDF를 다운 받을 수 있다. 그리고 자바객체를 JSON으로 변환해주는 객체도 만들수 있다.

하지만 별도의 라이브러리를 다운로드 하여 사용한다.


※ 커스텀VIEW 엑셀다운로드 - http://snoopy81.tistory.com/327?category=469766

※ 커스텀VIEW PDF다운로드 - http://snoopy81.tistory.com/328?category=469766

※ 커스텀 VIEW JSON응답 - http://snoopy81.tistory.com/329?category=469766


'Programming > Spring' 카테고리의 다른 글

CRUD 구현하기 - front end  (0) 2018.02.16
Ajax  (0) 2018.02.15
Spring 진행과정  (0) 2018.02.14
root-context & servlet-context  (0) 2018.02.14
ModelAndView 와 ViewResolver  (0) 2018.02.14

Request가 들어오면 Dispatcher Servlet이 받아서 Handler Mapping에게 Request를보내서 해당하는 Controller를 찾아온다.

그리고 Dispatcher Servlet은 찾아온 controller에게 Request를 보내고 해당 Controller는 Service, Dao를 차례로 실행시켜서 마지막에 ModelAndView객체를 리턴한다. 이 ModelAndView객체에는 파라미터값이 addObject()로 들어가있고, addViewName으로 접근한 ViewName이 선언되어있다.

이를 받은 Dispatcher Servletsms View Resolver에게 ViewName을 보내서 해당하는 View를 리턴받는다.

그리고 ModelAndView에서 addObject()로 받은 모델을 View로 전송한다.

그렇게 View에서 response를 전송하면, 마지막으로 Dispatcher Servlet는 최종 Respose를 넘겨준다.

'Programming > Spring' 카테고리의 다른 글

Ajax  (0) 2018.02.15
ViewResolver  (0) 2018.02.14
root-context & servlet-context  (0) 2018.02.14
ModelAndView 와 ViewResolver  (0) 2018.02.14
파일 업로드/ 다운로드  (0) 2018.02.09

root-context

view지원을 제외한 bean

Ex) service

repository

비즈니스로직

AOP



servlet-context

view를 포함한 bean

Ex ) Controller

MultipartResolver

Interceptor

URI

Client의 요청을 받기위한 설정 등



View 단이 JSP 에서 다른 형태로 변경된다고 했을때, 이때는 servlet-context.xml 설정만 변경하시면 됩니다.


토비의 스프링에서 늘 이야기 하듯 변화의 시기와 관심이 다르면 분할(SoC)하라.

'Programming > Spring' 카테고리의 다른 글

ViewResolver  (0) 2018.02.14
Spring 진행과정  (0) 2018.02.14
ModelAndView 와 ViewResolver  (0) 2018.02.14
파일 업로드/ 다운로드  (0) 2018.02.09
MariaDB Auto_Increment  (0) 2018.02.08

ModelAndView객체는 Model객체와 데이터를 넘길 페이지값을 가진 return을 합친 것입니다.
즉, 데이터와 넘길 페이지의 값을 모두 가지고 있는 객체입니다.

@Controller
public class TestModelAndView {
@RequestMapping("models/modelAndView")
    public ModelAndView testModelAndView(){
        ModelAndView mv = new ModelAndView();
        mv.addObject("name", "이름");
        mv.setViewName("/sample/modelAndView");
        return mv;
    }
}


혹은 생성자에서 ModelAndView("/sample/modelAndView") 해주어도 됩니다.

return mv하게되면 ViewResolver를 거쳐서 View를 보여주게됩니다.


ViewResolver는

Controller가 넘긴 View이름을 통해 알잡은 view를 찾는 역할을 합니다.

Controller는 ModelAndView객체에 응답할 view이름을 넣어 넘기면

DispatchServlet은 ViewResolver에게 응답한 view를 요청한다

ViewResolver는 View이름을 이용해 알맞은 viwe객체를 찾아서 DispatchResolver에게 전달한다.


viewResolver를 context에 선언해주어야한다. 


    <!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
    <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <beans:property name="prefix" value="/WEB-INF/views/" />
        <beans:property name="suffix" value=".jsp" />
    </beans:bean>


'Programming > Spring' 카테고리의 다른 글

Spring 진행과정  (0) 2018.02.14
root-context & servlet-context  (0) 2018.02.14
파일 업로드/ 다운로드  (0) 2018.02.09
MariaDB Auto_Increment  (0) 2018.02.08
DAO, DTO, VO  (0) 2018.02.08

+ Recent posts