适用场景

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

  

redis订阅发布简单实现的更多相关文章

  1. ServiceStack.Redis订阅发布服务的调用(Z)

      1.Redis订阅发布介绍Redis订阅发布是一种消息通信模式:发布者(publisher)发送消息,订阅者(Subscriber)接受消息.类似于设计模式中的观察者模式.发布者和订阅者之间使用频 ...

  2. ServiceStack.Redis订阅发布服务的调用

    1.Redis订阅发布介绍 Redis订阅发布是一种消息通信模式:发布者(publisher)发送消息,订阅者(Subscriber)接受消息.类似于设计模式中的观察者模式. 发布者和订阅者之间使用频 ...

  3. Redis 订阅发布 - Jedis实现

    Redis 订阅发布 - Jedis实现 我想到使用Redis的订阅发布模式是用来解决推送问题的-. 对于概念性的叙述,多多少少还是要提一下的: ​ 什么是Redis发布订阅?Redis发布订阅是一种 ...

  4. ASP.NET MVC 学习笔记-2.Razor语法 ASP.NET MVC 学习笔记-1.ASP.NET MVC 基础 反射的具体应用 策略模式的具体应用 责任链模式的具体应用 ServiceStack.Redis订阅发布服务的调用 C#读取XML文件的基类实现

    ASP.NET MVC 学习笔记-2.Razor语法   1.         表达式 表达式必须跟在“@”符号之后, 2.         代码块 代码块必须位于“@{}”中,并且每行代码必须以“: ...

  5. 责任链模式的具体应用 ServiceStack.Redis订阅发布服务的调用

    责任链模式的具体应用   1.业务场景 生产车间中使用的条码扫描,往往一把扫描枪需要扫描不同的条码来处理不同的业务逻辑,比如,扫描投入料工位条码.扫描投入料条码.扫描产出工装条码等,每种类型的条码位数 ...

  6. python 实现redis订阅发布功能

    redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合).zset(sorted set ...

  7. 2016022611 - redis订阅发布命令集合

    redis消息订阅发布命令 参考地址:http://www.yiibai.com/redis/redis_pub_sub.html 消息发送者发送消息,通过redis的channal,消息接收者获取消 ...

  8. Linux--6 redis订阅发布、持久化、集群cluster、nginx入门

    一.redis发布订阅 Redis 通过 PUBLISH .SUBSCRIBE 等命令实现了订阅与发布模式. 其实从Pub/Sub的机制来看,它更像是一个广播系统,多个Subscriber可以订阅多个 ...

  9. redis 订阅&发布(转载)

    https://segmentfault.com/a/1190000016898228?utm_source=coffeephp.com 方法一: redis_helper.py: 封装发布订阅方法 ...

随机推荐

  1. Python--day28--set去重

    set去重:set依赖对象hash eq

  2. gradle 生成 pom,引用mybatis-plus源代码到自己的工程中

    一 前情概要 自己的maven工程使用mybatis-plus,然后想用热部署加载mapping文件.经过各种探索之后实现了,但是修改了xml文件后,就不断在控制台提示“mapper xxx is i ...

  3. java.lang.ClassCastException: com.sun.proxy.$Proxy6 cannot be cast to com.etc.service.serviceImpl.BankServiceImpl

    错误原因: java.lang.ClassCastException: com.sun.proxy.$Proxy6 cannot be cast to com.etc.service.serviceI ...

  4. H3C 多区域OSPF配置示例

  5. P1076 单词覆盖还原

    题目描述 一个长度为 \(l(3\le l\le 255)\) 的字符串中被反复贴有 boy 和 girl 两单词,后贴上的可能覆盖已贴上的单词(没有被覆盖的用句点表示),最终每个单词至少有一个字符没 ...

  6. 新手该如何学习JavaScript ?

    JavaScript入门 - 01 准备工作 在正式的学习JavaScript之前,我们先来学习一些小工具,帮助我们更好的学习和理解后面的内容. js代码位置 首先是如何编写JavaScript代码, ...

  7. 2018-8-10-win10-uwp-MVVM-语义耦合

    title author date CreateTime categories win10 uwp MVVM 语义耦合 lindexi 2018-08-10 19:16:53 +0800 2018-2 ...

  8. linux 运行处理者

    如同前面建议的, 当内核收到一个中断, 所有的注册的处理者被调用. 一个共享的处理者 必须能够在它需要的处理的中断和其他设备产生的中断之间区分. 使用 shared=1 选项来加载 short 安装了 ...

  9. Vue中的computed和watch

    看了网上很多资料,对vue的computed讲解自己看的都不是很清晰,今天忙里抽闲,和同事们又闲聊起来,对computed这个属性才有了一个稍微比较清晰的认识,下面的文章有一部分是转自: https: ...

  10. Java 学习笔记(9)——java常用类

    之前将Java的大部分语法都回顾完了,后面添加一些常见的操作,基础语法就结束了.至于在这里再次提到常用类是由于有一部分体现在使用它的继承类或者接口之类的.这些需要有面向对象编程的基础 Object类 ...