2015년 4월 9일 목요일

Node.js # 소개 및 내부구조

node.JS에 대한 소개

node.js는 single thread 기반으로 동작하는 고성능의 비동기 IO (Async / Non-blocking IO)를 지원하는 네트워크 서버이다. 2009년 Ryan Dahl에 의해서 개발이 시작되어 있으며, 현재 수많은 지원 모듈과, 레퍼런스, 에코 시스템을 가지고 있는 오픈 소스 프로젝트 중에 하나이다.
Google Chrome V8 엔진으로 개발되어 있으며, 프로그래밍 언어로는 Java script를 사용하며, Event 기반의 프로그래밍 모델을 사용한다. 

node.JS 의 장점

먼저 node.js의 장점을 짚고 넘어가보면 다음과 같다.
Javascript 기반이고, 개발 구조가 매우 단순화 되어 있어서 빠르게 개발이 가능하다. 즉 클라이언트에서 front end를 자바스크립트를 통해서 개발하던 FE(front end) 개발자들도 손쉽게 서버 프로그래밍이 가능하다는 것이고, 조직의 입장에서도 FE와 BE(BackEnd) 엔지니어의 기술셋을 나눌 필요가 없다는 것이다. node.js가 빠르다고는 하지만, 실제 성능보다는 이러한 Learning curve나, 조직내의 FE/BE 기술 통합에서 오는 장점이 더 큰이유가 아닐까 싶다.
다음으로는 socket.io를 이용한 웹 push 구현이 매우 쉽게 구현이 가능하다. 여타 플랫폼도 WebSocket을 이용한 Push 를 지원하기는 하지만,WebSocket은 브라우져 종류나 버전에 따라서 제한적으로 동작한다. node.js의 경우 웹브라우져의 종류에 따라서 WebSocket뿐만 아니라, Long Polling등 다른 push 메커니즘을 브라우져 종류에 따라서 자동으로 선택하여 사용하고 있으며, 이러한 push 메커니즘은 socket.io API 내에 추상화 되어 있기 때문에, 어떤 기술로 구현이 되어 있던간에 개발자 입장에서는 socket.io만 쓰면 간단하게 웹 기반의 push 서비스가 구현이 가능하다.
마지막으로, non-blocking IO 모델을 지원하는데, 뒤에서 자세하게 설명하겠지만, 일반적인 서버들은 io 요청을 보낸후, 요청을 보낸 thread나process가 io 요청이 끝날 때 까지 io wait 상태로 응답을 기다리고 있다. 이로 인해서, 동시에 서비스할 수 있는 클라이언트 수 (thread가 계속 기다리기 때문에)에 제약이 있고, CPU 사용 효율에도 제약을 갖는다. 이러한 문제를 해결하기 위해서 node.js는 non-blocking io 컨셉을 사용하는데, io 요청이 있으면, io 처리를 던져 놓고, thread나 process는 다른 일을 하고 있다가, io 처리가 끝나면 이에 대한 이벤트를 받아서, 응답을 처리하는 형태가 된다.

node.js의 내부 작동 원리 구조

다음으로는 node.js의 내부 구조에 대해서 가볍게 살펴보도록 하자.
Node.js는 Google의 Chrome V8 자바스크립트 엔진을 기본으로 동작한다. 이를 기반으로 Single Thread 기반의 Event Loop (libuv)가 돌면서 요청을 처리하며, 시스템적으로 non-blocking io를 지원하지 않는 io 호출이 있는 경우, 이를 비동기 처리 하기 위해서 내부의 Thread pool (libio)을 별도 이용하여 처리한다.
그 위에 네트워크 프로토콜을 처리하는 socket, http 바인딩 모듈이 로드 되고, 맨 윗단에, node.js에서 제공하는 standard library (파일 핸들링, console등)이 로드 된다.


## 

Async / Non blocking IO

