redis訂閱發佈簡單實現

適用場景

  1. 業務流程遇到大量異步操作,並且業務不是很複雜
  2. 業務的健壯型要求不高
  3. 對即時場景要求不高

原理介紹

redis官網文檔:https://redis.io/topics/notifications#configuration

spring集成訂閱發佈:https://docs.spring.io/spring-data/redis/docs/1.7.1.RELEASE/reference/html/#redis:pubsub:subscribe

 

相關demo

業務發佈.java

 //        異步通知郵件
        String expiredEmail = RedisConstants.REDIS_EXPIRE_Email_Send.getExpired()+ uuid;
        ValueOperations<Serializable, Object> operations = redisTemplate3.opsForValue();
//由於使用的org.springframework.data.redis.core.StringRedisTemplate,所以value必須是String類型
        operations.set(expiredEmail, "1", email_expire_time, TimeUnit.SECONDS);

 

EmailSyncEventListener.java

package com.redis.listeners;


import com.carapi.services.order.FxjTCarInvoiceOrderService;
import com.common.constant.RedisConstants;
import com.exception.ErrorException;
import com.model.fxjTCarInvoiceOrder.FxjTCarInvoiceOrder;
import com.model.fxjTCarOrderList.FxjTCarOrderList;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;

import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * 郵件發送
 */
public class EmailSyncEventListener extends KeyExpirationEventMessageListener {
    private static final Logger log = LoggerFactory.getLogger(EmailSyncEventListener.class);
  //可以使用自動注入,或者xml配置
    public EmailSyncEventListener(RedisMessageListenerContainer listenerContainer) {
        super(listenerContainer);
    }

    @Autowired
    private FxjTCarInvoiceOrderService tCarInvoiceOrderService;
//    @Autowired
//    public EmailSyncEventListener(RedisMessageListenerContainer listenerContainer) {
//        super(listenerContainer);
//    }
    @Override
    public void onMessage(Message message, byte[] pattern) {

            String channel = new String(message.getChannel(), StandardCharsets.UTF_8);
            //過期的key
            String key = new String(message.getBody(),StandardCharsets.UTF_8);
            if(StringUtils.isNotEmpty(key) && key.indexOf(RedisConstants.REDIS_EXPIRE_Email_Send.getExpired()) != -1){
                System.out.println(key);
                key = key.substring(key.indexOf(RedisConstants.REDIS_EXPIRE_Email_Send.getExpired())+RedisConstants.REDIS_EXPIRE_Email_Send.getExpired().length());
                log.info(key);
                try {

                    FxjTCarInvoiceOrder invoiceOrder = tCarInvoiceOrderService.selectByPrimaryKey(key);
                    if(invoiceOrder!=null){

                       tCarInvoiceOrderService.resendEmail(invoiceOrder.getEmail(),invoiceOrder.getInvoiceReqSerialNo());
                   }
                } catch (ErrorException e) {
                    log.info("異步發送郵寄失敗,驗證失敗" );
                } catch (Exception e) {
                    log.info("異步發送郵件失敗");
                    e.printStackTrace();
                }
                log.info("異步發送郵寄成功");
                log.info("redis key 過期:pattern={},channel={},key={}",new String(pattern),channel,key);
            }
    }
}

  

spring-cache.xml

    <bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
        <property name="connectionFactory" ref="jedisConnFactory"></property>
        <property name="keySerializer">
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"></bean>
        </property>
        <property name="valueSerializer">
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"></bean>
        </property>
        <property name="hashKeySerializer">
            <bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
        </property>
        <property name="hashValueSerializer">
            <bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
        </property>
        <property name="stringSerializer">
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
        </property>
    </bean>




    <bean id="jedisConnFactory"
          class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="hostName" value="${redis.host}" />
        <property name="port" value="${redis.port}" />
        <property name="password" value="${redis.password}" />
        <property name="database" value="2" />
        <property name="timeout" value="${redis.timeout}" />
        <property name="poolConfig" ref="jedisPoolConfig" />
        <property name="usePool" value="true" />
    </bean>

    <!--去掉redis client的CONFIG-->
    <util:constant static-field="org.springframework.session.data.redis.config.ConfigureRedisAction.NO_OP"/>

    <bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
        <property name="maxInactiveIntervalInSeconds" value="3600" />
    </bean>




    <!-- 將監聽實現類註冊到spring容器中 -->
    <bean id="emailSyncEventListener" class="com.redis.listeners.EmailSyncEventListener">
        <constructor-arg ref="redisMessageListenerContainer"></constructor-arg>
    </bean>

 

 

出現的問題

  1. 用戶的session存入redis後,redis的負載不平衡,出現了ttl爲0的key刪除延遲較長

Redis 並不保證生存時間(TTL)變爲 0 的鍵會立即被刪除:如果程序沒有訪問這個過期鍵, 或者帶有生存時間的鍵非常多的話,那麼在鍵的生存時間變爲 0 , 直到鍵真正被刪除這中間,可能會有一段比較顯著的時間間隔。

因此,Redis 產生 expired 通知的時間爲過期鍵被刪除的時候,而不是鍵的生存時間變爲 0 的時候。如果 Redis 正確配置且負載合理的,延時不會超超過 1s。

RedisExpiredQuartz.java

package com.redis.quart;

import com.common.constant.RedisConstants;
import com.redis.listeners.EmailSyncEventListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;

import javax.annotation.Resource;
import java.util.Set;

/**
 * Redis 並不保證生存時間(TTL)變爲 0 的鍵會立即被刪除:
 * 如果程序沒有訪問這個過期鍵, 或者帶有生存時間的鍵非常多的話,
 * 那麼在鍵的生存時間變爲 0 ,
 * 直到鍵真正被刪除這中間,可能會有一段比較顯著的時間間隔。
 *
 * so加個定時器
 */
public class RedisExpiredQuartz {
    private static final Logger log = LoggerFactory.getLogger(EmailSyncEventListener.class);
    @Resource
    private RedisTemplate redisTemplate3;

    public synchronized  void onRedisExpiredQuartz(){
        log.trace("------------------------------------------");
        for (RedisConstants value : RedisConstants.values()) {
            Set keys = redisTemplate3.keys(value.getExpired() + "*");
            log.debug("業務需要正常通知的keys:{}",keys);
        }
    }
}

RedisConstants.java

package com.common.constant;

public enum  RedisConstants {
    /**
     * 月卡過期取消key前綴
     */
    REDIS_EXPIRE_Sub_Card("redisExpiredSubCard_"),
    /**
     * 延時郵件發送key前綴
     */
    REDIS_EXPIRE_Email_Send("redisExpiredEmail_Send_"),
    ;

    private String expired;

    RedisConstants(String expired) {
        this.expired = expired;
    }

    public String getExpired() {
        return expired;
    }

    public void setExpired(String expired) {
        this.expired = expired;
    }

    // 獲得 enum 對象
    public static RedisConstants get(String  expired) {
        for (RedisConstants item : values()) {
            if (expired == item.getExpired()) {
                return item;
            }
        }
        return null;
    }
}

  

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