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

网友投稿 1483 2023-03-07

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

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 http://class RedisPoolerFactory {

private static JedisPoolhttp://er 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.setTJRImuAQLTTime(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中定义:

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:写微信小程序用什么插件好(微信小程序一般用什么写)
下一篇:本地小程序开发环境(微信小程序本地开发)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~