제4장 JNDI Naming Server

내용 목차

4.1. 개요
4.2. 기본 개념과 구조
4.2.1. 기본 개념
4.2.2. 바인딩된 객체 확인
4.2.3. JNDI Naming Server 아키텍처
4.2.4. JNDI 클러스터링
4.3. JNDI Naming Server 설정
4.3.1. JNSServer 설정
4.3.2. JNSClient 설정
4.4. 클러스터링 환경에서 JNDI
4.5. JNDI 프로그래밍
4.5.1. JEUS 환경설정
4.5.2. InitialContext를 위한 프로퍼티 설정
4.5.3. Context를 사용한 Named Object의 Lookup
4.5.4. Named Object 사용
4.5.5. Context 닫기
4.5.6. 클러스터링 Context 생성
4.5.7. 원격으로 Lookup 실행

본 장은 JEUS JNDI의 기본적인 개념과 용어 그리고 환경설정 방법 및 애플리케이션을 개발 방법에 대해서 설명한다.

Java Naming and Directory Interface™ (JNDI)는 Java 애플리케이션이 네트워크에서 객체를 찾고 가져올 수 있도록 하는 표준 API이다. 애플리케이션은 객체의 논리적인 이름을 통해 해당하는 객체를 찾아서 사용할 수 있다. 사용자의 관점에서는 이전의 엔터프라이즈 환경보다 쉽게 객체를 찾아서 사용할 수 있는 환경을 제공해준다.

JEUS JNDI는 JNDI 1.2 API와 호환되며, Sun Microsystems에서 제안한 표준 JNDI API를 지원한다. 그리고 엔터프라이즈 환경에 적합하도록 JNDI Service Provider Interface(이하 SPI)도 제공한다. 즉, JNDI SPI를 구현한 제품은 JEUS JNDI 트리의 객체를 사용할 수 있다.

JEUS JNDI 서비스는 JEUS 시스템 전반적으로 사용되므로 EJB, Servlet/JSP, JMS, JDBC 등을 사용할 때마다 보게 될 것이다.

JEUS JNDI는 객체를 bind하고 Lookup하는 고유의 아키텍처를 가지고 있다. 우선 JEUS JNDI의 구조와 기본 개념에 대해서 설명한다.

MS를 시작하면 JEUS는 자동적으로 JNDI 서비스를 준비한다. JNDI 서비스는 JNDI Naming Server가 제공하는데, 이것이 실행될 때 내부적으로 JEUS Naming Service Server(이하 JNSServer)가 실행된다. 이 JNSServer와 통신하기 위한 클라이언트 역할을 하는 것이 JEUS Naming Service Client(이하 JNSClient)이다.

JNSClient는 JNSServer와 접속되어 있어서 객체의 bind와 Lookup은 JNSClient에서 먼저하고, 다음으로 JNSServer에서 진행된다. 하나의 JNSServer는 하나 이상의 JNSClient와 접속되어 있으며, 또한 JNSServer들도 다른 JNSServer와 접속하고 있어(특히 클러스터링 환경일 때) 트리와 같은 구조를 이루고 있다. 이러한 Naming Repository구조를 JNDI 트리라고 한다.

JNDI 트리는 객체를 bind하거나 Lookup할 때 사용되는데, 모든 객체는 서버들을 통해서 JNDI 트리로 bind되고 Lookup된다. 애플리케이션에서 JNSClient로 객체의 bind를 요청하면 JNDI 트리로 전달되어 bind되며, 이렇게 bind된 객체는 각 JNSClient를 통해 애플리케이션에서 Lookup할 수 있다.

JNDI 트리의 객체를 액세스할 때는 InitialContext를 통해서만 가능하다. 그러므로 애플리케이션에서 JNDI를 사용하려면 반드시 InitialContext 객체를 생성해야만 한다. 이 객체는 JNDI 트리에 접근해서 객체를 핸들링할 수 있도록 해준다. 그리고 객체를 bind하거나 Lookup할 뿐만 아니라, 객체의 목록을 가져올 수 있으며 제거할 수 있는 기능을 한다.

