Redis有效时间设置以及时间过期处理操作

本文对redis的过期处理机制做个简单的概述,让大家有个基本的认识。

Redis中有个设置时间过期的功能,即对存储在redis数据库中的值可以设置一个过期时间。作为一个缓存数据库,这是非常实用的。如我们一般项目中的token或者一些登录信息,尤其是短信验证码都是有时间限制的,按照传统的数据库处理方式,一般都是自己判断过期,这样无疑会严重影响项目性能。

一、有效时间设置:

redis对存储值的过期处理实际上是针对该值的键(key)处理的,即时间的设置也是设置key的有效时间。Expires字典保存了所有键的过期时间,Expires也被称为过期字段。

四种处理策略

EXPIRE 将key的生存时间设置为ttl秒

PEXPIRE 将key的生成时间设置为ttl毫秒

EXPIREAT 将key的过期时间设置为timestamp所代表的的秒数的时间戳

PEXPIREAT 将key的过期时间设置为timestamp所代表的的毫秒数的时间戳

其实以上几种处理方式都是根据PEXPIREAT来实现的,设置生存时间的时候是redis内部计算好时间之后在内存处理的,最终的处理都会转向PEXPIREAT。

1、2两种方式是设置一个过期的时间段,就是咱们处理验证码最常用的策略,设置三分钟或五分钟后失效,把分钟数转换成秒或毫秒存储到redis中。

3、4两种方式是指定一个过期的时间 ,比如优惠券的过期时间是某年某月某日,只是单位不一样。

二、过期处理

过期键的处理就是把过期键删除,这里的操作主要是针对过期字段处理的。

Redis中有三种处理策略:定时删除、惰性删除和定期删除。

定时删除:在设置键的过期时间的时候创建一个定时器,当过期时间到的时候立马执行删除操作。不过这种处理方式是即时的,不管这个时间内有多少过期键,不管服务器现在的运行状况,都会立马执行,所以对CPU不是很友好。

惰性删除:惰性删除策略不会在键过期的时候立马删除,而是当外部指令获取这个键的时候才会主动删除。处理过程为:接收get执行、判断是否过期(这里按过期判断)、执行删除操作、返回nil(空)。

定期删除:定期删除是设置一个时间间隔,每个时间段都会检测是否有过期键,如果有执行删除操作。这个概念应该很好理解。

看完上面三种策略后可以得出以下结论:

4. 1、3为主动删除,2为被动删除。

5. 1是实时执行的,对CPU不是很友好,但是这在最大程度上释放了内存,所以这种方式算是一种内存优先优化策略。

6. 2、3为被动删除,所以过期键应该会存在一定的时间,这样就使得过期键不会被立马删除,仍然占用着内存。但是惰性删除的时候一般是单个删除,相对来说对CPU是友好的。

7. 定期键这种删除策略是一种让人很蛋疼的策略,它既有避免1、2两种策略劣势的可能,也有同时发生1、2两种策略劣势的可能。如果定期删除执行的过于频繁就可能会演变成定时删除,如果执行的过少就有可能造成过多过期键未被删除而占用过多内存,如果时间的设置不是太好,既可能占用过多内存又同时对CPU产生不好的影响。所以。使用定期删除的时候一定要把握好这个删除的时间点。存在即为合理,既然开发的时候有这种策略,就说明定期删除还是有他的优势的,具体大家可以自己琢磨。

三、主从服务器删除过期键处理

参考书上说的有三种:RDB持久化、AOF持久化和复制功能。

RDB:

1. 主服务器模式运行在载入RDB文件时,程序会检查文件中的键,只会加载未过期的,过期的会被忽略,所以RDB模式下过期键不会对主服务器产生影响。

2. 从服务器运行载入RDB文件时,会载入所有键,包括过期和未过期。当主服务器进行数据同步的时候,从服务器的数据会被清空,所以RDB文件的过期键一般不会对从服务器产生影响。

AOF:

AOF文件不会受过期键的影响。如果有过期键未被删除,会执行以下动作:

客户端请求时(过期键):