먼저 동기식 IO는 다음과 같이 동작한다. file write io를 예를 들어보면, file_write를 호출하면, 디스크에 파일 쓰기 요청을 하고, 디스크가 파일을 쓰는 동안 프로그램은 file_write 부분에 멈춰서 대기하게 된다. (블록킹상태). 파일을 쓰는 동안에는 CPU가 사용되지 않기 때문에, CPU는 놀고,파일이 다 써지만 디스크에서 리턴해서 file_write 함수 다음 코드로 진행을 하게 된다.



 비동기식 IO는 어떻게 처리가 될까?

파일 쓰기 요청을 할 때, 파일 요청이 끝나면 호출될 함수(callback)를 같이 넘긴다.
파일 쓰기 요청이 접수되면 프로그램은 파일이 다 써지는 것을 기다리지 않고, 요청만 던지고 다음 코드로 진행을 계속하낟. 파일을 다 쓰고 나면 앞에서 등록했던 callback 함수를 호출하여 파일 쓰기가 다 끝났음을 알리고 다음 처리를 한다.
비교를 해서 설명하자면 파일 쓰기가 떡뽁이를 주문하는 과정이라면, 앞의 블록킹 IO 방식의 경우에는 떡뽁이를 주문해놓고, 나오기 까지 기다리고 있는 형태라면, 비동기식 방식은 떡뽁이를 주문해놓고, 나가서 다른 일들을 하다가 나오면, 떡뽁이가 나왔으니 가져가라고 전화(이벤트)를 하는 형태가 된다.


Single Thread Model

Tomcat,JBoss와 같은 웹애플리케이션 서버나 Apache와 같은 일반적인 웹서버는 Multi Process 또는 Multi Thread의 형태를 가지고 있다.


톰캣과 같은 서버는 위의 그림과 같이 Client에서부터 요청이 오면, Thread를 미리 만들어 놓은 Thread Pool에서 Thread를 꺼내서 Client의 요청을 처리하게 하고, 요청이 끝나면 Thread Pool로 돌려보낸 후, 다른 요청이 오면 다시 꺼내서 요청을 처리하게 하는 구조이다. 동시에 서비스 할 수 있는 Client의 수는 Thread Pool의 Thread 수와 같은데,물리적으로 생성할 수 있는 Thread의 수는 한계가 있다. 예를 들어 Tomcat의 경우500개 정도의 쓰레드를 생성할 수 있다. (물론 2,000개 정도까지도 생성할 수 있지만, 한계가 있다.) 즉 동시에 처리할 수 있는 Client 수에 한계가 있다.
또한 IO 효율면에서도 보면, 아래 그림과 같이 Client에 할당된 Thread는 IO 작업 (DB,Network,File)이 있을 경우 IO 호출을 해놓고, Thread는CPU를 사용하지 않는 Wait상태로 빠져 버리게 된다.



이런 문제를 해결 하기 위한 것이 Single Thread 기반의 비동기 서버인데, 하나의 Thread만을 사용해서 여러 Client로부터 오는 Request를 처리한다. 단, IO 작업이 있을 경우 앞에서 설명한 비동기 IO방식으로 IO 요청을 던져놓고, 다시 돌아와서 다른 작업을 하다가 IO 작업이 끝나면 이벤트를 받아서 처리하는 구조이다.
아래 그림에서 처럼, Client A가 요청을 받으면, CPU 작업을 먼저하다가 IO작업을 던져놓고, Client B에서 요청이 오면, CPU작업을 하다가 IO작업을 던져놓고, Client A의 IO작업이 끝나면 이를 받아서 Client A에 리턴하는 식의 구조이다. IO작업시 기다리지 않기 때문에(Block 되지 않기 때문에), 하나의 Thread가 다른 요청을 받아서 작업을 처리할 수 있는 구조가 된다.  이 요청을 받아서 처리하는 Thread를 ELP (Event Loop Thread)라고 한다.


Thread pool
Node.js도 single thread만 사용하는 것이 아니라 내부적으로 multi thread pool을 사용하기는 한다. 예를 들어 file open등과 같은 일부 IO는 OS에 따라서 nonblocking function을 지원하지 않는 경우가 있기 때문에, 이러한 blocking io function을 호출할 경우에는 어쩔 수 없이 blocking이 발생하는 데, 이 경우 single thread로 구현된 event loop thread가 정지되기 때문에 이러한 문제를 해결 하기 위해서 내부적으로 thread pool을 별도로 운영하면서 blocking function call의 경우에는 thread pool의 thread를 이용하여 IO 처리를 하여 event loop thread가 io에 의해서 block되지 않게 한다.


