一、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);
除非你拥有多个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
来关闭全局的会话工厂。