MetaQ 实例之一

一、简单实例:

一、JAVA客户端例子

<dependency>
    <groupId>com.taobao.metamorphosis</groupId>
    <artifactId>metamorphosis-client</artifactId>
    <version>1.4.6.2</version>
</dependency>

请注意,1.4.3及以上版本的java客户端只能连接1.4.3及以上版本的MetaQ服务器,而1.4.3之前的老客户端则没有限制。主要是因为1.4.3引入了发布和订阅topic的分离,1.4.3的新客户端只能查找到新版本的broker

二、消息会话工厂类

        在使用消息生产者和消费者之前,我们需要创建它们,这就需要用到消息会话工厂类——MessageSessionFactory,由这个工厂帮你创建生产者或者消费者。除了这些,MessageSessionFactory还默默无闻地在后面帮你做很多事情,包括:

1.服务的查找和发现,通过diamond和zookeeper帮你查找日常的meta服务器地址列表

2.连接的创建和销毁,自动创建和销毁到meta服务器的连接,并做连接复用,也就是到同一台meta的服务器在一个工厂内只维持一个连接。

3.消息消费者的消息存储和恢复,后续我们会谈到这一点。

4.协调和管理各种资源,包括创建的生产者和消费者的。

因此,我们首先需要创建一个会话工厂类,MessageSessionFactory仅是一个接口,它的实现类是MetaMessageSessionFactory;

请注意,MessageSessionFactory应当尽量复用,也就是作为应用中的单例来使用,简单的做法是交给spring之类的容器帮你托管。

注意:①MessageSessionFactory应当尽量复用,也就是作为应用中的单例使用,简单的做法是交给Spring类的容器托管。

三、消息生产者

package com.taobao.metamorphosis.example;

import java.io.BufferedReader;
import java.io.InputStreamReader;

import com.taobao.metamorphosis.Message;
import com.taobao.metamorphosis.client.MessageSessionFactory;
import com.taobao.metamorphosis.client.MetaClientConfig;
import com.taobao.metamorphosis.client.MetaMessageSessionFactory;
import com.taobao.metamorphosis.client.producer.MessageProducer;
import com.taobao.metamorphosis.client.producer.SendResult;
import com.taobao.metamorphosis.utils.ZkUtils.ZKConfig;

public class Producer {
    public static void main(String[] args) throws Exception {
        final MetaClientConfig metaClientConfig = new MetaClientConfig();
        final ZKConfig zkConfig = new ZKConfig();
        //设置zookeeper地址
        zkConfig.zkConnect = "127.0.0.1:2181";
        metaClientConfig.setZkConfig(zkConfig);
        // New session factory,强烈建议使用单例
        MessageSessionFactory sessionFactory = new MetaMessageSessionFactory(metaClientConfig);
        // create producer,强烈建议使用单例
        MessageProducer producer = sessionFactory.createProducer();
        // publish topic
        final String topic = "test";
        producer.publish(topic);

        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        String line = null;
        while ((line = reader.readLine()) != null) {
            // send message
            SendResult sendResult = producer.sendMessage(new Message(topic, line.getBytes()));
            // check result
            if (!sendResult.isSuccess()) {
                System.err.println("Send message failed,error message:" + sendResult.getErrorMessage());
            }
            else {
                System.out.println("Send message successfully,sent to " + sendResult.getPartition());
            }
        }
    }
}

消息生产者的接口是MessageProducer,你可以通过它来发送消息。创建生产者很简单,通过MessageSessionFactory的createProducer方法即可以创建一个生产者。在Meta里,每个消息对象都是Message类的实例,Message表示一个消息对象,它包含这么几个属性:

  • id: Long型的消息id,消息的唯一id,系统自动产生,用户无法设置,在发送成功后由服务器返回,发送失败则为0。

  • topic: 消息的主题,订阅者订阅该主题即可接收发送到该主题下的消息,生产者通过指定发布的topic查找到需要连接的服务器地址,必须。

  • data: 消息的有效载荷,二进制数据,也就是消息内容,meta永远不会修改消息内容,你发送出去是什么样子,接收到就是什么样子。消息内容通常限制在1M以内,我的建议是最好不要发送超过上百K的消息,必须。数据是否压缩也完全取决于用户。

  • attribute: 消息属性,一个字符串,可选。发送者可设置消息属性来让消费者过滤。