1.从数据库充删除被访问的过期键;

2.追加一条DEL 命令到AOF文件;

3.向执行请求的客户端回复nil(空)。

复制:

1.主服务器删除过期键之后,向从服务器发送一条DEL指令,告知删除该过期键。

2.从服务器接收到get指令的时候不会对过期键进行处理,只会当做未过期键一样返回。(为了保持主从服务器数据的一致性)

3.从服务器只有接到主服务器发送的DEL指令后才会删除过期键。

参考书籍:《Redis设计与实现》黄健宏著

补充知识:redis缓存数据需要指定缓存有效时间范围段的多个解决方案 Calendar+quartz

在实现积分项目业务中,对不同场景设置了不同的key-value缓存到了redis中。但是因为对不同业务的key需要缓存的时间不尽相同,这里自定义工具类来实现。

设置redis缓存key,截取部分代码:

try{
 //cacheManager就相当从redis链接池获取一个连接,具体工厂类获取在后面备注
 cacheManager = (RedisCacheManager) CacheManagerFactory.getCacheManager();
 totalMonCount = Float.parseFloat(cacheManager.getString(monthKey)) + centCount;
 if (centLimitByMonth != -1){
   if (totalMonCount > centLimitByMonth) {
      // 超出月上限不再累计
      logger.error("exceeds the month limit cents! [" + totalMonCount + "]  code:[" + code + "]");
      return null;
   }
}

//当未超出月额度,此时要对日额度进行判断;只需判断其是否超出日上限积分
if (dayKey != null){
  //累积积分;因为签到其实是没有每日积分的,是按次数规则累积的;但为了统一,直接用centCount代替(都是签一次1分)
  totalDayCount = Float.parseFloat(cacheManager.getString(dayKey)) + centCount;
  if (centLimitByDay != -1){
     if (totalDayCount > centLimitByDay){
      logger.info("[ERROR]teacher everyday assign cents > month limit! total: ["+totalDayCount+"]");
      return null;
     }
   }
   cacheManager.set(dayKey,totalDayCount.toString(),DateUtil.getSecsToEndOfCurrentDay());

}
//对月限制key进行积分累加
//每月1号凌晨1点启动脚本删除,同时设置了保存到月底缓存时间双重保障
cacheManager.set(monthKey, totalMonCount.toString(), DateUtil.getSecsToEndOfCurrentDay());
logger.info("==monthkey:"+monthKey+"---value:"+totalMonCount);
 }
              ...
}catch(Exception e){
          logger.error("===cache redis fail!");
          e.printStackTrace();

      }finally {

          if (cacheManager != null){
              cacheManager.close();
          }
      }

//工厂类获取redis链接
public class CacheManagerFactory {
  private static ICacheManager cacheManager;
  private CacheManagerFactory(){
  };

  public static ICacheManager getCacheManager(){
    if(cacheManager == null){
      synchronized (CacheManagerFactory.class) {
        if(cacheManager == null){
          JedisPooler jedisPooler = RedisPoolerFactory.getJedisPooler();
          cacheManager = new RedisCacheManager(jedisPooler);
        }
      }
    }
    return cacheManager;
 }
}

//redis链接池工厂类获取链接
public class RedisPoolerFactory {
  private static JedisPooler jedisPooler;
  private RedisPoolerFactory(){
  };

  public static JedisPooler getJedisPooler(){
    if(jedisPooler == null){
      synchronized (RedisPoolerFactory.class) {
        if(jedisPooler == null){
          jedisPooler = new JedisPooler();
        }
      }
    }
    return jedisPooler;
 }
}

/**
 *
 * Redis 连接池实例
 *
 * @author Ethan.Lam
 * @createTime 2011-12-3
 *
 */
public class JedisPooler {

  private JedisPool pool;
  private String REDIS_HOST;
  private String REDIS_PSW;
  private int REDIS_PORT;
  private int REDIS_MaxActive;
  private int REDIS_MaxIdle;
  private int REDIS_MaxWait;

  public JedisPooler(String config) {
    __init(config);
  }

