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

      

  • 相关阅读:
    Golang 单例模式 singleton pattern
    golang可见性规则(公有与私有,访问权限)
    golang init方法和main方法初始化顺序
    Golang的面向对象编程【结构体、方法、继承、接口】
    Convert Sorted List to Binary Search Tree——将链表转换为平衡二叉搜索树 &&convert-sorted-array-to-binary-search-tree——将数列转换为bst
    C/C++程序到内存分配(转)
    有关C/C++指针的经典面试题(转)
    C结构体之位域(位段)(转)
    Balanced Binary Tree——数是否是平衡,即任意节点左右字数高度差不超过1
    Path SumI、II——给出一个数,从根到子的和等于它
  • 原文地址:https://www.cnblogs.com/dmeck/p/11498964.html
Copyright © 2020-2023  润新知