2015년 3월 31일 화요일

AMQP에 대해서

1. AMQP란 무엇인가?

- Advanced Message Queing Protocol의 약자로, 흔히 알고 있는 MQ의 오픈소스에 기반한 표준 프로토콜을 의미한다. AMQP 자체는 프로토콜을 의미하기 때문에 이 프로토콜에 따른 실제 MQ 제품들은 여러가지가 존재할 수 있으나 최근 가장 많이 사용되는것은 아무래도 Erlang과 자바로 작성된 RabbitMQ라고 할 수 있다.



2. 등장배경

- 이전에도 상용화된 MQ 제품들은 많았지만, 한가지 문제가 있다면 대부분 플랫폼 종속적인 제품들이었기 때문에 서로 다른 이기종간에 메시지를 교환하기 위해서는 메시지 포멧 컨버전을 위한 메시지 브릿지를 이용하거나 (속도 저하 발생) 시스템 자체를 통일시켜야 하는 불편함과 비효율성이 있었다. (시스템을 교체하는 것은 논외로 치고 메시지 브릿지의 경우 MQ가 금융쪽에서도 많이 사용된다는걸 감안하면 속도 및 응답성의 저하는 치명적인 약점일 수 밖에 없다) 이러한 기존의 MQ들의 약점을 보완하기 위해 등장한것이 AMQP이다. 즉, AMQP의 목적은 서로 다른 시스템간에 (비용/기술/시간적인 측면에서) 최대한 효율적인 방법으로 메시지를 교환하기 위한 MQ 프로토콜인 것이다.
  벤더에 종속되는것을 방지하기 위해 AMQP는 대략 아래와 같은 조건을 충족한다. (용어에 대한 설명은 차후 진행)
  • 모든 broker들은 똑같은 방식으로 동작할 것
  • 모든 client들은 똑같은 방식으로 동작할 것
  • 네트웍상으로 전송되는 명령어들의 표준화
  • 프로그래밍 언어 중립적


3. AMQP Routing model

- AMQP의 라우팅 모델은 아래와 같은 3개의 중요한 component 들로 구성된다.
  • Exchange
  • Queue
  • Binding
  AMQP는 이 3가지의 구성요소들이 서로간에 어떻게 통신하는지를 정의한 프로토콜이라고 볼 수 있다. 전체적인 흐름은 아래와 같다.

482
- Exchange
  Publisher로부터 수신한 메시지를 적절한 큐 또는 다른 exchange로 분배하는 라우터의 기능을 한다. 각 큐나 exchange는 'Binding'을 사용해서 exchange에 바인드 되어 있고, 따라서 exchange는 수신한 메시지를 이 binding에 따라 적당한 큐나 exchange로 라우팅한다. Binding과 메시지를 매칭시키기 위한 라우팅 알고리즘을 정의한것을 Exchange type이라고 하고, exchange type은 라우팅 알고리즘의 클래스이다. 브로커는 여러개의 exchange type 인스턴스를 가질 수 있다. binding과 exchange type이 혼동될 수 있는데, exchange type은 메시지를 어떤 방법으로 라우팅 시킬지를 결정하는 것이고, binding을 이러한 방법을 이용해 실제로 어떤 메시지를 어떤 큐에 보낼지를 결정하는 라우팅 테이블이라고 할 수 있다. (아래 관련 항목 참조) 예를 들어 명함정보가 들어오면 그것을 특정 큐로 보내는 broker가 존재한다면, 명함의 사람 이름 성씨를 보고 큐를 결정하겠다는 것은 exchange type이고, 김씨는 1번큐, 박씨는 2번큐, 이씨는 3번큐로 보내겠다는 것은 binding이다.

- Queue
  일반적으로 알고있는 큐이다. 메모리나 디스크에 메시지를 저장하고, 그것을 consumer에게 전달하는 역할을 한다. 큐는 스스로가 관심있는 메시지 타입을 지정한 Binding을 통해 exchange에 말그대로 bind된다.

- Binding
  exchange와 큐와의 관계를 정의한 일종의 라우팅 테이블이다. 같은 큐가 여러개의 exchange에 bind 될 수도 있고, 하나의 exchange에 여러개의 큐가 binding 될 수도 있다.

- Routing Key
  Publisher에서 송신한 메시지 헤더에 포함되는 것으로 일종의 가상 주소라고 보면 된다. Exchange는 이것을 이용해서 어떤 큐로 메시지를 라우팅할지 결정할 수 있다. (이것을 사용하지 않고 다른 룰을 이용할 수도 있음) AMQP의 표준 exchange type은 이 라우팅 키를 이용하도록 되어있다.

- Standard Exchange Type
  대부분의 MQ에서 가능한 여러가지 상황에 대하여 AMQP에서 정의한 표준 라우팅 알고리즘이다. 아래 별도 항목을 참고.


4. Standard Exchange Type

Exchange Type은 메시지를 어떤 원칙 내지는 방법으로 라우팅할지를 결정하는 일종의 알고리즘이다. AMQP에서는 표준 Exchange Type으로 라우팅 키에 기반한 아래 3개의 라우팅 알고리즘과 key-value 헤더에 기반한 1개 유형의 Exchange Type들을 반드시 정의하도록 되어있다.

- Direct Exchange


682
  메시지의 라우팅 키를 큐에 1:N으로 매칭시키는 방법이다. 가장 일바나적인 경우는 큐의 이름을 바인딩하고자 하는 라우팅 키와 동일하게 작성하는 방법이 있다.(물론 그렇게 하지 않아도 상관없다.) 위와 같은 경우 "sales_order"라는 라우팅 키를 갖는 메시지는 1번과 3번 큐로 전달된다.

- Topic Exchange


