一、问题:

java对象经过序列化后可存储到Redis中。同样,一个List也可以经过序列化后存储到Redis中。

现在有一个需求,记录某个网站不同ip的访问次数,或者是记录每个接口的访问次数,那么最终存储的数据就有可能是这样子:

123.1.2.1 : 10次

123.1.2.2 : 50次

220.121.205.9 : 17次   ..................

/login : 1000次

/resetPassword : 100次

/data/findData : 50001次  ....................

1、当然可以以字符串+ip为key存储次数,不过麻烦的是当你要取出数据的时候必须知道ip才能取出ip对应的次数。想查看某一天所有ip的访问次数时就比较麻烦。虽然也可以通过key like进行模糊查询,单用like查询终归不大好;

2、把每个ip及次数组装成一个对象,放到List中,然后再放到Redis中。这样一次性读取所有数据方便了,但是读取单个数据或者修改单个数据就麻烦了。需要一次性取出所有数据,然后找到要修改的那一条,修改完成后又整个存回去。这样效率是非常低的。

3、使用Redis的Hash功能,可以很方便的解决这个问题。下面介绍着重介绍这个方法-------------->>>>>>>>>>

二、Redis用Hash存储List

如果只是单纯的存储次数,Jedis中有一个hincrBy方法非常合适,该方法为Hash的某个域递增一个值并返回结果;

首先创建一个model类,包含key和value;

RedisHashObject.java

package com.lan.LanUtil.utils;

import java.io.Serializable;

public class RedisHashObject implements Serializable{

	private static final long serialVersionUID = 6478533647755905534L;

	private String field;
	
	private Object value;

	public RedisHashObject(String field, Object obj) {
		this.field = field;
		this.value = obj;
	}

	//get set方法略
}

然后编写一个RedisUtil,这个类功能比较全,先关心hashIncrease方法和getHashLongList方法。

RedisUtil.java