细心的朋友可能注意到,我们在sendMessage之前还调用了MessageProducer的publish(topic)方法

producer.publish(topic);

这一步在发送消息前是必须的,你必须发布你将要发送消息的topic,这是为了让会话工厂帮你去查找接收这些topic的meta服务器地址并初始化连接。这个步骤针对每个topic只需要做一次,多次调用无影响。

总结下这个例子,从标准输入读入你输入的数据,并将数据封装成一个Message对象,发送到topic为test的服务器上。

注意:②MessageProducer是线程安全的,完全可重复使用,因此最好在应用中作为单例来使用,一次创建,到处使用,配置为spring里的singleton bean。MessageProducer创建的代价昂贵,每次都需要通过zk查找服务器并创建tcp长连接。

四、消息消费者

package com.taobao.metamorphosis.example;

import java.util.concurrent.Executor;
import com.taobao.metamorphosis.Message;
import com.taobao.metamorphosis.client.MessageSessionFactory;
import com.taobao.metamorphosis.client.MetaClientConfig;
import com.taobao.metamorphosis.client.MetaMessageSessionFactory;
import com.taobao.metamorphosis.client.consumer.ConsumerConfig;
import com.taobao.metamorphosis.client.consumer.MessageConsumer;
import com.taobao.metamorphosis.client.consumer.MessageListener;
import com.taobao.metamorphosis.utils.ZkUtils.ZKConfig;

public class AsyncConsumer {
    public static void main(String[] args) throws Exception {
        final MetaClientConfig metaClientConfig = new MetaClientConfig();
        final ZKConfig zkConfig = new ZKConfig();
        //设置zookeeper地址
        zkConfig.zkConnect = "127.0.0.1:2181";
        metaClientConfig.setZkConfig(zkConfig);
        // New session factory,强烈建议使用单例
        MessageSessionFactory sessionFactory = new MetaMessageSessionFactory(metaClientConfig);
        // subscribed topic
        final String topic = "test";
        // consumer group
        final String group = "meta-example";
        // create consumer,强烈建议使用单例
        MessageConsumer consumer = sessionFactory.createConsumer(new ConsumerConfig(group));
        // subscribe topic
        consumer.subscribe(topic, 1024 * 1024, new MessageListener() {
            
            /**
             * 接收到消息列表,只有message不为空并且不为null的情况下会触发此方法
             */ 
            public void recieveMessages(Message message) {
                System.out.println("Receive message " + new String(message.getData()));
            }
            /**
             * 处理消息的线程池
             */ 
            public Executor getExecutor() {
                // Thread pool to process messages,maybe null.
                return null;
            }
        });
        // complete subscribe
        consumer.completeSubscribe();
    }
}

通过createConsumer方法来创建MessageConsumer,注意到我们传入一个ConsumerConfig参数,这是消费者的配置对象。每个消息者都必须有一个ConsumerConfig配置对象,我们这里只设置了group属性,这是消费者的分组名称。Meta的Producer、Consumer和Broker都可以为集群。消费者可以组成一个集群共同消费同一个topic,发往这个topic的消息将按照一定的负载均衡规则发送给集群里的一台机器。同一个消费者集群必须拥有同一个分组名称,也就是同一个group。我们这里将分组名称设置为meta-example。

订阅消息通过subscribe方法,这个方法接受三个参数

  • topic,订阅的主题

  • maxSize,因为meta是一个消费者主动拉取的模型,这个参数规定每次拉取的最大数据量,单位为字节,这里设置为1M,默认最大为1M。

  • MessageListener,消息监听器,负责消息消息。

消息的消费过程可以是一个并发处理的过程,getExecutor返回你想设置的线程池,每次消费都会在这个线程池里进行。recieveMessage方法用于实际的消息消费处理,message参数即为消费者收到的消息,它必不为null。

