一、问题:
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-15private 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),如果超时就抛异常, 小于零:阻塞不确定的时间, 默认-1config.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记录并展示ip访问量的问题,同样道理也可以用于记录接口的访问量。这种功能只需写一个拦截器来进行记录即可。假如需要对展示的数据进行分页查看也是可以的,只需修改RedisUtil中的getHashLongList方法,对field进行排序后取分页的某一段数据。缺点是不能按照访问量进行排序,若要按照访问量进行排序只能在内存中对所有数据进行sort后再分页展示;
三、结语
用Jedis的lset方法本身是可以存储List数据,但并不大适用于本文的记录访问量问题,因为lset只能根据index下标存储,不能根据字符索引进行存储。用Hash功能就可以很好解决这个问题。
此外,假如需要存储的信息不是次数,而是某个复杂的java对象,好比如session对象,只需用RedisUtil中的setHashObject、getHashObject即可,而获取某个key下的所有域及对象只需用getHashList方法即可。详细逻辑,看RedisUtil中的代码。
author:蓝何忠
email:lanhezhong@163.com