package com.lan.LanUtil.utils;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

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

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public class RedisUtil {
	private static String redisUrl = "127.0.0.1";
	private static int redisPort = 6379;
	private static String redisPassword = null;
	private static int database = 1;// 可选0-15

	private static final Logger logger = LoggerFactory.getLogger(RedisUtil.class);

	private static volatile JedisPool jedisPool = null;

	private RedisUtil() {
	}
	/**
	 * 服务器整个应用关闭后(不是单个方法结束后),可考虑调用此方法销毁连接池
	 * @author LAN
	 * @date 2018年11月14日
	 */
	public static void destroy() {
		if(jedisPool==null) return;
		if(!jedisPool.isClosed()) jedisPool.close();
		jedisPool.destroy();
	}

	private static Jedis getConnection() {
		if (jedisPool == null) {
			synchronized (RedisUtil.class) {// 线程安全
				if (jedisPool == null) {
					logger.debug("=================创建jedisPool Start=================");
					JedisPoolConfig config = new JedisPoolConfig();
					config.setMaxTotal(200);//最大连接数, 默认8个
					config.setMaxIdle(8);//最大空闲连接数, 默认8个
					config.setMaxWaitMillis(1000 * 100);//获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常, 小于零:阻塞不确定的时间,  默认-1
					config.setTestOnBorrow(true);
					
					jedisPool = new JedisPool(config, redisUrl, redisPort, 100000, redisPassword, database);
					logger.debug("=================创建jedisPool End=================");
				}
			}
		}
		return jedisPool.getResource();
	}

	/**
	 * 
	 * @author LAN
	 * @date 2018年11月14日
	 * @param key     存储的键
	 * @param o       存储的java对象
	 * @param expire  设置过期时间,单位:秒,小于0时为永不过期
	 */
	public static void set(String key, Object o, int expire) {
		Jedis jedis = null;
		try {
			jedis = getConnection();
			if(o==null) {
				jedis.del(key.getBytes());
				return;
			}
			byte[] data = KryoSerializeUtil.serialize(o);
			if(expire>0) {
				jedis.setex(key.getBytes(), expire, data);
			}else {
				jedis.set(key.getBytes(), data);
			}
		}finally {
			if(jedis!=null) jedis.close();//新版本的close方法,如果是从JedisPool中取出的,则会放回到连接池中,并不会销毁。
		}
	}
	
	public static void set(String key, Object o) {
		set(key, o, -1);
	}
	
	public static <T> T get(String key, Class<T> clazz) {
		Jedis jedis = null;
		try {
			jedis = getConnection();
			byte[] data = jedis.get(key.getBytes());
			if(data==null || data.length==0){
				return null;
			}
			T t = (T) KryoSerializeUtil.unserialize(data, clazz);
			return t;
		}finally {
			if(jedis!=null) jedis.close();//新版本的close方法,如果是从JedisPool中取出的,则会放回到连接池中,并不会销毁。
		}
	}

	/**
	 * 存:setHashObject("UserTimesHash", "1001", new Integer(10));
	 * 取:getHashObject("UserTimesHash", "1001", Integer.class);
	 * 批量取全部:getHashList("UserTimesHash");
	 * 
	 * 用hash存入值,方便批量查询
	 * expire=-1表示永不失效
	 * @author LAN
	 * @date 2018年9月17日
	 * @param key
	 * @param field
	 * @param o
	 * @param expire
	 */
	public static void setHashObject(String key, String field, Object o, int expire) {
		Jedis jedis = null;
		try {
			jedis = getConnection();
			if(o==null) {
				jedis.hdel(key.getBytes(), field.getBytes());
				return;
			}
			byte[] data = KryoSerializeUtil.serialize(o);
			jedis.hset(key.getBytes(), field.getBytes(), data);
			if(expire!=-1){
				jedis.expire(key.getBytes(), expire);
			}
		}finally {
			if(jedis!=null) jedis.close();
		}
	}
	
	private static <T> T getHashObject(byte[] key, byte[] field, Class<T> clazz){
		Jedis jedis = null;
		try {
			jedis = getConnection();
			byte[] data = jedis.hget(key, field);
			if(data==null || data.length==0){
				return null;
			}
			T t = KryoSerializeUtil.unserialize(data, clazz);
			return t;
		} finally {
			if(jedis!=null) jedis.close();
		}
	}
	
	/**
	 * 存:setHashObject("UserTimesHash", "1001", new Integer(10));
	 * 取:getHashObject("UserTimesHash", "1001", Integer.class);
	 * 批量取全部:getHashList("UserTimesHash");
	 * @author LAN
	 * @date 2018年9月17日
	 * @param key
	 * @param field
	 * @param clazz
	 * @return
	 */
	public static <T> T getHashObject(String key, String field, Class<T> clazz) {
		return getHashObject(key.getBytes(), field.getBytes(), clazz);
	}
	
	/**
	 * 存:setHashObject("UserTimesHash", "1001", new Integer(10));
	 * 取:getHashObject("UserTimesHash", "1001", Integer.class);
	 * 批量取全部:getHashList("UserTimesHash");
	 * @author LAN
	 * @date 2018年9月17日
	 * @param key
	 * @param clazz
	 * @return
	 */
	public static <T> List<RedisHashObject> getHashList(String key, Class<T> clazz) {
		Jedis jedis = null;
		try {
			jedis = getConnection();
			Set<byte[]> hkeys = jedis.hkeys(key.getBytes());
			if(hkeys==null || hkeys.size()==0){
				return null;
			}
			List<RedisHashObject> list = new ArrayList<>(); 
			for(byte[] field:hkeys){
				T obj = getHashObject(key.getBytes(), field, clazz);
				list.add(new RedisHashObject(new String(field), obj));
			}
			return list;
		} finally {
			if(jedis!=null) jedis.close();
		}
	}
	
	/**
	 * 获取hashIncrease方法某个key下设置的所有值
	 * @author LAN
	 * @date 2018年11月13日
	 * @param key
	 * @return
	 */
	public static <T> List<RedisHashObject> getHashLongList(String key) {
		Jedis jedis = null;
		try {
			jedis = getConnection();
			Set<byte[]> hkeys = jedis.hkeys(key.getBytes());
			if(hkeys==null || hkeys.size()==0){
				return null;
			}
			List<RedisHashObject> list = new ArrayList<>(); 
			for(byte[] field:hkeys){
				Long value = jedis.hincrBy(key, new String(field), 0l);
				list.add(new RedisHashObject(new String(field), value));
			}
			return list;
		} finally {
			if(jedis!=null) jedis.close();
		}
	}
	
	/**
	 * 为Redis的Hash某个key中的某个域field递增某个值
	 * @author LAN
	 * @date 2018年11月13日
	 * @param key
	 * @param field
	 * @param increase
	 * @param expire
	 */
	public static Long hashIncrease(String key, String field, Long increase, int expire) {
		Jedis jedis = null;
		try {
			jedis = getConnection();
			Long n = jedis.hincrBy(key, field, increase);
			if(expire!=-1){
				jedis.expire(key, expire);
			}
			return n;
		} finally {
			if(jedis!=null) jedis.close();
		}
	}
}