682
  와일드카드를 이용해서 메시지를 큐에 매칭시키는 방법이다. 라우팅 키는 "."으로 구분된 0개 이상 단어의 집합으로 간주되고, 와일드카드 문자들을 이용해서 특정 큐에 binding한다. "*"는 하나의 단어, "#"은 0개 이상의 단어를 의미한다. 즉 위와 같은 경우 해당 메시지는 1번 큐와 3번 큐로 라우팅 된다.

- Fanout Exchange

682
  모든 메시지를 모든 큐로 라우팅하는 유형이다.

- Headers Exchange

682
  key-value로 정의된 헤더에 의해 라우팅을 결정한다. 큐를 바인딩할 때 x-match라는 특별한 argument로 헤더를 어떤식으로 해석하고 바인딩할지를 결정하는데, x-match가 all이면 바인딩 조건을 모두 충족시켜야 한다는 것이고(AND), any이면 하나만 충족시키면 된다는 것이다.(OR) 위와 같은 경우 메시지는 1번과 3번 큐로 라우팅된다.

rabbitMQ Configuration설정 및 consumer&producer

java config설정 listener추가

RabbitMqConfig.java

@Configuration
@PropertySource("classpath:/conf/property/rabbitmq.properties")
public class RabbitMqConfig {
    @Autowired private Environment env;
 
    @Bean
    public ConnectionFactory connectionFactory() {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setPort(Integer.parseInt(env.getProperty("rabbitmq.port")));
        connectionFactory.setUsername(env.getProperty("rabbitmq.user"));
        connectionFactory.setPassword(env.getProperty("rabbitmq.password"));
        connectionFactory.setHost(env.getProperty("rabbitmq.ip"));

        return connectionFactory;
    }

    @Bean
    public RabbitTemplate rabbitTemplate() {
         RabbitTemplate rabbitTemplate = new RabbitTemplate();
         rabbitTemplate.setExchange(env.getProperty("rabbitmq.exchange"));
         rabbitTemplate.setRoutingKey(env.getProperty("rabbitmq.routingKey"));
         rabbitTemplate.setQueue(env.getProperty("rabbitmq.queueName"));
         rabbitTemplate.setConnectionFactory(new CachingConnectionFactory(connectionFactory()));
         return rabbitTemplate;
    }
 
    @Bean
    public SimpleMessageListenerContainer messageListenerContainer() {
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(new CachingConnectionFactory(connectionFactory()));
        container.setQueueNames(env.getProperty("rabbitmq.queueName"));
        container.setMessageListener(exampleListener());
        return container;
    }


    @Bean
    public MessageListener exampleListener() {
        return new MessageListener() {
            public void onMessage(Message message) {
                System.out.println("received: " + new String(message.getBody()));
            }
        };
    }
 
}

Producer 테스트
@RunWith( SpringJUnit4ClassRunner.class )
@ContextConfiguration(classes = {RabbitMqConfig.class})
public class ProducerTest {
    @Autowired
    RabbitTemplate rabbitTemplate;
 
    @Test
    public void productTest(){
        rabbitTemplate.convertAndSend("Hello World");
    }
}


consummer테스트 
 - ExecutorService 이용한 Multi Thread처리 

@RunWith( SpringJUnit4ClassRunner.class )
@ContextConfiguration(classes = {RabbitMqConfig.class})
public class ConsumerTest{
    
    protected final Logger log = LoggerFactory.getLogger(this.getClass());
    
    @Autowired 
    RabbitTemplate rabbitTemplate;
    @Autowired
    ConnectionFactory connectionFactory;
    
