아시는분이 Java Agent에 대한 정보를 주셔서 해당 기능을 Tomcat 요청에 대한 모니터링툴을 만들어보고 있다.


현재 겉모습은 제니퍼의 X-View 와 비슷하게 따라 하려는중이다.ㅎㅎ




1차적으로 요청 URI, 요청 시간, 종료 시간, 처리 시간, HttpStatus 값 정도만 차트에 표현하였다.


구조는 Tomat에 Agent를 띄운 후 Agent에서 모니터링툴 WAS에 WebSocket 로 보낸 후 데이터를 받은 모니터링툴에서 모니터링에 접속한 Client Browser 에 WebSocket로 데이터를 보내주는 방식이다.


모니터링툴에 붙은 Client가 많았을 경우 어떻게 될지 아직 잘 모르겠다.

상황봐서 Queue에 담아서 한다던가 고려해볼 생각이고 현재에는 비슷하게 구현만 하는게 목적이다.




인터넷의 자료를 통해서 javassist 를 활용하여 Byte Code Instrumentation 시도해보았지만, 일반적인 Java Main Program은 잘 되는데 Tomcat 의 특정 Class를 제어 하려고 했더니 잘 되지 않았다.

많은 삽질 끝에 ClassPool을 얻어 오는 부분을 조금 수정 하니 잘 되었다.

(이 부분때문에 몇일을 삽질 하다.. 포기도 할까 하고, Application Level Servlet Filter를 이용하는 방법으로 선회 할까도 했음...)


검색을 하두 해서 어디서 이 정보를 얻었는지 기억이 안나네요.



[해결 부분]

ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

ClassPool pool = ClassPool.getDefault();

pool.insertClassPath(new LoaderClassPath(classLoader));


[2015. 7. 6.]

WAS Application 요청시 org.apache.catalina.core.StandardEngineValve.invoke(Request request, Response response) 부분을 잡아 처리 하였다.

(만들어 가며 바뀔 수도 있는 부분임..)

이렇게 함으로 인해서 어떠한 문제가 발생할지는 미지수임...

HttpStatus=302 케이스에서 문제 발생


[2015. 8. 10.]

