目前用到一些Metaq的东西,虽然对Metaq的使用很少,并且不是特别深,但还是觉得应该针对其消息的分发以及简单的机制进行一些记录。
Metaq 的简单介绍:
MetaQ(全称Metamorphosis)是一个高性能、高可用、可扩展的分布式消息中间件,,MetaQ具有消息存储顺序写、吞吐量大和支持本地和XA事务等特性,适用于大吞吐量、顺序消息、广播和日志数据传输等场景,METAQ在阿里巴巴各个子公司被广泛应用,每天转发250亿+条消息。主要应用于异步解耦,Mysql数据复制,收集日志等场景。但是目前就我接触的来看,其广播机制是通过定义多个group来进行实现。其对单个消息的大小建议不超过1M,最好几百K。
Metaq的消息机制:
Metaq的消息会对应相应的消息生产者,消息消费者,一种对应的关系可以用下面一张图简单概括:
我们选择发送的每一个消息MessageA一定会选择发送到某个Topic下,那么对于该Topic,可以有若干个Group进行订阅,当某条消息发送到某个Topic下时,所有订阅该Topic的group都会收到MessageA的一个复制消息。而当消息从Group下发到某个机器时,会根据负载等其他机制选择该group下的某台机器进行处理。也就是对于GroupA 下的Machine 1,Machine 2, Machine 3等机器,只会有一台机器收到MessageA对应的复制消息。那么对于每一条消息的Tag是用来做什么呢? 当某台机器得到MessageA时,可以根据其Tag标来进行选择不同的处理器进行处理,这里其实主要是用来对消息进行处理过滤。例如:对于MessageA 加入有标签:newuser,那么我们可以选择newuser对应的processer进行处理,如果标签为olduser,我们可以选择olduser的processer进行处理。
对于Metaq的消息消费,是客户机拉取的方式进行。
简单的生产者,消费者的Java例子。
自己简单写的一个metaq的生产者例子:
<span style="font-size:14px;">public class MetaqProducer {
//private static final Logger log = LoggerFactory.getLogger(MetaqProducer.class);
private static MetaqProducer sender = null;
private static MetaqConsumer receiver = null;
static {
try {
sender = new MetaqProducer();
} catch (MQClientException e) {
}
}
private MetaProducer producer = new MetaProducer("TestGroup");
private MetaqProducer() throws MQClientException {
producer.setInstanceName(UUID.randomUUID().toString());
producer.start();
}
private SendResult send(String topic, String tag, String key, byte[] body) {
Message msg = new Message(topic, tag, key, body);
try {
return producer.send(msg);
}catch(Exception e) {
return new SendResult();
}
}
public static SendResult sendMessage(String topic, String tag, String key, byte[] body) {
return sender.send(topic, tag, key, body);
}
/**
* 测试用
* @throws MQClientException
* @throws InterruptedException
*/
public static void main(String[] args) throws MQClientException, InterruptedException{
System.out.println("Test Start!");
int j = 100;
int i =0 ;
for( ; i < j ; i++){
System.out.println("i="+i);
SendResult r = sendMessage("testTopic", "testTag", "TestOnly"+ UUID.randomUUID().toString(), ("Hello world" + i).getBytes());
System.out.println(r.getMsgId()+" ");
System.out.println(r.getSendStatus().toString());
}
}
}</span><span style="font-size: 18px;">
</span>
自己简单写的一个metaq的消费者例子:
<span style="font-size:14px;">public class MetaqConsumer {
private static MetaPushConsumer consumer = null;
public static MetaPushConsumer getMetaqClient(){
consumer = new MetaPushConsumer("TestGroup");
consumer.setInstanceName(UUID.randomUUID().toString());
consumer.setConsumeThreadMax(10);
consumer.setConsumeThreadMin(5);
try {
consumer.subscribe("testTopic", "testTag");
} catch (MQClientException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return consumer;
}
public static void receiveMsg() throws MQClientException{
consumer = getMetaqClient();
consumer.registerMessageListener(new MessageListenerConcurrently() {
/**
* 1、默认msgs里只有一条消息,可以通过设置consumeMessageBatchMaxSize参数来批量接收消息<br>
* 2、如果设置为批量消费方式,要么都成功,要么都失败。<br>
* 3、此方法由MetaQ客户端多个线程回调,需要应用来处理并发安全问题<br>
* 4、抛异常与返回ConsumeConcurrentlyStatus.RECONSUME_LATER等价<br>
* 5、每条消息失败后,会尝试重试,重试5次都失败,则丢弃<br>
*/
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
if (msgs == null || msgs.size() == 0) {
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
if (msgs.size() == 1) {// 一个消息
String data = new String(msgs.get(0).getBody());
System.out.println("Test");
System.out.println(data);
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}else{
for(MessageExt s : msgs){
String data = new String(s.getBody());
System.out.println("Test");
System.out.println(data);
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
}
});
// Consumer对象在使用之前必须要调用start初始化,初始化一次即可
consumer.start();
}
public static void main(String[] args) throws MQClientException{
receiveMsg();
}
}</span>
一个很好的消息中间件,目前可能对他的了解还很浅薄。希望能够在更多的应用场景中对其有所了解。