笔者近期参与一个分析log的项目。主要流程是:读取Log文件,对每一行Text解析成对应的Object,解析器会将多个Object存放到一个List中并发送到ActiveMQ的Queue中,即Queue中的一个Message即应一个Objects List。然后数据处理thread会consume存放在Queue中的Message,并将处理的结果保存到db。
采用JMS来实现读取Log和分析Log之间的异步运行,使用ActiveMQ的可持久化的Queue,当Message被放进Queue中并持久化后,就会更新Log的读取进度,这样即使程序break down,也不会导致数据被漏掉。
使用Spring来配置JMS,则可以简化和方便JMS的使用,同时还可以使用到Transaction Management。
由于程序并不是很复杂,同时也不需要单独提供ActiveMQ server来运行,所以这里使用的是embed的方式。
总的来说,程序运行起来之后,将会启动以下几个Service:
1. JMS service,这个service会启动一个embed broker。
2. Data Reader & Parser service,这个service会引用一个JMS Message Provider,用于发送Message到Queue中。
3. 使用Message Listener Containers来监听Queue,并使用MDP(Message Drive POJO)的方法,来处理并消费掉Message。
本文主要说明如何通过Spring的配置来实现JMS和ActiveMQ的应用,对于这个程序的其他的代码不涉及。
第一步,JMS Service并start embed broker
public class JMSService extends AbstractService { private BrokerService broker; private String mqConfigFile = "xbean:activemq.xml"; public void start() throws Exception { if(broker == null) { broker = BrokerFactory.createBroker(new URI(mqConfigFile)); broker.start(); broker.waitUntilStarted(); } super.start(); } public void stop() throws Exception { if(broker != null && broker.isStarted()) { broker.stop(); broker.waitUntilStopped(); } super.stop(); } }
这里使用BrokerService broker = BrokerFactory.createBroker(new URI(someURI));来创建一个broker,关于someURI的配置,详见这里:http://activemq.apache.org/broker-configuration-uri.html
我这里的xbean:activemq.xml如下:
mw.properties
第二步,配置JMS ConnectionFactory和Destination
vm://localhost:61616 UserMessageQueue
第三步,配置JMS Template,以及Message converter
注意,这里使用了一个 SingleConnectionFactory,这是spring对ConnectionFactory的一个实现,这个实现会在调用createConnection的地方返回相同的Connection并且忽略所有的close()方法,这样多个JMSTemplate可以共用一个相同的connection,避免每次都重复创建connection造成资源的浪费。
并且,我没有配置MessageConverter,而且使用Spring自带的SimpleMessageConverter,这是默认选项,不配就是使用SimpleMessageConverter。
第四步,配置JMS Message producer
Message Producer通过JMS Template和destination来发送Message。下面是代码实现:
public class UserMessageProducer implements IMessageProducer { protected JmsTemplate template; protected Queue destination; public void setTemplate(JmsTemplate template) { this.template = template; } public void setDestination(Queue destination) { this.destination = destination; } public void send(Object object) { template.convertAndSend(this.destination, object); } }
因为我并没有为JMS template指定特定的MessageConverter,所以这里template.convertAndSend()应该就是SimpleMessageConverter.convertAndSend()来实现的。
第五步,配置JMS Message Consumer
我并没有在spring的配置文件单独配置一个Message Consumer,这是一个非常简单的POJO,这里只是为测试写的一个简单示例:只是简单把读到的Message打印出来。
public class UserMessageConsumer { public void printUser(User user) { user.getId(); user.getName(); System.out.println(user); } }
第六步,配置JMS Message Listener Container以及Listerer
这里使用org.springframework.jms.listener.adapter.MessageListenerAdapter来实现一个Listener,这样做的好处是,可以使用非常简单的POJO来作为一个consumer,就是上面的UserMessageConsumer一样。当Queue里有一条Message的时候,会使用UserMessageConsumer的printUser来消费掉这条Message。
Spring JMS提供3种ListenerContainer,最常使用的是DefaultMessageListenerContainer。这里把Listener注入,来实现对Message的Listener。
参考:
1. Chapter 19. JMS (Java Message Service)
2. ActiveMQ5.0实战三:使用Spring发送,消费topic和queue消息