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