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;
    }
}

  

猜你喜欢

转载自www.cnblogs.com/dmeck/p/11498964.html