我们这里简单地打印收到的消息内容就完成消费。如果在消费过程中抛出任何异常,该条消息将会在一定间隔后重新尝试提交给MessageListener消费。在多次消费失败的情况下,该消息将会存储到消费者应用的本次磁盘,并在后台自动恢复重试消费。

细心的你一定还注意到,在调用subscribe之后,我们还调用了completeSubscribe方法来完成订阅过程。请注意,subscribe仅是将订阅信息保存在本地,并没有实际跟meta服务器交互,要使得订阅关系生效必须调用一次completeSubscribe,completeSubscribe仅能被调用一次,多次调用将抛出异常。 为什么需要completeSubscribe方法呢,原因有二:

  • 首先,subscribe方法可以被调用多次,也就是一个消费者可以消费多种topic

  • 其次,如果每次调用subscribe都跟zk和meta服务器交互一次,代价太高

因此completeSubscribe一次性将所有订阅的topic生效,并处理跟zk和meta服务器交互的所有过程。

注意:③MessageConsumer也是线程安全的,创建的代价不低,因此也应该尽量复用。

二、消息

一、属性

        MetaQ的消息在Java客户端里是com.taobao.metamorphosis.Message类,它主要包括这么几个属性:

  • id 一个64位的消息ID,它在同一个MetaQ集群内唯一并且单调递增。也就是说在同一个线程内发送的消息,他们的消息id是顺序递增的。这个id不可设置,它是在服务端帮你自动生成,你只能读取这个id,通过getId方法。通常,我们可以利用这个唯一的id来做消息去重。

  • topic 也就是消息的主题,如果以我们收看电视为例,就是所谓频道,比如CCAV。

  • partition,消息所在的分区,同一个topic还根据配置分为不同的分区,你可以将topic理解为一个上层目录,而分区就是这个目录里的子目录(实际实现也是类似)。发送的消息将存储到这个topic下的某个分区,要查询消息的存储分区,就要通过getPartition方法,返回的分区对象属于com.taobao.metamorphosis.cluster.Partition类,它包含了消息所在的broker id以及具体的分区号码.

  • data 消息的载体,也就是消息的具体内容,同样以收看电视为例,就是频道的节目内容,以邮递为例,就是邮递的东西。

  • attribute 消息的附加属性,一个字符串,可有可无。例如电视可能是高清频道,也可能是3D频道,3D或者高清就是个频道的属性,但不是必须的。你可以通过hasAttribute方法来检测消息是否有属性。

  • readOnly 消息是否为只读,可将消息设置为只读状态(通过setReadOnly(true)),只允许读,不可以更改。但是只读属性只是临时的,你在发送的时候将消息设置为只读,消费者接收到的消息仍然是一个可变的消息,也就是说这个状态不会被服务端持久化(persistence).

  • rollback 消息是否回滚。这只对消费者有意义,消费者可以主动回滚一个消息,通过setRollbackOnly(true)。那么这个消息将会被重新消费。这个状态也是临时的,下次消费这条消息的时候,它仍然为false。比喻来说,这类似电视频道的重放功能,你想将当前播放的节目回滚保存,因为现在你不想看,待会你想重新从这个节目开始看起。(将在消息的消费者一节介绍

这些属性一般都有相应的getter/setter方法用于获取和设置。

我们通过一些例子来熟悉这些属性。

二、属性使用实例:

        1、创建消息:

  topic  ;   [] data  getBytes();   attribute  ;   msg   (topic, data, attribute); outprintln(msghasAttribute());

    data是作为消息载体就是一个byte数组,你可以将你的消息内容使用任何序列化方式序列化为byte数组,比如最常见的Java序列化。 topic就是一个字符串,你想发送的topic必须在Broker端配置后才可以发送。如何发送任意Topic请看FAQ一节。

    消息属性只是一个简单的字符串,通常你可以利用消息属性来传递一些额外信息给生产者或者消费者,用来做分区选择或者消息过滤。

        2、设置只读消息

msgsetReadOnly(); outprintln(msgisReadOnly()); msgsetTopic(newTopic);  msgsetReadOnly();
msgsetTopic(newTopic);

将消息设置为只读后,再尝试修改消息的topic或者data,都会抛出IllegalStateException异常。一般你会将消息设置为只读来防止消息的意外修改。

        3、发送后获取id和分区

 rt  producersend(msg);  id  msggetId();  part  msggetPartition();  brokerId  partgetBrokerId();  partition  partgetPartition();

发送后,MessageProducer会自动地将服务端返回的id和分区信息填入message。

三、客户端配置

        在创建生产者或者消费者之前,我们需要配置客户端,这都是通过com.taobao.metamorphosis.client.MetaClientConfig类。

1、属性

这个类最重要的几个属性如下:

  • zkConfig,zookeeper配置。到这里你应该清楚MetaQ是通过zookeeper来做服务的路由和查找。服务端将自己注册到zookeeper,而客户端连接到zookeeper,查找到服务端的信息,然后再跟服务端打交道(生产或者消费)。比喻来说,zookeeper类似电话本,你将信息写到电话本,下次无论什么时候都可以根据姓名找到你想找的电话号码。详细的ZkConfig配置参见下一小节。

  • recoverThreadCount,客户端后台恢复线程数,默认CPUS个。当一条消息重复消费多次还是失败,那么根据你配置的策略,这条消息可能被丢弃,也可能存入客户端的本地磁盘做恢复重试,这个参数设置恢复线程数。

  • recoverMessageIntervalInMills, 客户端后台恢复线程的恢复间隔,默认5分钟,单位毫秒。

  • 其他一些属性你基本不会用到。但是某些情况下,你可能想直接连接某个服务器,那么可以设置serverUrl这个属性,比如设置为meta://10.100.100.25:8120,这种情况下创建的客户端将只连接这台服务器。通常用于调试,测试或者监控。

这些属性都有相应的getter/setter方法来设置。

2、zkConfig配置

zkConfig是com.taobao.metamorphosis.utils.ZkUtils.ZKConfig类,它包括6个属性:

  • zkRoot, MetaQ服务端集群的根path,必须跟服务端配置保持一致,这就类似电话本里的姓名,姓名不对,你就找不到正确的电话号码。zkRoot不对,你也找不到正确的MetaQ服务端集群。

  • zkConnect, zookeeper集群地址列表,形如host:port,host:port的字符串。

  • zkSessionTimeoutMs, zookeeper客户端连接的最大超时时间,默认为30秒,单位毫秒,具体请看zookeeper关于session timeout的文档。

  • zkConnectionTimeoutMs, 连接zk集群的最大超时时间,默认也是30秒,单位毫秒。

  • zkSyncTimeMs, zookeeper集群内的数据同步最大延迟,这个需要你去测量和估计,默认是5秒,单位毫秒。消费者在负载均衡失败后,会至少等待这么长时间再尝试。

代码:

  metaClientConfig   ();           zkConfig   ();
zkConfigzkConnect  ;
zkConfigzkRoot  ;
metaClientConfigsetZkConfig(zkConfig);

四、MessageSessionFactory

        除非你拥有多个MetaQ集群,否则在你的应用里一般也只会拥有一个单例的消息会话工厂。

        1、除了可以创建生产者和消费者,还能获取broker统计信息

        MessageSessionFactory除了一些createXXXX的方法用于创建producer,consumer或者topic browser之外,还有一个方法getStats用于获取服务端的统计信息。getStats有几个重载方法,可以获取所有已链接broker或者特定broker的统计信息(请注意,必须是已经链接的broker的信息才能获取,因此要调用getStats方法,通常必须在创建producer,consumer之后)。

     stats  sessionFactorygetStats();

返回的map的key是broker服务器socket地址,而StatsResult就是具体的统计信息。

当然,通常来说你不会使用这个方法,直接telnet到服务器,执行stats命令是更便捷的获取统计信息的方法。

        注意:创建的消息会话工厂,必须明确地调用shutdown方法才能关闭,否则会有资源泄漏的隐患。如果你使用Spring框架,可以设置destroy-method来关闭;如果你是在Web容器比如tomcat里,你可以写一个ServletContextListener来关闭全局的会话工厂。

你可能感兴趣的:(MetaQ 实例之一)