RedisUtil可以通过hashIncrease把单个值递增,通过getHashLongList把所有值拿出来;

写一个测试类

TestRedis.java

package com.lan.LanUtil;

import java.util.List;

import com.lan.LanUtil.utils.RedisHashObject;
import com.lan.LanUtil.utils.RedisUtil;

public class TestRedis {

	public static void main(String[] args) {
		//模拟设置访问量
		RedisUtil.hashIncrease("visitTimes-20181115", "192.168.1.1", 1l, -1);
		RedisUtil.hashIncrease("visitTimes-20181115", "192.168.1.2", 1l, -1);
		RedisUtil.hashIncrease("visitTimes-20181115", "192.168.1.1", 1l, -1);
		RedisUtil.hashIncrease("visitTimes-20181115", "192.168.1.5", 1l, -1);
		RedisUtil.hashIncrease("visitTimes-20181115", "192.168.1.2", 1l, -1);
		RedisUtil.hashIncrease("visitTimes-20181115", "192.168.1.1", 1l, -1);
		RedisUtil.hashIncrease("visitTimes-20181115", "192.168.1.1", 1l, -1);
		//获取所有ip访问量
		List<RedisHashObject> list = RedisUtil.getHashLongList("visitTimes-20181115");
		for(RedisHashObject rho:list) {
			System.out.println(rho.getField()+" : "+rho.getValue());
		}
		RedisUtil.destroy();
	}
}

运行结果:

redis 用户访问量设计 redis记录访问量_jedis list hash

再运行一次,结果:

redis 用户访问量设计 redis记录访问量_记录记录访问量_02

        这就很好的解决了Redis记录并展示ip访问量的问题,同样道理也可以用于记录接口的访问量。这种功能只需写一个拦截器来进行记录即可。假如需要对展示的数据进行分页查看也是可以的,只需修改RedisUtil中的getHashLongList方法,对field进行排序后取分页的某一段数据。缺点是不能按照访问量进行排序,若要按照访问量进行排序只能在内存中对所有数据进行sort后再分页展示;

三、结语

用Jedis的lset方法本身是可以存储List数据,但并不大适用于本文的记录访问量问题,因为lset只能根据index下标存储,不能根据字符索引进行存储。用Hash功能就可以很好解决这个问题。

此外,假如需要存储的信息不是次数,而是某个复杂的java对象,好比如session对象,只需用RedisUtil中的setHashObject、getHashObject即可,而获取某个key下的所有域及对象只需用getHashList方法即可。详细逻辑,看RedisUtil中的代码。