javax.servlet.http.HttpServlet.service(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse  으로 변경

이러면.. JSP Direct 호출이 안 잡히는군.. 쉽지 않구만...


다시 org.apache.catalina.core.StandardEngineValve.invoke(Request request, Response response) 원복!!

Spring Security LOGOUT HttpStatus 302일때문 문제가 발생!!

문제 발생 부분 request.getSession().getId()

이 부분을 Before 일때만 하고, After 일때는 주석


Before, After 요청 정보 맵핑을 Thread.currentThread().getId() 로 처리


우선 잘 된다...


[MonitorAgent.java]

package pe.kr.ddakker.monitor.agent;

import java.lang.instrument.Instrumentation;

import pe.kr.ddakker.monitor.agent.transformer.TomcatTransformer;

public class MonitorAgent {
	
	public static void premain(String args, Instrumentation inst) throws Exception {
		inst.addTransformer(new TomcatTransformer());
	}

	public static void agentmain(String args, Instrumentation inst) throws Exception {
		premain(args, inst);
	}
}


[TomcatTransformer.java]

package pe.kr.ddakker.monitor.agent.transformer;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtBehavior;
import javassist.CtClass;
import javassist.LoaderClassPath;
import javassist.NotFoundException;

/**
 * Tomcat 요청 가로채서 수정!!
 * @author ddakker 2015. 6. 14.
 */
public class TomcatTransformer implements ClassFileTransformer {
	ClassPool pool = null;
	public TomcatTransformer() {
		this.pool = ClassPool.getDefault();
	}
	
	public byte[] transform(ClassLoader loader, String className, Class redefiningClass, ProtectionDomain domain,
			byte[] bytes) throws IllegalClassFormatException {
		if (className.contains("StandardEngineValve")) {
			System.out.println("className: " + className);
			return transformClass(redefiningClass, bytes);
		} else {
			return bytes;
		}
	}

	private byte[] transformClass(Class classToTransform, byte[] b) {
		CtClass cl = null;
		try {
			ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
			
			ClassPool pool = ClassPool.getDefault();
			pool.insertClassPath(new LoaderClassPath(classLoader));
			if (pool != null) {
				cl = pool.makeClass(new java.io.ByteArrayInputStream(b));
				if (cl.isInterface() == false) {
					CtBehavior[] methods = cl.getDeclaredBehaviors();
					for (int i = 0; i < methods.length; i++) {
						System.out.println("methods[" + i + "]: " + methods[i]);
						if (methods[i].isEmpty() == false) {
							doTransform(methods[i]);
						}
					}
				}
				b = cl.toBytecode();
			}
		} catch (Exception e) {
			System.err.println("e111: " + e);
		} finally {
			if (cl != null) {
				cl.detach();
			}
		}
		return b;
	}
	
	private void doTransform(CtBehavior method) throws NotFoundException, CannotCompileException {
		if (method.getName().equals("invoke")) {
			
			System.out.println("0");
			try {
				System.out.println("1");
				method.insertBefore(""
						+ "String jvmRoute = System.getProperty(\"jvmRoute\");"
						+ "String sessionId = $1.getSession().getId();"
						+ "String uri = $1.getRequestURI();"
						+ "pe.kr.ddakker.monitor.websocket.WSClient.send(\"{"
						+ "server: '\" + jvmRoute + \"' "
						+ ", sessionId: '\" + sessionId + \"' "
						+ ", uri: '\" + uri + \"' "
						+ ", stTime: '\" + System.currentTimeMillis() + \"' "
						+ "}\");");
				System.out.println("2");
				method.insertAfter(""
						+ "String jvmRoute = System.getProperty(\"jvmRoute\");"
						+ "String sessionId = $1.getSession().getId();"
						+ "String uri = $1.getRequestURI();"
						+ "pe.kr.ddakker.monitor.websocket.WSClient.send(\"{"
						+ "server: '\" + jvmRoute + \"' "
						+ ", sessionId: '\" + sessionId + \"' "
						+ ", uri: '\" + uri + \"' "
						+ ", edTime: '\" + System.currentTimeMillis() + \"' "
						+ ", status: '\" + $2.getStatus() + \"' "
						+ "}\");");
				System.out.println("3");
			} catch (Exception e) {
				System.err.println("Aa e: " + e);
			}
			System.out.println("4");
		}
		
	}

}



[build.gradle]

jar {
	manifest {
		attributes 'Implementation-Title': 'Gradle Quickstart',
				   'Implementation-Version': version,
				   'Premain-Class': 'pe.kr.ddakker.monitor.agent.MonitorAgent',
				   'Agent-Class': 'pe.kr.ddakker.monitor.agent.MonitorAgent',
				   'Can-Redefine-Classes': true
	}
}


instrument 시 필요한 외부 Library들은 Agent jar에 포함 시켰다.


[실행]

-javaagent:D:\..\tomcat-monitor-agent-1.0.jar


N대의 WAS에서 성능을 위한 Local cache와 Memory효율을 위한 Server Cache 를 사용중이다.

Spring에서 동시에 사용해보자.

Local Cache 는 Clustring 하지않은 Ehcache,
Server Cache 는 Infinispan Server HotRoad 방식을 사용한다.




[context-cache.xml]


<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:util="http://www.springframework.org/schema/util"
	xmlns:cache="http://www.springframework.org/schema/cache"
	xsi:schemaLocation="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
    					http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
    					http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">

	<cache:annotation-driven />
	
	<!-- EHCache Local 형 -->
	<bean id="ehCacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
        <property name="cacheManager">
            <bean class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
                <property name="configLocation" value="classpath:config/cache/ehcache.xml"></property>
            </bean>
        </property>
    </bean>
    
    <!-- Infinispan Server 형 -->
    <util:properties id="hotrod_data" location="classpath:properties/hotrod_data.properties" />
	<bean id="ispnCacheManager" class="org.infinispan.spring.provider.SpringRemoteCacheManagerFactoryBean">
		<property name="configurationProperties" ref="hotrod_data" />
	</bean>

	<bean id="cacheManager" class="org.springframework.cache.support.CompositeCacheManager">
		<property name="cacheManagers">
			<list>
				<ref bean="ehCacheManager" />
				<ref bean="ispnCacheManager" />
			</list>
		</property>
	</bean>
</beans>

[ehcache.xml]

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd" updateCheck="false">
    <diskStore path="java.io.tmpdir" />

    <defaultCache
        maxElementsInMemory="50000"
        eternal="false"
        timeToIdleSeconds="300"
        timeToLiveSeconds="600"
        overflowToDisk="false"
        diskPersistent="false"
        diskExpiryThreadIntervalSeconds="120"
        memoryStoreEvictionPolicy="LRU">
    </defaultCache>

    <cache name="EHCACHE_MENU"
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="0"
        timeToLiveSeconds="600"
        overflowToDisk="false"
        diskPersistent="false"
        diskExpiryThreadIntervalSeconds="120"
        memoryStoreEvictionPolicy="LRU">
    </cache>
</ehcache>

[Service.java]

@Cacheable("ISPN_EVENT")
public Map getEvent(String eventKey) {
	return query...
}
@Cacheable("EHCACHE_MENU")
public Map getMenu(String menuKey) {
	return query...
}

Jolokia를 알게되서 최근까지 Servlet Container가 지원되는 부분에서만 이용했었는데 Servlet Container가 지원되지 않는 JVM 기반 Daemon의 MBean 을 모니터링 할 일이 생겨서 봤더니, Jolokia 에  JVM-Agent 있었군..



#!/bin/sh

..
..

PID=`ps -ef | grep java | grep "$DAEMON_NAME " | awk '{print $2}'`
echo " +$PID"

java -jar ...../jolokia-jvm-1.3.1-agent.jar start $PID --port=$MONITOR_PORT --host=$BIND_ADDR


# -- log
# +19102
# Started Jolokia for PID 19102
# http://---:---/jolokia/



Servlet Container 가 지원 될경우에는 WAR-Agent(jolokia-war-x.war) 를 Tomcat 이라면 webapps/ 하위에 복사 하거나 server.xml 에서 Context 부분에 추가 해주면 된다.


최근 fastjson 이란 알리바바에서 공개한 JSON Parser 을 보게되어 현재 회사에서 사용중인 Library 들을 비교해 보았다.


jackson-databind-2.1.3.jar

json-lib-2.2.1-jdk15.jar

fastjson-1.2.5.jar


[테스트 상황]

Vo 안에 2개의 Vo가 있는 객체 활용하여, to JSON String, to JAVA Object 로 변환 및 역변환을 10,000 실행하였다.




결과는 위에 보여지는 바와 같습니다.
  - to JSON String = fastjson > jackson > json-lib

  - to Object = jackson > fastjson > json-lib

변환 및 역변환 수치 상으로는 알리바바의 fastjson와 jackson 두가지가 근소한 차이의 성능을 보였습니다.
하지만 알리바바의 fastjson의 경우 유효하지 않는 데이터의 경우 표현자체를 하지 않으므로 인해서 JSON String 길이가 작습니다.
따라서 네트워크 전송 비용까지 감안 한다면 알리바바의 fastjson 의 가장 우수 한것으로 판단됩니다.

※ JSON Str Size가 다른 것은 각 Libaray 마다 null 데이터 처리 규칙이 조금 달라서 그렇고, 유효한 데이터에서는 동일합니다.
    - jackjon = null -> 문자열 null 로 표시
    - json-lib = null -> 문자열 공백으로 표시
    - fastjson = null -> key/value 모두 표시하지 않음


서버 모니터링 시스템 구축 중 Tomcat Comet으로 했던걸 WebSocket로 변경 하였다.

단순함!


WebSocket Servlet 에서는 Client Open/Close 만 관리하는 public static Set 객체에 Client 정보를 관리하고, Queue Receive 에서 해당 Set 객체에 접근 하여 각각의 Client 에 데이터를 Push 한다.


Thread 간 통신에 public static 변수를 사용하는게 좋은건지 모르겠으나.. Thread 관련 지식이 부족한 관계로 현재는 우선 패스한다.



import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;

import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


@ServerEndpoint(value = "/ws/was")
public class WasWebSocket {

	private static Logger log = LoggerFactory.getLogger(WasWebSocket.class);
	

    public static Set connections = new CopyOnWriteArraySet<>();

    private Session session;
    
    public WasWebSocket() {
        log.info("----------- thread start");
	}

    public Session getSession() {
    	return this.session;
    }

    @OnOpen
    public void onOpen(Session session) {
    	log.info("onOpen this: {}, this.sessson: {}", this.getClass(), session);
        this.session = session;
        synchronized (connections){
        	connections.add(this);
        }
    }


    @OnClose
    public void onClose() {
    	log.info("onClose");
    	synchronized (connections){
    		connections.remove(this);
    	}
    }


    @OnMessage
    public void onMessage(String message) {
    	log.info("@OnMessage");
    	log.info("websocket message: " + message);
    }



    @OnError
    public void onError(Throwable t) throws Throwable {
        log.error("e: ", t.toString());
        synchronized (connections){
    		connections.remove(this);
    	}
    }
}



function wasStatusListen() {
	var oSocket = new WebSocket(WS_URL + "/ws/was");
	 
    oSocket.onmessage = function (e) { 
        var json = eval("(" + e.data + ")");
		if (json.result && json.result == "0000") {
			var wasNode = json.data.name;
			var heapUsedPercent = json.data.heapUsedPercent;
			..
			..
			처리
			
		}
    };
 
    oSocket.onopen = function (e) {
        console.log("open");
    };
 
    oSocket.onclose = function (e) {
        console.log("close");
        wasStatusListen();
    };
    
    window.unload = function() {
		if (oSocket.readyState != 3)
			oSocket.disconnect();
	}
}
wasStatusListen();


Authentication Basic 로 인증되는 서버에서 제공하는 API를 이용 하는 중 Spring RestTemplate 활용 방법


[환경]

Spring-4.1.6

HttpClient-4.3.5


1. 호출 URL 에서 인증 하는 방법

Map<String, Object> resultMap = ezRestTemplate.get("http://admin:1234@localhost:12345/api/overview", HashMap.class);



2. 설정 부분에서 인증 하는 방법

[설정]

import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;

import javax.net.ssl.SSLContext;

import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.HttpClient;
import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContexts;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import pe.kr.ddakker.framework.support.wrapper.http.EzRestTemplate;

@ImportResource("classpath:config/spring/context-*.xml")
@Configuration
public class ApplicationContext {
	
	@Bean(name = "ezRestTemplate")
	public EzRestTemplate getEzRestTemplate() throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException {
		SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, new TrustSelfSignedStrategy()).useTLS().build();
		SSLConnectionSocketFactory connectionFactory = new SSLConnectionSocketFactory(sslContext, new AllowAllHostnameVerifier());
		BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();


		credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials("admin", "1234"));
		 
		HttpClient httpClient = HttpClientBuilder.create()
		                                        .setSSLSocketFactory(connectionFactory)
		                                        .setDefaultCredentialsProvider(credentialsProvider)
		                                        .build();
		 
		ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
				
		return new EzRestTemplate(new RestTemplate(requestFactory));
	}
}


