달력

4

« 2024/4 »

  • 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
2014. 8. 4. 12:03

[펌] Spring 4.0에서 간단한 Rest 서버 구축하기 2014. 8. 4. 12:03

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

2014. 8. 1. 15:59

[펌] JAVA 의 ENUM 정리. I.lib()/I.lib(Java)2014. 8. 1. 15:59

.. .. ..

.

[펌] http://iilii.egloos.com/4343065

 

java enum 정리 1 - 기본 개념

enum에 대해서 총 3부작으로 연재를 할려고 합니다.

1 - 기본 개념
2 - enum의 메쏘드
3 - java.lang.Enum 클래스와 enum의 실체

이 글은 그 첫번째로 enum에 대한 개념을 잡는 글입니다.

enum은 비스무레한 원소들이 상수로 정의되는 클래스를 대신하기 위한 것입니다. java 1.5에서 추가되었죠.
Gender를 정의하려면 enum을 사용하지 않으면 다음과 같은 class로 정의할 수 있습니다.

public class Gender{
    public static final int MALE = 1;
    public static final int FEMALE = 2;
}

위와 같은 방법이 가지는 문제점이 몇 가지 있습니다. 
첫째, MALE은 무조건 1이지만, 1이라는 숫자가 MALE만 나타내는 것은 아닙니다.
둘째, 이 Gender의 값들을 받아서 쓰는 메쏘드에서는 1,2 이외의 숫자가 들어오면 어떤 식으로 처리를 해 주어야 할지 난감합니다.

이런 경우 Gender은 다음과 같은 enum으로 정의하면 됩니다.

public enum Gender {
    MALE, FEMALE 
}

이제 Gender를 인자로 받아서 어떤 성별이 들어왔는지 찍어주는 메쏘드를 만들어봅시다.

public class GenderTest {
    public static void printGender (Gender d){
        System.out.println(d);
    }
    public static void main(String[] args) {
        printGender(Gender.MALE);
    }
}

enum은 swtich - case에서도 사용할 수 있습니다. 이번에는 남자면 "싫어", 여자면 "좋아"를 출력해주는 메쏘드를 만들어봅시다.

public class GenderTest {
    public static void printGender(Gender d){
        System.out.println(d);
    }
    public static void print(Gender d){
        switch (d) {
        case MALE: System.out.println("싫어");break;
        default:        System.out.println("좋아");break;
        }
    }
    public static void main(String[] args) {
        printGender(Gender.MALE);
        print(Gender.MALE);
        print(Gender.FEMALE);
    }
}


여기서 유의할 점은 case MALE: 부분입니다. case Gender.MALE이 아니고 그냥 MALE입니다. switch 에 enum이 들어올 경우 컴파일러가 알아서 case 쪽에는 그 enum의 타입을 적용해 줍니다.

Gender에 어떤 요소들이 들어가 있는 지 살펴볼 수 있는 방법을 소개하죠.

for(Gender g : Gender.values()){
    System.out.println(g);
}

.
:
Posted by .07274.
2014. 7. 4. 10:50

Gradle 정보 정리 I.lib()/I.lib(etc)2014. 7. 4. 10:50

.. .. ..

 

 

Gradle 기반 Spring Boot 프로젝트 구축하기

http://theeye.pe.kr/archives/2014

 

Gradle 사용자 가이드 (한글)

http://kwonnam.pe.kr/wiki/gradle

 

Maven을 넘어 Gradle로 가자.

http://kwon37xi.egloos.com/viewer/4747016

 

 

gradle로 이클립스 Spring MVC 프로젝트 생성

 

http://huskdoll.tistory.com/8

 

 

 

.
:
Posted by .07274.
2014. 7. 2. 19:13

방송 통신 용어 정리 I.lib()/I.lib(etc)2014. 7. 2. 19:13

.. .. ..
유니캐스트 (Unicast)

- 1:1 통신 방법으로 특정 목적지의 주소 하나만 가지고 통신하는 방식이다.
- 그 목적지가 아닌 다른 pc들은 건드리지 않아서 cpu의 성능을 저하시키지 않는다.
  그 이유는 자신의 mac 주소가 아니라고 판단 되면 랜카드에서 프레임을 버리기 때문이다.
- 동일한 정보를 많은 호스트에게 전달할 때에는 비효율 적이다.

 

 

브로드 캐스트 (Broadcast)

- 단일 대상이 아닌 내가 속한 네트워크 안(로컬 LAN) 에 있는 모든 네트워크 장비들에게 전달하는 방식
-쉽게 말하면 시골마을(로컬LAN)안에서 이장님이 마이크로 방송하는 것이라고 생각하면됨
-브로드캐스트도메인 : 방송을 하면 들리는 영역 , 라우터를 기준으로 나눌수 있다.
-유니캐스트처럼 거부권이 없어서 무조건 받아야 한다 CPU는 하던일을 멈추고 이것(인터럽트)을 먼저
  수행해야 하기때문에 PC에 부담이 간다.

 

멀티캐스트 (Multicast)

-200명의 사용자 중에서 150명에게만 정보를 동시에 보내고 싶을때 사용하는것
-유니캐스트와 브로드캐스트를 합친것이라고 생각하면 된다.
-멀티캐스트는 보내고자 하는 그룹맴버들 에게만 한번에 보낼수 있다.
-단점이라면 스위치나 라우터가 이 멀티캐스트 기능을 지원해야 가능하다. (아래의 IGMP)

 

IGMP (Internet Group Message Multicast)

 

- 네트워크 멀티캐스트 트래픽을 조절 및 제한 하여 수신자 그룹에게 메시지를 동시에 전송하는데 사용

- IGMP 는 위의 3개 중 멀티캐스트 에 관여되는 프로토콜로 멀티캐스트 그룹을 관리하는 역할

 

 

.
:
Posted by .07274.
2014. 7. 1. 11:52

[펌] 필터란 무엇인가! I.lib()/I.lib(Java)2014. 7. 1. 11:52

.. .. ..

 

[펌] : http://javacan.tistory.com/entry/58

 

 

서블릿 2.3에 새롭게 추가된 필터가 무엇이며, 어떻게 구현하는지에 대해서 살펴본다.

필터!!