Event Loop

그러면 이 하나의 Thread로 여러 클라이언트의 요청, 즉 여러 개의 socket connection을 어떻게 처리할까? 방법은 Multiplexing에 있다. 여러 개의 socket이 동시에 연결되어 있는 상태에서 하나의 Thread는 어느 socket으로부터 메시지가 들어오는 지 보다가, socket에서 메시지가 들어오면, 그 메시지를 꺼내 받아서 처리를 하는 방식이다. (epoll, kqueue, dev/poll ,select등을 이용)



개념적으로 생각하면,
socket fd = array[연결된 socket connections]
for(int i=0;i<fd.length;i++){
if(fd 가 이벤트가 있으면){
   알고리즘 처리
}// if
}// if
와 같이 표현할 수 있다. (실제 구현체는 다르지만.).
단 이런 single thread 모델에서 주의해야 하는 점은 CPU 작업이 길어질 경우에는 다음 request를 처리하지 못하기 때문에, 다음 request처리가 줄줄이 밀려버릴 수 있다는 것이다. 예를 들어 보자 커피 전문점이 있다고 보자, 주문을 받는 사람이 Single Thread이다. 커피 주문이 들어오면 들어오면 주방에서 일하는 사람에게 커피 주문을 넘기고, 다음 고객의 주문을 받는다. 앞에서 주문한 커피가 주방에서 나오면 이를 주문한 사람에게 커피를 넘겨준다.
커피 주문을 request, 커피를 response, 그리고 커피를 만드는 과정을 IO라고 생각해보자. 만약에 커피를 주문받거나 커피를 건네주는 과정에 많은 시간이 소요된다면 (CPU 작업이 많다면), 뒤에 손님이 기다리는 일이 발생하게 된다. 예를 들어 주문에 1분씩 소요된다면, 첫번째 손님은 1분을, 두번째는 2분을… 60번째는 60분을 기다리게 된다. 그래서 이러한 single thread model에서는 각 request가 CPU를 많이 사용하는 경우request 처리가 줄줄이 지연되면서 성능에 심각한 영향을 줄 수 있기 때문에 CPU intensive한 작업에는 적절하지가 않다.


언제 node.js를 쓰거나 쓰지 말아야 할까?

node.js의 사용 용도에 대해서는 논쟁이 많기는 하지만, 공통적으로 공감하는 부분은 prototyping에는 무지 빠르다. mysql이나 mongodb같은  persistence를 이용해서 CRUD rest api를 implementation하는데, 코딩양이 20~30줄? 정도밖에 안된다. (자동 생성되는 코드를 빼면 이것 보다 적을지도.)
Async IO를 사용하기 때문에, file upload/download와 같은 network streaming 서비스에 유리하다. 또한 real time web application, 예를 들어 채팅 서비스 같은 곳에 socket.io를 이용하면 쉽게 만들 수 있으며, Single page app 개발에 좋다. 가볍고 생산성이 높은 웹 개발 프레임웍을 가지고 있고, 간단한 로직을 가지면서 대용량 그리고 빠른 응답 시간을 요구로 하는 애플리케이션에 적절하다.
그러면 어디에 쓰지 말아야 할것인가?
공통적으로 대답하는 것은. CPU 작업이 많은 애플리케이션에는 절대 적당하지 않다. Node.js는 single thread 구조이다. 그래서 하나의  request를 처리할 때 CPU를 많이 사용하면 다른 요청 처리가 지연되게 되고, 전체적인 응답시간 저하로 연결된다.
그리고 다소 이견이 있기는 하지만 CRUD가 많고 페이지가 많은 웹개발에는 적절하지 않다고 한다.  일단 기존의 Ruby on Rails나 Python,PHP등의 웹 프레임웍의 성숙도가 높기 때문이라고 한다.  (직접 테스트 해보니, express와 같은 많은 웹 개발 프레임웍이 있는데, 사실 보면 성숙도가 꽤 높다. 다른 이유가 있는지는 모르겠지만 일반적인 웹 개발에는 추천하지 않는다.)
마지막으로, 초보자나 초보팀이 쓰기에는 적절하지 않다는 것이 본인의 의견이다. javascript언어의 특성상 에러를 가지고 있는 코드 위치에 진입할 때 그때 에러가 나고, 에러가 나면 대부분 서버가 죽어버리기 때문에 운영 관점에서 trouble shooting등이 어려울 수 있으며,  single event loop의 특성상, 하나의 코드가 잘못되서 시스템이 느려지게 되면 전체 request 처리에 문제가 올 수 있기 때문에, 잘 짜야진 코드는 필수이다. 물론 자바 기반의 일반 application server 나 다른 application server도 마찬가지이기는 하지만, 코드가 잘못되었다고 node.js처럼 무작정 서버가 내려않거나, 전체 시스템이 hang up (멈춤)지는 않는다.