    @Test
    public void consumTest(){
        int maxthread =20;
        ExecutorService es = Executors.newFixedThreadPool(maxthread);
        try {
            Connection conn = connectionFactory.newConnection(es);

            // Thread 당 다른 Channel 을 사용하기 위해서 Thread수 만큼 별도의 채널을 생성하낟.
           for(int i=0;i<maxthread;i++){
                   Channel channel = conn.createChannel();     
                   channel.basicQos(1);
                   channel.basicConsume("test",false,new MyQueueConsumer(channel));
           }
           System.out.println("Invoke "+maxthread+" thread and wait for listening");   
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        //System.out.println(rabbitTemplate.receiveAndConvert());
        //System.exit(1);
    }
    
    public class MyQueueConsumer extends DefaultConsumer {
        
        Channel channel;
        public MyQueueConsumer(Channel channel) {
               super(channel);
               // TODO Auto-generated constructor stub
               this.channel = channel;
        }
        @Override
        public void handleDelivery(String consumeTag,
                                   Envelope envelope,
                                   AMQP.BasicProperties properties,
                                                        byte[] body)
               throws IOException
        {
               String routingKey = envelope.getRoutingKey();
               String contentType = properties.getContentType();
               long deliveryTag = envelope.getDeliveryTag();
              
               // message handling logic here
               String msg = new String(body);
               UUID uuid = UUID.randomUUID();
               System.out.println(uuid+" S Channel :"+channel+" Thread:"+Thread.currentThread()+" msg:"+msg);
              
               // multiple - false if we are acknowledging multiple messages with the same delivery tag
               this.channel.basicAck(deliveryTag, false);
        }
    }
}

2015년 3월 30일 월요일

[RabbitMQ] 3. 초보를 위한 RabbitMQ 클러스터와 미러링 구성하기

2장까지 계정설정하고 3장에서 exchange 한다고 예고까지 해놓고 뜬금없이 클러스터와 미러링 구성하기를 작성하게 되었습니다. 방금 회사에서 클러스터랑 미러링을 구성해서 까먹기전에 후딱 써야하는 조금함때문인거 같습니다. exchange 설명 달기도 귀찮기도 하구요. 여튼 시작합니다.

0. 목표와 작업 순서

메시징 서비스를 하는데 큐의 의존도가 매우 커졌습니다. 그래서 고가용성 큐를 구성하려고 합니다. 다행히 RabbitMQ에서 완전 짱짱맨인 클러스터, 미러링 기능을 제공하니 그냥 그거 가져다 쓰면 될거 같습니다. 작업 순서는 대충 다음과 같습니다.
  1. 서버간 얼랭 쿠키 맞추기
  2. 클러스터 구성하기
  3. 클러스터 노드 타입 변경
  4. 미러링 구성하기
  5. 클러스터에서 빠져나오기
본 예제는 3대의 서버를 클러스터, 미러링 하는 것을 기준으로 작성되고 있습니다. 사실 생각보다 무지 쉽습니다. (사이트에서 하라고 하는대로만 잘 따라하면 됩니다. 물론 전 청개구리 모드가 작동하여 하지말라는 순서대로 하다가 MQ 한대 바보로 만들고 두대는 꼬여서 전체 재설치하는 곤욕을 치르기도 했습니다. 으하하!

1. 서버간 얼랭 쿠키 맞추기

서버간 얼랭 쿠키를 맞춰줘야 합니다. 자세한 이유는 저도 Document 읽기 귀찮아 생략합니다. 어쨋든 맞춰줘야 한다고 합니다.
  1. Erlang 쿠키 위치 확인
    보통 리눅스의 경우 “/var/lib/rabbitmq/.erlang.cookie” 이나 “$HOME/.erlang.cookie” 에 있다고 합니다. 이건 OS나 설치환경 차이가 있으니 각자 알아서 찾는 방법밖에 없습니다. 여튼 클러터를 구성할 3대의 서버중 1대를 기준으로 잡아 scp로 파일 카피를 하거나 vi화면으로 copy&paste 해버리면 됩니다. 혹시 모르니 RabbitMQ를 꺼놓고 작업하도록 합시다.
  2. 복사가 제대로 안되어 RabbitMQ가 작동하지 않을때…
    파일 복사가 제대로 이루어지지 않으면, 보통 vi 로 copy&paste하다 삑사리 나면 발생합니다 하하하, RabbitMQ가 구동되지 않습니다. 이럴때는 그냥 쿠키 파일을 지워버리고 다시 시작하면 됩니다. 그럼 새 쿠키카 생성되는데, 여기에 다시 작업하시면 됩니다.

2. 클러스터 구성하기

정말 별거 없습니다. 그냥 가이드(https://www.rabbitmq.com/clustering.html) 에서 시키는데로 따라하면 됩니다.
  1. 각 서버 상태 확인
    클러스터링이 되기 전 ‘rabbitmqctl cluster_status’ 명령으로 각 서버 상태를 살펴봅니다. (아래 코드들은 가이드에서 그냥 베껴왔습니다)
    # 1번서버 확인, 자기 자신 노드만 확인됨 
    rabbit1$ rabbitmqctl cluster_status
    Cluster status of node rabbit@rabbit1 ...
    [{nodes,[{disc,[rabbit@rabbit1]}]},{running_nodes,[rabbit@rabbit1]}]
    ...done.
    
    # 2번 서버 확인, 자기 자신 노드만 확인됨
    rabbit2$ rabbitmqctl cluster_status
    Cluster status of node rabbit@rabbit2 ...
    [{nodes,[{disc,[rabbit@rabbit2]}]},{running_nodes,[rabbit@rabbit2]}]
    ...done.
    
    # 3번 서버 확인, 자기 자신 노드만 확인됨
    rabbit3$ rabbitmqctl cluster_status
    Cluster status of node rabbit@rabbit3 ...
    [{nodes,[{disc,[rabbit@rabbit3]}]},{running_nodes,[rabbit@rabbit3]}]
    ...done.
  2. 서버들을 클러스터로 묶기
    작업 시작전 주의사항으로 rabbitmqctl로 노드를 정지시킬때 반드시 stop_app을 사용해야합니다. 실수로 stop을 해버리면 rabbitmqctl 명령 자체가 먹지 않습니다. 아마 stop은 RabbitMQ node 자체를 종료 시켜버리고 stop_app은 RabbitMQ의 Management Application을 정지시키기 때문이 아닌가 합니다. 그리고 Cluster를 묶을때 해당 서버의 hostname을 적어 주어야 하는데 정말! 반드시! hostname을 사용해야 합니다. IP이런거 절대 안됩니다. 제가 이거로 반나절 날렸습니다. 어흐흐흐흑! 그리고 /etc/hosts 에 상대 서버의 hostname을 등록할때는 상대 서버에서 cluster_status로 확인되는 hostname으로 등록해주도록 합니다. hostname을 동일하게 맞추지 않고 구성을 해보지 않아 꼭 맞춰야 되는건지는 알수 없지만 그냥 안전하게 맞추도록 합시다. (그거까지 테스트하기 귀찮아요)이제 1번 서버를 기준으로 클러스터를 묶도록 하겠습니다. 기준이 되는 1번서버는 별다른 작업을 해줄 필요가 없습니다. 그러니 2번 서버부터 작업을 시작해 봅시다.
    #2번서버 작업, 
    #관리 앱 정지
    rabbit2$ rabbitmqctl stop_app
    Stopping node rabbit@rabbit2 ...done.
    
    # 1번 서버로 붙는다. hostname의 경우 1번서버에서 cluster_status로 이름을 확인하여 맞춰 설정하도록 하자.
    rabbit2$ rabbitmqctl join_cluster --ram rabbit@rabbit1
    Clustering node rabbit@rabbit2 with [rabbit@rabbit1] ...done.
    rabbit2$ rabbitmqctl start_app
    Starting node rabbit@rabbit2 ...done.
    
    3번 서버도 마저 작업합니다. 2번 설정할때와 동일합니다.
    rabbit3$ rabbitmqctl stop_app
    Stopping node rabbit@rabbit3 ...done.
    rabbit3$ rabbitmqctl join_cluster rabbit@rabbit2
    Clustering node rabbit@rabbit3 with rabbit@rabbit2 ...done.
    rabbit3$ rabbitmqctl start_app
    Starting node rabbit@rabbit3 ...done.
    
    서버별로 cluster_status 명령을 사용하여 실제 서로 노드를 인식하는지 확인해 봅시다. 클러스터링이 잘 되었다면 status에서 더이상 혼자가 아닌 Node(http://www.youtube.com/watch?v=R85lOwRsnvk)를 볼 수 있습니다. 상태를 보면 ram, disc 이런게 좀 신경쓰이는데 이건 곧 설명하도록 하겠습니다.
    #1번 서버 확인
    rabbit1$ rabbitmqctl cluster_status
    Cluster status of node rabbit@rabbit1 ...
    [{nodes,[{disc,[rabbit@rabbit1,rabbit@rabbit3]},{ram,[rabbit@rabbit2]}]},
     {running_nodes,[rabbit@rabbit3,rabbit@rabbit2,rabbit@rabbit1]}]
    ...done.
    
    #2번 서버 확인
    rabbit2$ rabbitmqctl cluster_status
    Cluster status of node rabbit@rabbit2 ...
    [{nodes,[{disc,[rabbit@rabbit1,rabbit@rabbit3]},{ram,[rabbit@rabbit2]}]},
     {running_nodes,[rabbit@rabbit3,rabbit@rabbit1,rabbit@rabbit2]}]
    ...done.
    
    #3번 서버 확인
    rabbit3$ rabbitmqctl cluster_status
    Cluster status of node rabbit@rabbit3 ...
    [{nodes,[{disc,[rabbit@rabbit3,rabbit@rabbit1]},{ram,[rabbit@rabbit2]}]},
     {running_nodes,[rabbit@rabbit2,rabbit@rabbit1,rabbit@rabbit3]}]
    ...done.
  3. 클러스터 최종 확인
    클러스터로 묶인 RabbitMQ들은 서로간의 계정, Virtual Host, policy, Queue, Exchange 등을 모두 공유합니다. 따라서 1번 서버에만 붙어도 2번, 3번 서버의 Queue에 Access할 수 있습니다. 네! 짱짱맨입니다! 간단한 테스트로 Management Plugin으로 접속하여 1번 서버에서 Queue를 만들고 메세지를 Publish해보면 2번, 3번 서버에서도 인식이 되는것을 확인할 수 있습니다.

3. 클러스터 노드 타입 변경

rabbitmqctl cluster_ctl 로 상태를 보면 dics, ram 이란 녀석들이 보입니다. 각 노드들이 현재 상태를 disc로 작동하는지 ram으로 작동하는지를 보여줌니다. 이 두개의 옵션에는 차이가 있는데 ram의 경우 node의 data가 ram에만 상주하여 작동 속도가 빠르고 disc의 경우 node의 data들이 disc에 기록되며 작동한다고 합니다. RabbitMQ 클러스터를 구성할 경우 서버 1대는 반드시 disc로 구성이 되어야하는데, 그 이유는 정말 재수가 없어서 서버가 전체다 내려갔을때 disc로 구성된 녀석을 통해 어느정도 복구가 되도록 하는거 같습니다. 물론 이건 문서를 대충 읽어서 정확한 내용은 아닙니다. 테스트 또한 해보진 않았습니다. 네, 저도 불안하긴 합니다. 여튼 1대는 반드시 disc로 구성하라고 합니다. (뭔가 찜찜하다.)
  1. Cluster Node Type 변경
    저는 1번과 3번을 빠른 ram으로 2번 서버를 disc로 구성하려 합니다. 다음과 같이 합니다.
    # disc로 작동할 2번 서버 먼저 작업한다. node중에 disc가 하나라도 없는 상황이 될 수 있으면 변경이 되지 않는다.
    rabbit2$ rabbitmqctl stop_app
    Stopping node rabbit@rabbit2 ...done.
    rabbit2$ rabbitmqctl change_cluster_node_type disc
    Turning rabbit@rabbit2 into a disc node ...
    ...done.
    Starting node rabbit@rabbit2 ...done.
    
    # 1번 서버 작업
    rabbit1$ rabbitmqctl stop_app
    Stopping node rabbit@rabbit1 ...done.
    rabbit1$ rabbitmqctl change_cluster_node_type ram
    Turning rabbit@rabbit1 into a ram node ...
    rabbit1$ rabbitmqctl start_app
    Starting node rabbit@rabbit1 ...done.
    
    # 3번 서버 작업
    rabbit3$ rabbitmqctl stop_app
    Stopping node rabbit@rabbit3 ...done.
    rabbit3$ rabbitmqctl change_cluster_node_type ram
    Turning rabbit@rabbit3 into a ram node ...
    rabbit3$ rabbitmqctl start_app
    Starting node rabbit@rabbit3 ...done.
  2. 확인하기
    1번서버에서 rabbitmqctl cluster_status로 cluster type이 잘 변경되었는지 확인합니다. 그리고 1번 서버를 stop/start를 하며 2번 서버로 cluster_status를 체크해보며 node 구성에서 1번 서버의 RabbitMQ가 빠졌다가 다시 들어오는것을 확인합니다. 모든게 잘 작동하면 클러스터게 제대로 구성되었다고 볼 수 있습니다.

 4. 미러링 구성하기

3번까지 클러스터를 구성하기는 했는데 아직 고가용성이라고 볼수는 없습니다. 클러스터는 서로의 내용을 공유만 할 뿐이어서 불의의 사고로 1번서버가 죽어버린다면 2번, 3번 서버를 통해서 1번서버 큐의 내용을 읽을 수 없게 되며 1번에 남아있던 message들은 모두 증발해 버립니다. 따라서 고가용성을 위해 클러스터의 노드들이 서로의 내용을 복사하여 저장하도록 해주어야 합니다. 노드간 미러링을 구성합니다!
  1. 미러링 정책 설정하기
    클러스터만 잘 구성되었다면 미러링은 정말 쉽습니다. 그냥 정책을 정해주는 명령 하나만 날려주면 됩니다. 전 그냥 다 미러링할 계획이라 ha-mode를 all로 했습니다.  옵션별 자세한 내용은 가이드 참조 바랍니다. (https://www.rabbitmq.com/ha.html)
    # ha-all은 새로 추가되는 정책의 이름, ^ha\.은 regexp로 표현된 미러링할 queue 이름, 뒤의 json은 정책 세부 사항,
    rabbitmqctl set_policy ha-all "^ha\." '{"ha-mode":"all"}'
  2. Virtual Host 별로 정책 설정해주기
    1번의 내용은 default virtual host에 적용이 됩니다. 새로 만든 virtual host에 ha 정책을 정해주려면 -p 옵션을 사용해야 합니다.
    # myQueue Virtual Host 속한 이름에 .ha 가 포함된 Queue를 모두 미러링 하는 정책
    rabbitmqctl set_policy -p myQueue ha-all "^.*\.ha.*" '{"ha-mode":"all"}'
  3. ha-sync-mode 에 대해
    미러링을 하면 node간 모든 내용들이 복사됩니다. 하지만 고민해야할 것이 하나 있습니다. 클러스터에 새로운 node가 추가되었을때 이 node에 다른 node들이 가지고 있는 과거의 Data를 Sync해야할 것인가? 에 대한 문제입니다. RabbitMQ는 기본적으로 새로 추가된 node(죽었다 살아난 node 포함)에 기존 node의 과거의 data들을 복사하지 않도록 합니다. Data Sync하는 동안 해당 Queue는 무응답 상태가 되어 버려 서비스 가용성에 좋지 않은 영향을 미칠수 있기 때문입니다. 그럼에도 불구하고 추가되는 노드에 반드시 과거 Data가 Sync되어야 한다면 ha-sync-mode 옵션으로 설정할 수 있습니다.
    # ha-sync-mode를 automatic으로 설정, 새로 들어오는 node에도 과거의 data가 모두 복사된다.
    rabbitmqctl set_policy -p myQueue ha-all "^.*\.ha.*" '{"ha-mode":"all", "ha-sync-mode":"automatic"}'
  4. Management Plugin 을 이용한 테스트 및 관리
    사실 미러링은 그냥 Virtual Host에 적용되는 정책에 따라 변할뿐입니다. 그리고 이러한 정책은 Management Plugin에서 Admin=>Policies 메뉴를 이용하면 좀더 편리하게 설정을 할 수 있습니다.

5. 클러스터에서 빠져나오기

클러스터를 구성했으면 클러스터에서 빠져 나올수도 있어야 합니다.
  1. 클러스터에서 빠져나오기
    이것도 간단합니다. 클러스터에서 뺄 서버에 가서 명령 하나만 쳐주면 됩니다. 3번 서버를 제거해보도록 하겠습니다.
    # 3번 서버를 클러스터에서 제거
    rabbit3$ rabbitmqctl stop_app
    Stopping node rabbit@rabbit3 ...done.
    rabbit3$ rabbitmqctl reset
    Resetting node rabbit@rabbit3 ...done.
    rabbit3$ rabbitmqctl start_app
    Starting node rabbit@rabbit3 ...done.
    
    # 2번 서버에서 확인해보기
    rabbit2$ rabbitmqctl cluster_status
    Cluster status of node rabbit@rabbit2 ...
    [{nodes,[{disc,[rabbit@rabbit1,rabbit@rabbit2]}]},
     {running_nodes,[rabbit@rabbit1,rabbit@rabbit2]}]
  2. 혹 클러스터에서 제거해야할 node가 정상적으로 작동이 되지 않는 상태라면?
    다른 서버에서 원격으로 제거할 수 있습니다.
    # 3번 서버가 정지되어있다면...
    rabbit3$ rabbitmqctl stop_app
    Stopping node rabbit@rabbit3 ...done.
    
    # 1번이나 2번 서버에서 원격으로 제거 한다. 나중에 3번 서버는 reset하여 클러스터에서 완전히 빠지도록 해준다.
    rabbit2$ rabbitmqctl forget_cluster_node rabbit@rabbit3
    Removing node rabbit@rabbit1 from cluster ...
    ...done.
마지막으로 클러스터와 미러링 구성이 잘 되었나 확인하는것도 역시 Management Plugin을 사용하면 정말 편합니다. 관리자 계정으로 접속하면 home 화면에 cluster가 어떻게 구성되었는지 뙇! 보여줍니다. 미러링이 잘되었는지는 Queue 메뉴에 들어가면 확인 가능합니다. Queue 리스트를 보면 미러링 되는 큐는 이름 옆에 (+2) 이런 표식이 나옵니다. 미러링 되고 있는 node의 수를 보여주는 값입니다. 저게 생기지 않는다면 클러스터에서 공유만 되고 미러링이 되고 있지 않는 Queue 입니다.

출처: 꿀오분 블로그 http://abh0518.net/tok/?p=411

[RabbitMQ] 2. 초보를 위한 RabbitMQ 서비스용 계정 및 Virtual Host 설정하기

앞장에서 RabbitMQ를 구동하기 위한 설치부터 기본 설정을 끝냈으니 이제 서비스가 가능하도록 계정과 Virtual Host, Queue, Exchange 설정 등을 끝내야 한다. Terminal에서 rabbitmqctl 사용하여 간지나게 작업하면 멋있겠지만 역시 초보는 초보답게 Management Plugin을 사용하여 설정하기로 함 (rabbitmqctl 공부하기 귀찮….)

1. 서비스용 계정 설정

관리자로 설정한 rabbitmq 아이디로 Management Plugin 에 접속해 보면 상단에 ‘Admin’ 이라는 메뉴가 보인다.  클릭하여 Users 기능으로 계정을 관리할 수 있다.

요런 화면이 나온다. 딱히 계정관리에 어려울건 없다. GUI니 그냥 계정 두어거 생성/삭제 해보면 금방 감이 온다. 단, 주의할 것은 Tags와 Virtual Host 부분이다.
  1. Tags : 게정의 권한을 부여하는 부분. Admin, Monitoring, Policymaker, Management, None 이렇게 5가지 종류가 있다. Admin은 말그대로 Rabbitmq를 마음대로 주무를 수 있는 관리자 계정이고 Monitoring은 조회만 가능하다. Policymaker,  Management 등등이 있는데 이 문서는 초보용이니 설명은 과감히 생략! 주의 사항으로 정의 권한을 최소한 Management 로 설정해 주어야 Management Plugin에 접속이 가능하다. (한마디로, 지금 rabbitmq를 None으로 설정해버리면 이제 Management Plugin 사용 못한다. 으하핫!) None으로 설정해버리면 오직 Client용 계정으로 작동하여 Management Plugin에 접속이 불가능해진다. 향후 관리자용 계정(Admin), 모니터링 계정(Monitoring), 서비스 Client 계정(None)으로 분리하여 사용할때 잘 구분지어 주도록 하자.  아! Tags 라는 이름 답게 여러개 설정이 가능하다.
  2. Virtual Host : Queue와 계정을 그룹핑 하는 개념이다. 사실 이것때문에 많이 방황(?)했다. 하나의 계정은 여러 Virtual Host을 할당 받을 수 있으며 자신에게 할당된 Virtual Host에 속한 Queue에만 접근이 가능하다. 자세한 설명은 뒤에서….
이미 앞선 장에서 rabbitmq 계정을 amin으로 생성했으니(그래서 Management Plugin에 접속이 가능!) 더이상 쓰지 않을 guest 계정은 삭제하고 서비스 Client 로 사용할 계정 두개를 만들어 보도록 하자. (None Tag로 pushService, smsService 두개를 만들어 보자)

2. Virtual Host 설정

나를 매우 혼란스럽게 해주었던 Virtual Host에 대해 알아보자! 앞서 대충 설명했듯이 일종의 Queue 접근에 대한 그룹핑이다. Exchange 란 애도 함게 설정이 되어야 하는데 일단 서비스용 계정 설정에 집중을 하도록 하자. Management Plugin에서 Admin => Virtula Hosts 메뉴에 들어가면 아래와 같은 설정 화면이 나온다

앞에서 설정한 계정 이름을 보면 예상이 되듯이 smsService 와 pushService 두가지 서비스가 RabbitMQ를 사용할 예정이다. 이 둘은 서로 관계가 없으니 서로의 Queue나 설정을 알필요도 관심도 없다. 그러니 서비스별로 서로 독립된 구역을 나누어주는게 좋은데 그 단위로 Virtual Host를 사용할 수 있다. smsHost와 pushHost를 당장 만들어보자. 만든 후에는 Users에서 계정별 설정을 사용하여 smsService에는 smsHost를 pushService에는 pushHost를 설정해주도록 하자. rabbitmq계정에는 모든 Virtual Host를 설정해주자. 제대로 설정했다면 Users에서 아래와 같은 화면을 볼 수 있다.

참고로 한개의 계정에는 여러 Virtual Host를 설정할 수 있다. 전체 관리자나 전체 모니터링 계정의 경우에는 모든 Virutal Host를 설정하여 쉽게 관리할 수 있도록 하는게 좋다. Virtual Host에 대한 이런 저런 Policy 설정도 가능하긴 한데 이건 초보용이니 과감히 생략! (사실 딱히 특이한 경우가 아니면 쓸일이 없을거 같다..)

그리고 다음으로 큐 설정을 하도록 하겠다. 
Queue의 주요 속성
Durability 설정: 
- durable 속성 : 메세지를 디스크에 저장. 
- memory에 저장하는 것은 transient라고 한다.

Auto delete 설정:
모든 consumer가 unsubscribe하면, 해당 queue는 자동으로 없어진다.

해당 큐는 admin 사이트에서 생성이 가능하고 실제 애플리케이션 사이드에서도 생성이 가능하다. 만약 해당 큐가 이미 존재하고 있다면, 다시 queue를 만들지 않고, queue가 없을 경우에만 만든다. rabbitmq는 어플리케이션 레벨에서 코드를 통해서 queue를 생성 하는 것이 가능하기 때문에, 손쉽게 배포가 가능하다.

Exchange 타입
- Direct Exchange : 각 Queue는 Routing Key에 Binding이 되어 있고, Exchange에 Routing Key가 들어오면, 그 Exchange에 Binding되어 있는 Queue중에서,  그 Key와 Mapping되어 있는 Queue로 메세지를 라우팅 한다.
- Fan out Exchange : Routing Key에 상관 없이 Exchange에 Binding되어 있는 모든 Queue에 메세지를 라우팅 한다. (1:N 관계로, 모든 Queue에 메세지를 복제해서 라우팅 한다.)
- Topic Exchange : Exchange에 mapping 되어 있는 Queue중에서 Routing key가 패턴에 맞는 Queue로 모두 메세지를 라우팅 한다.
- Headers Exchange

Binding : Exchange와 Queue를 연결하는 것을 binding이라고 하며, Binding은 routing key와 Exchange type을 attribute로 optional로 동반한다. (routing key는 일종의 filter key 처럼 동작 한다.)
Channel : 하나의 물리적인 connection 내에 생성되는 가상 논리적인 connection들. Consumer의 process나 thread는 각자 이 channel을 통해서 queue에 연결 될 수 있다.

[Rabbitmq] 1. 초보를 위한 RabbitMQ 후딱 설치하고 설정하기






1.  설치 (ubuntu 기반)


좀 매니악한 개발자 코스프레좀 해볼까 하고 소스 부터 설치하려했다가 erlang부터 설치해야하는 귀찮니즘의 압박으로 결국 apt-get 느님의 도움을 받기로 함
참고 페이지 : https://www.rabbitmq.com/download.html
$>sudo apt-get install rabbitmq-server

2. Management Plugin 활성화

설치가 완료 되면 terminal이 익숙치 않은 초보(?)를 위해 제공하는 Management Plugin도 활성화 시켜 놓자. 플러그인 설치 후에는 RabbitMQ를 restart 해주어야 변경 사항이 반영된다.
참고 페이지 : https://www.rabbitmq.com/management.html
$>sudo rabbitmq-plugins enable rabbitmq_management
$>sudo service rabbitmq-server restart

3. 관리자 계정  추가

처음 설치하면 guest 계정을 제공하기는 하는데 안타깝게도 localhost에서만 접속을 허용한다. 즉, 설치하자마자 Management Plugin의 유려한 UI로 막막 설정해보고 싶었지만 로그인 조차 안되는 난감함이 기다린다. 그러니 일단 RabbitMQ를 설치하고 난 뒤 바로 Terminal에서 관리자 계정을 설정해주도록 하자. 아래 예제에서는 관리자 계정을rabbitmq로 설정했다.
$>sudo rabbitmqctl add_user rabbitmq password
$>sudo rabbitmqctl set_user_tags rabbitmq administrator

4. Management Plugin 접속

유저 설정까지 끝냈으면 Management Plugin으로 RabbitMQ를 관리해보도록 하자.
웹브라우저로 http://serverip:15672/  에 접속하면 아래와 같은 화면이 나온다
이제 3번에서 설정한 관리자 id로 접속을 해보자. 로그인에 성공하고 아래와 같은 화면이 나오면 성공!
축하합니다! 기본적인 RabbitMQ의 설치 및 기본 설정을 끝내셨습니다!
다음 시간엔 서비스용 계정 및 Virtual host 설정 하는 법을 진행해볼까 합니다.

참고 centos 설치 방법
CentOS EPEL Repository가 설치되어 있지 않다면등록
wget http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm  
wget http://rpms.famillecollet.com/enterprise/remi-release-6.rpm  
rpm -Uvh remi-release-6*.rpmepel-release-6*.rpm

erlang을 설치
wget -O /etc/yum.repos.d/epel-erlang.repo http://repos.fedorapeople.org/repos/peter/erlang/epel-erlang.repo 
yum install erlang (yum install -y erlang

RabbitMQ 서버를 설치
wget http://www.rabbitmq.com/releases/rabbitmq-server/v3.3.4/rabbitmq-server-3.3.4-
rpm --importhttp://www.rabbitmq.com/rabbitmq-signing-key-public.asc
yum installrabbitmq-server-3.3.4-1.noarch.rpm

Enabling the Management Console /WebUI 관리 플러그인 설정
rabbitmq-plugins enablerabbitmq_management

[포트 개방 (6938,15672)]
iptables -I INPUT -m tcp -p tcp --dport6938 -j ACCEPT
iptables -I INPUT -m tcp -p tcp --dport15672 -j ACCEPT

service iptables save
service iptables restart

Rabbit MQ 관련

RabbitMQ는 간단하게 말하면 표준 AMQP (Advanced Message Queueing Protocol) 메세지 브로커 소프트웨어(message broker software) 오픈소스이다. RabbitMQ는 erlang언어로 만들어졌을 뿐만 아니라, clustering과 failover를 위한 OTP framework로 서버가 만들어져 있다. RabbitMQ는 VMware에서 지원해주고 있는데 spring source 프로젝트중에 spring AMQP가 정식으로 1.0으로 릴리즈되면서 RabbitMQ의 지원은 더 적극적인것 같다. RabbitMQ는 다양한 언어로된 RabbitMQ client를 지원하고 있고 공식적인 온라인 문서에서는 Python과 Java에 대한 소스코드를 예제로 공개하고 있다.
RabbitMQ를 이해하기 위해서는 우선 MQ(Message Queuing)에 대한 이해가 필요하다. 프로그래밍에서 MQ는 프로세스 또는 프로그램 인스턴스가 데이터를 서로 교환할때 사용하는 방법이다. 이때 데이터를 교환할때 시스템이 관리하는 메세지 큐를 이용하는 것이 특징이다. 이렇게 서로 다른 프로세스나 프로그램 사이에 메시지를 교환할때 AMQP(Advanced Message Queueing Protocol)을 이용한다. AMQP는 메세지 지향 미들웨어를 위한 open standard application layer protocol 이다. AMQP를 이용하면 다른 벤더 사이에 메세지를 전송하는 것이 가능한데 JMS (Java Message Service)가 API를 제공하는것과 달리 AMQP는 wire-protocol을 제공하는데 이는 octet stream을 이용해서 다른 네트워크 사이에 데이터를 전송할 수 있는 포멧인데 이를 사용한다. 이러한 복잡한 설명과 달리 RabbitMQ 튜토리얼에서는 RabbitMQ를 매우 간단하게 편지를 작성하여 받는 사람에게 보낼 우체통, 우체국, 우편배달부가 있듯, post box, post office and postman라고 비유적으로 설명하고 있다. 단지 다른것은 데이터의 바이너리 blobs을 accept, store, forward 시키는 것만 다른것이라 말한다.
RabbitMQ와 같은 Message Queueing은 대용량 데이터를 처리하기 위한 배치 작업이나, 체팅 서비스, 비동기 데이터를 처리할때 사용한다. RabbitMQ를 찾아보게 된 계기도 비동기식 데이터 처리를 하기 위해서 찾아보게 되었다. 프로세스단위로 처리하는 웹 요청이나 일반적인 프로그램을 만들어서 사용하는데 사용자가 많아지거나 데이터가 많아지면 요청에 대한 응답을 기다리는 수가 증가하다가 나중에는 대기 시간이 지연되어서 서비스가 정상적으로 되지 못하는 상황이 오기 때문에 기존에 분산되어 있던 데이터 처리를 한곳으로 집중하면서 메세지 브로커를 두어서 필요한 프로그램에 작업을 분산 시키는 방법을 하고 싶었기 때문이다. ActiveMQ와 RabbitMQ를 후보에 두었는데 RabbitMQ가 먼저 리뷰하게 되었다.

Mac OS X에 RabbitMQ 서버 설치

우선 RabbitMQ를 사용하기 위해서는 RabbitMQ server가 필요하다.http://www.rabbitmq.com/download.html 에 가서 자신에게 맞는 서버를 설치하여 사용하면 된다. 테스트할 서버는 Ubuntu인데 사용설명을 위해서 현재 사용중인 Macbook Pro에도 RabbitMQ server를 설치하였다. 이번 포스트에서는 RabbitMQ 서버를 설치하는 방법을 간단히 설명하고 “RabbitMQ를 이용하여 비동기 데이터 처리 시스템 구축하기 - 2편 메세지 전송과 수신 방법”에서 RabbitMQ 공식 문서에 나와 있는 예제를 바탕으로 포스팅을 할 예정이다.
우선 Mac용에서 설치하는 방법을 먼저 살펴보자. 개발자에게 Mac은 서버와 클라이언트를 모두 가질수 있게 해주는 개발자에게 가장 어울리는 운영체제가 아닌가 싶다. 개인적으로 Linux/Unix 환경에 개발하면서도 윈도우즈만큼 (이젠 더 편리한) GUI를 함께 사용한다는 것은 개발자에게 큰 행운이라고 본다. 서버 프로그램을 자주하는 개발자라면 아마도 Mac을 사용할때 가장 먼저 하는 것이 macport를 설치하는 것이 아닐까 생각든다. fink도 있는데 개인적으로 bsd의 port를 좋아하기 때문에 macport를 사용한다. 이것은 Unix software을 dawin 기반의 mac에서도 사용할수 있게 포팅해주는 패키지관리 프로그램이라고 생각하면 된다. RabbitMQ 공식 사이트에서도 Mac에서 설치할때는 macport를 사용하고 나와 있다. 하지만 이 포스팅을 작성할때 macport로 RabbitMQ를 설치하니 에러가 발생하면서 정상적으로 설치가 되지 않았다. 그러던 중에 macport의 의존성 패키지 업데이트에 대해 twitter에 글을 작성하다가 @andrwj 님께서 brew에 대해서 잠깐 언급하셔서 이번에 homebrew를 설치하여서 brew를 이용해서 RabbitMQ를 설치하였다.
brew search rabbitmq
라고 검색하면 rabbitmq가 패키지가 있는 것을 확인하고 install로 RabbitMQ 를 설치하면 된다.
brew install rabbitmq
brew는 macport와 달리 /usr/local/Cellar/{패키지명}/{버전} 으로 패키지가 설치되는데 현재 RabbitMQ의 최신 버전은 2.7.1이 설치가 된다. RabbitMQ는 erlang으로 만들어졌는데 RabbitMQ를 설치하면 erlang도 함께 자동으로 설치가 된다. RabbitMQ 서버를 실행해보자.
/usr/local/Cellar/rabbitmq/2.7.1/sbin/rabbitmq-server start
서버를 실행하면 RabbitMQ의 심볼이 나타나면서 rabbit boot start, external infrastructure ready, kernel ready, core initialized, message delivery logic read 순으로 부팅이 완료되고 메세지를 받기 위해 대기한다.
RabbitMQ의 상태를 확인하기 위해서는 rabbitmqctl을 사용하면 된다.
/usr/local/Cellar/rabbitmq/2.7.1/sbin/rabbitmqctl status

Ubuntu에 RabbitMQ 설치

이제 Ubuntu에 설치하는 방법을 살펴보자. Ubuntu는 apt-get 이라는 패키지 툴로 패키지를 관리할 수 있는데 사실 이 패키지툴은 굉장히 편리하다. apt를 이용해서 패키지를 설치하려면 리소스 리스트를 관리하는 툴에 리파지토리를 추가해줘야한다.
sudo vi /etc/apt/sources.list
파일의 맨 마지막에 다음 문장을 추가하고 저장하고 vi 에디터를 닫고 나온다.
deb http://www.rabbitmq.com/debian/ testing main
하지만 이 패키지는 rabbitmq에서 배포하는 패키지이고 ubuntu에서 정식 패키지로 인증되지 않은 것이기 때문에 RabbitMQ의 public key를 ubuntu가 신뢰할 수 있는 키로 등록해줘야한다. 머저 RabbitMQ에서 public 키를 wget으로 받아 온다.
wget http://www.rabbitmq.com/rabbitmq-signing-key-public.asc
다운받은 키를 apt-key를 이용해서 인증키로 등록한다.
sudo apt-key add rabbitmq-signing-key-public.asc
이제 apt-get 을 이용하여 RabbitMQ 서버를 설치한다.
sudo apt-get install rabbitmq-server
RabbitMQ는 공식적으로 Mac OS X, Debian/Ubuntu, RPM-based Linux, General Unix, Solaris, EC2 에서 설치가 가능하다. 여기서는 Mac과 Ubuntu 기반으로 RabbitMQ에 대해서 사용하는 방법을 포스팅하기 위해서 두가지 설치 방법만 작성했지만 공식 문서에 가면 다른 운영체제에서 설치하는 것도 어렵지 않게 따라할수 있을거라 생각이 든다. 다음 포스팅은 RabbitMQ의 공식 문서를 기반으로 실제 메세지를 전송하고 받는 방법에 대한 예제를 포스팅할 예정이다.