현재 서블릿 2.3 규약은 Proposed Final Draft 2 상태에 있다. 조만간 서블릿 2.3과 JSP 1.2 최종 규약이 발표될 것으로 예상되며 우리는 당연히 새롭게 추가된 것들이 무엇인지에 관심이 쏠리게 된다. 서블릿 2.3 규약에 새롭게 추가된 것 중에 필자가 가장 눈여겨 본 것은 바로 필터(Filter) 기능의 추가이다.

그 동안 필자는 서블릿 2.2와 JSP 1.1에 기반하여 웹 어플리케이션을 구현하는 동안 몇몇 부분에서 서블릿 2.2 규약의 부족한 면을 느낄 수 있었으며, 특히 사용자 인증 처리, 요청 URL에 따른 처리, XSL/T를 이용한 XML 변환(Transformation) 등 개발자들이 직접 설계해야 하는 부분이 많았었다. 하지만, 이제 서블릿 2.3 규약에 새롭게 추가된 필터(Filter)를 사용함으로써 개발자들이 고민해야 했던 많은 부분을 덜어낼 수 있게 되었다. 이 글에서는 필터가 무엇이며 어떻게 필터를 구현하는지에 대해 살펴볼 것이다.

간단하게 말해서, 필터는 'HTTP 요청과 응답을 변경할 수 있는 재사용가능한 코드'이다. 필터는 객체의 형태로 존재하며 클라이언트로부터 오는 요청(request)과 최종 자원(서블릿/JSP/기타 문서) 사이에 위치하여 클라이언트의 요청 정보를 알맞게 변경할 수 있으며, 또한 필터는 최종 자원과 클라이언트로 가는 응답(response) 사이에 위치하여 최종 자원의 요청 결과를 알맞게 변경할 수 있다. 이를 그림으로 표현하면 다음과 같다.

그림1 - 필터의 기본 구조
그림1에서 자원이 받게 되는 요청 정보는 클라이언트와 자원 사이에 존재하는 필터에 의해 변경된 요청 정보가 되며, 또한 클라이언트가 보게 되는 응답 정보는 클라이언트와 자원 사이에 존재하는 필터에 의해 변경된 응답 정보가 된다. 위 그림에서는 요청 정보를 변경하는 필터와 응답 정보를 변경하는 필터를 구분해서 표시했는데 실제로 이 둘은 같은 필터이다. 단지 개념적인 설명을 위해 그림1과 같이 분리해 놓은 것 뿐이다.

필터는 그림1에서처럼 클라이언트와 자원 사이에 1개가 존재하는 경우가 보통이지만, 여러 개의 필터가 모여 하나의 체인(chain; 또는 사슬)을 형성할 수도 있다. 그림2는 필터 체인의 구조를 보여주고 있다.

그림2 - 필터 체인
그림2와 같이 여러 개의 필터가 모여서 하나의 체인을 형성할 때 첫번째 필터가 변경하는 요청 정보는 클라이언트의 요청 정보가 되지만, 체인의 두번째 필터가 변경하는 요청 정보는 첫번째 필터를 통해서 변경된 요청 정보가 된다. 즉, 요청 정보는 변경에 변경에 변경을 거듭하게 되는 것이다. 응답 정보의 경우도 요청 정보와 비슷한 과정을 거치며 차이점이 있다면 필터의 적용 순서가 요청 때와는 반대라는 것이다. (그림2를 보면 이를 알 수 있다.)

필터는 변경된 정보를 변경하는 역할 뿐만 아니라 흐름을 변경하는 역할도 할 수 있다. 즉, 필터는 클라이언트의 요청을 필터 체인의 다음 단계(결과적으로는 클라이언트가 요청한 자원)에 보내는 것이 아니라 다른 자원의 결과를 클라이언트에 전송할 수 있다. 필터의 이러한 기능은 사용자 인증이나 권한 체크와 같은 곳에서 사용할 수 있다.

필터 관련 인터페이스 및 클래스

필터를 구현하는데 있어 핵심적인 역할을 인터페이스 및 클래스가 3개가 있는 데, 그것들은 바로 javax.servlet.Filter 인터페이스, javax.servlet.ServletRequestWrapper 클래스, javax.servlet.ServletResponseWrapper 클래스이다. 이 중 Filter 인터페이스는 클라이언트와 최종 자원 사이에 위치하는 필터를 나타내는 객체가 구현해야 하는 인터페이스이다. 그리고 ServletRequestWrapper 클래스와 SerlvetResponseWrapper 클래스는 필터가 요청을 변경한 결과 또는 응답을 변경할 결과를 저장할 래퍼 클래스를 나타내며, 개발자는 이 두 클래스를 알맞게 상속하여 요청/응답 정보를 변경하면 된다.

Filter 인터페이스

먼저, Filter 인터페이스부터 살펴보자. Filter 인터페이스에는 다음과 같은 메소드가 선언되어 있다.

  • public void init(FilterConfig filterConfig) throws ServletException
    필터를 웹 콘테이너내에 생성한 후 초기화할 때 호출한다.
  • public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws java.io.IOException, ServletException
    체인을 따라 다음에 존재하는 필터로 이동한다. 체인의 가장 마지막에는 클라이언트가 요청한 최종 자원이 위치한다.
  • public void destroy()
    필터가 웹 콘테이너에서 삭제될 때 호출된다.
위 메소드에서 필터의 역할을 하는 메소드가 바로 doFilter() 메소드이다. 서블릿 콘테이너는 사용자가 특정한 자원을 요청했을 때 그 자원 사이에 필터가 존재할 경우 그 필터 객체의 doFilter() 메소드를 호출하며, 바로 이 시점부터 필터가 작용하기 시작한다. 다음은 전형적인 필터의 구현 방법을 보여주고 있다.

  public class FirstFilter implements javax.servlet.Filter {
  
     public void init(FilterConfig filterConfig) throws ServletException {
        // 필터 초기화 작업
     }
     
     public void doFilter(ServletRequest request,
                          ServletResponse response,
                          FilterChain chain)
                          throws IOException, ServletException {
        // 1. request 파리미터를 이용하여 요청의 필터 작업 수행
        // 2. 체인의 다음 필터 처리
        chain.doFilter(request, response);        // 3. response를 이용하여 응답의 필터링 작업 수행
     }
     
     public void destroy() {
        // 주로 필터가 사용한 자원을 반납
     }
  }