바인딩되어 있는 객체는 WebAdmin과 콘솔 툴을 통해 확인할 수 있다.

본 절에서는 JNDI 트리 구조가 실제로 어떻게 작동되는지 자세히 설명한다.

JNDI 트리는 JNSServer와 JNSClient로 구성되어 있다. JNSServer는 MS의 JVM에 존재하며, JNSClient는 MS 또는 클라이언트의 JVM에 존재한다. JNSServer는 JEUS JNDI 아키텍처의 메인으로 JNDI 트리를 생성하고 관리한다. JNSServer는 그 하위에 JNSClient를 둬서 관리한다.

다음 그림은 JNSServer와 JNSClient의 관계를 나타낸다.


전체 JNDI 트리를 액세스하기 위해서는 JNSClient에서 JNSServer로 요청을 보내야 한다.

JNSServer는 다른 MS의 JNSServer와 연결되어서 클러스터링을 구성한다. JNSClient는 JNSServer와 MS 내에서 상호 작용을 하며, 클라이언트의 액세스 요청을 처리한다. 클라이언트는 JNSServer로 바로 접근하는 것이 아니라 JNSClient를 통해서 객체를 bind하고 Lookup한다.

JNSServer

JNSServer는 JNDI 트리를 관리하며, JNSClient가 JNDI 트리를 액세스할 수 있도록 하는 독립적인 Naming Server이다. JNDI 트리를 확장하기 위해서 여러 개의 JNSServer를 연결할 수 있다. JNSServer는 다른 MS의 JNSServer와 직접적으로 연결할 수 있기 때문이다. JEUS에서는 MS가 시작되면 JNSServer는 자동적으로 JNSClient의 접속을 기다린다.

JNSClient

JNSClient의 기본적인 기능은 JNSServer로 접속하여 애플리케이션의 요청을 전송하고 JNSServer의 결과를 다시 돌려주는 것이다. 각 JVM에서는 하나의 서버에 대해 하나의 JNSClient Singleton 인스턴스만 존재한다. 그래서 Lookup을 처리할 때 하나의 JNSClient만 사용하므로 엔터프라이즈 환경에서 EJB와 Servlet을 사용할 때 효과적이다.

다음은 JNSClient의 중요 기능이다.

  • JNDI 트리 접속

    JNSClient는 JNSServer에 접속해서 MS가 관리하는 JNDI 트리로 접속하는 방법을 제공한다. bind되고 Lookup되는 객체는 전체 JNDI 트리에서 공유되거나, 클라이언트의 설정에 따라서 특정 클라이언트만 액세스할 수도 있다.

  • Lookup된 객체의 캐싱

    JNSClient는 자주 사용되는 객체를 캐싱해서 클라이언트가 더 빠르게 사용할 수 있도록 한다. JNSClient는 JNSServer와 통신을 하면서 객체를 캐싱한다.

  • JNSServer와의 연결 관리

    JNSClient는 클라이언트의 요청을 받아서 JNSServer로 전달하고 그 결과를 받아서 리턴한다. 클라이언트가 있는 JVM에 JNSClient가 존재하므로 별다른 I/O 없이 효율적으로 통신할 수 있다.

JNSClient에는 JNSServer의 위치(같은 MS인지 아니면 원격지에 있는지)에 따라서 2가지 종류로 나뉜다.

JNSClient설명
Server-side

MS의 각 엔진에 있는 EJB Bean, Servlet, JSP 등이 사용한다.

Server-side JNSClient를 사용하려면 java.naming.factory.initial 프로퍼티 값을 jeus.jndi.JNSContextFactory로 설정한다. 이 값은 기본적으로 MS가 기동될 때 설정되므로 별다르게 고려할 필요는 없다.

Client-side

MS에서 동작하지 않는 애플릿, 애플리케이션 클라이언트 등이 사용한다.

Client-side JNSClient를 사용하려면 java.naming.factory.initial 프로퍼티값을 jeus.jndi.JNSContextFactory로 설정한다.