[사용]

// EzRestTemplate 는 RestTemplate 감쌓놓은 Wrapper Class임.
@Resource EzRestTemplate	ezRestTemplate;

Map<String, Object> resultMap = ezRestTemplate.get("http://localhost:12345/api/overview", HashMap.class);
System.out.println("resultMap: " + resultMap);


서버 모니터링 시스템을 만들고 있다.


구조는 서버의 MBean 및 Background 테스팅 도구가 실행한 정보를 각각의 N대의 서버에서 Message Qeue에 전달 하고, 모니터링 WAS에서 Queue 를 Receive 하여 Tomcat Long Pooling 을 활용하여 N대의 Client Browser 에 전송 해 준다.


public static List connections = new ArrayList<>(); 부분이 접속된 Client Browser 정보이므로, Queue Receive Event 시점에 connections Client 갯수만큼 PrintWriter 활용 해서 write 하고, connections 의 CometEvent 객체를 Close 하고, connections에서 Remove() 시킨다.


접속된 Client 정보를 public static 변수로 다른 Thread 에서 제어 하는게 잘 하는건지는 모르겠지만. 잘 된다...


사실 처음에 Tomcat Comet 방식을 이용했지만 지금은 WebSocket 방식으로 동일 하게 활용중이다.

다음 글은 동일한 상황에서 Comet 를 Websocket로 변환.. 거의 동일함..


