起底SpringAmqp

1.背景

Spring-amqp是Spring为了方便开发RabbitMQ程序而做的一个第三方类库。Amqp全名Advanced Message Queuing Protocol,即高级消息队列协议。旨在提供一种跨平台跨语言的消息服务,AMQP是一种应用层协议,不同于JMS(API接口),目前实现AMQP的开源项目包括RabbitMQ, Aman Gupta等,RabbitMQ是应用最广泛的AMQP的实现,使用erlang开发使其具有了较高的读写性能。RabbitMQ官方提供了Java,.NET,erlang版本的客户端,提供了基本的消息操作实现。

2.相关概念

AMQP中的基本概念:

Queue:队列,可以看做消息发送的目的地,接受消息的必备参数。若无consumer接受,将会一直存在服务器内存中。

Exchange:交换机,发送消息必须指定的参数,一般需要和routingkey组合路由到指定的队列中去。

ExchangeType:交换机类型,有四种:direct(严格按照key寻找queue),fanout(不需要key只要是绑定到该交换机的queue都能收到消息),topic(正则匹配key寻找queue),system(不常用),自定义(不常用)

RoutingKey:路由键,配合Exchange确定发送消息的目标队列,在Fanout类型的Exchange中忽略此参数。

Binding:绑定关系,将交换机与队列绑定,依据交换机类型确定不同参数。

Virtual hosts:虚拟机,感觉像是命名空间的概念,为了区分不同类别的资源

关系如下图所示:

 

3.Srping的封装

Spring对RabbitMQ的封装分成两个部分,一部分专门做接口,对应AMQP,项目名称也是spring-amqp,另一部分针对RabbitMQ做了具体的实现,项目名称是spring-rabbit。这一点也是我们要在项目规划中要注意和学习的,把变的部分(具体实现)和不变的部分(接口、协议、规范)区分开来,分别发展,提高隔离性。

Spring的封装增强了以下几点能力:

1)         channel做了缓存

这部分主要依赖CachingConnectionFactory实现。其中的private Channel getChannel(boolean transactional)方法,主要是首先判断是否是事务的,这里Spring把事务的和非事务的channel分别存在不同的LinkedList中,然后判断是否有缓存的channel,有则取之,无则创建新的。

Spring在创建的时候运用了代理,生成一个Channel的代理,然后在这个代理中规范了几个方法执行时的特殊处理方法,这一点非常赞的,在不影响原来的标准Channel类的情况下,接管了其部分方法,具体如何实现缓存的呢?仔细看源码,发现在执行close方法时,会判断一下当前缓存的个数是否超出设置的最大值,如果没有,则执行一个logicalClose方法,这个方法就是实现缓存的关键了。源码摘录如下:

获取channel方法:

private Channel getChannel(boolean transactional) {

                   LinkedList<ChannelProxy> channelList = transactional ? this.cachedChannelsTransactional

                                     : this.cachedChannelsNonTransactional;

                   Channel channel = null;

                   synchronized (channelList) {

                            if (!channelList.isEmpty()) {

                                     channel = channelList.removeFirst();

                            }

                   }

                   if (channel != null) {

                            if (logger.isTraceEnabled()) {

                                     logger.trace("Found cached Rabbit Channel");

                            }

                   } else {

                            channel = getCachedChannelProxy(channelList, transactional);//发现没有缓存,直接新建一个channel

                   }

                   return channel;

         }

 

private ChannelProxy getCachedChannelProxy(LinkedList<ChannelProxy> channelList, boolean transactional) {

                   Channel targetChannel = createBareChannel(transactional);

                   if (logger.isDebugEnabled()) {

                            logger.debug("Creating cached Rabbit Channel from " + targetChannel);

                   }

                   getChannelListener().onCreate(targetChannel, transactional);

                   return (ChannelProxy) Proxy.newProxyInstance(ChannelProxy.class.getClassLoader(),

                                     new Class[] { ChannelProxy.class }, new CachedChannelInvocationHandler(targetChannel, channelList,

                                                        transactional));//精彩之笔,代理扩展channel行为

         }

 

 

代理中的handle方法:

if (methodName.equals("close")) {

                                     // Handle close method: don't pass the call on.

                                     if (active) {

                                               synchronized (this.channelList) {

                                                        if (this.channelList.size() < getChannelCacheSize()) {

                                                                 logicalClose((ChannelProxy) proxy);//逻辑上关闭channel,实际上可以理解为归还channel

                                                                 // Remain open in the channel list.

                                                                 return null;

                                                        }

                                               }

                                     }

 

private void logicalClose(ChannelProxy proxy) throws Exception {

                            if (this.target != null && !this.target.isOpen()) {

                                     synchronized (targetMonitor) {

                                               if (this.target != null && !this.target.isOpen()) {

                                                        this.target = null;

                                                        return;

                                               }

                                     }

                            }

                            // Allow for multiple close calls...

                            if (!this.channelList.contains(proxy)) {

                                     if (logger.isTraceEnabled()) {

                                               logger.trace("Returning cached Channel: " + this.target);

                                     }

                                     this.channelList.addLast(proxy);//归还Channel

                            }

                   }

 

 

总结:Java的代理用对地方将会提高Java的动态性,使程序的扩展性大大提高,缓存可以有效解决创建耗时耗资源对象的性能问题。

 

2)         可以添加Connection和Channel的监听器

AbstractConnectionFactory中加入了如下方法:

         public void setConnectionListeners(List<? extends ConnectionListener> listeners) {

                   this.connectionListener.setDelegates(listeners);

         }

 

         public void addConnectionListener(ConnectionListener listener) {

                   this.connectionListener.addDelegate(listener);

         }

 

         public void setChannelListeners(List<? extends ChannelListener> listeners) {

                   this.channelListener.setDelegates(listeners);

         }

 

         public void addChannelListener(ChannelListener listener) {

                   this.channelListener.addDelegate(listener);

         }

分别可以设置Connection和Channel的打开、关闭、创建的监听器,通过设置监听器,可以在适当的时机加入一些自己想做的事情。

 

3)         自动接收消息,接收消息模仿了JMS的事件的方式

Spring提供了MessageListener接口来定义了收到消息执行的方法onMessage,这点非常像JMS,可能是为了讨好JMS程序员吧^-^。我本人非常喜欢这种事件驱动的设计方式,感觉非常符合事物发展的规律,凡是有因果。下面来看具体的执行原理,最终实现类SimpleMessageListenerContainer,抽象类AbstractMessageListenerContainer,实现了SmartLifecycle接口,而SmartLifecycle继承了Spring标准的Lifecycle接口,即Spring容器启动的时候会自动执行SimpleMessageListenerContainer的start方法,类间的关系如图:

 

下面看SimpleMessageListenerContainer中的start方法,执行doStart方法:

protected void doStart() throws Exception {

                   super.doStart();

                   synchronized (this.consumersMonitor) {

                            initializeConsumers();

                            if (this.consumers == null) {

                                     logger.info("Consumers were initialized and then cleared (presumably the container was stopped concurrently)");

                                     return;

                            }

                            Set<AsyncMessageProcessingConsumer> processors = new HashSet<AsyncMessageProcessingConsumer>();

                            for (BlockingQueueConsumer consumer : this.consumers) {

                                     AsyncMessageProcessingConsumer processor = new AsyncMessageProcessingConsumer(consumer);//最终执行类

                                     processors.add(processor);

                                     this.taskExecutor.execute(processor);

                            }

                            for (AsyncMessageProcessingConsumer processor : processors) {

                                     FatalListenerStartupException startupException = processor.getStartupException();

                                     if (startupException != null) {

                                               throw new AmqpIllegalStateException("Fatal exception on listener startup", startupException);

                                     }

                            }

                   }

         }

 

AsyncMessageProcessingConsumer中的run方法:

 

while (isActive() || continuable) {//实现无限接受消息

                                               try {

                                                        // Will come back false when the queue is drained

                                                        continuable = receiveAndExecute(consumer) && !isChannelTransacted();

                                               } catch (ListenerExecutionFailedException ex) {

                                                        // Continue to process, otherwise re-throw

                                               }

                                     }

 

