九、主题
前面的路由教程中改进了日志系统,使用了direct
类型,而不是只能进行虚拟广播的fanout
的交换器,并且有可能选择性地接收日志。
但前面的direct
类型交换器还是有局限性。在日志系统中,不仅要查看指定error
级别的日志消息,还要知道是哪个地方、哪个类出现的错误消息,例如下面日志消息
INFO c.z.springboot01logging.controller.service.InfoTest - 这是InfoTest的日志
WARN c.z.springboot01logging.controller.service.WarnTest - 这是WarnTest日志
ERROR c.z.springboot01logging.controller.service.ErrorTest - 这是ErrorTest日志
上面日志消息除了标记info
级别,还有日志消息的来源:c.z.springboot01logging.controller.service
下InfoTest
、WarnTest
、ErrorTest
三个类。
如上面日志所示,要查看哪些类的日志消息,如果用前面的direct
类型查询,只查像上面几个类日志还好,如果成百上千条,就不能一个个写,此时就需要topic
类型交换器。
日志消息发送客户端:
/**
* @author Hayson
* @date 2018/11/26 17:17
* @description 路由发送消息
*/
public class Send {
final static String EXCHANGE = "logs_topic";
public static void main(String[] args) throws IOException, TimeoutException {
Map<String, String> message = new HashMap<>();
message.put("error.err.rabbit", "error message");
message.put("error.info.rabbit2", "error2 message");
message.put("error.rabbit.message", "error2 message");
message.put("warning.warn.rabbit", "warning message");
message.put("info.rabbit", "info message");
send(message);
}
public static void send(Map<String, String> message) throws IOException, TimeoutException {
//获取连接
Connection connection = ConnectionUtils.getConnection();
//通过连接创建信道
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE, BuiltinExchangeType.TOPIC);
//发送消息
Iterator<Map.Entry<String, String>> iterator = message.entrySet().iterator();
while (iterator.hasNext()){
Map.Entry<String, String> next = iterator.next();
String key = next.getKey();
String value = next.getValue();
channel.basicPublish(EXCHANGE, key, null, value.getBytes("utf-8"));
System.out.println("发送:" + key + " : " + value);
}
//关闭信道和连接
channel.close();
connection.close();
}
}
日志消息接收客户端:
/**
* @author Hayson
* @date 2018/11/23 13:41
* @description rabbitmq消费者接收消息2
*/
public class Receiver {
final static String EXCHANGE = "logs_topic";
public static void main(String[] args) throws IOException, TimeoutException {
String[] routings = {"error.#"};
recevier(routings);
}
public static void recevier(String[] routings) throws IOException, TimeoutException {
//获取连接
Connection connection = ConnectionUtils.getConnection();
//通过连接创建信道
Channel channel = connection.createChannel();
//创建交换器
channel.exchangeDeclare(EXCHANGE, BuiltinExchangeType.TOPIC);
//创建队列
String queue = channel.queueDeclare().getQueue();
for (String routing : routings) {
//交换器和队列绑定
channel.queueBind(queue, EXCHANGE, routing);
}
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
throws IOException {
String routingKey = envelope.getRoutingKey();
String message = new String(body, "UTF-8");
System.out.println("接收到消息:" + routingKey + " : " + message);
}
};
channel.basicConsume(queue, true, consumer);
//关闭信道、连接
//channel.close();
//connection.close();
}
}
上面消费客户端接收所有routingKey
以rabbit
开头的消息。
关于topic
类型的匹配规则:
匹配规则
*
匹配一个单词
#
匹配0个或多个字符
*
,#
只能写在.
号左右,且不能挨着字符
单词和单词之间需要.
隔开。
例子
-
BindingKey是
user.log.#
,因为#
是匹配0个或多个字符,所以下面RoutingKey的可以匹配:user.log user.log.info user.log.a user.log.info.login
-
BindingKey是
user.log.*
,因为*
匹配一个单词,所以user.log.info 可以匹配 user.log 不能匹配 user.log.info.login 不能匹配,二个单词
-
BindingKey是
#.log.#
, 可以匹配:log user.log log.info user.log.info user.log.info.a
-
BindingKey是
*.log.*
log 不匹配 user.log 不匹配 log.info 不匹配 user.log.info 匹配,前后各一个单词 user.log.info.a 不匹配 a.user.log.info 不匹配
-
BindingKey是
*.action.#
action 不符合 action.log 不符合 user.action.log 符合 user.action.log.info 符合 user.action 符合 user.log.action 不符合
-
BindingKey是
#.action.*
action 不符合 user.action 不符合 user.action.action 符合 user.action.login 符合 user.action.login.count 不符合
-
BindingKey是
*
表示匹配一个单词 -
BindingKey是
#
,或者#.#
表示匹配所有