  public JedisPooler() {
    __init("/jedisPool.properties");
  }

  private void __init(String conf) {
    // 完成初始化工作
    Properties prop = new Properties();
    try {
      InputStream _file = loadConfig(conf);
      prop.load(_file);
      REDIS_HOST = prop.getProperty("REDIS.HOST");
      REDIS_PSW = prop.getProperty("REDIS.PSW");
      REDIS_PORT = Integer.parseInt(prop.getProperty("REDIS.PORT").trim());
      REDIS_MaxActive = Integer.parseInt(prop.getProperty("REDIS.MaxActive").trim());
      REDIS_MaxIdle = Integer.parseInt(prop.getProperty("REDIS.MaxIdle").trim());
      REDIS_MaxWait = Integer.parseInt(prop.getProperty("REDIS.MaxWait").trim());
    } catch (Exception e) {
      e.printStackTrace();
      REDIS_HOST = "localhost";
      throw new NullPointerException(conf + " is not found !");
    }
    JedisPoolConfig config = new JedisPoolConfig();
    config.setMaxActive(REDIS_MaxActive);
    config.setMaxIdle(REDIS_MaxIdle);
    config.setMaxWait(REDIS_MaxWait);
    config.setTestOnBorrow(true);
    System.out.println("REDIS Cache服务信息: 当前连接的服务IP为:" + REDIS_HOST + ":" + REDIS_PORT);
    if (null == REDIS_PSW || "".equals(REDIS_PSW.trim())){
      pool = new JedisPool(config, REDIS_HOST, REDIS_PORT, 5000);
    }
    else{
      pool = new JedisPool(config, REDIS_HOST, REDIS_PORT, 5000, REDIS_PSW);
    }
  }

  public Jedis getJedis() {
    return pool.getResource();
  }

  public void returnResource(Jedis jedis) {
    pool.returnResource(jedis);
  }

  public JedisPool getPool() {
    return pool;
  }

  InputStream loadConfig(String configPath) throws Exception {
    InputStream _file = null;
    try {
      String file = JedisPooler.class.getResource(configPath).getFile();
      file = URLDecoder.decode(file);
      _file = new FileInputStream(file);
    } catch (Exception e) {
      System.out.println("读取jar中的配置文件....");
      String currentJarPath = URLDecoder.decode(JedisPooler.class.getProtectionDomain()
          .getCodeSource().getLocation().getFile(), "UTF-8"); // 获取当前Jar文件名
      System.out.println("currentJarPath:" + currentJarPath);
      java.util.jar.JarFile currentJar = new java.util.jar.JarFile(currentJarPath);
      java.util.jar.JarEntry dbEntry = currentJar.getJarEntry("jedisPool.properties");
      InputStream in = currentJar.getInputStream(dbEntry);
      _file = in;
    }
    return _file;
  }
}

可以看到,这里cacheManager.set(monthKey, totalMonCount.toString(), DateUtil.getSecsToEndOfCurrentDay()); 就用到了工具类获取了指定的时间范围。

对于redis这种希望指定缓存有效时间,现在提供3种方案:

1、自定义确切时间:

public static final long LoginCentTimeByDay = 86400;//s 未认证失效时间 1天

public static final long LoginCentTimeByMonth = 86400*30; //s 时效时间 30天

直接指定:

cacheManager.set(monthKey, totalMonCount.toString(),LoginCentTimeByDay)

2、自定义工具类,获取当前时间到第二天的零点、下个月1号零点的时间差(s):

cacheManager.set(monthKey, totalMonCount.toString(), DateUtil.getSecsToEndOfCurrentDay());
public class DateUtil {
  /**
   *获取每月最后一天时间
   * @param sDate1
   * @return
   */
  public static Date getLastDayOfMonth(Date sDate1)  {
    Calendar cDay1 = Calendar.getInstance();
    cDay1.setTime(sDate1);
    final int lastDay = cDay1.getActualMaximum(Calendar.DAY_OF_MONTH);
    Date lastDate = cDay1.getTime();
    lastDate.setDate(lastDay);
    return lastDate;
  }

