티스토리 뷰

프로그래밍/RabbitMQ

[RabbitMQ] Topics

kkd927 kkd927 2015.07.10 05:04

[RabbitMQ] Topics

(using the Bunny client)


이전 강의에서 우리의 로그 시스템을 발전 시켜봤습니다. 단순히 브로드캐스팅을 하는 fanout exchange 대신에 direct 를 이용하여 메시지 로그를 선택적으로 수신할 수 있게 해보았습니다.


비록 direct exchange를 사용하여 우리의 시스템을 진보시켜보았지만 여전히 다중 기준에 의한 라우팅을 할 수 없는 한계가 존재합니다.


우리의 로그 시스템은 info, warning, error 와 같은 규칙에 분류한 구독을 할 수 있지만 로그를 출력하는 프로그램에 따른 구독은 할 수 없습니다. 유닉스의 syslog 툴의 개념을 생각하면 됩니다. syslog는 규칙(info/warn/cri..)와 출처(auth/cron/kern..) 둘 다에 기반한 라우팅을 지정할 수 있습니다.


이는 엄청난 유연성을 가져다 줍니다. 예를들어, cron 으로부터 오는 메시지들에서는 critical, errors 메시지를, kern 으로부터 오는 메시지들에 대해서는 모든 메시지들을 수신하게 할 수 있습니다.


이를 우리 로그 시스템에 시행하기 위해선 더 복잡한 topic exchange 에 대해 배울 필요가 있습니다.


전제조건


이 튜토리얼에서 RabbitMQ가 localhost에 설치되어있고 기본 포트인 5672로 동작 중인 것을 가정으로 진행됩니다. 다른 호스트나, 포트를 사용할 경우 적절한 세팅을 하셔야합니다.



Topic exchange

topic exchange를 이용하여 메시지를 보낼 때는 routing_key를 마음대로 정할 수 없습니다. 단어와 범위표시자를 점을 구분으로한 리스트여야만 합니다. 단어는 아무것이나 할 수 있지만, 보통 해당 메시지의 특징을 나타내는 것으로 합니다. 몇 개의 가능한 예제입니다: "stock.usd.nyse", "nyse.vmw", "quick.orange.rabbit". routing key로 255byte 이하의 단어들을 사용할 수 있습니다.


binding key 도 같은 양식을 따릅니다. topic exchange의 로직은 direct와 비슷합니다. 메시지는 특정 routing key와 보내지고 binding key와 일치하는 모든 queue에게 전송됩닏나. 하지만 binding key에 중요하고 특별한 두 경우가 있습니다.


> *(별)은 단어 한 단어를 대체합니다.

> #(해쉬)는 0 단어 이상을 대체합니다. 


이를 쉽게 표현한 예제입니다.



이 예제에서 동물들을 묘사하는 메시지를 보낼 것 입니다. 메시지들은 3개의 단어들(2개의 점)로 구성된 routing key가 적용됩니다. routing key의 첫 번째 단어는 속도를, 두 번째는 색깔을, 세 번째는 종을 묘사합니다. "<speed>.<colour>.<species>"


저희는 세 개의 binding을 만들 것 입니다. Q1는 "*.orange.*" , Q2는 "*.*.rabbit"과 "lazy.#" binding key에 bind 됩니다.


이 binding 들을 요약하자면:

> Q1는 orange 동물들에 대해 수신합니다.

> Q2는 모든 rabbit 들 혹은 lazy 동물들에 대해 수신합니다.


"quick.orange.rabbit"의  경우 두 queue에 모두 전달됩니다. "lazy.orange.elephant" 또한 두 queue 모두에 전달되게 됩니다. 한편으로 "quick.orange.fox"의 경우 Q1만, "lazy.brown.fox"의 경우 Q2에만 전달되게 됩니다. "lazy.pink.rabbit"은 Q2의 두 binding과 일치하지만 한번만 Q2에 전달되게 됩니다. "quick.brown.fox"는 어느 binding에도 일치하지 않으므로 버려지게 됩니다.


만약 "orange"나 "quick.orange.male.rabbit"과 같이 한 단어나 4개의 단어로 이루어진 key로 메시지를 보내면 어떻게 될까요? 이 경우 메시지는 어느 binding과 일치하지 않아 버려지게 됩니다.


한편 "lazy.orange.male.rabbit"의 경우 4 단어이지만 lazy.#와 일치하게 되어 Q2로 보내지게 됩니다.


Topic exchange


Topic exchange는 강력하고 또한 다른 exchange의 기능들을 구현할 수 있습니다.

queue가 "#"(hash) binding key로 bind 되어있을 때 routing key와 상관없이 모든 메시지들을 수령하게 됩니다. - fanout exchange와 같이


"*"(star)이나 "#"(hash)가 binding에 사용되지 않으면 topic exchange는 direct와 같이 사용됩니다.



종합하기

저희의 로그 시스템에 topic exchange를 사용할 것 입니다. routing key가 두 단어를 가진다고 가정합니다: "<facility>.<severity>"


이 코드에서 대부분은 이전 강의에서 사용했던 것 입니다.


emit_log_topic.rb 코드입니다:

#!/usr/bin/env ruby
# encoding: utf-8

require "bunny"

conn = Bunny.new
conn.start

ch       = conn.create_channel
x        = ch.topic("topic_logs")
severity = ARGV.shift || "anonymous.info"
msg      = ARGV.empty? ? "Hello World!" : ARGV.join(" ")

x.publish(msg, :routing_key => severity)
puts " [x] Sent #{severity}:#{msg}"

conn.close

(emit_log_topic.rb)


receive_logs_topic.rb 코드입니다:

#!/usr/bin/env ruby
# encoding: utf-8

require "bunny"

if ARGV.empty?
  abort "Usage: #{$0} [binding key]"
end

conn = Bunny.new
conn.start

ch  = conn.create_channel
x   = ch.topic("topic_logs")
q   = ch.queue("", :exclusive => true)

ARGV.each do |severity|
  q.bind(x, :routing_key => severity)
end

puts " [*] Waiting for logs. To exit press CTRL+C"

begin
  q.subscribe(:block => true) do |delivery_info, properties, body|
    puts " [x] #{delivery_info.routing_key}:#{body}"
  end
rescue Interrupt => _
  ch.close
  conn.close
end

(receive_logs_topic.rb)


모든 로그를 수신하려면:

$ ruby -rubygems receive_logs_topic.rb "#"


"kern" facility 의 모든 로그를 수신하려면:

$ ruby -rubygems receive_logs_topic.rb "kern.*"


오직 "critical" 로그만 수신하려면:

$ ruby -rubygems receive_logs_topic.rb "*.critical"


다중 binding을 생성하려면:

$ ruby -rubygems receive_logs_topic.rb "kern.*" "*.critical"


"kern.critical" 타입의 로그를 만드려면:

$ ruby -rubygems emit_log_topic.rb "kern.critical" "A critical kernel error"


다음 강의에서는 원격 프로시져 호출과 같은 round trip 메시지에 대해 살펴보겠습니다.

댓글