private boolean doReceiveAndExecute(BlockingQueueConsumer consumer) throws Throwable {

 

                   Channel channel = consumer.getChannel();

 

                   for (int i = 0; i < txSize; i++) {

 

                            logger.trace("Waiting for message from consumer.");

                            Message message = consumer.nextMessage(receiveTimeout);//如果没有消息,会在这里阻塞,否则接受新消息继续进行

                            if (message == null) {

                                     break;

                            }

                            try {

                                     executeListener(channel, message);//执行MessageListener的onMessage方法。

                            } catch (ImmediateAcknowledgeAmqpException e) {

                                     break;

                            } catch (Throwable ex) {

                                     consumer.rollbackOnExceptionIfNecessary(ex);

                                     throw ex;

                            }

 

                   }

 

此外,SimpleMessageListenerContainer还提供了shutdown方法,如果需要停止接收信息,即可调用此方法。

这里面还有一个比较有意思的地方SimpleMessageListenerContainer中的私有类AsyncMessageProcessingConsumer中有一个容量为1的CountDownLatch锁,这主要是为了实现一个AsyncMessageProcessingConsumer实例仅执行一次

时序图如下:

 

总结:用一个阻塞队列来存储接受的信息可以保证读取消息的一致性,巧妙利用了LifeCycle接口的start,实现自动开始监听消息。

 

4)         最大的亮点,增加了转换器的概念,提供了内置的JSON,Object,Simple转换器

这点是Spring创新的最大亮点,可以自由转换消息的格式,通过RabbitTemplate的convertAndSend方法来实现消息的封装发送,receiveAndConvert来实现消息的接受和封装,所有的转换器都实现MessageConverter接口,如下方法:

Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException;

Object fromMessage(Message message) throws MessageConversionException;

toMessage是在发送消息前封装消息的方法,fromMessage是在接受消息后封装消息的方法。

已有的实现JsonMessageConverter是专门封装Json对象的,Spring用了jackson来实现解析json。SimpleMessageConverter是封装普通文本或者java对象的实现。SerializerMessageConverter是专门实现Java对象的封装。

最关键的是你可以实现自己的messageConver,根据自己的需求来from和to。这一点是非常值得推荐的。

 

5)         实现了队列、交换机和绑定的自动创建。

一般来说在配置文件中如果定义了Queue,Exchange,Binding对象的实例,Spring会自动创建这些定义,但前提是一定要定义一个RabbitAdmin实例。这里面有什么猫腻呢?通过源码可以看到RabbitAdmin实现了InitializingBean接口,这是Spring的标准接口,意义是在该bean的属性设置完以后立即执行,继续看源码,发现这个方法里添加了一个ConnectionListener,在Connetion创建完成后,寻找Spring中定义的所有Queue,Exchange,Binding类型的Bean,并利用rabbitTemplate的execute回调方法创建Queue,Exchange,Binding。

时序图如下:

 

6)         提供了一个log4J版本的Appender,可以实现分布式日志服务

  如果我们的应用实现了集群,要查某条日志的时候不知道这条日志会输出在哪台机器上,如果集群的数量非常大的话这将是一场灾难,所以实现日志统一输出成为了必要,利用Springamqp提供的Appender可以轻松实现所有集群节点的日志有序的输出在同一目的地。

  实现类是org.springframework.amqp.rabbit.log4j. AmqpAppender,主要原理就是执行日志记录时会实现public void append(LoggingEvent event)方法,然后会启动多个线程专门扫描接受消息的queue的内容,有则取出,封装并发送至目标队列。具体地,首先执行startSenders方法,创建一个缓冲式线程池,根据设置的缓冲大小提交一系列EventSender任务,EventSender的run方法中会取出events的元素,events是一个阻塞队列当为空时会发生阻塞,然后封装这个取出的event对象,并发送。

总结:良好的运用了线程池的概念,多线程监控同一阻塞队列,阻塞队列能保证数据的一致性,多线程能提高吞吐效率。

 

 

总结

Spring提供了操作RabbitMQ的一种解决方案,不但兼容了RabbitMQ的标准操作,还创造性的增加了几个新特性,其中包含了很多在软件开发过程中可以借鉴的编程思想,设计方法。

posted @ 2013-01-13 12:10  琥珀光  阅读(1546)  评论(0编辑  收藏  举报