  /*
  获取下一个月第一天凌晨1点
   */
  public static Date nextMonthFirstDate() {
    Calendar calendar = Calendar.getInstance();
    calendar.set(Calendar.HOUR_OF_DAY, 1);  //设置为每月凌晨1点
    calendar.set(Calendar.DAY_OF_MONTH, 1);  //设置为每月1号
    calendar.add(Calendar.MONTH, 1);  // 月份加一,得到下个月的一号
//    calendar.add(Calendar.DATE, -1);   下一个月减一为本月最后一天
    return calendar.getTime();
  }

  /**
   * 获取第二天凌晨0点毫秒数
   * @return
   */
  public static Date nextDayFirstDate() throws ParseException {
    Calendar cal = Calendar.getInstance();
    cal.setTime(new Date());
    cal.add(Calendar.DAY_OF_YEAR, 1);
    cal.set(Calendar.HOUR_OF_DAY, 00);
    cal.set(Calendar.MINUTE, 0);
    cal.set(Calendar.SECOND, 0);
    return cal.getTime();
  }

  //*********

  /**
   * 获取当前时间到下个月凌晨1点相差秒数
   * @return
   */
  public static Long getSecsToEndOfCurrentMonth(){

    Long secsOfNextMonth = nextMonthFirstDate().getTime();
    //将当前时间转为毫秒数
    Long secsOfCurrentTime = new Date().getTime();
    //将时间转为秒数
    Long distance = (secsOfNextMonth - secsOfCurrentTime)/1000;
    if (distance > 0 && distance != null){
      return distance;
    }
    return new Long(0);
  }

  /**
   * 获取当前时间到明天凌晨0点相差秒数
   * @return
   */
  public static Long getSecsToEndOfCurrentDay() throws ParseException {

    Long secsOfNextDay = nextDayFirstDate().getTime();
    //将当前时间转为毫秒数
    Long secsOfCurrentTime = new Date().getTime();
    //将时间转为秒数
    Long distance = (secsOfNextDay - secsOfCurrentTime)/1000;
    if (distance > 0 && distance != null){
      return distance;
    }
    return new Long(0);
  }
}

3、使用定时任务定时清空redis缓存;避免出现定时任务异常,我的业务代码里都保障了两种方案都适用。

定时任务保证,到指定时间直接调用代码进行操作;代码里直接调用shell脚本直接删掉相关redis的缓存数据。

quartz定时任务就需要注意定义相应的cron时间:

我的定时任务的配置文件quartz.xml中定义:

  <!--定时任务1-->
  <!-- 每天12点将当天用户积分行为缓存清掉 -->
  <bean id="deleteRedisCacheDayUsersJob" class="cn.qtone.xxt.cent.quartz.delRedisCacheCentUsers" />
  <bean id="deleteRedisCacheDayUsersTask" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
    <property name="targetObject" ref="deleteRedisCacheDayUsersJob" />
    <property name="targetMethod" value="delCurrentDayCacheUsersByDay" /><!-- 定时执行 doItem 方法 -->
    <property name="concurrent" value="false" />
  </bean>
  <bean id="deleteRedisCacheDayUsersTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
    <property name="jobDetail" ref="deleteRedisCacheDayUsersTask" />
    <property name="cronExpression" value="59 59 23 * * ?" /><!-- 每天凌晨23:59:59 点执行 -->
    <!--<property name="cronExpression" value="0 */1 * * * ?" />&lt;!&ndash; 每隔1min执行一次 &ndash;&gt;-->
  </bean>
  <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="triggers">
      <list>
         <ref bean="deleteRedisCacheDayUsersTrigger" />
        <ref bean="deleteRedisCacheMonthUsersTrigger" />
        <!--暂时不用-->
         <!--<ref bean="centUpdateByMonthTrigger" />-->
      </list>
    </property>
  </bean>

