• Singleton 및 잡다 연습장

    initialization on demand holder idiom

    • Inner Class 로드 시점을 이용해서 멀티 스레드 상황의 Lazy 초기화 문제 해결 (처음에는 DLC-dual lock check- 나옴)
    • 내장 클래스의 private static final로 Singleton 클래스 선언

    enum 이 singleton pattern 으로 사용

    • INSTANCE 가 생성될 때, multi thread 로 부터 안전하다. (추가된 methed 들은 safed 하지 않을 수도 있다.)
    • 단 한번의 인스턴스 생성을 보장한다.
    • 사용이 간편하다.
    • enum value는 자바 프로그램 전역에서 접근이 가능하다.

    Map

    • Map: K/V, 중복 불가
    • HashMap: Null Key 허용, 비동기, Key 값을 Hashing해서 보관, 인덱싱 성능 우수 - Hashing 기준으로 탐색하기 때문에 순서 보장 안됨
    • HashTable: Null Key 허용 안함, 동기
    • TreeMap: 정렬
    • LinkedHashMap: 입력 순서 보장
    • ConcurrentHashMap: HashMap의 톡성에 동기화 보장

    Collection

    • Set: 중복 불가, 탐색만가능 –> HashSet: 비동기, 순서없음, NULL허용 –> LinkedHashSet: 비동기, 순서보장, NULL 허용 –> TreeSet: 비동기, 정렬
    • List: 중복허용, 순서보장 –> ArrayList: 비동기, 단방향 포인터-순차적접근빠름- –> Vector: 동기 –> LinkedList: 비동기, 양방향 포인터-추가/삭제빠름-, Stack/Queue/Deque 동작
    • Queue: 중복허용, 우선순위에 따른 요소 순서 존재

    Iterator

    • Collection 객체 탐색을 위한 표준 인터페이스 (hasNext, next, remove)

    HashMap 메모리 할당

    • HashMap 객체가 최초 생성되면 HashMap 객체와 더불어 HashMap$Entry 객체 배열이 생성되고 배열의 기본 크기가 16개다.
    • 텅 빈 HashMap 객체가 128byte의 메모리를 소비
    • 추가: 입력 요소마다 추가 36 bytes 씩 증가

    LinkedList 메모리 할당

    • LinkedList의 기본 용량은 1인데, 새로운 요소가 추가되거나 기존의 요소가 제거될 때마다 즉각적으로 이 값이 변경된다.

    ArrayList 메모리 할당

    • ArrayList가 생성되었을 때, ArrayList 객체를 위해 32byte, 그리고 크기가 10인 Object 객체 배열을 위해 56byte가 할당되어 총 88byte가 할당된다.
    • 데이터 추가시 새로운 사이즈의 배열(+1)을 다시 할당 (메모리 재할당)

    ArrayList/LinkedList 저장 차이

    • n개의 자료를 저장할 때, ArrayList는 자료들을 하나의 연속적인 묶음으로 묶어 자료를 저장하는 반면, LinkedList는 자료들을 저장 공간에 불연속적인 단위로 저장

    GC 종류 별 특징 추가 정리

    • Java8에서는 perm 영역이 metaspace로 변경되었으며 native 영역이므르 dump로 확인할 수 없다.
    • young generation이 작으면 short live object가 old generation으로 넘어갈 확률이 높아지고, major full gc가 자주 발생하므로 느려진다.
    • JSP 페이지가 너무 과다하게 deploy되면 Permanent 영역 누수있음
    • Parallel GC: old generation gc는 단일 프로세서로 동작하여 serial collector와 동일함
    • CMS GC:
      • young generation gc는 parallel Collector와 동일함 (full gc에서 4단계 수행)
      • 전체적인 gc에 걸리는 시간은 결코 짧지 않음, 짧게 멈추면서 자주 gc하는 방식
      • 여러 개의 cpu를 가지고, client에게 빠른 응답을 줘야 할 프로그램에서 사용하기 좋음
      • stop-the-world pause 가 자주 일어나지만 시간 자체를 짧게함
      • 메모리가 가득차면 Mark-Sweep-Compact 로 full gc를 수행하므로 old generation의 남은 용량이 중요함
      • CMS Collector의 단점
        • gc를 수행하는데 표시 작업을 3번이나 진행함
        • 중요: 다른 collector와 달리 compaction을 진행하지 않으므로 “메모리 단편화”가 발생함 (다른 collector는 compaction을 하므로 빈 공간 시작 주소값이 있지만, CMS는 그렇지 않음)
        • 다른 collector 보다 heap 영역을 더 사용함
        • 이미 marking했지만 더이상 사용하지 않는 객체도 발생함 : floating garbage

    TPS

    • TPS = Concurrent Users / Res. Time + Think Time (모바일은 대략 3초)
    • Active User = TPS (위에서 구한) * Res. Time

    tmpC

    • TPC-C 벤치마크 시나리오에 대한 1분당 최대처리건수
    • 동시사용자수×분당 트랜잭션(사용자수×트랜잭션 복잡도(50%))+인터페이스(가중치%)×네트워크 보정(30%)×피크 타임 보정(50%)×I/O 부하(20%)×년간 업무증가 및 여유율(연 20%)
    • 메모리 용량 = {(OS 커널(100M)+[SGA()]+사용자수×5MB)+[Webserver()]+인터페이스(가중치%)}+여유율(30%)

    Tomcat Connector

    • Class: Http11NioProtocol Http11Nio2Protocol Http11AprProtocol
    • Servlet 자체가 Blocking이기 때문에 NIO라도 어차피 Connection과 Thread는 비슷
    • APR: APR은 Apache Web Server의 io module을 사용한다. 그래서 C라이브러리를 JNI 인터페이스를 통해서 로딩해서 사용하는데, 속도는 APR이 가장 빠른것으로 알려져 있지만, JNI를 사용하는 특성상, JNI 코드 쪽에서 문제가 생기면, 자바 프로세스 자체가 core dump를 내면서 죽어 버리기 때문에 안정성 측면에서는 BIO나 NIO보다 좋다.

    Design Pattern

    • Singleton, Builder, Factory Method, Composite, Template Method
  • 내가 생각하는 모바일 Backend 트렌드

    회사에서 미션을 받아 가트너 10대 전략 기술 옆에 모바일 Backend 분야에 트렌드 환경을 한번 그려보았다

    Mobile Message Transfer

    • Mobile Device & Service Mesh (가트너, ‘2016년 10대 전략 기술’)의 서버 기술 핵심은 다양한 디바이스간 메시징 처리 기술

      • http://www.gartner.com/smarterwithgartner/top-ten-technology-trends-signal-the-digital-mesh/
    • Message Broker의 비동기/분산/다중 프로토콜 메시징 기능은 모바일 서버 생태계의 데이터 Push/UX 상호작용/데이터 수집 채널 등 다양한 요건을 만족할 수 있으므로 주요 OSS 영역이라 할 수 있다.

    • 사업 규모 : Worldwide $10.3 billion in 2013 → $32.7 billion in 2020 (WinterGreen Research)

      • https://www.reportbuyer.com/product/963518/middleware-messaging-market-shares-strategies-and-forecasts-worldwide-2014-2020.html

    Cloud Delivery & OSS Prototyping

    • OSS 기반의 모바일 생태계의 기술 선도 관건은 빠른 검증 및 활용 능력이다.

    • 클라우드 환경을 통한 OSS Prototyping은 빠르고 실제 외부 환경의 모바일 서비스 검증에 효과적이다.

    • Micro Service 같은 경량화 서비스 구성에 빠른 이행 및 초기 사업 도입/검증을 위해 서버 인력의 AWS 활용 능력이 뒷받침 되어야 한다.

      • 조금은 슬픈게 AWS Lambda를 보면 더 절감이 되는게 이제는 단순 Backend에는 많은 시간와 구조를 요구하려 하지 않는다. 훌륭한 회사들이 앞다퉈 Server-less Architecture를 제공하고 서비스 하려는 걸 보면 이제는 기술 중요도가 Frontend와 Deep-Backend(Big Data, AI)에 편중되고 있다할까? 살아남기 위한 노력의 시기가 왔고 Backend의 기술 중요가 아직 살아있는 메시징 기술에 대한 깊이를 다져야 할 것 같다. 나는 ㅋㅋ

    Infra + Application Service Monitoring/Analysis

    • 단순 인프라 모니터링 뿐만 아니라 모바일 플랫폼/서비스는 내/외 연계를 통한 Data Mix 특성으로 인해 외부 연계에 대한 모니터링 및 이력 분석이 중요하다.

    • Micro Service 같은 경량화 서비스 구성에 빠른 이행 및 초기 사업 도입/검증을 위해 AWS 활용 능력이 뒷받침 되어야 한다.

  • Spring RestTemplate의 사설인증서 연계를 위한 SSLConnectionSocketFactory 생성

    Spring RestTemplate를 이용하여 외부 연계 개발시 활용 가능한 모듈 정리

    • 비용의 문제겠지만 개발 및 테스트 웹 시스템 환경의 TLS/SSL 인증서는 사설 인증서 또는 원래 호스트와 다른 도메인의 인증서가 적용되어 있는 경우가 많다.

    • 위와 같은 환경에 대해 RestTemplate(org.springframework.web.client.RestTemplate - Apache HttpComponents 기반)를 활용하여 HTTPS 연계를 수행할 경우 기본적으로 수행하는 인증서 검증(호스트명과 인증서 대조) 기능 때문에 오류를 발생하기도 한다.

    • 아래 클래스의 명세는 RestTemplate의 기반인 Apache HttpComponents의 ‘SSLConnectionSocketFactory’ 클래스를 커스텀 하여 구현된 Spring Factory Bean 클래스이다.

    • 이 클래스를 RestTemplate에 적용하고 property 설정을 통해 사설 인증서 및 잘못된 Host 정보의 인증서에 대해서도 허용할지 여부를 결정할 수 있다.

    • 검증 버전: Apache HttpComponents 4.5.x

    클래스

    
    import javax.net.ssl.HostnameVerifier;
    import javax.net.ssl.SSLContext;
    
    import org.apache.http.conn.ssl.NoopHostnameVerifier;
    import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
    import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
    import org.apache.http.ssl.SSLContexts;
    import org.springframework.beans.factory.FactoryBean;
    
    /**
     * <PRE>
     * SSLConnectionSocketFactory 생성 클래스
     * - HTTPS VERIFIER 수정: NoopHostnameVerifier
     * - TRUST CA 전략 수정 : TrustSelfSignedStrategy
     * SSLConnectionSocketFactory.getSocketFactory() 커스텀을 위해 FactoryBean 형태로 구현
     * </PRE>
     *
     * @author    윤순혁
     * @version   1.0
     * @see       SSLConnectionSocketFactory
     */
    public class CustomSSLConnectionSocketFactory implements FactoryBean<SSLConnectionSocketFactory> {
    
    	private boolean allowAllHostname;
    
    	private boolean allowSelfSignedCa;
    
    	/**
    	 * @param allowAllHostname the allowAllHostname to set
    	 */
    	public void setAllowAllHostname( boolean allowAllHostname ) {
    		this.allowAllHostname = allowAllHostname;
    	}
    
    	/**
    	 * @param allowSelfSignedCa the allowSelfSignedCa to set
    	 */
    	public void setAllowSelfSignedCa( boolean allowSelfSignedCa ) {
    		this.allowSelfSignedCa = allowSelfSignedCa;
    	}
    
    	/* (non-Javadoc)
    	 * @see org.springframework.beans.factory.FactoryBean#getObject()
    	 */
    	@Override
    	public SSLConnectionSocketFactory getObject() throws Exception {
    		/*
    		 * SSLConnectionSocketFactory.getSocketFactory() 커스텀
    		 *  - HTTPS VERIFIER 수정: NoopHostnameVerifier
    		 *  - TRUST CA 전략 수정 : TrustSelfSignedStrategy
    		 */
    		// HTTPS 요청시 인증서 오류(개발/검증 서버의 사설 인증서 적용 상황)를 막기위한 SSLSocket Context 생성 과정
    		HostnameVerifier hostnameVerifier = this.allowAllHostname ? NoopHostnameVerifier.INSTANCE : SSLConnectionSocketFactory.getDefaultHostnameVerifier();
    		SSLContext sslContext = allowSelfSignedCa ? SSLContexts.custom().loadTrustMaterial( new TrustSelfSignedStrategy() ).build() : SSLContexts.createDefault();
    		SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory( sslContext, hostnameVerifier );
    
    		return sslSocketFactory;
    	}
    
    	/* (non-Javadoc)
    	 * @see org.springframework.beans.factory.FactoryBean#getObjectType()
    	 */
    	@Override
    	public Class<?> getObjectType() {
    		return SSLConnectionSocketFactory.class;
    	}
    
    	/* (non-Javadoc)
    	 * @see org.springframework.beans.factory.FactoryBean#isSingleton()
    	 */
    	@Override
    	public boolean isSingleton() {
    		return false;
    	}
    }
    

    설정 (XML)

    • 아래는 RestTemplate을 선언한 Spring Context XML 파일이며 ‘PoolingHttpClientConnectionManager’ Bean 내부의 HTTPS Socket 생성 설정에 위 클래스를 적용한다.
      • allowAllHostname: HTTPS 연동시 TLS/SSL 인증서의 모든 Hostname 허용 여부 (true/false)
      • allowSelfSignedCa: HTTPS 연동시 TLS/SSL 사설인증서 허용 여부 (true/false)
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
    	xmlns:context="http://www.springframework.org/schema/context"
    	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:p="http://www.springframework.org/schema/p"
    	xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
    		http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
    		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    
    <!--====================================================================================================
    = 외부 시스템과 REST 연동을 위한 REST Template 설정 (Apache HttpComponents Wrapper)
    =====================================================================================================-->
    
    <!-- RestTemplate -->
    <bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
    	<property name="requestFactory">
    		<bean id="httpComponentClientFactory" class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory">
    			<constructor-arg><bean id="httpClient" factory-bean="httpClientBuilder" factory-method="build" /></constructor-arg>
    		</bean>
    	</property>
    </bean>
    
    <!-- HTTP Client Builder -->
    <bean id="httpClientBuilder" class="org.apache.http.impl.client.HttpClientBuilder" factory-method="create">
    	<property name="connectionManagerShared" value="true" />
    	<property name="connectionManager">
    		<!-- Connection Pool Manager : PoolingHttpClientConnectionManager 클래스 + 별도 SSL Socket Factory 적용 -->
    		<bean id="connManager" class="org.apache.http.impl.conn.PoolingHttpClientConnectionManager">
    			<constructor-arg name="socketFactoryRegistry">
    				<bean class="org.apache.http.config.Registry">
    					<constructor-arg>
    						<map>
    							<entry key="http">
    								<bean class="org.apache.http.conn.socket.PlainConnectionSocketFactory" factory-method="getSocketFactory" />
    							</entry>
    							<entry key="https">
    								<bean class="package.CustomSSLConnectionSocketFactory">
    									<!-- HTTPS 연동시 TLS/SSL 인증서의 모든 Hostname 허용 여부 -->
    									<property name="allowAllHostname" value="true" />
    									<!-- HTTPS 연동시 TLS/SSL 사설인증서 허용 여부 -->
    									<property name="allowSelfSignedCa" value="true" />
    								</bean>
    							</entry>
    						</map>
    					</constructor-arg>
    				</bean>
    			</constructor-arg>
    			<!-- 각 host(IP와 Port의 조합)당 Connection Pool에 생성가능한 Connection 수 -->
    			<property name="defaultMaxPerRoute" value="10" />
    			<!-- Connection Pool의 수용 가능한 최대 사이즈 -->
    			<property name="maxTotal" value="50" />
    			<!-- 특정 시간 이후 유휴 커넥션 정리 (ms) -->
    			<property name="validateAfterInactivity" value="60000" />
    		</bean>
    	</property>
    </bean>
    </beans>
    
  • RabbitMQ와 WebSocket 기반 모바일 실시간 웹 메시징

    작성자가 직접 수행한 의미있는 아키텍처 설계 및 구축 작업을 간략히 소개함

    실시간 웹 메시징

    • ‘실시간 웹 메시징’은 모바일/PC 브라우저 환경에서 웹 접속 사용자가 수행하는 동작 또는 메시지 입력 같은 조작을 데이터로 변환하여 다른 사용자가 화면에 접속한 상태에서 실시간으로 브라우저 상에서 전달 받는 기능을 의미한다.

    • 본 시스템을 사용하는 참여자들로 구성된 사용자 그룹을 임의로 구성하고 같은 목적을 가지고 특정 기능을 동시에 사용한다.

    • 위의 기능요건을 시스템으로 표현 시 ‘실시간 웹 메시징’이라고 정의하며 사용 과정을 업무 기능으로 표현 시 ‘상호작용’ 이라고 명명한다.

    • 구축의 요건은 소규모 동시 사용자 (약 5백명) 이지만, 업무 특성(교육)을 고려 HA 구성이 필수적이다.

    • 기술 요구는 HTML5 기반 모바일 웹에 수행할 수 있는 실시간 웹 메시징이다.

    실시간 웹 메시징 기술 상세

    • 로그인 또는 별도 인증된 사용자만 실시간 웹 메시징을 수행할 수 있어야 하며 모바일 웹 시스템에 필요한 보안 요소를 지원해야 한다.

    • 메시지를 송/수신 하는 범위는 ‘상호작용’ 기능을 활용하여 논리적으로 만들어진 참여자들로 구성된 사용자 그룹이다. 즉, 동일 ‘상호작용 그룹’에 속한 참여자(사용자)들끼리만 메시지를 송/수신 할 수 있다.

    • 상호작용 그룹이 같을 경우 물리적 제약 없이 상호 메시지 전달이 가능해야 한다.

    • 서버는 실시간으로 메시지를 전달하기 위해 Push 방식으로 하나의 Target Client에서 요청한 이벤트를 다른 Target Client에 전달한다.

    • Target Client의 기능 중단 없이 접속하는 사용자에게 기능을 제공하는 응용 프로그램 또는 서버간 Failover를 유도할 수 있어야 한다.

    Message Broker 필요성

    • Target Client간 또는 Target Client와 서버간 실시간 웹 메시징 기능 수행을 구축일정 단축, 성능 및 분산/이중화 메시징 아키텍처 구성 측면을 고려하여. 별도 ‘Message Broker’ 같은 별도 미들웨어를 도입하여 메시지 송/수신 채널 역할을 수행한다.

    • Message Broker: 다중 사용자-서버간 메시지 요청/응답을 중계/저장/전달처리 하는 미들웨어 또는 Server Side Application

    • HTML5 Websocket 기능을 서버에서 효과적으로 제공하기 위해 반드시 필요한 Layer로 결정함.

    Message Broker 선택

    무지막지한 대형 시스템이 아닌이상, 간단하면서도 원하는걸 쉽게쉽게 만족하기에는 RabbitMQ 만한게 없다. 이 빠돌이!

    • Message Broker 도입 시 검토할 수 있는 기능 및 요건은 아래와 같다.

      • 반드시 웹 브라우저를 대상으로 하는 클라이언트 프로토콜(Websocket) 지원

      • 자체 인증 또는 외부 시스템 연계를 통한 확장 인증 방법 지원

      • 클러스터를 통한 이중화 구성 가능

      • 관리자 도구를 통한 전체적은 모니터링이 용이

      • 사용자 수신 전 까지 메시지를 유실하지 않고 저장하는 기능

      • 오픈 소스 임에도 불구하고 오랜 기간 전 세계적으로 검증되었거나 오픈 소스 및 상용화 버전을 모두 제공하면서 해당 분야에서 검증된 제품이어야 함

    • RabbitMQ 결정 이유: 검토한 결과 위의 요건을 만족하고, Java Integration 개발 지원(Spring Framework 지원)이 강력하고, 사용하기 편리하고 (관리 Web 페이지 제공), 주요 운영체제에서 모두 실행 가능하며, 오픈 소스 제공 뿐만 아니라 상용 및 사후 지원 제품(vFabric RabbitMQ)을 제공한다. (고객의 OSS 거부감 배려)

    WebSocket 선택

    • 모바일/PC 브라우저 상에서 실시간 웹 메시징 기능은 HTML5의 Websocket 또는 SockJS 등 관련 기술 적용이 필요하다.

    • Websocket 기술을 사용할 경우 PC 웹에서 제공하는 기능은 브라우저 제약을 감수 해야 한다.

    • 사용자 환경(모바일 or PC의 경우 Chrome Browser 유도)을 고려하여 ‘Websocket’ 기술을 핵심으로 사용하기로 했다.

    Stomp

    Streaming Text Oriented Message Protocol, Simple Text Oriented Message Protocol; 약자

    • 스트리밍 및 단순 텍스트 지향 메시지 프로토콜 (텍스트/Binary 기반)

    • HTTP로 부터 영감을 받은 디자인

    • 메시지 지향 미들웨어에서 사용하기 위해 설계됨

    • 다양한 언어, 플랫폼에서 클라이언트, 브로커 사용가능 (Client: Javascript, Server: SpringFramework)

    • Websocket 조합과 함께 널리 사용되고 있다.

    이렇게 구성해보았다

    1. 아키텍처 컨셉1

    1. 아키텍처 컨셉2: 요청 부터 응답까지

    • 왜 이렇게 했는가: 사용자가 참여하는 메시지는 이력유지가 되어야 하고 메시지를 수신해야 하는 사용자 그룹은 반드시 전달 받아야 한다.

    • 사용자 참여(요청) 메시지는 AJAX 요청이지만 서버에서는 동기식으로 처리하여 메시지를 서버에서 반드시 확보한다. (흘리지 않는다)

    • 흘리지 않기 위해 요청하는 메시지 이력은 별도 데이터 저장소에 관리한 다음 Message Broker에 전달한다.

    • 사용자는 Websocket 연결을 유지하기 위한 메커니즘을 제공받고 Push 형태로 사용자가 받아야 하는 메시지를 받는다.

    • 즉 서버가 Produce 역할을 수행하고 메시지 중요성(반드시 전달, 인스턴트)에 따라 클라이언트는 Consume 또는 Subscribe 수행.

    • 과정 요약

      1) RabbitMQ의 메세지는 ‘Producer’(WAS)로부터 발행이 되어 ‘Exchanges’(우체통이나 우체국에 해당함)에게 보내지게 된다.

      2) Exchanges는 메시지를 ‘Bindings’이라 불리는 룰에 의해 전달 되어야 하는 Queue에 분배한다.

      3) RabbitMQ가 Queue를 구독하는 ‘Consumers’(사용자-모바일/PC 웹 브라우저/웹앱)에게 메시지를 전달한다.

    1. 아키텍처 컨셉3: RabbitMQ 활용

    • RabbitMQ의 WebSocket-Stomp Adapter Plugin을 활용하여 사용자 Front End 연결 및 서버간 메시지 중계

    • 메시지 확인 중요도에 따라 관리자 등급은 Consume 방식의 Private Queue 할당

    • 사용자 그룹 참여자의 경우 Subscribe 방식의 동적 Queue(Topic 메시지 처리 특징)가 할당되며 메시지 수신 무결성 확보를 위해

      • Health Check (Stomp)

      • 일정 시간 이후 메시지 수신이 없나면 비동기로 마지막 시점 이후 메시지 데이터 Sync를 서버에서 받는다.

    1. 아키텍처 컨셉4: Topic Exchange

      • Topic Exchange 는 메시지를 이미 Exchange에 등록된 큐 중에서 ‘Routing Key’나 ‘Routing Key Pattern’이 매칭되는 경우 전달한다. 보통 Multicast 라우팅에 많이 쓰인다.

      • 실시간 웹 메시징은 1:N, N:1 (관리자가 상호작용 그룹 참여자에게 메시지 전달 또는, 여러 참여자가 해당 상호작용 그룹의 관리자에게 메시지를 전송) 방식의 메시징 설계가 필요하므로 RabbitMQ에서 제공하는 Exchange 종류 중 ‘Topic Exchange’를 활용(기본으로 제공하는 ‘amq.topic’ Exchange 활용)한다.

      • Routing key(Pattern)는 다음과 같이 dot<.>로 구분된 단어 리스트 형식을 가져야 한다.

      “단어.단어”, “단어.단어.단어”, “*.단어.*”, “단어.#” 등등..
      - Wildcard -
      1) *(star): 임의의 한 단어를 대신합니다.
      2) #(hash): 0 개 이상의 단어를 대신합니다.
      
      • Topic exchange는 다른 Exchange 타입과 같은 동작을 할 수 있는데, 만약 Routing Key=”#” 를 지정한다면 모든 message를 받기 때문에 ‘Fanout Exchange’와 같이 동작하며, routing key=”단어” 와 같이 Wildcard(‘#’, ‘*’)를 쓰지 않는다면 ‘Direct Exchange’와 같이 동작하게 된다.

      • Websocket 클라이언트 접속 후, Subscribe를 수행하면 RabbitMQ는 자동으로 ‘stomp-subscription-‘로 시작하는 Queue를 생성한 다음 클라이언트가 Subscribe하려는 Topic을 확인해서 해당 Queue와 자동으로 Binging 처리한다. 이렇게 자동으로 생성된 Queue는 클라이언트가 Subscribe를 종료(Connection 종료)하면 자동으로 삭제된다. 이러한 연동은 여려명의 참여자 클라이언트에서 사용한다.

      • 상호작용 참여 방 생성시 개발자는 제공되는 공통 모듈을 이용하여 미리 Queue(Name: “acdm.mo.interact.private.상호작용일련번호”, Binding: “acdm.mo.interact.*.상호작용 일련번호”)를 생성하고 사용자(관리자/참여자)는 해당 Queue에 접속하기 위해 특정 Queue에 Subscribe를 수행한다. 따라서 관리자 1인에게 전달되는 메시지를 처리(질문)하거나 상호작용 참여 그룹간 주고 받는 메시지를 모두 처리할 수 있다.

      • 이렇게 생성된 Queue는 장시간 미사용 시 Queue Expire 정책(RabbitMQ 설정: 1일)을 적용하여 접속이 끊겼더라도 삭제되지 않는다. 따라서 학습자는 상호작용 참여 방 생성 후 관리자의 접속 없이 바로 Action을 수행하더라도 관리자는 상호작용 화면에 돌입하기 전까지 현재까지 수신 받은 메시지를 한꺼번에 받을 수 있다. 이러한 메시징 아키텍처 Concept은 관리자가 전체 참여 인원과 화면을 공유하여 제공하기 네트워크 문제에도 사용자들 간 Action 메시지를 유실하지 않고 처리하기 위한 목적으로 설계되었다.

      • 이러한 메시징 아키텍처 구성은 STOMP 프로토콜 자체가 Topic또는 Queue에 대한 Subscribe를 선택하여 접속하는 것을 지원하기 때문에 가능하다. 메시지 전문에서 JSON Body를 제외한 상위 영역을 헤더라고 정의하는데, ‘/topic/~’, ‘/queue/~’로 시작하는 주소를 STOMP에서는 ‘destination’이라고 정의하는 헤더에 설정해야 한다.

    2. 응용 레벨

    • Spring Integration AMQP를 활용하고, Publish Channel을 설정하여 서버-RabbitMQ간 요청 채널 설정

    • 모바일 웹 채널 TLS/SSL 접속이 필요하다.

    • Client가 RabbitMQ에 직접 접속하기에는 네트워크 구성 제약(DMZ에 RabbitMQ 구성 불가 정책) 고려

    • RabbitMQ HA 구성시 Client 접근에 대한 Load Balancing 필요

    • 위와 같은 이유로 Apache HTTPD 서버의 ‘mod_proxy_wstunnel’를 활용한다.

    • Apache 서버를 이용해 Websocket 통신을 Proxy 연동 할 경우, 기존 웹 서버 공인인증서를 활용하여 WSS(TLS Websocket – HTTPS에 해당) 통신을 수행하기 용이하다.

    • Apache 서버는 일반 HTTP 요청은 WAS에 전달하며(MOD_JK), Websocket 요청일 경우 RabbitMQ의 Websocket Channel에 Bypass 한다.

    • WAS에 배포되는 응용 프로그램은 ‘Spring Integration AMQP’ 모듈을 통해 RabbitMQ에 메시지 Push를 요청한다. Spring Framework에 AMQP를 위한 연동 기능이 지원되기 때문에 Websocket과 무관하게 서버간 통신은 AMQP로 수행한다. ‘Topic Exchange’ 요청을 통해 다수 사용자(Fanout Exchange과 유사하게 동작) 또는 관리자가 메시지를 수신할 Queue에 집적 메시지를 전달(Direct Exchange와 유사하게 동작)할 수 있다.

    HA

    • 무중단 서비스 목적을 달성하기 위해 기본적으로 이중화 구성으로 서버를 구축하며 RabbitMQ서버의 경우 클러스터 구성을 통해 Failover를 가능하게 한다.
      • Web → RabbitMQ: Active/Active, Apache Websocket Proxy를 통한 Load Balancing (mod_proxy_wstunnel + mod_proxy_balancer 확장)
      • WAS → RabbitMQ: Active/Active, Spring Integration AMQP를 통한 Load Balancing
      • RabbitMQ: Cluster 구성, 참여자-Shared Queue(서버간 Queue 내용 공유), 관리자-HA Queue(서버간 동기화: Replication)을 통해 서로 다른 RabbitMQ서버에 접속한 사용자간 메시징 처리를 가능하게 하고 관리자의 경우 RabbitMQ서버 중 한대가 중단되더라도 Failover 가능(다른 서버에 복제된 Queue에 그대로 서비스 가능)

    삽질의 순간

    • mod_proxy_wstunnel 의 기능은 생각보다 별로 없다.

    • RabbitMQ에 대한 접속 보안을 위해 ‘RabbitMQ HTTP Authentication Plugin’를 선택했지만 2016년 8월 당시 버전으로는 GET 방식의 연계만 가능했다…

  • Apache Httpd 2.4 간단 구축

    설치 작업 전 확인사항

    1. 시스템코드 확인 : 시스템 설치를 위한 구성정보 요청서에 명시된 5자리 이하의 입력코드

    2. 엔진 설치여부 및 엔진관리 계정(apaadm) 생성 확인

    3. 엔진 미설치시 openssl / GCC compiler 설치여부 확인

      • openssl과 openssl-devel (openssl lib로 openssl관련 헤더 파일정보가 있어 compile시 필요)

        예제>

         [root@XXXXXDEV01 extra]# rpm -qa | grep openssl
         openssl-0.9.8e-12.el5_4.1
         openssl-devel-0.9.8e-12.el5_4.1
        
      • GCC는 apache 바이너리 설치파일 compile시 필수적임

        예제>

         -bash-3.2$ rpm -qa | grep gcc
         compat-gcc-34-3.4.6-4
         compat-libgcc-296-2.96-138
         gcc-gfortran-4.1.2-48.el5
         gcc-gnat-4.1.2-48.el5
         gcc-java-4.1.2-48.el5
         libgcc-4.1.2-48.el5
         gcc-c++-4.1.2-48.el5
         gcc-objc-4.1.2-48.el5
         gcc-4.1.2-48.el5
         libgcc-4.1.2-48.el5
         ...등등...
        

        없으면 설치: yum install gcc gc gcc-c++ make apr-util openssl openssl-devel zlib zlib-devel unzip perl

    4. 시스템코드+adm(application 관리) 계정 생성 확인

    5. 사용 포트 선정 (가장 최근 설치된 도메인의 Port Set 확인 및 netstat -an grep 포트)

    설치 작업

    1. apr, apr-util,pcre 다운로드 및 설치

      • 압축 풀어서 apache 컴파일일 경로에 옮긴다

      • APR: 아파치와 한꺼번에 컴파일
        mv ./apr-1.5.1 ./httpd-2.4.9/srclib/apr
        mv ./apr-util-1.5.3 ./httpd-2.4.9/srclib/apr-util
        
      • CRONOLOG (rotatelogs를 사용하지 않기로 결정할 경우)
        [root] cd cronolog
        [root] ./configure --prefix=/usr/local/cronolog 
        [root] make && make install
        
      • PCRE
        [root] cd pcre-8.35
        [root] ./configure --enable-unicode-properties=yes
        [root] make && make install
        
    2. apache 컴파일 옵션 및 엔진 설치

      • apache 2.4.xx 설치
      [root] cd /engn001/apaadm/apache2
      [root] ./configure --prefix=/engn001/apaadm/apache24 --enable-modules=all --enable-mods-shared=most --enable-mpms-shared=all --enable-rewrite --enable-proxy --enable-so --enable-proxy-http --enable-proxy-connect --enable-cache --enable-mem-cache --enable-disk-cache --enable-deflate --enable-ssl --with-ssl=/usr/include/openssl --with-included-apr --with-included-apr-util --enable-nonportable-atomics=yes
      [root] make && make install
      
    3. AJP compile 및 설치

      • tomcat-connectors 1.2.28 설치
      [root] cd tomcat-connectors-1.2.28-src
      [root] cd native
      [root] ./configure --with-apxs=/engn001/apaadm/apache24/bin/apxs; make ; make install
      
    4. Apache 홈 디렉토리 구조 잡기

      • 엔진 홈 (예> /engn001/[서버기동계정:예) apaadm]/apache24/)
        • servers/ : 웹서버 인스턴스를 설치하기 위한 디렉토리로 임의로 생성함
        • bin/ : apache 인스턴스 기동 및 정지 등 실행에 관련된 쉘이 위치함
        • modules/ : *.so 등의 apache 에서 사용 가능한 모듈들의 파일이 위치
        • logs/ : apache에서 제공하는 기본 로그 디렉토리로 임의로 pid 등의 중요 로그를 위치시키는데 사용함
        • conf/ : apache에서 기본적으로 제공되는 template conf 파일이 위치하며 인스턴스별로 해당 디렉토리를 복사해서 사용함
    5. Apache 인스턴스 집합 디렉토리 생성

      • 엔진 홈 디렉토리(예> /engn001/apaadm/apache24/) 아래 servers/ 하위에 인스턴스 디렉토리들이 위치함

      예제>

      [apaadm]$ cd /engn001/apaadm/apache24
      [apaadm]$ mkdir servers
      [apaadm]$ chmod 755 ./servers
      
    6. 인스턴스 생성

      • 인스턴스 디렉토리 명은 xxxxx[_NN](xxxxx[_NN] : 5자리 이하의 시스템코드, 이중화 구성시 ‘_NN’ 붙여 구분함)으로 디렉토리 생성함

      예제>

      [apaadm]$ cd /engn001/apaadm/apache24/servers
      [apaadm]$ mkdir xxxxx[_NN]
      [apaadm]$ chmod 755 ./xxxxx[_NN]
      

      ex> /engn001/apaadm/apache24/servers/test_10, /engn001/apaadm/apache24/servers/test_20

    7. 인스턴스 conf 디렉토리 생성

      • 인스턴스 디렉토리 아래 apache에서 기본으로 제공되는 /engn001/apaadm/apache24/conf 디렉토리를 복사해서 수정하여 사용함

      예제>

      [apaadm]$ cd /engn001/apaadm/apache24/servers/xxxxx[_NN]
      [apaadm]$ cp -r /engn001/apaadm/apache24/conf /engn001/apaadm/apache24/servers/xxxxx[_NN]
      
    8. 로그 디렉토리 생성

      • 로그를 위한 파일시스템 (/logs001 등) 이 존재하는 경우 : 로그 파일시스템 하위에 Apache관리계정명/apache24 디렉토리를 생성하고 해당 인스턴스를 위한 로그 디렉토리를 생성. 파일시스템 하위 로그 디렉토리를 바라보는 논리적인 링크인 logs를 각 인스턴스 디렉토리 아래에 생성

      예제>

      [apaadm]$ cd /logs001
      [apaadm]$ mkdir ?p apaadm/apache24/xxxxx[_NN]
      mkdir: cannot create directory `xxxxx': Permission denied
      [apaadm]$ ls -al
      drwxr-xr-x 13 root      root       4096 May  7 10:45 .
      drwxr-xr-x 38 root      root       4096 May  4 17:50 ..
      [apaadm]$ su (root 혹은 그에 준하는 권한 가진 계정으로 switch user)
      [apaadm]$ mkdir -p apaadm/apache24/xxxxx[_NN]
      [apaadm]$ chown -R apaadm:apaadma ./apaadm
      [apaadm]$ chmod 755 ./apaadm
      [apaadm]$ exit
      [apaadm]$ cd /engn001/apaadm/apache24/servers/xxxxx[_NN]
      [apaadm]$ ls
      conf  start.sh  stop.sh
      [apaadm]$ ln -s /logs001/apaadm/apache24/xxxxx[_NN] ./logs
      [apaadm]$ ls -rlt
      drwxr-xr-x  4 apaadm apaadm 4096 5?? 7 10:48 conf
      lrwxrwxrwx  1 apaadm apaadm 19  5?? 7 10:48 logs -> /logs001/apaadm/apache24/xxxxx[_NN]
      
    9. 기동 쉘 작성

      • 해당 인스턴스 디렉토리 아래에 기동 쉘 작성

      예제>

      [apaadm]$ vi start.sh
         
      (편집)
      /engn001/apaadm/apache24/bin/apachectl -f /engn001/apaadm/apache24/servers/xxxxx[_NN]/conf/httpd.conf -k start
         
      [apaadm]$ chmod 744 start.sh
      
      • 해당 인스턴스 디렉토리 아래에 정지 쉘 작성

      예제>

      [apaadm]$ vi stop.sh
         
      (편집)
      /engn001/apaadm/apache24/bin/apachectl -f /engn001/apaadm/apache24/servers/xxxxx[_NN]/conf/httpd.conf -k stop
         
      [apaadm]$ chmod 744 stop.sh
      
    10. 설정 변경

    1) http.conf 수정

    • 서비스 포트 지정
       #Listen 12.34.56.78:80
       Listen 6010
       #다수 서비스포트 추가시 Listen 포트 계속 추가함
       #Listen 6020
    
    • 유저/그룹 지정
       # User/Group: The name (or #number) of the user/group to run httpd as.
       # It is usually good practice to create a dedicated user and group for
       # running httpd, as with most system services.
       # 로그인 불가능한 계정으로 설정해야 함 : /bin/false
       User nobody
       Group nobody
    
    • 서버명 지정
       ServerName localhost
    
    • DocumentRoot 지정 및 directory index 비활성화 조치
       #DocumentRoot "/engn001/apaadm/apache24/htdocs"
       DocumentRoot "/sorc001/cppeadm/applications/htdocs"
    
       <Directory />
        Options FollowSymLinks
        AllowOverride None
        <LimitExcept GET POST HEAD>
        	Order deny,allow
        	Deny from all
        </LimitExcept>
       </Directory>
    
       #<Directory "/engn001/apaadm/apache24/htdocs">
       <Directory "/sorc001/qmntadm/applications/htdocs">
        #
        # Possible values for the Options directive are "None", "All",
        # or any combination of:
        #   Indexes Includes FollowSymLinks SymLinksifOwnerMatch ExecCGI MultiViews
        #
        # Note that "MultiViews" must be named *explicitly* --- "Options All"
        # doesn't give it to you.
        #
        # The Options directive is both complicated and important.  Please see
        # http://httpd.apache.org/docs/2.2/mod/core.html#options
        # for more information.
        #
        Options -Indexes FollowSymLinks
    
        #
        # AllowOverride controls what directives may be placed in .htaccess files.
        # It can be "All", "None", or any combination of the keywords:
        #   Options FileInfo AuthConfig Limit
        #
        AllowOverride None
    
        #
        # Controls who can get stuff from this server.
        #
        Order allow,deny
        Allow from all
       </Directory>
    
    • welcome 페이지 설정
       <IfModule dir_module>
        DirectoryIndex index.html index.jsp
       </IfModule>
    
    • 로그 수정
       #ErrorLog "logs/error_log"
       ErrorLog "|/engn001/apaadm/apache24/bin/rotatelogs /engn001/apaadm/apache24/servers/xxxxx[_NN]/logs/error_log.%Y.%m.%d 86400"
    
       #LogLevel warn
       LogLevel error
    
       #  LogFormat "%h %l %u %t \"%r\" %>s %b" common
       LogFormat "%h %l %u %t \"%r\" %>s %b %D" common
    
       #CustomLog "logs/access_log" common
       CustomLog "|/engn001/apaadm/apache24/bin/rotatelogs /engn001/apaadm/apache24/servers/xxxxx[_NN]/logs/access_log.%Y.%m.%d 86400" common
    
    • 주석풀고 경로 수정
       # Server-pool management (MPM specific)
       Include servers/xxxxx[_NN]/conf/extra/httpd-mpm.conf
       
       # Various default settings
       Include servers/xxxxx[_NN]/conf/extra/httpd-default.conf
    
    • 하단에 mod_jk 설정
       Include servers/xxxxx[_NN]/conf/mod_jk.conf
    
    • 상태 모니터링 페이지 설정
       <Location /server-status>
        SetHandler server-status
        Order deny,allow
        Deny from all
        Allow from 156.xx.xxx
       </Location>
    
       <Location /server-info>
        SetHandler server-info
        Order deny,allow
        Deny from all
        Allow from 156.xx.xxx
       </Location>
    
    • TRACE 메소드 무효화 (보안 이슈해결)
       TraceEnable Off
    

    2) httpd-default.conf 중 해당 설정을 아래와 같이 변경

       Timeout 300
       KeepAlive Off
                 Off (불특정 다수 사용시)
                 On (용량 큰 데이터 많이 오갈 때, 설정시 keepalive timeout 3초로)
       ServerTokens Prod
       ServerSignature Off
       HostnameLookups Off
    

    3) httpd-mpm.conf 중 해당 설정을 아래와 같이 변경

       PidFile "logs/xxxxx[_NN]_httpd.pid"
       LockFile "logs/xxxxx[_NN]_accept.lock"
    
    # event MPM
    # StartServers: initial number of server processes to start
    # MinSpareThreads: minimum number of worker threads which are kept spare
    # MaxSpareThreads: maximum number of worker threads which are kept spare
    # ThreadsPerChild: constant number of worker threads in each server process
    # MaxRequestWorkers: maximum number of worker threads
    # MaxConnectionsPerChild: maximum number of connections a server process serves
    #                         before terminating
    <IfModule mpm_event_module>
        StartServers             8
        MinSpareThreads        128
        MaxSpareThreads        256
        ThreadsPerChild         64
        MaxRequestWorkers     1024
        MaxConnectionsPerChild   0
    </IfModule>
    

    4) mod_jk.conf 생성

       [apaadm]$ cd /engn001/apaadm/apache24/servers/xxxxx[_NN]/conf
       [apaadm]$ vi mod_jk.conf
    
       # Load mod_jk module
       LoadModule jk_module modules/mod_jk.so
    
       # Specify the filename of the mod_jk lib
       <IfModule mod_jk.c>
       # Where to find workers.properties
       JkWorkersFile servers/xxxxx[_NN]/conf/workers.properties
    
       # Where to put jk logs
       JkLogFile "|/engn001/apaadm/apache24/bin/rotatelogs /engn001/apaadm/apache22/servers/xxxxx[_NN]/logs/modjk_log/mod_jk_%Y%m%d.log 86400"
       JkLogLevel error
       JkLogStampFormat "[%a %b %d %H:%M:%S %Y]"
       JkRequestLogFormat "%w %V %T"
    
       # JkOptions indicates to send SSK KEY SIZE
       # Note: Changed from +ForwardURICompat.
       # See http://tomcat.apache.org/security-jk.html
       JkOptions +ForwardkeySize +ForwardURICompatUnparsed -ForwardDirectories
    
       # Set the jk runtime status file
       JkShmFile servers/xxxxx[_NN]/logs/jk.shm
    
       # You can use external file for mount points
       # It will be checked for updates each 60 secondes
       # The format of the file is : /url=worker
       # /examples/*=loadbalancer
       # 정적 파일 경로는 Mod_JK 연동 안함
       SetEnvIf Request_URI "/resources/*" no-jk
       # 기타 연동안하는 URL 정의
       SetEnvIf Request_URI "favicon.ico" no-jk
       SetEnvIf Request_URI "/robots.txt" no-jk
       # RESTful 서비스 고려
       JkMount /* node_xxxx_01
       </ifModule>
    

    5) workers.properties 생성

       worker.list=node_xxxx_01,jkstatus
    
       # Templates을 선언해두어야 상속해서 확장하기 편하다
       worker.template.type=ajp13
       worker.template.maintain=60
       worker.template.lbfactor=1
       worker.template.ping_mode=A
       worker.template.ping_timeout=2000
       worker.template.prepost_timeout=2000
       # 전체 요청 대기시간 고려 (WAS 응답 Timeout + alpha)
       worker.template.socket_timeout=90
       worker.template.socket_connect_timeout=2000
       worker.template.socket_keepalive=true
       # WAS에서 의미있는 응답 대기 (ms) : 파일 업로드 시간이 길 경우 'socket_timeout' 값과 함께 늘려주어야 한다.
       worker.template.reply_timeout=60000
       # AJP Connection Timeout과 동일하게 구성
       worker.template.connection_pool_timeout=60
       worker.template.connect_timeout=2000
       worker.template.connection_pool_size=64
       worker.template.recovery_options=7
    
       # Set properties for node_xxxx_01(ajp13)
       worker.node_xxxx_01.reference=worker.template
       worker.node_xxxx_01.host=XXX.XXX.XXX.XXX
       worker.node_xxxx_01.port=8009
    
       # example) This is development server : Non Clustering
       #worker.xxxx_lb.type=lb
       #worker.xxxx_lb.balance_workers=node_xxxx_01
       #worker.xxxx_lb.method=Session
       #worker.xxxx_lb.sticky_session=True
    
       worker.jkstatus.type=status