Redis實現消息隊列

基於Redis消息隊列-實現短信服務化

1.Redis實現消息隊列原理

常用的消息隊列有RabbitMQ,ActiveMQ,個人覺得這種消息隊列太大太重,本文介紹下基於Redis的輕量級消息隊列服務。 
一般來說,消息隊列有兩種模式,一種是發佈者訂閱模式,另外一種是生產者和消費者模式。Redis的消息隊列,也是基於這2種原理的實現。 
發佈者和訂閱者模式:發佈者發送消息到隊列,每個訂閱者都能收到一樣的消息。 
生產者和消費者模式:生產者將消息放入隊列,多個消費者共同監聽,誰先搶到資源,誰就從隊列中取走消息去處理。注意,每個消息只能最多被一個消費者接收。

2.Redis消息隊列使用場景

在我們的項目中,使用消息隊列來實現短信的服務化,任何需要發送短信的模塊,都可以直接調用短信服務來完成短信的發送。比如用戶系統登錄註冊短信,訂單系統的下單成功的短信等。

3.SpringMVC中實現Redis消息隊列

因爲我們短信只需要發送一次,所以我們使用的是消息隊列的生產者和消費者模式。

3.1引入Maven依賴

引入Redis相應的maven依賴,這裏需要spring-data-redis和jedis

    //pom.xml
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-redis</artifactId>
        <version>1.6.0.RELEASE</version>
    </dependency>

      <!-- jedis -->
      <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
             <version>2.5.1</version>
        </dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

3.2配置redis生成者消費者模式

    //applicationContext-redis.xml
    <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:cache="http://www.springframework.org/schema/cache"
    xmlns:redis="http://www.springframework.org/schema/redis"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
                            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-34.0.xsd     
                            http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
                            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
                            http://www.springframework.org/schema/aop  http://www.springframework.org/schema/aop/spring-aop.xsd
                            http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-4.0.xsd
                            http://www.springframework.org/schema/redis http://www.springframework.org/schema/redis/spring-redis-1.0.xsd">
    <description>spring-data-redis配置</description>

    <bean id="redisConnectionFactory"
        class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="hostName" value="${redis.host}"></property>
        <property name="port" value="${redis.port}"></property>
        <property name="usePool" value="true"></property>
    </bean>

    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
        <property name="connectionFactory" ref="redisConnectionFactory"></property>
    </bean>

    <bean id="jdkSerializer"
        class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />

    <bean id="smsMessageListener"
        class="org.springframework.data.redis.listener.adapter.MessageListenerAdapter">
        <property name="delegate" ref="smsMessageDelegateListener" />
        <property name="serializer" ref="jdkSerializer" />
    </bean>

    <bean id="sendMessage" class="com.djt.common.cache.redis.queue.SendMessage">
        <property name="redisTemplate" ref="redisTemplate"/>
    </bean>

    <redis:listener-container>
        <redis:listener ref="smsMessageListener" method="handleMessage"
            serializer="jdkSerializer" topic="sms_queue_web_online" />
    </redis:listener-container>

    <!-- jedis -->
    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxIdle" value="300" /> <!-- 最大能夠保持idel狀態的對象數  -->
        <property name="maxTotal" value="60000" /> <!-- 最大分配的對象數 -->
        <property name="testOnBorrow" value="true" /> <!-- 當調用borrow Object方法時,是否進行有效性檢查 -->
    </bean>

    <bean id="jedisPool" class="redis.clients.jedis.JedisPool">
        <constructor-arg index="0" ref="jedisPoolConfig" />
        <constructor-arg index="1" value="${redis.host}" />
        <constructor-arg index="2" value="${redis.port}" type="int" />
    </bean>

</beans>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60

主要的配置說明: 
1.序列化:一般我們向Redis發送一個消息定義的Java對象,這個對象需要序列化。這裏使用JdkSerializationRedisSerializer:

<bean id="jdkSerializer" class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
  • 1

2.發送者:

<bean id="sendMessage" class="com.djt.common.cache.redis.queue.SendMessage">
        <property name="redisTemplate" ref="redisTemplate"/>
    </bean>
  • 1
  • 2
  • 3