import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.catalina.comet.CometEvent;
import org.apache.catalina.comet.CometProcessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


@WebServlet("/comet/testCase")
public class TestCaseServlet extends HttpServlet implements CometProcessor {
	private static Logger log = LoggerFactory.getLogger(TestCaseServlet.class);
	
	public static List connections = new ArrayList<>();

	public void init() throws ServletException {
        log.info("----------- thread start");
    }

    public void destroy() {
    	log.info("---------- thread stop");
    	
    	synchronized (connections) {
			connections.clear();
		}
    }

	@Override
	public void event(CometEvent event) throws IOException, ServletException {
		log.info("testCase event: " + event.getEventType());
		HttpServletRequest request = event.getHttpServletRequest();
		HttpServletResponse response = event.getHttpServletResponse();
		response.setCharacterEncoding("UTF-8");
		event.setTimeout(1000*60*60);
		
		if (event.getEventType() == CometEvent.EventType.BEGIN) {
            log.info("Begin for request: {}, response: {}, session: {}", request, response, request.getSession(true).getId());
            synchronized(connections) {
            	connections.add(event);
            }
        } else if (event.getEventType() == CometEvent.EventType.ERROR) {
            log.info("Error for session: {}", request.getSession(true).getId());
            synchronized(connections) {
            	connections.remove(event);
            }
            PrintWriter writer = response.getWriter();
            writer.println("{result: '9999', msg: 'timeout'}");
            event.close();
        } else if (event.getEventType() == CometEvent.EventType.END) {
            log.info("End for session: " + request.getSession(true).getId());
            synchronized(connections) {
            	connections.remove(event);
            }
            PrintWriter writer = response.getWriter();
            writer.println("{result: '0000', msg: 'end'}");
            event.close();
        } else if (event.getEventType() == CometEvent.EventType.READ) {
        	 log.info("read");
            while (true) {
            	;
            }
        }
	}
}