위 코드에서 Filter 인터페이스의 doFilter() 메소드는 javax.servlet.Servlet 인터페이스의 service() 메소드와 비슷한 구조를 갖는다. 즉 만약 클라이언트의 자원 요청이 필터를 거치는 경우, 클라이언트의 요청이 있을 때 마다 doFilter() 메소드가 호출되며, doFilter() 메소드는 서블릿과 마찬가지로 각각의 요청에 대해서 알맞은 작업을 처리하게 되는 것이다.

위 코드를 보면 doFilter() 메소드는 세번째 파라미터로 FilterChain 객체를 전달받는 것을 알 수 있다. 이는 클라이언트가 요청한 자원에 이르기까지 클라이언트의 요청이 거쳐가게 되는 필터 체인을 나타낸다. FilterChain을 사용함으로써 필터는 체인에 있는 다음 필터에 변경한 요청과 응답을 건내줄 수 있게 된다.

위 코드를 보면서 우리가 또 하나 알아야 하는 것은 요청을 필터링한 필터 객체가 또 다시 응답을 필터링한다는 점이다. 위 코드의 doFilter() 메소드를 보면 1, 2, 3 이라는 숫자를 사용하여 doFilter() 메소드 내에서 이루어지는 작업의 순서를 표시하였는데, 그 순서를 다시 정리해보면 다음과 같다.

  1. request 파리미터를 이용하여 클라이언트의 요청 필터링
    1 단계에서는 RequestWrapper 클래스를 사용하여 클라이언트의 요청을 변경한다.
  2. chain.doFilter() 메소드 호출
    2 단계에서는 요청의 필터링 결과를 다음 필터에 전달한다.
  3. response 파리미터를 사용하여 클라이트로 가는 응답 필터링
    3 단계에서는 체인을 통해서 전달된 응답 데이터를 변경하여 그 결과를 클라이언트에 전송한다.
1단계와 3단계 사이에서 다음 필터로 이동하기 때문에 요청의 필터 순서와 응답의 필터 순서는 그림2에서 봤듯이 반대가 된다.

필터의 설정

필터를 사용하기 위해서는 어떤 필터가 어떤 자원에 대해서 적용된다는 것을 서블릿/JSP 콘테이너에 알려주어야 한다. 서블릿 규약은 웹 어플리케이션과 관련된 설정은 웹 어플리케이션 디렉토리의 /WEB-INF 디렉토리에 존재하는 web.xml 파일을 통해서 하도록 하고 있으며, 필터 역시 web.xml 파일을 통해서 설정하도록 하고 있다.

web.xml 파일에서 필터를 설정하기 위해서는 다음과 같이 <filter> 태그와 <filter-mapping> 태그를 사용하면 된다.

  <web-app>
     
     <filter>
        <filter-name>HighlightFilter</filter-name>
        <filter-class>javacan.filter.HighlightFilter</filter-class>
        <init-param>
           <param-name>paramName</param-name>
           <param-value>value</param-value>
        </init-param>
     </filter>
     
     <filter-mapping>
        <filter-name>HighlightFilter</filter-name>
        <url-pattern>*.txt</url-pattern>
     </filter-mapping>
     
  </web-app>

여기서 <filter> 태그는 웹 어플리케이션에서 사용될 필터를 지정하는 역할을 하며, <filter-mapping> 태그는 특정 자원에 대해 어떤 필터를 사용할지를 지정한다. 위 예제의 경우는 클라이언트가 txt 확장자를 갖는 자원을 요청할 경우 HithlightFilter가 사용되도록 지정하고 있다.

<init-param> 태그는 필터가 초기화될 때, 즉 필터의 init() 메소드가 호출될 때 전달되는 파라미터 값이다. 이는 서블릿의 초기화 파라미터와 비슷한 역할을 하며 주로 필터를 사용하기 전에 초기화해야 하는 객체나 자원을 할당할 때 필요한 정보를 제공하기 위해 사용된다.

<url-pattern> 태그는 클라이언트가 요청한 특정 URI에 대해서 필터링을 할 때 사용된다. 서블릿 2.3 규약의 11장을 보면 다음과 같이 url-pattern의 적용 기준을 명시하고 있다.

  • '/'로 시작하고 '/*'로 끝나는 url-pattern은 경로 매핑을 위해서 사용된다.
  • '*.'로 시작하는 url-pattern은 확장자에 대한 매핑을 할 때 사용된다.
  • 나머지 다른 문자열을 정확한 매핑을 위해서 사용된다.