Client-side JNSClient는 일정시간 동안 통신이 없을 때 커넥션을 끊고, 다시 필요할 때 커넥션을 생성해서 리소스를 효율적으로 관리한다. 단, 클라이언트 컨테이너에서 동작하는 애플리케이션은 클라이언트 컨테이너가 실행될 때는 이 값을 시스템 프로퍼티로 설정하기 때문에 애플리케이션에서 별도로 설정할 필요는 없다.

JNDI 트리는 JNSServer와 JNSClient 간의 연결로 구성되어 있다. 이 구조는 하나의 컴포넌트(JNSClient)에서 객체에 대한 정보의 변화가 있을 때 다른 컴포넌트(JNSClient)로 전달될 수 있도록 하며, 또한 여러 개의 MS를 지원(클러스터링)할 수 있도록 한다. 즉, JNDI Naming 클러스터링은 각각의 독립적인 JNDI를 하나의 확장된 JNDI 트리로 구성하는 것이라 할 수 있으며 이때도 마찬가지로 각 JNDI 트리에서 발생하는 객체에 대한 정보의 변화가 있을 때에는 나머지 다른 JNDI로 그 내용이 전달된다. 따라서 JNDI Naming 클러스터링 환경에서는 하나의 Naming Server에서 바인딩한 객체를 다른 여러 Naming Server에서 이 객체를 Lookup할 수 있게 된다.

예를 들어 다음 그림처럼 A, B, C, D라는 4개의 MS가 클러스터링을 구성하고 있는 환경에서 MS A에 obj1이라는 객체를 'objName1'이라는 이름으로 바인딩하면, 이는 나머지 3개의 MS B, C, D에도 이 객체가 전달된다. 따라서 처음에 클라이언트가 직접 바인딩을 시도한 MS A가 아닌 다른 3개의 MS에서도 'objName1'으로 Lookup하면 obj1이라는 객체를 얻어올 수 있게 된다.

그러나 바인딩한 모든 객체가 클러스터링에 속한 MS로 복제되는 것은 아니고, 클러스터링 대상으로 바인딩한 객체만 복제된다. 예를 들어 데이터소스같이 각 MS에 특화된 객체는 각 MS의 JNS Server에만 바인딩된다.


각 MS는 각각의 JNSServer를 관리할 책임을 가지고 있다. 각 JNSServer는 JEUS 시스템이 기동될 때 시작되어서 다른 MS의 JNSServer와 연결된다. 각 JEUS 엔진은 InitialContext를 가져옴과 동시에 JNSClient가 JNSServer로 연결된다. 클라이언트가 JNDI 트리의 객체를 Lookup할 때 JNSClient로 요청하고 이어서 해당 MS의 JNSServer로 요청이 보내진다. 그리고 이에 대한 객체를 클라이언트가 받게 된다.

클러스터링 환경에서 원격으로 Lookup

별도의 설정을 하지 않은 경우 JNDI Lookup은 자신이 포함된 JEUS JNDI 클러스터링 영역에 대해서 수행된다. 그러나 애플리케이션이 클러스터링에 있지 않은 다른 MS의 JNDI 서버에 있는 내용을 Lookup할 때에는 PROVIDER URL(JEUS Reference 안내서”의 “1.4. JNDI 시스템 프로퍼티” 참고)을 지정해서 Context를 생성하거나 Lookup할 때 JEUS에서 생성한 jh(JEUS Host) 프로토콜을 사용한 이름으로 Lookup해야 한다. 이에 대한 내용은 “4.5. JNDI 프로그래밍”을 참고한다.

JNSServer Replication

JEUS의 각 JNSServer는 다른 서버와 연결되어서 상호 작용하기를 기다리고 있다. 기존의 클러스터로 새로운 MS가 들어오면, 새로 들어온 MS의 JNSServer는 시작할 때 이미 존재하는 다른 JNSServer로 통보를 보내게 된다. 이때 각 JNSServer는 자신의 데이터를 새로 들어온 JNSServer로 전송하게 되며, 이렇게 해서 새로 들어온 JNSServer에서도 기존에 bind되어 있는 객체를 Lookup할 수 있게 된다.