以上这篇Redis有效时间设置以及时间过期处理操作就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • springBoot集成redis的key,value序列化的相关问题

    使用的是maven工程 springBoot集成redis默认使用的是注解,在官方文档中只需要2步; 1.在pom文件中引入即可 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-redis</artifactId> </dependency> 2.编写一个CacheService接口,使用redisCach

  • Java使用RedisTemplate模糊删除key操作

    Redis模糊匹配批量删除操作,使用RedisTemplate操作: public void deleteByPrex(String prex) { Set<String> keys = redisTemplate.keys(prex); if (CollectionUtils.isNotEmpty(keys)) { redisTemplate.delete(keys); } } prex为迷糊匹配的key,如cache:user:* 这里需要判断keys是否存在,如果一个都匹配不到会报错:

  • 基于redis key占用内存量分析

    Redis的指令看不出哪一类型的key,占用了多少内存,不好分析redis内存开销大的情况下,各应用程序使用缓存的占比. 借助第3方工具进行分析 1.采用2个工具结合 redis-rdb-tools+sqlite 2.sqlite linux服务器都会自带,安装redis-rdb-tools 使用pip安装 pip install redis-rdb-tools 源码安装 git clone https://github.com/sripathikrishnan/redis-rdb-tools

  • 浅谈Redis的key和value大小限制

    今天研究了下将java bean序列化到redis中存储起来,突然脑袋灵光一闪,对象大小会不会超过redis限制?不管怎么着,还是搞清楚一下比较好,所以就去问了下百度,果然没多少人关心这个问题,没找到比较合适的答案,所以决定还是去官网找吧. 找到两句比较关键的话, 截图如下. 结论 redis的key和string类型value限制均为512MB. 补充知识:Redis获取所有键值 通过遍历获取目标键值: import redis redis = redis.Redis(host='192.24

  • spring使用redis操作key-value的示例代码

    连接到 Redis Redis 连接工厂会生成到 Redis 数据库服务器的连接.Spring Data Redis 为四种 Redis 客户端实现提供了连接工厂: JedisConnectionFactory JredisConnectionFactory LettuceConnectionFactory SrpConnectionFactory 具体选择哪一个取决于你.我建议你自行测试并建立基准,进而确定哪一种 Redis 客户端和连接工厂最适合你的需求.从 Spring Data Redi

  • php操作redis常见方法示例【key与value操作】

    本文实例讲述了php操作redis常见方法.分享给大家供大家参考,具体如下: 关于key的操作: 1.获取所有key,不包括值: $redis ->keys("*"); 2.获取一个或多个key的值,[不限制数据类型]: $redis ->mget([$key1,$key2]);//参数为数组: 3.设置指定key的生命周期: $redis ->expire($key,30);//设置生命周期为30秒: 4.获取指定key的剩余生命周期: $redis->tTl

  • Redis有效时间设置以及时间过期处理操作

    本文对redis的过期处理机制做个简单的概述,让大家有个基本的认识. Redis中有个设置时间过期的功能,即对存储在redis数据库中的值可以设置一个过期时间.作为一个缓存数据库,这是非常实用的.如我们一般项目中的token或者一些登录信息,尤其是短信验证码都是有时间限制的,按照传统的数据库处理方式,一般都是自己判断过期,这样无疑会严重影响项目性能. 一.有效时间设置: redis对存储值的过期处理实际上是针对该值的键(key)处理的,即时间的设置也是设置key的有效时间.Expires字典保存

  • Python操作Redis之设置key的过期时间实例代码

    Expire 命令用于设置 key 的过期时间.key 过期后将不再可用. r.set('2', '4028b2883d3f5a8b013d57228d760a93') #成功就返回True 失败就返回False,下面的20表示是20秒 print r.expire('2',20) #如果时间没事失效我们能得到键为2的值,否者是None print r.get('2') 对于一个已经存在的key,我们可以设置其过期时间,到了那个时间后,当你再去访问时,key就不存在了 有两种方式可以设置过期时间

  • java操作Redis缓存设置过期时间的方法

    关于Redis的概念和应用本文就不再详解了,说一下怎么在java应用中设置过期时间. 在应用中我们会需要使用redis设置过期时间,比如单点登录中我们需要随机生成一个token作为key,将用户的信息转为json串作为value保存在redis中,通常做法是: //生成token String token = UUID.randomUUID().toString(); //把用户信息写入redis jedisClient.set(REDIS_USER_SESSION_KEY + ":"

  • redis 设置生存和过期时间的原理分析

    目录 在了解原理前 先来看使用方法 原理 过期键的判定 Redis的过期键删除策略原理 在了解原理前 先来看使用方法 通过EXPIRE命令或者PEXPIRE命令,客户端可以以秒或者毫秒精度为数据库中的某个键设置生存时间,在经过指定的秒数或者毫秒数之后,服务器就会自动删除生存时间为0的键. SETEX命令可以在设置一个字符串键的同时为键设置过期时间(只能用于字符串键) 与EXPIRE命令和PEXPIRE命令类似,客户端可以通过EXPIREAT命令或PEXPIREAT命令,以秒或者毫秒精度给数据库中

  • python redis 批量设置过期key过程解析

    这篇文章主要介绍了python redis 批量设置过期key过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 在使用 Redis.Codis 时,我们经常需要做一些批量操作,通过连接数据库批量对 key 进行操作: 关于未过期: 1.常有大批量的key未设置过期,导致内存一直暴增 2.rd需求 扫描出这些key,rd自己处理过期(一般dba不介入数据的修改) 3.dba 批量设置过期时间,(一般测试可以直接批量设置,线上谨慎操作) 通过

  • Springsecurity Oauth2如何设置token的过期时间

    1.设置token的过期时间 如果我们是从数据库来读取客户端信息的话 我们只需要在数据库设置token的过期时间 1.1 oauth_client_details表每个列的作用 client_id:客户端的id 用于唯一标识每一个客户端(client):注册时必须填写(也可以服务端自动生成),这个字段是必须的,实际应用也有叫app_key resource_ids:资源服务器的id,多个用,(逗号)隔开 客户端能访问的资源id集合,注册客户端时,根据实际需要可选择资源id,也可以根据不同的额注册

  • Vue设置长时间未操作登录自动到期返回登录页

    Vue设置长时间未操作登录以后自动到期返回登录页 首先我们写在main.js文件中 import routerUtil from "@/utils/routerutil";//先将js文件在main.js中引入 routerUtil(router); 我们会在登陆成功后调用sessionUtil文件中的setSession,sessionUtil下面写的有 import sessionUtil from '@/utils/sessionutil' sessionUtil.setSess

  • localStorage设置有效期和过期时间的简单方法

    目录 前言 实现思路 实现代码 总结 前言 总所周知localStorage默认是不会过期的,也没有可设置过期的api,如果要实现过期清除就需要自己实现一个api 实现思路 在Storage原型上新增两个方法setCanExpireLocal,getCanExpireLocal,分别用于设置和获取值 实现过期功能至少需要三个时间,存值时间,取值时间,有效时间 存取时间戳都是在函数被调用时在函数内通过Date.now()生成 有效时间在存值的时候传入 如果存值时间戳加上有效时间小于当前时间戳说明还

  • Redis分布式锁如何设置超时时间

    目录 Redis分布式锁设置超时时间 网络抖动 Redis宕机 Redis分布式锁的超时问题 Redis分布式锁设置超时时间 Redis分布式锁主要依靠Redis服务来完成,我们的应用程序其实是Redis节点的客户端,一旦客户端没有释放锁,服务端就会一直持有这个锁,其他进程中的线程就无法获取到这把锁,于是就会发生锁死的情况. 所以我们在使用Redis分布式锁的时候,务必要设置锁的过期时间. 主要基于下面两点: 网络抖动 客户端A中的一个线程获取到了锁,然后执行finally中的释放锁的代码时,这

  • feign客户端设置超时时间操作

    我就废话不多说了,大家还是直接看代码吧~ /** * feign调用客户端 */ @FeignClient(name = "user", url = "${user.url}", configuration = MyFeignDecoder.class) public interface UserClient { @GetMapping("/rest/user/get/detail/{loginName}") JSONObject getUser

随机推荐