RabbitMQ - topic
在publish/subscribe模式中使用fanout类型有个缺陷,就是不能选择性接收的消息。
我们可以让consumer获得所有已发布的消息中指定的几个消息。
在之前的例子中我们这样绑定exchange和队列:
channel.queueBind(queueName, EXCHANGE_NAME, "");
暂且不论该代码中绑定的exchange类型,这里空着的参数就是routing key。
routing key的意义与exchange类型有关,比如使用fanout类型就会忽略掉routing key。
而解决这一问题的就是direct类型。
direct exchange并不复杂,只不过是producer和consumer双方的exchange对应时还需要对应routing key。
以下代码中,同一个exchange和两个队列进行绑定,两个队列分别和不同的binding key绑定。
(PS:当然,我们也可以将同一个routing key绑定给不同的队列也没有问题。)
另外,SERVERITY变量是rounting数组,假设将日志通过exchange发送出去,consumer根据自己的需要获取不同级别的日志:
final class ChannelFactory_{ private final static ConnectionFactory connFactory = new ConnectionFactory(); public final static String EXCHANGE_NAME = "direct_exchange"; public final static String[] SEVERITY = {"info","warning","error"}; static { Channel temp = getChannel(); try { temp.exchangeDeclare(EXCHANGE_NAME, ExchangeTypes.DIRECT); } catch (IOException e) { e.printStackTrace(); } } public static Channel getChannel(int channelNumber){ try { Connection connection = connFactory.newConnection(); return connection.createChannel(channelNumber); } catch (IOException e) { e.printStackTrace(); }return null; } public static Channel getChannel(){ try { Connection connection = connFactory.newConnection(); return connection.createChannel(); } catch (IOException e) { e.printStackTrace(); }return null; } public static void closeChannel(Channel channel) throws IOException { channel.close(); channel.getConnection().close(); } }
确认定义:
consumer只需要warning和error级别(routing)的日志消息:
public static void main(String[] args) throws IOException, InterruptedException { Channel channel = ChannelFactory_.getChannel(); String queueName = channel.queueDeclare().getQueue(); channel.queueBind(queueName, ChannelFactory_.EXCHANGE_NAME,"warning"); channel.queueBind(queueName, ChannelFactory_.EXCHANGE_NAME,"error"); QueueingConsumer consumer = new QueueingConsumer(channel); channel.basicConsume(queueName,true,consumer); while(true){ QueueingConsumer.Delivery delivery = consumer.nextDelivery(); String message = new String(delivery.getBody()); String routingKey = delivery.getEnvelope().getRoutingKey(); System.out.println(" [x] Received '" + routingKey + "':'" + message + "'"); } }
producer将所有级别的日志都发送出去:
public static void main(String[] args) throws IOException { Channel channel = ChannelFactory_.getChannel(); String content = "message "+new Date(); for (int i = 0; i <ChannelFactory_.SEVERITY.length ; i++) { channel.basicPublish(EXCHANGE_NAME,ChannelFactory_.SEVERITY[i],null,content.getBytes()); } ChannelFactory_.closeChannel(channel); }
运行结果:
direct exchange可以让我们有选择性地接受消息。
但这样做仍然有缺陷。
虽然我可以只要求error和warning级别的日志,但是我不能再进行细分。
比如我只想要数据库相关的error和warning级别的日志。
为了实现这一点,我们需要使用另一个exchange类型——Topic。
exchange类型为topic时,routing key是一组用"."隔开的词,但仅限255bytes。
比如:"stock.usd.nyse", "nyse.vmw", "quick.orange.rabbit"
topic和direct的不同点还有在consumer中定义routing key时我们可以使用通配符,比如:
符号'*':可以匹配某一个词。
符号'#':可以匹配0~N个词。
举个例子说明,假设我们用rounting key描述一个动物。
格式为: <性格>.<颜色>.<种类>
用符号'*',我想要得到桔***的动物,即:"*.orange.*"
用符号'#',我想要得到懒散的动物,即:"lazy.#"
如果使用过程中有人破坏了格式,即使rounting key为"lazy.orange.male.rabbit"也可以匹配"lazy.#"。
稍微修改上面的代码,首先定义一个topic exchange。
public final static String EXCHANGE_NAME = "topic_exchange";
temp.exchangeDeclare(EXCHANGE_NAME, ExchangeTypes.TOPIC);
确认定义:
发送sql相关的log:
public static void main(String[] args) throws IOException { Channel channel = ChannelFactory_.getChannel(); String content = "message #$#$#$#$#$#$"; channel.basicPublish(EXCHANGE_NAME,"warning.sql.connection.close",null,content.getBytes()); channel.basicPublish(EXCHANGE_NAME,"error.sql.syntax",null,content.getBytes()); ChannelFactory_.closeChannel(channel); }
consumer接收所有sql相关的warning和所有error:
public static void main(String[] args) throws IOException, InterruptedException { Channel channel = ChannelFactory_.getChannel(); String queueName = channel.queueDeclare().getQueue(); channel.queueBind(queueName, ChannelFactory_.EXCHANGE_NAME,"warning.sql.#"); channel.queueBind(queueName, ChannelFactory_.EXCHANGE_NAME,"error.#"); QueueingConsumer consumer = new QueueingConsumer(channel); channel.basicConsume(queueName,true,consumer); while(true){ QueueingConsumer.Delivery delivery = consumer.nextDelivery(); String message = new String(delivery.getBody()); String routingKey = delivery.getEnvelope().getRoutingKey(); System.out.println(" [x] Received '" + routingKey + "':'" + message + "'"); } }
运行结果: