巴拉巴拉
小魔仙

Redis 给 哈希表 的Field设置过期时间

Redis是一个非常棒的key-value的内存性缓存,处理速度非常的快。还可以很方便的对key设置过期时间,让我们很轻松的就能让不需要保存很久的缓存自己就移除掉。但是Redis里面并没有对哈希表Hash里面的Field有设置过期时间的配置,所以我自己写了一套可以设置过期时间field缓存的工具。

这是一个配合定时器来工作的工具类。我是利用quartz,想知道quartz的朋友可以自行百度

或者看我的博客:Quartz-Spring

 

这里先做几个介绍

  1. TreeSet,这是一个有序的Set不可重复集合里面的集合都要按照某种规律来按顺序排列,对象需要实现Comparable接口,这个接口是用来实现compareTo排序方法的。
  2. 利用TreeSet的有序性,来排列即将要删除的Key-Filed,这样当每次刷新迭代时,总是先遍历到最先过期的Field。那么当我遍历到第n个对象时,它没有过期,那么就不需要处理它,以及之后的了对象了。
  3. 利用Set的不可重复的特性,我们来重写对象的equals方法和hashCode方法,让他们只根据key和field来判断是不是同一个对象,其他参数就不管啦。这样我们就能保证当我重复添加一个filed进来,只是为了更新它的过期时间,而不是重复添加一个同名的Key-Filed,这样很容易出现一个问题:先进来的同名Key-Filed已经过期了,但是却移除了我们不想删除的更新过后的数据。

注意:这个刷新机制和quartz的更新挂钩,在quartz中每次都会来执行flush方法,所以我们设置的过期时间只是一个大概时间,并非非常的精确,假设quartz我设置了每分钟来执行一次,那么他就会每分钟来判断是否有字段需要被删除。
举个例子运气好的话,正好碰到1min中刷新阶段,这个field被删了,运气不好的话,也许要到下一分钟这个字段才会被删除
所以这个工具只适用于时间精确度并不高的需求。

package com.againfly.dex.cache;

import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.againfly.dex.util.Const;
import com.againfly.dex.util.Redis;
import com.againfly.dex.util.StringUtil;
import com.alibaba.fastjson.JSON;


public class RedisHashFieldCache {
    /**
     * 等待删除的集合,利用TreeSet的有序性,来排列即将过期的Field
     */
	private static final Set<Cache> WAIT_DEL_SET = Collections.synchronizedSet(new TreeSet<Cache>());
	/**
	 * 缓存等待删除的集合
	 * 存放到REDIS的一个哈希表里面,这是key值常量
	 */
	private static final String REDIS_HASH_KEY = "REDIS_HASH_FILED_CACHE";
	/**
	 * 缓存等待删除的集合
     * 存放到REDIS的一个哈希表里面,这是field值常量
	 */
	private static final String REDIS_HASH_FIELD;
	private static final Logger logger = LoggerFactory.getLogger(RedisHashFieldCache.class);
	static{
	    /**
	     * 用内网ip来区分每台机器的field值,当然你也可以改成其他的方式来区分本机方式
	     * 或者你觉得不需要用很多机器,只有一台机器,那你也可以不用哈希表,
	     * 你可以用一个普通的字符串Key就好啦.
	     * set或者list也是可以的.
	     * 我这里因为要区分很多机器,所以用内网ip来做FIELD常量
	     */
		REDIS_HASH_FIELD = "local_ip_" + Const.LOCAL_IP;
	}
	
	/**
	 * 初始化,每次系统启动的时候,从redis里面读取缓存队列,反序列化到WAIT_DEL_SET里面
	 */
	public static void init(){
		String json = Redis.hget(REDIS_HASH_KEY, REDIS_HASH_FIELD);
		List<Cache> list = null;
		try{
			if(!StringUtil.isEmpty(json) && !"[]".equals(json) && null != (list = JSON.parseArray(json, Cache.class))){
				WAIT_DEL_SET.addAll(list);
			}
		}catch (Exception e) {
			e.printStackTrace();
		}
		logger.info("RedisHashFieldCache init()!");
	}
	
	/**
	 * 设置一个redis里面的key和field,以及field的值,最后设置这个field的过期时间
	 * 过期时间是秒数,为了和redis里面expire设置key过期时间一致用秒数
	 * 所以这里统一使用了秒数来做为单位
	 */
	public static void put(String key, String field, String value, int seconds){
		if(StringUtil.isEmpty(key) || StringUtil.isEmpty(field)){
			return;
		}
		if(null == value){
			value = String.valueOf(System.currentTimeMillis());
		}
		Redis.hset(key, field, value);
		WAIT_DEL_SET.add(new Cache(seconds * 1000 + System.currentTimeMillis(), key, field));
		Redis.hset(REDIS_HASH_KEY, REDIS_HASH_FIELD, JSON.toJSONString(WAIT_DEL_SET));
	}
	
	/**
	 * 利用定时器Quartz(当然你也可以用其他的,Timer或者自己写一个线程,都可以)
	 * 利用定时功能,定时的来刷新这个缓存,并且从Reids中删除已经过期的field
	 */
	public static void flushCache(){
		Iterator<Cache> it = WAIT_DEL_SET.iterator();
		long now = System.currentTimeMillis();
		while(it.hasNext()){
			Cache c = it.next();
			if(c.expiredTime > now){
				break;
			}
			Redis.hdel(c.getKey(), c.getField());
			it.remove();
		}
		Redis.hset(REDIS_HASH_KEY, REDIS_HASH_FIELD, JSON.toJSONString(WAIT_DEL_SET));
	}
	
	/**
	 * 静态内部类Cache
	 * 这里面存放了过期时间,Reids里面的key,和field
	 * 重写了hashcode和equals方法,让他们只和key,field来判断唯一性
	 * 这里实现了Comparable方法,这个方法是为了配合TreeSet的有序集合来使用的
	 * compareTo方法中,将按照过期时间,顺序排列
	 * 这样当每次执行刷新方法的时候,迭代集合,总是先迭代最先必须要删除的Cache
	 * 如果当第n个Cache没有过期,那么后面的Cache都肯定没过期,所以就不需要迭代了
	 * 那么就不需要继续迭代后面的缓存了
	 */
	@SuppressWarnings("unused")
	private static class Cache implements Comparable<Cache>{
		private long expiredTime;
		private String key;
		private String field;
		
		public Cache() {}
		
		public Cache(long expiredTime, String key, String field) {
			super();
			this.expiredTime = expiredTime;
			this.key = key;
			this.field = field;
		}
		public long getExpiredTime() {
			return expiredTime;
		}
		public String getKey() {
			return key;
		}
		public String getField() {
			return field;
		}
		
		public void setExpiredTime(long expiredTime) {
			this.expiredTime = expiredTime;
		}
		public void setKey(String key) {
			this.key = key;
		}
		public void setField(String field) {
			this.field = field;
		}
		@Override
		public int compareTo(Cache o) {
			if(o == null){
				return -1;
			}
			if(o.getExpiredTime() == this.getExpiredTime()){
				return 0;
			}
			return this.getExpiredTime() > o.getExpiredTime() ? 1 : -1;
		}
		
		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + ((field == null) ? 0 : field.hashCode());
			result = prime * result + ((key == null) ? 0 : key.hashCode());
			return result;
		}
		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			Cache other = (Cache) obj;
			if (field == null) {
				if (other.field != null)
					return false;
			} else if (!field.equals(other.field))
				return false;
			if (key == null) {
				if (other.key != null)
					return false;
			} else if (!key.equals(other.key))
				return false;
			return true;
		}
		
		@Override
		public String toString() {
			return "Cache [expiredTime=" + expiredTime + ", key=" + key + ", field=" + field + "]";
		}
	}
}

==========================

结尾放一下怎么获取内网IP吧,自己百度了一下,大多数通过

InetAddress.getLocalHost().getHostAddress();

来获取。但是我试了一下,总是获取到的都是127.0.0.1,查询了之后好像是因为hosts文件绑定了。要修改这个文件,但是又觉得麻烦没有尝试,于是乎继续爬帖,找到一个比较靠谱的。

public static String getLocalIp(){ 
	try{
		Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces();
		while(en.hasMoreElements()){
			NetworkInterface ni = en.nextElement();
			Enumeration<InetAddress> addresses = ni.getInetAddresses();
			while(addresses.hasMoreElements()){
				InetAddress address = addresses.nextElement();
				if (!address.isLoopbackAddress() && !address.isLinkLocalAddress()
						&& address.isSiteLocalAddress()) {
					return address.getHostAddress().toString();
				}
			}
		}
	}catch (Exception e) {
	}
	return "127.0.0.1";
}

 

 

赞(1) 打赏
如果文章对你有帮助,欢迎你来评价反馈。AgainFly » Redis 给 哈希表 的Field设置过期时间
标签:

评论 2

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
  • Q Q(选填)
  1. #2
    头像

    哥,能分享下这个Field设置过期时间的代码?急需!急需!急需

    拿铁锅炖自己7个月前 (03-27)回复
  2. #1
    头像

    不需要序列化再Redis.hset把,直接一个hash对应一项

    朝野布告1年前 (2018-05-31)回复

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