Spring Redis與ActiveMQ發佈訂閱模式源碼分析

文章對SpringRedis和ActiveMQ的源碼及使用簡要分析一下,希望能起到拋磚引玉的作用。筆者之前在博客中寫了一篇關於生產消費者模式的一個工程使用案例,10W級數據更新操作__生產消費者模式。其實發布訂閱模式與生產消費者只是兩種模式上的不同,在業務處理邏輯上還是比較相似的。前者數據結構是一對多隊列模式,後者是點對點隊列模式。

Spring-Redis

Spring-Redis的隊列監聽容器爲org.springframework.data.redis.listener.RedisMessageListenerContainer,繼承關係如下。
SpringRedis監聽器

  • 如何注入的監聽?
    註冊監聽對象和Topic保存在Map中,注入的set方法創建MessageListener-Topic映射,決定哪個Topic由哪個MessageListener去處理。一個Listener可對應多個Topic.
    set方法

  • Topic如何創建
    Topic是個接口,有兩個實現類ChannelTopic和PatternTopic,一般Redis的發佈訂閱模式用ChannelTopic,只需要創建ChannelTopic注入到上述的Map即可。
    Topic創建

SpringIOC一般有4種注入方式,set方法、構造器、靜態工廠方法、實例工廠方法,常見的有set和構造器注入。如果採用註解的方法的話就只需要在屬性上註解即可,Spring會通過反射進行注入。而Xml的方法只能通過上述4種方法之一。
查看ChannelTopic的源碼,發現沒有set方法注入channelName,只有一個構造器方法。因此需要通過構造器方法注入這點要尤其注意。

ChannelTopic注入

  • MessageListener的創建和注入
    org.springframework.data.redis.connection.MessageListener接口的實現類如下,顯然這不是一個標準的JMS協議接口實現:
    這裏寫圖片描述

常見的是用MessageListenerAdapter,通過注入delegate來處理訂閱的消息。需要注入3個屬性,實現handleMessage(Object[] obj)方法的delegate實例、實現Message序列化的serializer屬性、實現topicName的stringSerializer屬性。
注意:兩個序列化方法要與Redis發佈消息所用的序列化對象一致,下文會講到。
delegate處理消息對象
delegate對象

  • 爲什麼delegate屬性需要實現handleMessage(Object[] obj)方法?
    反射調用的地方如下
    這裏寫圖片描述
    invoker實例初始化
    invoken

methoName的獲取,默認是handleMessage
這裏寫圖片描述
這裏寫圖片描述

注意:delegate也可以實現MessageListener接口,實現onMessage方法處理消息。

  • 序列化方法
    關於在訂閱消息在哪裏將Message反序列化成Object,可以看調用serializer屬性的源碼。

RedisTemplate 中的convertAndSend方法即是把message內容發佈到channel通道中。
這裏寫圖片描述

convertAndSend發佈時channel的序列化是採用stringSerializer,該屬性在RedisTemplate中已經初始化new了一個對象,因此默認不用注入該屬性。
這裏寫圖片描述

關於RedisTemplate 的一些屬性
這裏寫圖片描述
message(Value)的序列化是採用valueSerivalizer,默認爲null,需要入注入。也可以注入defaultSerializer,也可以不用注入,RedisTemplate在Spring初始化時已經new JdkSerializationRedisSerializer方法了。所以最好保證在訂閱方也採用這種方法序列化。

這裏寫圖片描述

  • SpringXml配置使用Redis發佈訂閱
<bean id="jdkSerializer" class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />

    <bean id="messageListener" class="org.springframework.data.redis.listener.adapter.MessageListenerAdapter">  
        <property name="delegate" ref="messageDelegateListener" />  
        <property name="serializer" ref="jdkSerializer" />  
 </bean>
<bean id="redisContainer" class="org.springframework.data.redis.listener.RedisMessageListenerContainer">
        <property name="connectionFactory" ref="jedisConnectFactory"/>
        <property name="messageListeners">
            <map>
                <entry key-ref="messageListener">
                    <bean class="org.springframework.data.redis.listener.ChannelTopic">
                        <constructor-arg value="java" />
                    </bean>
                </entry>
</map>
        </property>
</bean>  

ActiveMQ

ActiveMQ消息隊列有兩種模式,點對點隊列和一對多隊列即發佈訂閱,ActiveMQ是一種實現了標準的JMS協議的中間件。

  • 消息訂閱
    常用訂閱監聽容器類爲org.springframework.jms.listener.DefaultMessageListenerContainer 它實現的接口和繼承的類關係如下圖
    這裏寫圖片描述
    DefaultMessageListenerContainer容器主要維護一些關於消息隊列的配置和處理線程,消息處理類和序列化主要在其超類org.springframework.jms.listener.AbstractMessageListenerContainer中進行定義。主要有兩個參數destination和messageListener,前者是JMS目的地也可以理解爲信息的來源(隊列的實例),後者是處理消息的類,這兩個參數分別需要實現javax.jms.Topic/javax.jms.Queue接口、javax.jms.MessageListener/org.springframework.jms.listener.SessionAwareMessageListener接口。

這裏寫圖片描述

  • 在ActiveMQ中,隊列的JMS接口都已經有實現,所以大部分情況直接拿來就用,注入隊列的名字即可。
    Topic的實現類爲org.apache.activemq.command.ActiveMQTopic
    Queue的實現類爲org.apache.activemq.command.ActiveMQQueue

javax.jms.MessageListenerorg.springframework.jms.listener.SessionAwareMessageListener作爲回調接口,則需要自己根據具體的業務邏輯去實現。當然也可選擇其中的一個實現類org.springframework.jms.listener.adapter.MessageListenerAdapter去實現自己的業務邏輯,這個類可參考上述Redis發佈訂閱模式,MessageListenerAdapter中有諸多默認的屬性如下,不過這個使用起來比較麻煩,需要設置destinationName或其它,不建議使用。
這裏寫圖片描述

  • 消息發佈
    org.springframework.jms.core.JmsTemplate的send方法。配置如下兩個方法即可連接到ActiveMQ註冊地址。
    這裏寫圖片描述

  • JmsTemplate的繼承實現關係
    這裏寫圖片描述

  • ActiveMQ的SpringXml配置使用

<!-- 連接工廠 -->
    <bean id="activeMqConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
        <property name="brokerURL" value="${AvtiveMQ.brokerURL}"/>
    </bean>
    <bean id="connectionFactory" class="org.springframework.jms.connection.CachingConnectionFactory">
        <property name="targetConnectionFactory" ref="activeMqConnectionFactory"/>
        <property name="sessionCacheSize" value="100"/>
    </bean>
<!-- 一對多隊列,即發佈訂閱者模式 -->
    <bean id="defaultTopicDestination" class="org.apache.activemq.command.ActiveMQTopic">
        <constructor-arg index="0" value="com.topic.default"/>
    </bean>

    <!-- 消費者 -->
    <bean id="defaultMessageQueueListener" class="com.zheng.cms.job.jms.DefaultMessageQueueListener"/>
    <bean id="jmsContainer"  class="org.springframework.jms.listener.DefaultMessageListenerContainer">
        <property name="connectionFactory" ref="connectionFactory"/>
        <property name="destination" ref="defaultTopicDestination"/>
        <property name="messageListener" ref="defaultMessageQueueListener"/>
        <property name="sessionTransacted" value="true"/>
    </bean>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章