에를 들어, 다음과 같이 <filter-mapping> 태그를 지정하였다고 해 보자.

     <filter-mapping>
        <filter-name>AuthCheckFilter</filter-name>
        <url-pattern>/pds/*</url-pattern>
     </filter-mapping>

이 경우 클라이언트가 /pds/a.zip 을 요청하든 /pds/b.zip 을 요청하는지에 상관없이 AuthCheckFilter가 필터로 사용될 것이다.

<url-pattern> 태그를 사용하지 않고 대신 <servlet-name> 태그를 사용함으로써 특정 서블릿에 대한 요청에 대해서 필터를 적용할 수도 있다. 예를 들면 다음과 같이 이름이 FileDownload인 서블릿에 대해서 AuthCheckFilter를 필터로 사용하도록 할 수 있다.

     <filter-mapping>
        <filter-name>AuthCheckFilter</filter-name>
        <servlet-name>FileDownload</servlet-name>
     </filter-mapping>
     
     <servlet>
        <servlet-name>FileDownload</servlet-name>
        ...
     </servlet>

래퍼 클래스

필터가 필터로서의 제기능을 하기 위해서는 클라이언트의 요청을 변경하고, 또한 클라이언트로 가는 응답을 변경할 수 있어야 할 것이다. 이러한 변경을 할 수 있도록 해 주는 것이 바로 ServletRequestWrapper와 ServletResponseWrapper이다. 서블릿 요청/응답 래퍼 클래스를 이용함으로써 클라이언트의 요청 정보를 변경하여 최종 자원인 서블릿/JSP/HTML/기타 자원에 전달할 수 있고, 또한 최종 자원으로부터의 응답 결과를 변경하여 새로운 응답 정보를 클라이언트에 보낼 수 있게 된다.

서블릿 요청/응답 래퍼 클래스로서의 역할을 수행하기 위해서는 javax.servlet 패키지에 정의되어 있는 ServletRequestWrapper 클래스와 ServletResponseWrapper 클래스를 상속받으면 된다. 하지만, 대부분의 경우 HTTP 프로토콜에 대한 요청/응답을 필터링 하기 때문에 이 두 클래스를 상속받아 알맞게 구현한 HttpServletRequestWrapper 클래스와 HttpServletResponseWrapper 클래스를 상속받는 경우가 대부분일 것이다.

HttpServletRequestWrapper 클래스와 HttpServletResponseWrapper 클래스는 모두 javax.servlet.http 패키지에 정의되어 있으며, 이 두 클래스는 각각 HttpServletRequest 인터페이스와 HttpServletResponse 인터페이스에 정의되어 있는 모든 메소드를 이미 구현해 놓고 있다. 필터를 통해서 변경하고 싶은 정보가 있을 경우 그 정보를 추출하는 메소드를 알맞게 오버라이딩하여 필터의 doFilter() 메소드에 넘겨주기만 하면 된다. 예를 들어, 클라이언트가 전송한 "company" 파리머터의 값을 무조건 "JavaCan.com"으로 변경하는 요청 래퍼 클래스는 다음과 같이 HttpServletRequestWrapper 클래스를 상속받은 후에 getParameter() 메소드를 알맞게 구현하면 된다.

  package javacan.filter;
  
  import javax.servlet.http.*;
  
  public class ParameterWrapper extends HttpServletRequestWrapper {
     
     public ParameterWrapper(HttpServletRequest wrapper) {
        super(wrapper);
     }
     
     public String getParameter(String name) {
        if ( name.equals("company") ) {
           return "JavaCan.com";
        } else {
           return super.getParameter(name);
        }
     }
  }

오버라이딩한 getParameter() 메소드를 살펴보면 값을 구하고자 하는 파라미터의 이름이 "company"일 경우 "JavaCan.com"을 리턴하고 그렇지 않을 경우에는 상위 클래스(즉, HttpServletRequestWrapper 클래스)의 getParameter() 메소드를 호출하는 것을 알 수 있다.

이렇게 작성한 래퍼 클래스는 필터 체인을 통해서 최종 자원까지 전달되어야 그 효과가 있을 것이다. 즉, 최종 자원인 서블릿/JSP에서 request.getParameter("company")를 호출했을 때 ParameterWrapper 클래스의 getParameter() 메소드가 사용되기 위해서는 ParameterWrapper 객체가 HttpServletRequest 객체를 대체해야 하는데, 이는 Filter 인터페이의 doFilter() 내에서 ParameterWrapper 객체를 생성한 후 파라미터로 전달받은 FilterChain의 doFilter() 메소드를 호출함으로써 가능하다. 좀 복잡하게 느껴질지도 모르겠으나 이를 코드로 구현해보면 다음과 같이 간단한다.

  package javacan.filter;
  
  import javax.servlet.*;
  import javax.servlet.http.*;
  
  public class ParameterFilter implements Filter {
     
     private FilterConfig filterConfig;
     
     public ParameterFilter() {
     }
     
     public void init(FilterConfig filterConfig) {
        this.filterConfig = filterConfig;
     }
     
     public void destroy() {
        filterConfig = null;
     }
     
     public void doFilter(ServletRequest request,
                          ServletResponse response,
                          FilterChain chain)
                          throws java.io.IOException, ServletException {
        // 요청 래퍼 객체 생성
        HttpServletRequestWrapper requestWrapper = 
                     new ParameterWrapper((HttpServletRequest)request);
        // 체인의 다음 필터에 요청 래퍼 객체 전달
        chain.doFilter(requestWrapper, response);     }
  }

응답 래퍼 클래스 역시 요청 래퍼 클래스와 비슷한 방법으로 구현된다.

앞에서도 언급했듯이 요청 정보의 변경 및 응답 정보 변경의 출발점은 래퍼 클래스이다. XML+XSL/T 기법이나 사용자 인증과 같은 것들을 최종 자원과 분리시켜 객체 지향적으로 구현하기 위해서 요청/응답 래퍼 클래스를 사용하는 것은 필수적이다. 2부에서 실제 예를 통해서 어떻게 필터와 요청/응답 래퍼 클래스를 효과적으로 사용할 수 있는 지 살펴보게 될 것이다.

필터 체인의 순서

앞에서 필터는 체인을 형성할 수 있다고 하였다. 체인을 형성한다는 것은 어떤 특정한 순서에 따라 필터가 적용된다는 것을 의미한다. 예를 들면, 여러분은 '인증필터->파라미터 변환 필터->XSL/T 필터->자원->XSL/T 필터->파라미터 변환 필터->인증필터'와 같이 특정 순서대로 필터를 적용하길 원할 것이다. 서블릿2.3 규약은 다음과 같은 규칙에 기반하여 필터 체인 내에서 필터의 적용 순서를 결정한다.

  1. url-pattern 매칭은 web.xml 파일에 표기된 순서대로 필터 체인을 형성한다.
  2. 그런 후, servlet-name 매칭이 web.xml 파일에 표기된 순서대로 필터 체인을 형성한다.
결론

이번 1 부에서는 서블릿 2.3 규약에 새롭게 추가된 필터가 무엇인지 그리고 필터를 어떻게 구현하며 또한 필터를 어떻게 서블릿이나 JSP와 같은 자원에 적용할 수 있는지에 대해서 알아보았다. 아직 구체적으로 필터의 응용방법에 대해서 설명하지 않았기 때문에 필터의 장점이 머리에 떠오르지 않을것이다. 다음 2 부에서는 구체적으로 필터의 예를 살펴봄으로써 필터의 활용함으로써 얻게 되는 장점에 대해서 살펴보도록 하자.

관련링크:

 

.
:
Posted by .07274.
2014. 6. 24. 19:01

[펌] In memory dictionary Redis 소개 I.lib()/I.lib(etc)2014. 6. 24. 19:01

.. .. ..

 

Redis 자료를 찾으려 하니 마침 조대협님의 금쪽같은 자료가 있어 퍼왔습니다.

 

[펌] : http://bcho.tistory.com/654

 

redis Introduction


Intro
Redis는 "REmote DIctionary System"의 약자로 메모리 기반의 Key/Value Store 이다.
Cassandra나 HBase와 같이 NoSQL DBMS로 분류되기도 하고, memcached와 같은 In memory 솔루션으로 분리되기도 한다.
성능은 memcached에 버금가면서 다양한 데이타 구조체를 지원함으로써 Message Queue, Shared memory, Remote Dictionary 용도로도 사용될 수 있으며, 이런 이유로 인스탄트그램, 네이버 재팬의 LINE 메신져 서비스, StackOverflow,Blizzard,digg 등 여러 소셜 서비스에 널리 사용되고 있다.
BSD 라이센스 기반의 오픈 소스이며 최근 VMWare에 인수되어 계속해서 업그레이드가 되고 있다.
16,000 라인정도의 C 코드로 작성되었으며, 클라이언트 SDK로는
Action Script,C,C#,C++,Clojure,Erlang,Java,Node.js,Objective-C,Perl,PHP,Python,Smalltalk,Tcl등 대부분의 언어를 지원한다. (참고 : http://www.redis.io/clients )

이번 글에서는 Redis란 무엇인지, 그리고 대략적인 내부 구조에 대해서 살펴보도록 한다.

1. Key/Value Store
Redis는 기본적으로 Key/Value Store이다. 특정 키 값에 값을 저장하는 구조로 되어 있고 기본적인 PUT/GET Operation을 지원한다.

단, 이 모든 데이타는 메모리에 저장되고, 이로 인하여 매우 빠른 write/read 속도를 보장한다. 그래서 전체 저장 가능한 데이타 용량은 물리적인 메모리 크기를 넘어설 수 있다. (물론 OS의 disk swapping 영역등을 사용하여 확장은 가능하겠지만 성능이 급격하게 떨어지기 때문에 의미가 없다.)
데이타 억세스는 메모리에서 일어나지만 server restart 와 같이 서버가 내려갔다가 올라오는 상황에 데이타를 저장을 보장하기 위해서 Disk를 persistence store로 사용한다.

2. 다양한 데이타 타입
단순한 메모리 기반의 Key/Value Store라면 이미 memcached가 있지 않은가? 그렇다면 어떤 차이가 있길래 redis가 유행하는 것일까?
redis가 Key/Value Store이기는 하지만 저장되는 Value가 단순한 Object가 아니라 자료구조를 갖기 때문에 큰 차이를 보인다.
redis가 지원하는 데이타 형은 크게 아래와 같이 5가지가 있다.

1) String
일반적인 문자열로 최대 512mbyte 길이 까지 지원한다.
Text 문자열 뿐만 아니라 Integer와 같은 숫자나 JPEG같은 Binary File까지 저장할 수 있다.

2) Set
set은 string의 집합이다. 여러개의 값을 하나의 Value 내에 넣을 수 있다고 생각하면 되며 블로그 포스트의 태깅(Tag)등에 사용될 수 있다.
재미있는 점은 set간의 연산을 지원하는데, 집합인 만큼 교집합, 합집합, 차이(Differences)를 매우 빠른 시간내에 추출할 수 있다.

3) Sorted Set
set 에 "score" 라는 필드가 추가된 데이타 형으로 score는 일종의 "가중치" 정도로 생각하면 된다.
sorted set에서 데이타는 오름 차순으로 내부 정렬되며, 정렬이 되어 있는 만큼 score 값 범위에 따른 쿼리(range query), top rank에 따른 query 등이 가능하다.


4) Hashes
hash는 value내에 field/string value 쌍으로 이루어진 테이블을 저장하는 데이타 구조체이다.
RDBMS에서 PK 1개와 string 필드 하나로 이루어진 테이블이라고 이해하면 된다.


5) List
list는 string들의 집합으로 저장되는 데이타 형태는 set과 유사하지만, 일종의 양방향 Linked List라고 생각하면 된다. List 앞과 뒤에서 PUSH/POP 연산을 이용해서 데이타를 넣거나 뺄 수 있고, 지정된 INDEX 값을 이용하여 지정된 위치에 데이타를 넣거나 뺄 수 있다. 


6) 데이타 구조체 정리
지금까지 간략하게 redis가 지원하는 데이타 구조체들에 대해서 살펴보았다.
redis의 데이타 구조체의 특징을 다시 요약하자면
  • Value가 일반적인 string 뿐만 아니라, set,list,hash와 같은 집합형 데이타 구조를 지원한다.
  • 저장된 데이타에 대한 연산이나 추가 작업 가능하다. (합집합,교집합,RANGE QUERY 등)
  • set은 일종의 집합, sorted set은 오름차순으로 정렬된 집합, hash는 키 기반의 테이블, list는 일종의 링크드 리스트 와 같은 특성을 지니고 있다.
이러한 집합형 데이타 구조 (set,list,hash)등은 redis에서 하나의 키당 총 2^32개의 데이타를 이론적으로 저장할 수 있으나, 최적의 성능을 낼 수 있는 것은 일반적으로 1,000~5,000개 사이로 알려져 있다.

데이타 구조에 따른 저장 구조를 정리해서 하나의 그림에 도식화해보면 다음과 같다.



3. Persistence
앞서도 언급하였듯이, redis는 데이타를 disk에 저장할 수 있다. memcached의 경우 메모리에만 데이타를 저장하기 때문에 서버가 shutdown 된후에 데이타가 유실 되지만, redis는 서버가 shutdown된 후 restart되더라도, disk에 저장해놓은 데이타를 다시 읽어서 메모리에 Loading하기 때문에 데이타 유실되지 않는다.
redis에서는 데이타를 저장하는 방법이 snapshotting 방식과 AOF (Append on file) 두가지가 있다.

1) snapshotting (RDB) 방식
순간적으로 메모리에 있는 내용을 DISK에 전체를 옮겨 담는 방식이다.
SAVE와 BGSAVE 두가지 방식이 있는데,
SAVE는 blocking 방식으로 순간적으로 redis의 모든 동작을 정지시키고, 그때의 snapshot을 disk에 저장한다.
BGSAVE는 non-blocking 방식으로 별도의 process를 띄운후, 명령어 수행 당시의 메모리 snaopshot을 disk에 저장하며, 저장 순간에 redis는 동작을 멈추지 않고 정상적으로 동작한다.
  • 장점 : 메모리의 snapshot을 그대로 뜬 것이기 때문에, 서버 restart시 snapshot만 load하면 되므로 restart 시간이 빠르다.
  • 단점 : snapshot을 추출하는데 시간이 오래 걸리며, snapshot 추출된후 서버가 down되면 snapshot 추출 이후 데이타는 유실된다.
    (백업 시점의 데이타만 유지된다는 이야기)
2) AOF 방식
AOF(Append On File) 방식은 redis의 모든 write/update 연산 자체를 모두 log 파일에 기록하는 형태이다. 서버가 재 시작될때 기록된  write/update operation을 순차적으로 재 실행하여 데이타를 복구한다. operation 이 발생할때 마다 매번 기록하기 때문에, RDB 방식과는 달리 특정 시점이 아니라 항상 현재 시점까지의 로그를 기록할 수 있으며, 기본적으로 non-blocking call이다.
  • 장점 : Log file에 대해서 append만 하기 때문에, log write 속도가 빠르며, 어느 시점에 server가 down되더라도 데이타 유실이 발생하지 않는다.
  • 단점 : 모든 write/update operation에 대해서 log를 남기기 때문에 로그 데이타 양이 RDB 방식에 비해서 과대하게 크며, 복구시 저장된 write/update operation을 다시 replay 하기 때문에 restart속도가 느리다.
3) 권장 사항
RDB와 AOF 방식의 장단점을 상쇠하기 위해서 두가지 방식을 혼용해서 사용하는 것이 바람직한데
주기적으로 snapshot으로 백업하고, 다음 snapshot까지의 저장을 AOF 방식으로 수행한다.
이렇게 하면 서버가 restart될 때 백업된 snapshot을 reload하고, 소량의 AOF 로그만 replay하면 되기 때문에, restart 시간을 절약하고 데이타의 유실을 방지할 수 있다.


4. Pub/Sub Model
redis는 JMS나 IBM MQ 같은 메세징에 활용할 수 있는데, 1:1 형태의 Queue 뿐만 아니라 1:N 형태의 Publish/Subscribe 메세징도 지원한다.(Publish/Subscribe 구조에서 사용되는 Queue를 일반적으로 Topic이라고 한다.)
하나의 Client가 메세지를 Publish하면, 이 Topic에 연결되어 있는 다수의 클라이언트가 메세지를 받을 수 있는 구조이다. (※ Publish/Subscribe 형태의 messaging 에 대해서는 http://en.wikipedia.org/wiki/Pub/sub  를 참고하기 바란다.)


재미있는 것중에 하나는 일반적인 Pub/Sub 시스템의 경우 Subscribe 하는 하나의 Topic에서만 Subscribe하는데 반해서, redis에서는 pattern matching을 통해서 다수의 Topic에서 message 를 subscribe할 수 있다.
예를 들어 topic 이름이 music.pop music,classic 이라는 두개의 Topic이 있을때, "PSUBSCRIBE music.*"라고 하면 두개의 Topic에서 동시에 message를 subscribe할 수 있다.

5. Replication Topology
redis는 NoSQL 계열의 Key/Store Storage인데 반해서 횡적 확장성을 지원하지 않는다.
쉽게 말해서 2.4.15 현재 버전 기준으로는 클러스터링 기능이 없다. (향후 지원 예정)
그래서 확장성(scalability)과 성능에 제약사항이 있는데, 다행이도 Master/Slave 구조의 Replication(복제)를 지원하기 때문에 성능 부분에 있어서는 어느정도 커버가 가능하다.

Master/Slave replication
Master/Slave Replication이란, redis의 master node에 write된 내용을 복제를 통해서 slave node에 복제 하는 것을 정의한다.
1개의 master node는 n개의 slave node를 가질 수 있으며, 각 slave node도 그에 대한 slave node를 또 가질 수 있다.


이 master/slave 간의 복제는 Non-blocking 상태로 이루어진다. 즉 master node에서 write나 query 연산을 하고 있을 때도 background로 slave node에 데이타를 복사하고 있다는 이야기고, 이는 master/slave node간의 데이타 불일치성을 유발할 수 있다는 이야기이기도 하다.
master node에 write한 데이타가 slave node에 복제중이라면 slave node에서 데이타를 조회할 경우 이전의 데이타가 조회될 수 있다.

Query Off Loading을 통한 성능 향상
그러면 이 master/slave replication을 통해서 무엇을 할 수 있냐? 성능을 높일 수 있다. 동시접속자수나 처리 속도를 늘릴 수 있다. (데이타 저장 용량은 늘릴 수 없다.) 이를 위해서 Query Off Loading이라는 기법을 사용하는데
Query Off Loading은 master node는 write only, slave node는 read only 로 사용하는 방법이다.
단지 redis에서만 사용하는 기법이 아니라, Oracle,MySQL과 같은 RDBMS에서도 많이 사용하는 아키텍쳐 패턴이다.
대부분의 DB 트렌젝션은 웹시스템의 경우 write가 10~20%, read가 70~90% 선이기 때문에, read 트렌젝션을 분산 시킨다면, 처리 시간과 속도를 비약적으로 증가 시킬 수 있다. 특히 redis의 경우 value에 대한 여러가지 연산(합집합,교집합,Range Query)등을 수행하기 때문에, 단순 PUT/GET만 하는 NoSQL이나 memcached에 비해서 read에 사용되는 resource의 양이 상대적으로 높기 때문에 redis의 성능을 높이기 위해서 효과적인 방법이다.

Sharding 을 통한 용량 확장
redis가 클러스터링을 통한 확장성을 제공하지 않는다면, 데이타의 용량이 늘어나면 어떤 방법으로 redis를 확장해야 할까?
일반적으로 Sharding이라는 아키텍쳐를 이용한다. Sharding은 Query Off loading과 마친가지로, redis 뿐만 아니라 일반적인 RDBMS나 다른 NoSQL에서도 많이 사용하는 아키텍쳐로 내용 자체는 간단하다.
여러개의 redis 서버를 구성한 후에, 데이타를 일정 구역별로 나눠서 저장하는 것이다. 예를 들어 숫자를 key로 하는 데이타가 있을때 아래와 그림과 같이 redis 서버별로 저장하는 key 대역폭을 정해놓은 후에, 나눠서 저장한다.
데이타 분산에 대한 통제권은 client가 가지며 client에서 애플리케이션 로직으로 처리한다.

 

현재 버전 2.4.15에서는 Clustering을 지원하지 않아서 Sharding을 사용할 수 밖에 없지만 2012년 내에 Clustering기능이 포함된다고 하니, 확장성에 대해서 기대해볼만하다. redis가 지원할 clustering 아키텍쳐는 ( http://redis.io/presentation/Redis_Cluster.pdf ) 를 참고하기 바란다.

6. Expriation
redis는 데이타에 대해서 생명주기를 정해서 일정 시간이 지나면 자동으로 삭제되게 할 수 있다.
redis가 expire된 데이타를 삭제 하는 정책은 내부적으로 Active와 Passive 두 가지 방법을 사용한다.
Active 방식은 Client가 expired된 데이타에 접근하려고 했을 때, 그때 체크해서 지우는 방법이 있고
Passive 방식은 주기적으로 key들을 random으로 100개만 (전부가 아니라) 스캔해서 지우는 방식이 이다.
expired time이 지난 후 클라이언트에 의해서 접근 되지 않은 데이타는 Active 방식으로 인해서 지워지지 않고 Passive 방식으로 지워져야 하는데, 이 경우 Passive 방식의 경우 전체 데이타를 scan하는 것이 아니기 때문에, redis에는 항상 expired 되었으나 지워지지 않는 garbage 데이타가 존재할 수 있는 원인이 된다.

7. Redis 설치(윈도우즈)
https://github.com/rgl/redis/downloads 에서 최신 버전 다운로드 받은후
redis-server.exe를 실행

클라이언트는 redis-cli.exe를 실행
아래는 테스트 스크립트
    % cd src
    % ./redis-cli
    redis> ping
    PONG
    redis> set foo bar
    OK
    redis> get foo
    "bar"
    redis> incr mycounter
    (integer) 1
    redis> incr mycounter
    (integer) 2
    redis> 


참고 자료

 

추가적으로 좋은 문서가 있어 공유합니다.

      http://www.slideshare.net/krisjeong/redis-intro

 

감사합니다.

 



'I.lib() > I.lib(etc)' 카테고리의 다른 글

Gradle 정보 정리  (0) 2014.07.04
방송 통신 용어 정리  (0) 2014.07.02
[펌] Memcached Server (메모리 캐쉬 서버)  (0) 2014.06.24
IT 기술 면접 질문 모음  (0) 2014.04.04
Angularjs 설정 & 개념 & 응용  (0) 2014.04.02
.
:
Posted by .07274.
.. .. ..

 

[펌] : http://www.slideshare.net/AshyGrey/about-memcached-13950629

 

  • about MEMCACHED afool Kim Sinhyub sinhyub@gmail.com
  • 1_ memcached 가 뭔가요? (항상) 메모리에 올라가 있는 map (Key – Value) dictionary map : Key-Value pair를 가지는 자료구조. dictionary 등으로도 불려요. Key를 사용하여 Value 를 빠르게 찾아 주는 특징을 가집니다. (항상)메모리에 올라가 있는 것은 아니구요. 물리적 메모리를 넘어선 만큼을 memcached에 할당하면, 당연히 하드디스크로 virtual memory 페이징이 일어나게 됩니다.
  • 2_ memcached 를 왜 사용하나요? _ 메모리 레벨 캐싱 (손쉽게) 메모리 레벨에서 원하는 값을 캐싱할 수 있음! 캐싱 : 자원을 읽고 쓰는 작업에서, 성능 향상을 목적으로 자원을 (접근 속도가 더 빠른) 다른 차원의 저장소에 저장해 두고 자원에 대한 요청이 왔을 때 빠르게 반환해 주는 방법.
  • 2_ memcached 를 왜 사용하나요? _ 메모리 레벨 캐싱 GET 556 NAME (Memory) (Disk) (김개똥)Contents Server GET 555 NAME memcached DB (박주영)1. Memory Access Time <<< Disk Access Time2. 자주 찾는 데이터일 수록 memcached레벨에서 처리 가능* memcached 는 LRU order로 자원을 삭제함
  • 2_ memcached 를 왜 사용하나요? _ 분산 캐시 서버 구축 메모리 캐시 서버를 (손쉽게) 분산하여 구축할 수 있음! memcached client 에서 해시 테이블을 사용하여 해당 자원의 memcached server에 접근 할 수 있도록 지원함 (key 값에 대한 hash값으로 해당 key-value가 저장될 memcached server를 결정하여 데이터를 분산시켜 저장하고, key 값에 대한 value를 찾을 때에도 해당 hash값으로 저장되어 있는 memcached server를 통해 데이터를 가져옴.) 물론, 이러한 내부적인 메커니즘을 몰라도 쉽게 사용할 수 있음! 단, 이러한 분산 메커니즘이 클라이언트 사이드에서 진행되므로 memcached client의 버전을 꼭 동일한 것으로 사용하는 것이 좋음.
  • 3_ memcached 사용 팁 _ installmemcached server : brew install memcachedmemcached client : pip install python-memcachedmemcached server : key-value map을 저장하는 저장소. 서버머신에서구동시킬 서버 어플리케이션.memcached client : memcached server로 요청을 보내어 원하는 key-value값을 저장하거나 읽어올 수 있는 작업을 하는 클라이언트. 물론,클라이언트는 각 환경마다 여러가지 버전이 존재 하니 입맛에 맞게골라쓰면 됨!
  • 3_ memcached 사용 팁 _ setting memcached 에 필요한 cpu costs는 매우 작음. default 4개의 thread로 동작함. 하지만 여러개의 코어가 달린 고성능 CPU를 필요로 하지는 않음. 셋팅 시 memcached에 할당할 메모리는 실제 머신의 물리메모리(에 조금의 여유를 남긴)만큼만 할당하는 것이 좋음. 메모리를 사용하여 퍼포먼스의 향상을 얻는 것이 memcached의 기본 컨셉으로, 물리메모리를 넘어서는 만큼의 메모리를 할당할 경우swapping(가상 메모리 페이징)으로 오히려 성능 하락이 발생할 수 있음.참고자료)http://code.google.com/p/memcached/wiki/NewHardware
  • 3_ memcached 사용 팁 _ setting 사용 중인 서버 머신에서, WebServer, App이 사용하지 않아 남는 메모리만큼을 Memcached에 할당해서 사용해도 좋음 DB Host에 Memcached를 사용하는 것은 어리석은 짓. 메모리를 최대한 DBMS로 주도록.참고자료)http://code.google.com/p/memcached/wiki/NewHardwarehttp://code.google.com/p/memcached/wiki/NewConfiguringServer
  • 3_ memcached 사용 팁 _ setting 사용하는 자원이 다 차면, LRU order로 object를 제거 . 가장 자주(최근에)쓰이는 것들이 캐시에 남아있게됨! 메모리 용량을 넘어서는 범위를 이터레이션하게 된다면, 한번씩만 쓰고, 캐시에서 빠져나가게 되는 object들로 인해 연이은 cache miss 가 일어날 수 있다. 따라서 어플리케이션에서 날리는 쿼리를 잘 파악하여, 매우 큰 범위(memcached capacity를 넘어서는)를 iteration하게 되는 set에 대해서는 바로 db로부터 retrieving받는 것이 더 낫다.참고자료)http://code.google.com/p/memcached/wiki/NewUserInternals
  • 3_ memcached 사용 팁 _ 추가 라이브러리 동기화 관련한 기능이 필요하다면 다른 녀석과 함께 사용하라. ex) get + set 을 atomic하게 처리해야 하는 트랜젝션 redis : memcached 와 같이 메모리 레벨 캐싱과 함께 유용한 atomic operation들을 제공해 줌!
  • 4_ memcached 얼마나 좋나요? (쉽게 말해서) (이름만 대면 알만한) 큰 서비스에서 사용하고 있음!
  • 4_ memcached 얼마나 좋나요? (쉽게 말해서) (이름만 대면 알만한) 큰 서비스에서 사용하고 있음!징가의 경우 하나의 세션마다, 세션이 유지되는 동안의 유저 플레이데이터를 memcached에 저장하고, 추후에 이를 DB에 저장하는방법으로 DB차원의 자원을 memcached를 이용하여 성능향상을얻었다고 함!youtube : http://video.google.com/videoplay?docid=-6304964351441328559Facebook : http://www.facebook.com/note.php?note_id=39391378919&ref=mfZynga-farmville : http://gigaom.com/2010/06/08/how-zynga-survived-farmville/
  • 4_ memcached 얼마나 좋나요? (이름만 대면 알만한)PAS에서는 API를 주고 서비스로제공해 주고 있음!
  • 5_ memcached 사용 후기는afool프로젝트 끝나고올려 드림
.
:
Posted by .07274.
2014. 4. 4. 14:36

IT 기술 면접 질문 모음 I.lib()/I.lib(etc)2014. 4. 4. 14:36

.. .. ..

 

http://blog.naver.com/PostView.nhn?blogId=korn123&logNo=30147908609

 

http://minamjun11.egloos.com/viewer/1217123

 

[초보 팀장의 일기] 면접을 볼때 마다 하는 질문들 :http://blog.java2game.com/401

 

[경력 면접 “이렇게 하면 떨어진다” 탈락 유형 7가지] : http://www.wikitree.co.kr/main/news_view.php?id=50108

 

[면접 평가의 기준을 알면 면접이 보인다]  : http://blogsabo.ahnlab.com/409

 

 

.
:
Posted by .07274.
2014. 4. 2. 20:15

Angularjs 설정 & 개념 & 응용 I.lib()/I.lib(etc)2014. 4. 2. 20:15

.. .. ..

 

 

AngularJS 사이트 : http://angularjs.org/

 

 

 

RequireJS 란? – JavaScript 파일 및 모듈 로더 : http://blog.javarouka.me/2013/04/requirejs-javascript.html

 

 

 

 

 

.
:
Posted by .07274.
.. .. ..

 

 

1. Nginx 다운받기

http://wiki.nginx.org/Install 에서 Stable 버젼(1.4.6) 다운 (파일명 : nginx-1.4.6.tar.gz)

 

2. Nginx 설치전 설치 경로및 설정 잡기

알아서 구글 검색 필요

 

3. Nginx 설치하기 (root 계정)

 -- gunzip nginx-1.4.6.tar.gz (Gunzip 해제)

  -- tar -xvf nginx-1.4.6.tar (Tar 해제)

  --./configure

  --make

  --sudo make install

 

4. 설치 완료

 

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

 

0. jBoss 기초설명

 

1. jBoss 다운(회원가입 필요) - jboss-eap-6.1.0.zip

 

2. jBoss EAP 설치하기

 

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

1. Nginx - jBoss 연동하기

 

-------------------------------------------------------------------------------------------------------------------연동중 오류 정리

1. jBoss 의 관리자 Web 접속 불가

 jBoss를 standalone 으로 올렸고, 원격지에서 IP(192.168.xxx.x:9990) 로 접근을 하지 오류가 났다.

       해당 원인은 jBoss의 설정의 문제였고 JBOSS_HOME/standalone/configuration/standalone.xml 설정파일의 수정이

       필요했다.

     <interfaces>
         <interface name="management">
                     <inet-address value="${jboss.bind.address.management:127.0.0.1}"/>
        </interface>
        <interface name="public">
                     <inet-address value="${jboss.bind.address:127.0.0.1}"/>
        </interface>
        <interface name="unsecure">
                      <inet-address value="${jboss.bind.address.unsecure:127.0.0.1}"/>
        </interface>
    </interfaces>

   

     위의 설정 부분중 127.0.0.1의 IP를 해당 아이피(192.168.xxx.x)로 변경해줘야 외부에서 접근이 가능하다.

 

2. 원격지의 NGINX에서 jBoss와 modjk 로 연동시 ajp Port가 뜨지않아 연동이 안되는 상황.

  원인은 AJP 포트가 뜨지 않아서 였다 . (netstat -an | grep "AJPPort") 로 확인하면 포트가 뜨지 않았다.

   관리자 포트 (9990) 도 뜨는데 왜안될까.. 하는 차에 지인의 도움으로 해결.

   JBOSS_HOME/standalone/configuration/standalone.xml  설정파일의 수정이 필요했다.

      

        <subsystem xmlns="urn:jboss:domain:web:1.4" default-virtual-server="default-host" native="false">
            <connector name="http" protocol="HTTP/1.1" scheme="http" socket-binding="http"/>
            <virtual-server name="default-host" enable-welcome-root="true">
                <alias name="localhost"/>
                <alias name="example.com"/>
            </virtual-server>
        </subsystem>

 

     위의 설정정보에 아래와 같은 내용이 추가되어야 한다

 

        <subsystem xmlns="urn:jboss:domain:web:1.4" default-virtual-server="default-host" native="false">
            <connector name="http" protocol="HTTP/1.1" scheme="http" socket-binding="http"/>
            <connector name="ajp" protocol="AJP/1.3" scheme="http" socket-binding="ajp" enabled="true"/>
            <virtual-server name="default-host" enable-welcome-root="true">
                <alias name="localhost"/>
                <alias name="example.com"/>
            </virtual-server>
        </subsystem>

 

댓글을 남겨주시면 힘이됩니다^^

.
:
Posted by .07274.