이런 확장성으로 인해 이상 작동하여 재기동된 JNSServer는 다른 JNSServer로부터 JNDI 트리 정보를 받아 정상 상태로 동작할 수 있다.

JNDI Naming Server는 JNSServer와 JNSClient로 구성되어 있다. 이 둘은 서로 다른 설정을 가진다.

JNSServer에서는 JNSClient의 커넥션을 받아들이는 설정과 다른 JNSServer와 접속하기 위한 설정이 필요하고, JNSClient는 JNSServer와 접속하기 위한 것과 JNDI 트리의 반영을 위한 설정이 필요하다.

본 절에서는 JNSServer와 JNSClient를 설정하는 방법에 대해서 설명한다.

WebAdmin의 서버 설정 화면으로 이동한 후 [Basic] > [Naming Server] 메뉴를 선택하면 JNSServer의 설정할 수 있는 화면으로 이동한다.


공용 Thread Pool 설정

WebAdmin이나 콘솔 툴을 사용해서 서버 전반적으로 공유할 Thread Pool을 설정할 수 있다. Thread Pool에 대한 기본적인 설명은 “2.3.4. Thread Pool 설정”을 참고한다.

서비스 전용 Thread Pool 설정

WebAdmin이나 콘솔 툴을 사용해서 서비스별 전용 Thread Pool을 설정할 수 있다.

참고

서비스의 Thread Pool 설정을 수정하는 것은 동적 반영 가능하기 때문에 서버를 재기동하지 않아도 된다. 하지만 공용 Thread Pool을 사용하다가 전용 Thread Pool을 사용하도록 변경하려면 서버를 재기동해야 한다.

MS 간에 클러스터링 환경이 구성되어 있다면 JNDI를 클러스터링하기 위해서 별도의 작업은 필요없다. 만약 MS가 클러스터링된다면 각 JNSServer도 자동적으로 클러스터링 환경을 구성한다. MS들을 클러스터링하는 것에 대해서는 JEUS Domain 안내서”의 “제5장 JEUS 클러스터링”을 참고한다.

기본적으로 JNSClient는 로컬 JNSServer로 접속한다. JNSClient는 MS의 JNSServer 주소를 Context.PROVIDER_URL로 제공받는데, 클러스터링 환경인 경우에도 클러스터링에 참여하고 있는 모든 MS의 주소를 다음과 같이 적어 주어야 한다.

JNSClient는 제공된 Context.PROVIDER_URL를 바탕으로 Load Balancing과 Failover를 수행하기 때문이다.

Hashtable ht = new Hashtable();
ht.put(Context.PROVIDER_URL, "host1:9736,host2:9736"); //cluster에 host1, host2가 참여

만약 Context.PROVIDER_URL에서 적혔는 MS 중에 특정 MS가 FAILED 상태가 되면 JNSClient는 FAILED 상태가 된 MS로 JNSServer의 JNDI 오퍼레이션할 때 이를 감지하여 다른 JNSServer로 Failover를 한다. FAILED 상태로 판단된 MS는 JNSClient에서 주기적으로 체크하여 RUNNING 상태가 되었는지 확인한다. -Djeus.jndi.cluster.recheckto 옵션으로 설정 가능하다. (기본값: 5, 단위: 분)

만약 JNSClient가 MASTER의 관리를 받는 MS에서 동작하고 있다면, 다음과 같이 MASTER에서 설정한 클러스터 이름을 적어서 사용할 수 있다.

Hashtable ht = new Hashtable();
ht.put(Context.PROVIDER_URL, "jeus://cluster1"); //cluster1은 MASTER에 설정한 클러스터 이름