2014년 11월 14일 금요일

JSP에서 JSTL과 EL(Expression Language) 사용


Expression Language는 JSP에서 기본으로 지원한다
세팅해야 할 것은 JSTL( JavaServer Pages Standard Tag Library ) 이다

1. 다운로드
http://tomcat.apache.org/ - Taglibs - Standard - JSTL 1.1 download - binaries - jakarta-taglibs-standard-1.1.2.zip

2. 설치

다운로드 받은 파일에 압축을 풀고 lib 폴더의 jstl.jar 와 standard.jar 파일을 /WEB-INF/lib 에 복사 후
JSP 파일 상단에 다음의 지시문을 추가한다 
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
( core 태그 lib만 추가하였고 그외  fmt, sql, x 태그를 사용하기 위해선 지시문을 추가해야 한다 )
tip) 이클립스에서 JSP 생성시 자동으로 소스 추가하는 방법
window - Prefrences - Web - JSP Files - Templates - JSP HTML에 내용 추가

3. Expression language
EL이란 JSP에서 저장객체를 출력할때 스크립팅을 전혀 쓰지 않을 수 있는 기술이다
이것으로 인해 <%=request.getParameter("name")%> 와 같은 스크립팅 들을 쓸필요가 없어진다
(1) 저장객체 접근

${param.name} => request.getParameter("name");

${member} => request.getAttribute("member");

    저장객체의 attribute 에 자동으로 접근하는데
    자동검색순위는 page, request, session, application 순이다

${member.name} => Member m = (Member)request.getAttribute("member");
                                    m.getName();

    attribute 저장된 member의 name속성(또는 키)에 접근
    도트(.) 의 왼쪽은 Beans 이거나 Map 타입이어야 한다


$
{list["0"]} => List list = (List)request.getAttribute("list");
                         list.get(0);
    attribute 저장된 list를 가져온다
    []안에 값은 list의 키값 or 프로퍼티명 or 인덱스이다
    []의 왼쪽은 Map, Beans, 배열, List 타입이 올 수 있다

    여기서 알아야 할 점이 하나 있는데 EL에서는 해당값이 null이거나 공백일 경우에는 
    아무 내용도 표시하지 않고 에러도 발생하지 않는다..

(2) 연산자
EL은 연산자들을 사용할 수 있다. 예제는 JSTL에서 같이 사용하는걸 보자

1) 산술연산자 : + - * / (div) % (mod)
2) 논리연산자 : &&(and) ||(or) !(not)
3) 관계연산자 : ==(eq) !=(ne) <(lt) >(gt) <=(le) >=(ge)
4) 삼항연산자 : ?:
예) ${colors == null ? "transparent" : colors}

5) 그 외 예약어 : true, false, null, instanceof, empty( null이거나 공백일때 )

4. JSTL 

        core태그 중에서 유용한 것들만 살펴보자 
        모든 내용을 보고 싶다면 JSTL 매뉴얼 참조
        태그에 진하게 표시된 속성은 필수 항목이다

(1) <c:out>

단순히 내용을 출력하는 기능이다

<c:out value="${member.name}" default="이재원" />