WebSocket 관련 개발을 하다가 데이터 송/수신 관련 데이터만 보고 싶다면..


Chrome 웹 스토어에서 "Dark WebSocket Terminal" 를 활용!!




프로세스와는 무관한 작업으로 인한 지연 현상이 발생하여 비동기 방식 처리를 고민하게되었다.


로그 히스토리를 DB에 기록하는 행위들인데 이 부분들에서 INSERT 시 지연이 발생하여 파일로 쌓았다가 긁어가야 하나.. 어떻게해야 하나 고민 하던 중 WAS 에서 제공하는 JMS 를 사용할까 하다가 ActiveMQ, RabbitMQ 를 셈플링 해보았다.


선택은 Cluster, Mirroring 설정이 간편한 RabbitMQ!!


[Cluster] https://www.rabbitmq.com/clustering.html

[Mirroring] https://www.rabbitmq.com/ha.html


그대로 따라 하기만 하면 잘 된다.


[설치]

esl-erlang_17.5-1~centos~6_amd64.rpm

rabbitmq-server-3.5.1-1.noarch.rpm


우선 서버당 /var/lib/rabbitmq/.erlang.cookie 값을 동일 하게 일치 시킨다.


하지만 난 하나의 물리 서버에 MQ서버 NODE 두개씩 올려보았다.


[rabbitmq@dev01]

   - mq11 - /usr/local/mq/domain/mq11

   - mq12 - /usr/local/mq/domain/mq12

[rabbitmq@dev02]

   - mq21 - /usr/local/mq/domain/mq21

   - mq22 - /usr/local/mq/domain/mq21



관리를 편리하게 하기 위해서 NODE 기반 명령어 스크립트를 만들어 둔다.

env.sh 에서 RabbitMQ 포트 번호 및 웹 관리 포트번호를 설정 하게 한다.


각각 디렉토리에 들어가서 각각 start.sh 를 실하면 알아서 뜬다.


env.sh - 기본 동적 설정

#!/bin/sh

NODE_PORT=각각 다르게 포트번호
NODE_MANAGEMENT_PORT=각각 다르게 포트번호(웹 관리 포트)
NODE_NAME=각각 NODE 이름


start.sh - MQ NODE 시작

#!/bin/sh

. ./env.sh


export RABBITMQ_NODE_PORT=$NODE_PORT 
export RABBITMQ_SERVER_START_ARGS="-rabbitmq_management listener [{port,$NODE_MANAGEMENT_PORT}]"
export RABBITMQ_NODENAME=$NODE_NAME
export RABBITMQ_MNESIA_DIR=/usr/local/mq/domains/$NODE_NAME/mnesia
export RABBITMQ_LOG_BASE=/usr/local/mq/domains/$NODE_NAME/logs
export RABBITMQ_CONFIG_FILE=/usr/local/mq/domains/$NODE_NAME/rabbitmq
/usr/sbin/rabbitmq-server -detached

echo "============================================================="
echo "   RabbitMQ Server 3.5.1-1.noarch           ddakker@gmail.com"
echo "-------------------------------------------------------------"
echo "RABBITMQ_NODENAME=$RABBITMQ_NODENAME"
echo "RABBITMQ_NODE_PORT=$RABBITMQ_NODE_PORT"
echo "NODE_MANAGEMENT_PORT=$NODE_MANAGEMENT_PORT"
echo "RABBITMQ_MNESIA_DIR=$RABBITMQ_MNESIA_DIR"
echo "RABBITMQ_LOG_BASE=$RABBITMQ_LOG_BASE"
echo "RABBITMQ_CONFIG_FILE=$RABBITMQ_CONFIG_FILE.config"
echo "============================================================="


stop.sh - MQ NODE 중지