클러스터 이름을 적어서 사용한 경우에는 JNSClient의 "jeus.jndi.cluster.recheckto" 옵션과 상관없이 MASTER로부터 늘 MS 서버의 최신 상태 정보를 받아서 클러스터링을 할 수 있다. 즉, 클러스터에 속한 MS가 FAILED 상태가 되거나 새로운 MS가 추가된 경우에 JNSClient에서 JNDI 오퍼레이션을 수행하기 전에 최신의 서버 상태로 업데이트되어 동작된다. 따라서 JNSClient가 MASTER의 관리를 받는 MS에서 동작된다면 클러스터 이름을 사용할 것을 권장한다.

Lookup

클러스터에 있는 클라이언트는 JNDI 트리에 bind되어 있는 어떤 객체든 Lookup할 수 있다. 만약 클라이언트가 JNSClient에 객체를 bind한다면 즉시 JNSServer를 통해서 클러스터링된 모든 MS에서 그것을 공유하게 된다. 또한 데이터 삭제나 변경이 발생하면 즉시 각 MS의 JNSClient에도 반영된다.

JEUS JNDI에서는 객체의 성격에 따라서 어떤 객체는 클러스터 전체에 공유되고 (Lookup을 위한 export name 같은 객체), 어떤 객체는 자신의 MS(데이터소스같은 MS에 의존적인 객체)에서만 보인다.

본 절에서는 JEUS JNDI를 사용하는 프로그래밍 방법에 대해서 설명한다.

Java 클라이언트는 InitialContext를 사용해서 JNDI 트리를 접근한다. InitialContext에 사용되는 프로퍼티는 JNDI 표준 프로퍼티와 JEUS용 프로퍼티가 있다. 먼저 JNDI 환경을 설정하고 그 다음으로 InitialContext를 사용해서 객체를 Lookup한 다음, 객체의 레퍼런스를 가져온다. 마지막으로 InitialContext를 사용한 다음에는 반드시 close시켜준다.

다음의 과정으로 Java 클라이언트는 JEUS JNDI 서비스를 사용한다.

  1. JEUS 환경설정

  2. InitialContext를 위한 프로퍼티 설정

  3. Context를 사용한 Named Object의 Lookup

  4. Named Object를 사용해서 객체의 레퍼런스 취득

  5. Context 닫기

각 단계에 대한 자세한 설명은 해당 절을 참고한다.

Java 클라이언트가 JEUS JNDI 서비스를 사용하기 전에 우선 InitialContext의 환경 프로퍼티를 설정한다.

설정하는 방법은 다음의 2가지가 있다.

  • JVM의 -D 옵션을 사용한다.

  • Hash Table을 생성하여 InitialContext의 생성자에게 넘긴다.

위의 2가지 방법 중 Hash Table을 생성하여 InitialContext의 생성자에게 넘기는 방법보다 JVM의 -D 옵션을 사용하는 방법이 우선한다.

JEUS JNDI의 InitialContext를 생성하기 위해서 설정해야 하는 프로퍼티는 다음과 같다.

  • Context.INITIAL_CONTEXT_FACTORY (required)

  • Context.URL_PKG_PREFIXES

  • Context.PROVIDER_URL

  • Context.SECURITY_PRINCIPAL

  • Context.SECURITY_CREDENTIALS

이 프로퍼티들은 Hash Table에 넣어서 InitialContext를 생성할 때 사용한다. 만약 서버 측 객체 내(EJB나 Servlet/JSP)에서만 InitialContext를 사용한다면 별도의 설정 없이 기본으로 설정된 InitialContext를 사용한다.

클라이언트 프로그램에서는 다음과 같이 설정한다.