위와 과 같이 쓸 경우 일반적으로 ${member.name} 을 쓴것과 같지만
${member.name}이 null일 경우 dafault에 있는 값으로 대채한다

(2) <c:set>

저장객체의 setAttriubte 기능을 한다

<c:set var="addr" value="myaddr" scope="session" /> 
<c:set var="no" value="${member.age+1}"  /> 
첫번째는 session 에 addr 이란이름으로 myaddr 스트링을 저장한 것이다
두번째는 no 이란이름으로 member.age+1으로 산술연산된 값을 저장하는데 
scope를 생략하면 기본으로 page에 저장된다

(3) <c:remove>
    removeAttribute의 기능을 한다

    <c:remove var="no" scope="page" />

    지정된 scope의 no란 이름이 attribute를 remove한다
    scope를 생략할 경우 모든 범위의 attribute 가 지워진다


(4) <c:if>

    if 조건문을 사용한다.. 단 else if 와 else는 지원하진 않고 유사한 <c:choose> 가 존재한다

    <c:if test="${member.age < 20}" var="result" scope="page">
        당신은 미성년자입니다
    </c:if>
    <c:if test="${!empty list}" >
        content
    </c:if>
    test에서 조건을 검사하고 true 일 경우에만 <c:if>태그안의 내용이 보여진다 
    여기서 EL 의 연산자들을 쓸 수 있다!!

    var는 조건을 검사하고 리턴되는 boolean값을 저장하는 attribute이름이고 
    scope는 var가 저장되는 범위입니다. scope가 없을시 기본값은 page이다


(5) <c:choose> 

if, else if, else 와 유사하다. 아니 똑같다고 봐도 된다
<c:choose> 태그안에는 <c:when> 과 <c:otherwise>가 들어간다
<c:choose>
<c:when test="${vo.type==1}">
    <img src="<%=cp%>/images/antenna/type1.jpg" />
</c:when>
<c:when test="${vo.type==2}">
    <img src="<%=cp%>/images/antenna/type2.jpg" />
</c:when>
<c:when test="${vo.type==0 or vo.type==3}">
    <img src="<%=cp%>/images/antenna/type_ment.jpg" />
</c:when>
<c:otherwise>
    <img src="<%=cp%>/images/antenna/type_heart.jpg" />
</c:otherwise>
</c:choose>

<c:choose>안에서 순서대로 <c:when> 의 조건문을 검사해 true가 나오면 
해당 <c:when> 태그안에 내용을 보여주고 <c:choose>문은 끝나게 된다

만약 모든 <c:when>의 조건문이 false일 경우 <c:otherwise>안에 내용을 보여주게된다


(6) <c:forEach>

Collection을 반복할때 쓰인다. 게시판 같은 반복적인 처리를 할때 매우 유용하다

지원되는 컬렉션을 다음과 같다
Arrays ( 배열 )
java.util.Collection
java.util.Iterator
java.util.Enumeration
java.util.Map
<table>
<c:forEach items="${list}"  var="notice"  varStatus="status">
<tr>
    <td>${status.count}</td>
    <td>${notice.title}</td>
    <td>${notice.writer}</td>
    <td>${notice.wdate}</td>
    <td>${notice.readcount}</td>
</tr>
</c:forEach>
</table>

items는 Collection 객체를 말한다
var는 Collection에서 객체를 하나씩 가져올때마다 담는 객체다
객체가 Beans일 경우 Beans클래스를 자동으로 찾아 담는다
varStatus는 일련의 속성들을 정의한 객체다 ( javax.servlet.jsp.jstl.core.LoopTagStatus )

다음은 일반적은 for문 처럼 사용한 예다

<c:forEach var="x" begin="0" end="10" step="2">
${x},
${x*x}
</c:forEach>
(7) <c:forTokens>

<c:forEach> 와 같이 반복 태그로 이것은 StringTokenizer 클래스를 이용한다.. 그다지 쓸일은 없다
구분자( delims ) 를 공백, 쉼표, 마침표 로 한 예이다

<c:forTokens items="${param.text}" delims=" ,." var="word" begin="1" end="10" step="1" >
    ${word}
</c:forTokens>