#!/bin/sh

. ./env.sh

rabbitmqctl -n $NODE_NAME stop


app_start.sh - MQ NODE 관리앱 시작

#!/bin/sh

. ./env.sh

rabbitmqctl -n $NODE_NAME start_app


app_stop.sh - MQ NODE 관리앱 중지

#!/bin/sh

. ./env.sh

rabbitmqctl -n $NODE_NAME stop_app


ctl.sh - rabbitmqctl NODE 옵션 지정한 명령어

#!/bin/sh

. ./env.sh

echo "rabbitmqctl -n $NODE_NAME $1 $2 $3"
rabbitmqctl -n $NODE_NAME $1 $2 $3


status.sh - 상태 정보

#!/bin/sh

. ./env.sh

rabbitmqctl -n $NODE_NAME status
rabbitmqctl -n $NODE_NAME cluster_status


rabbitmq.config - 기본 설정 파일

[
   {mnesia, [{dump_log_write_threshold, 1000}]},
   {rabbit, [
        {log_levels, [{connection, info}]}
   ]},
   {rabbitmq_management, [
       {redirect_old_port, false}
   ]}
].


[NODE 별로 기동]

[rabbitmq@dev01 mq11]$ ./start.sh

[rabbitmq@dev01 mq12]$ ./start.sh

[rabbitmq@dev02 mq21]$ ./start.sh

[rabbitmq@dev02 mq22]$ ./start.sh


[Clustering] - mq11 을 기준으로 한다.(mq11 에서는 안 해도 됨)

[rabbitmq@dev01 mq12]$ ./app_stop.sh

[rabbitmq@dev01 mq12]$ ./ctl.sh join_cluster mq11@dev01

[rabbitmq@dev01 mq12]$ ./app_start.sh



[rabbitmq@dev02 mq21]$ ./app_stop.sh

[rabbitmq@dev02 mq21]$ ./ctl.sh join_cluster mq11@dev01

[rabbitmq@dev02 mq21]$./app_start.sh


[rabbitmq@dev02 mq22]$ ./app_stop.sh

[rabbitmq@dev02 mq22]$ ./ctl.sh join_cluster mq11@dev01

[rabbitmq@dev02 mq22]$ ./app_start.sh


[Mirroring] - Cluster 되면 아무곳에서 해도 됨

./ctl.sh set_policy 사용해도 되고, 그냥 "admin Tab" -> "Policies" 메뉴에서 UI상으로 하는게 편리!!

애래 Node 부분에 "+3" 부분이 보여야 함(정상/비정상 여부에 따라서 복제된 정보가 파랑숫자, 빨강숫자로 표현됨)

장애 후 복구된 서버에 이전데이터 동기(복제) 처리는 하지 않았음




[웹 접속 계정 생성 및 권한 부여] - Cluster 되면 아무곳에서 해도 됨

[rabbitmq@dev01 mq11]$ ./ctl.sh add_user test 1234

[rabbitmq@dev01 mq11]$ ./ctl.sh set_user_tags test administrator

그냥 "admin Tab" -> "Users" 메뉴에서 UI상으로 하는게 편리!!



앞단에 L4 를 붙이면 좋겠음








BindResult 삽질기!!!


회사 동료가 Spring Validation 기능을 활용해보자고 해서 해보는데 엄청난 시간 소모가...

결론은 BindResult 파라미터 선언을 @Valid 바로 뒤에 해야 한다는...


[TestWeb.java]

import javax.validation.Valid;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping("test")
public class TestWeb {
	private static Logger log = LoggerFactory.getLogger(TestWeb.class);

	@RequestMapping(value="case1", method=RequestMethod.GET)
	public String test(@Valid TestVo testVo, BindingResult bindResult, Model model) {
		log.debug("bindResult.hasErrors(): {}", bindResult.hasErrors());
		if (bindResult.hasErrors()) {
			throw new RuntimeException("파라미터 설정이 잘 못 되었습니다..");
			//return "coupon/mainError";	// 공통 Exception 처리 Throws 하거나 에러 뷰 return
		}
		return "test/case1";
	}
	
	
	
}

[TestVo.java]

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import lombok.Getter;
import lombok.Setter;

@Getter @Setter
public class TestVo {
	@Size(min=0, max=5)
	private String test1;
	
	@NotNull
	private String test2;
}

[build.gradle]

dependencies {
	compile 'spring.... 4.1.x...'
	compile 'org.hibernate:hibernate-validator:5.1.3.Final'
}

+ Recent posts