Context ctx = null;
Hashtable ht = new Hashtable();
ht.put(Context.INITIAL_CONTEXT_FACTORY, "jeus.jndi.JNSContextFactory”);
ht.put(Context.URL_PKG_PREFIXES, “jeus.jndi.jns.url”);
ht.put(Context.PROVIDER_URL, “<hostname>”);
ht.put(Context.SECURITY_PRINCIPAL, “<username>”);
ht.put(Context.SECURITY_CREDENTIALS, “<password>”);

try {
    ctx = new InitialContext(ht);
    // use the context
} catch (NamingException ne) {
    // fail to get an InitialContext
} finally {
    try{
        ctx.close();
    catch (Exception e) {
        // an error occurred
    }
}

클러스터링 환경에서 JNDI를 사용할 때는 JNDI 트리를 구성하고 JNSClient의 내부적인 작동을 효과적으로 관리하기 위해서 jeus.jndi.JNSContext의 추가적인 환경 프로퍼티를 사용할 수 있다.

  • Hash Table을 사용해서 Context 생성하기

    InitialContext의 환경 프로퍼티를 설정할 때 Hash Table(java.util.Hashtable)의 키로 프로퍼티 이름을 넣고, 값으로 프로퍼티의 데이터를 넣은 후에 InitialContext 생성자의 파라미터로 넣어준다.

  • Security Context 생성하기

    JEUS Security 도메인의 사용자를 실행 Thread에 적용시키기 위해서 몇 가지 보안 관련 환경 프로퍼티를 사용할 수 있다. InitialContext가 생성될 때 Security Context는 다음의 프로퍼티로 구성한다.

    • 사용자명 프로퍼티 : java.naming.security.principal

    • 패스워드 프로퍼티 : java.naming.security.credentials

인증이 성공하면 인증된 사용자의 정보가 실행 Thread에 설정되며, JEUS Security Manager가 관리하는 리소스를 액세스할 수 있다. 만약 인증에 실패하면 Thread는 guest 사용자로 인식된다.

Security Context가 설정되어도 InitialContext가 생성되기 전에 JEUS Security API를 사용해서 로그인한 경우 Security Context는 무시된다. 즉, 이미 로그인된 subject를 사용해서 JNDI 통신을 수행한다.

참고

1. 사용자 정보 설정에 관한 더 자세한 내용과 JEUS Security Service에 대한 내용은 "JEUS Security 안내서"를 참고한다.

2. InitialContext 설정 관련 프로퍼티와 환경 프로퍼티에 대한 자세한 내용은 JEUS Reference 안내서”의 “1.4. JNDI 시스템 프로퍼티”를 참고한다.

JEUS 클러스터링 환경에서 사용할 수 있는 Context를 생성하기 위해서는 다음과 같이 Context.PROVIDER_URL에 여러 개의 호스트를 설정한다.

Hashtable ht = new Hashtable();
ht.put(Context.PROVIDER_URL, "host1:9736,host2:9736");

도메인에 설정한 클러스터 이름을 알고 있다면 다음과 같이 설정하여 사용할 수도 있다.

Context.PROVIDER_URL에 'jeus://' + <클러스터 이름> 

단, 클러스터에 속한 MS에서만 사용이 가능하고, 독립된 클라이언트(Standalone Client)에서는 클러스터 정보를 알 수 없어 사용할 수 없다.

Hashtable ht = new Hashtable();
ht.put(Context.PROVIDER_URL, "jeus://cluster1");

클러스터링된 Context에서 Lookup하는 경우 어떤 MS에서 객체를 가져올 것인지에 대한 정책을 정할 수 있다.

jeus.jndi.clusterlink.selection-policy 프로퍼티를 제공하며 다음과 같은 3가지 값을 설정할 수 있다. 또한 이 값들은 System property를 주어 설정할 수도 있으며, 이 경우에는 Hashtable을 통한 설정이 우선된다.

구분설명
locallinkPreference로컬 MS에 있는 객체를 사용한다.
roundrobin처음 요청에서는 random하게 선택한 MS의 객체를 사용하고, 그 후 요청부터는 하나씩 증가하면서 서버를 선택한다.
random클러스터링된 MS들 중에서 random하게 하나를 선택해서 사용한다.

다음은 설정 예제이다.

Hashtable ht = new Hashtable();
ht.put(Context.PROVIDER_URL, "host1:9736,host2:9736");
ht.put("jeus.jndi.clusterlink.selection-policy", "random");