3.監聽者:

    <bean id="smsMessageListener"
        class="org.springframework.data.redis.listener.adapter.MessageListenerAdapter">
        <property name="delegate" ref="smsMessageDelegateListener" />
        <property name="serializer" ref="jdkSerializer" />
    </bean>
    <redis:listener-container>
        <redis:listener ref="smsMessageListener" method="handleMessage"
            serializer="jdkSerializer" topic="sms_queue_web_online" />
    </redis:listener-container>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

smsMessageListener:消息監聽器 
redis:listener-container:定義消息監聽,method:監聽消息執行的方法,serializer:序列化,topic:監聽主題(可以理解爲隊列名稱)

3.3代碼實現

1.定義短信消息對象SmsMessageVo

public class SmsMessageVo implements Serializable {
    //id
    private Integer smsId;

    //手機號
    private String mobile;

    //類型,1:驗證碼 2:訂單通知
    private Byte type;

    //短信創建時間
    private Date createDate;

    //短信消息處理時間
    private Date processTime;

    //短信狀態,1:未發送 2:發送成功 3:發送失敗
    private Byte status;

    //短信內容
    private String content;

    //省略setter和getter方法
    ...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

2.定義消息隊列發送對象SendMessage

//SendMessage.java
public class SendMessage {

    private RedisTemplate<String, Object> redisTemplate;


    public RedisTemplate<String, Object> getRedisTemplate() {
        return redisTemplate;
    }



    public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }



    public void sendMessage(String channel, Serializable message) {
        redisTemplate.convertAndSend(channel, message);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

3.發送消息

    String smsContent = templateToContent(template.getContent(),
                    regMsgCode);
    SmsMessageVo smsMessageVo = new SmsMessageVo();
    smsMessageVo.setMobile(mobile);
    smsMessageVo.setType((byte) SmsType.VERIFICATION.getType());
    smsMessageVo.setChannelId(1);
    smsMessageVo.setContent(smsContent);
    smsMessageVo.setCreateDate(new Date());
    smsMessageVo.setStatus((byte) SmsSendStatus.TO_SEND.getType());
    smsMessageVo.setTemplateId(1);

    //先把待發送的短信存入數據庫
    SmsQueue smsQueue = new SmsQueue();
    BeanUtils.copyProperties(smsQueue, smsMessageVo);
    smsQueueService.addSmsQueue(smsQueue);

    //異步發送短信到redis隊列
    sendMessage.sendMessage(Constants.REDIS_QUEUE_SMS_WEB, smsMessageVo);
    //Constants.REDIS_QUEUE_SMS_WEB = "sms_queue_web_online",和applicationContext-redis中topic配置一樣

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

4.監聽消息

//SmsMessageDelegateListener.java
@Component("smsMessageDelegateListener")
public class SmsMessageDelegateListener {

    @Autowired
    private SmsQueueService smsQueueService;

    //監聽Redis消息
    public void handleMessage(Serializable message){
        if(message instanceof SmsMessageVo){
            SmsMessageVo messageVo = (SmsMessageVo) message;

            //發送短信
            SmsSender smsSender = SmsSenderFactory.buildEMaySender();
            smsSender.setMobile(messageVo.getMobile());
            smsSender.setContent(messageVo.getContent());
            boolean sendSucc = false;
            //判斷短信類型
            //驗證碼短信
            if(messageVo.getType() == (byte)SmsType.VERIFICATION.getType()){
                sendSucc = smsSender.send();
            }


            if(!sendSucc){
                return;
            }

            // 異步更新短信表狀態爲發送成功
            final Integer smsId = messageVo.getSmsId();
            Executor executor = Executors.newSingleThreadExecutor();
            executor.execute(new Runnable() {
                public void run() {
                    SmsQueue smsQueue = new SmsQueue();
                    smsQueue.setSmsId(smsId);
                    smsQueue.setStatus((byte)SmsSendStatus.SEND.getType());
                    smsQueue.setProcessTime(new Date());
                    smsQueueService.updateSmsQueue(smsQueue);
                }
            });

        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

4.總結

下面使用一張流程圖,來總結Redis消息隊列和短信服務。 
這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章