一、简介
1、Jedis就是集成了Redis的一些命令操作,封装了Redis的java客户端
2、本文使用JedisPool封装获取jedis的工具类,包括一些基本配置介绍。
3、调用JedisPool封装的工具类封装了jedis的一些基本操作。
4、本人水平有限,还望各位大佬指正文中的问题。
二、使用Jedis时遇到的问题
1、在高并发操作redis时,如果对Jedis操作不当会抛出 java.lang.ClassCastException: [B cannot be cast to java.lang.Long,类似的异常,然后redis就挂掉了,只能重新启动服务。
上网查询原因,发现在多线程中,使用 Jedis操作redis的时候,对底层执行redis命令做了缓存,所以如果某一次redis操作出现异常,jedis实例中的缓存数据不会被清空,而直接放回连接池中。下一次从池中取出了同一个jedis对象,发送的命令用的还是上一个线程的数据。所以如果两个线程使用的数据类型不一样,就会发生上面的问题。但是加入两个线程使用的数据类型是一样的,那么系统不会报异常,但是数据可能全是错乱的,后果将不可设想。
有兴趣的可以执行以下Demo重现。(此Demo放到文章最后)
三、Jedis连接池封装工具类
package com.gscaifu.gsTool.util;
import com.gscaifu.gsTool.RedisConfig;
import com.gscaifu.gsTool.StringUtil;
import org.apache.log4j.Logger;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
* redis连接池工具类
* Created by qx.zhangbj02320 on 2018/4/23.
*/
public class JedisPoolUtils {
private static Logger logger = Logger.getLogger(JedisPoolUtils.class);
//访问密码
private static String AUTH = "";
//Redis的端口号
private static int PORT = Integer.parseInt(RedisConfig.getInstance().getValue("redis.port"));
private static String redisHost = com.gscaifu.gsTool.RedisConfig.getInstance().getValue("redis.host");
/**
* 可用连接实例的最大数目,默认为8
* 如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)。
*/
private static int MAX_ACTIVE = 500;
/**
* 控制一个pool最多有多少个状态为idle(空闲的)的jedis实例,默认值也是8
*/
private static int MAX_IDLE = 100;
/**
* 控制一个pool最少有多少个状态为idle(空闲的)的jedis实例,默认值也是8
*/
private static int MIN_IDLE = 50;
/**
* 等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException;
*/
private static int MAX_WAIT = 10 * 1000;
/**
* 超时时间,当池内没有可用对象返回时,最大等待时间
*/
private static int TIMEOUT = 10 * 1000;
/**
* 在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的;
*/
private static boolean TEST_ON_BORROW = true;
/**
* 在return给pool时,是否提前进行validate操作;
*/
private static boolean TEST_ON_RETURN = true;
/**
* 如果为true,表示有一个idle object evitor线程对idle object进行扫描,如果validate失败,此object会被从pool中drop掉;这一项只有在timeBetweenEvictionRunsMillis大于0时才有意义;
*/
private static boolean TEST_WHILE_IDLE = true;
private static JedisPool jedisPool = null;
/**
* 初始化Redis连接池
*/
private static void createJedisPool() {
try {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(MAX_ACTIVE);
config.setMaxIdle(MAX_IDLE);
config.setMinIdle(MIN_IDLE);
config.setMaxWaitMillis(MAX_WAIT);
config.setTestOnBorrow(TEST_ON_BORROW);//使用时进行扫描,确保都可用
config.setTestWhileIdle(TEST_WHILE_IDLE);//Idle时进行连接扫描
config.setTestOnReturn(TEST_ON_RETURN);//还回线程池时进行扫描
//表示idle object evitor两次扫描之间要sleep的毫秒数
config.setTimeBetweenEvictionRunsMillis(30000);
//表示idle object evitor每次扫描的最多的对象数
config.setNumTestsPerEvictionRun(50);
//表示一个对象至少停留在idle状态的最短时间,然后才能被idle object evitor扫描并驱逐;这一项只有在timeBetweenEvictionRunsMillis大于0时才有意义
config.setMinEvictableIdleTimeMillis(60000);
if (StringUtil.isValid(AUTH)) {
jedisPool = new JedisPool(config, redisHost, PORT, TIMEOUT, AUTH);
} else {
jedisPool = new JedisPool(config, redisHost, PORT, TIMEOUT);
}
} catch (Exception e) {
logger.error("First create JedisPool error : " + e);
try {
//如果第一个IP异常,则访问第二个IP
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(MAX_ACTIVE);
config.setMaxIdle(MAX_IDLE);
config.setMaxWaitMillis(MAX_WAIT);
config.setTestOnBorrow(TEST_ON_BORROW);
jedisPool = new JedisPool(config, redisHost, PORT, TIMEOUT, AUTH);
} catch (Exception e2) {
logger.error("Second create JedisPool error : " + e2);
}
}
}
/**
* 在多线程环境同步初始化
*/
private static synchronized void poolInit() {
if (jedisPool == null) {
createJedisPool();
}
}
/**
* 同步获取Jedis实例
*
* @return Jedis
*/
public static Jedis getJedis() {
if (jedisPool == null) {
poolInit();
}
Jedis jedis = null;
try {
if (jedisPool != null) {
jedis = jedisPool.getResource();
}
} catch (Exception e) {
logger.error("Get jedis Error : " + e.getMessage(), e);
}
return jedis;
}
/**
* 释放jedis资源
*
* @param jedis
*/
public static void returnResource(final Jedis jedis) {
logger.debug("执行释放jedis资源方法returnResource:");
if (jedis != null && jedisPool != null) {
jedisPool.returnResource(jedis);
}
}
/**
* 关闭连接池
*/
public static void closePool() {
if (jedisPool != null) {
jedisPool.close();
}
}
}
四、Jedis基本操作封装
package com.gscaifu.gsMMscene.service.impl;
import com.gscaifu.gsTool.util.JedisPoolUtils;
import redis.clients.jedis.Jedis;
import java.util.Map;
import java.util.Set;
/**
* Created by qx.zhangbj02320 on 2017/11/13.
*/
public class BaseRedisServiceImpl {
/**
* 将字符串保存到redis中
*
* @param key
* @param value
* @return 成功 OK
*/
protected String set(String key, String value) {
Jedis jedis = JedisPoolUtils.getJedis();
try {
return jedis.set(key, value);
} finally {
//使用结束后要释放jedis资源
JedisPoolUtils.returnResource(jedis);
}
}
/**
* 获取指定的值
*
* @param key redis中值的键
* @return
*/
protected String get(String key) {
Jedis jedis = JedisPoolUtils.getJedis();
try {
return jedis.get(key);
} finally {
JedisPoolUtils.returnResource(jedis);
}
}
/**
* 追加字符串
*
* @param key
* @param value
* @return
*/
protected long append(String key, String value) {
Jedis jedis = JedisPoolUtils.getJedis();
try {
return jedis.append(key, value);
} finally {
JedisPoolUtils.returnResource(jedis);
}
}
/**
* 判断指定的键是否存在
*
* @param key
* @return true-存在,false-不存在
*/
protected boolean exists(String key) {
Jedis jedis = JedisPoolUtils.getJedis();
try {
return jedis.exists(key);
} finally {
JedisPoolUtils.returnResource(jedis);
}
}
/**
* 删除指定的键,
*
* @param key
* @return 0表示指定的键不存在, 大于0表示删除一个或多个。
*/
protected long del(String key) {
Jedis jedis = JedisPoolUtils.getJedis();
try {
return jedis.del(key);
} finally {
JedisPoolUtils.returnResource(jedis);
}
}
/**
* 将Map保存到redis中
*
* @param key
* @param value
* @return
*/
protected String setMap(String key, Map<String, String> value) {
Jedis jedis = JedisPoolUtils.getJedis();
try {
return jedis.hmset(key, value);
} finally {
JedisPoolUtils.returnResource(jedis);
}
}
/**
* 向已有Map中添加键值对
*
* @param key Map的key
* @param field map集合中的key
* @param value map集合中的value
* @return 0-更新 1-创建
*/
protected long setMapValue(String key, String field, String value) {
Jedis jedis = JedisPoolUtils.getJedis();
try {
return jedis.hset(key, field, value);
} finally {
JedisPoolUtils.returnResource(jedis);
}
}
/**
* 获取redis中的Map
*
* @param key
* @return
*/
protected Map<String, String> getMap(String key) {
Jedis jedis = JedisPoolUtils.getJedis();
try {
return jedis.hgetAll(key);
} finally {
JedisPoolUtils.returnResource(jedis);
}
}
/**
* 获取指定Map中的某键对应的值
*
* @param key Map对应的key值
* @param field Map中键值对中的键
* @return
*/
protected String getMapValue(String key, String field) {
Jedis jedis = JedisPoolUtils.getJedis();
try {
return jedis.hget(key, field);
} finally {
JedisPoolUtils.returnResource(jedis);
}
}
/**
* 删除已有Map中的键值对
*
* @param key
* @param fields
* @return
*/
protected long delMapValues(String key, String... fields) {
Jedis jedis = JedisPoolUtils.getJedis();
try {
return jedis.hdel(key, fields);
} finally {
JedisPoolUtils.returnResource(jedis);
}
}
/**
* 删除Map
*
* @param key
* @return
*/
protected long delMap(String key) {
Jedis jedis = JedisPoolUtils.getJedis();
try {
return jedis.del(key);
} finally {
JedisPoolUtils.returnResource(jedis);
}
}
/**
* 设置键的超时时间
*
* @param key 键
* @param seconds 秒
* @return
*/
protected long setExpire(String key, int seconds) {
Jedis jedis = JedisPoolUtils.getJedis();
try {
return jedis.expire(key, seconds);
} finally {
JedisPoolUtils.returnResource(jedis);
}
}
/**
* 向集合中添加元素
*
* @param key
* @param members
* @return
*/
protected long setList(String key, String... members) {
Jedis jedis = JedisPoolUtils.getJedis();
try {
return jedis.sadd(key, members);
} finally {
JedisPoolUtils.returnResource(jedis);
}
}
/**
* 取出集合中所有的元素
*
* @param key
* @return
*/
protected Set<String> getList(String key) {
Jedis jedis = JedisPoolUtils.getJedis();
try {
return jedis.smembers(key);
} finally {
JedisPoolUtils.returnResource(jedis);
}
}
/**
* 移除集合中指定元素
*
* @param key
* @param members
* @return
*/
protected long delList(String key, String... members) {
Jedis jedis = JedisPoolUtils.getJedis();
try {
return jedis.srem(key, members);
} finally {
JedisPoolUtils.returnResource(jedis);
}
}
}
五、Bug复现
调用setMapValue方法的同时,快速持续调用getMapValue方法。由于 setMapValue方法中有循环设置值,并且循环每次都会sleep 10毫秒。所以调用一次setMapValue方法,在setMapValue方法结束前快速调用getMapValue方法,问题将会重现。话不多说,直接贴代码。package com.gscaifu.gsMMscene.service.impl;
import com.gscaifu.gsTool.util.JedisPoolUtils;
import redis.clients.jedis.Jedis;
import java.util.Map;
import java.util.Set;
/**
* Created by qx.zhangbj02320 on 2017/11/13.
*/
public class BaseRedisServiceImpl {
private static final String redisHost = com.gscaifu.gsTool.RedisConfig.getInstance().getValue("redis.host");
private static final Integer redisPort = Integer.parseInt(com.gscaifu.gsTool.RedisConfig.getInstance().getValue("redis.port"));
private static Jedis jedis = new Jedis(redisHost, redisPort);
private Jedis getValidJedis() {
try {
System.out.println(jedis.ping());
} catch (Exception e) {
jedis = new Jedis(redisHost, redisPort);
e.printStackTrace();
}
return jedis;
}
/**
* 将字符串保存到redis中
*
* @param key
* @param value
* @return 成功 OK
*/
protected String set(String key, String value) {
getValidJedis();
return jedis.set(key, value);
}
/**
* 获取指定的值
*
* @param key redis中值的键
* @return
*/
protected String get(String key) {
getValidJedis();
return jedis.get(key);
}
/**
* 追加字符串
*
* @param key
* @param value
* @return
*/
protected long append(String key, String value) {
getValidJedis();
return jedis.append(key, value);
}
/**
* 判断指定的键是否存在
*
* @param key
* @return true-存在,false-不存在
*/
protected boolean exists(String key) {
getValidJedis();
return jedis.exists(key);
}
/**
* 删除指定的键,
*
* @param key
* @return 0表示指定的键不存在, 大于0表示删除一个或多个。
*/
protected long del(String key) {
getValidJedis();
return jedis.del(key);
}
/**
* 将Map保存到redis中
*
* @param key
* @param value
* @return
*/
protected String setMap(String key, Map<String, String> value) {
getValidJedis();
return jedis.hmset(key, value);
}
/**
* 向已有Map中添加键值对
*
* @param key Map的key
* @param field map集合中的key
* @param value map集合中的value
* @return 0-更新 1-创建
*/
protected long setMapValue(String key, String field, String value) {
getValidJedis();
for (int i=0;i<10000;i++){
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
jedis.hset(key, field, value+i);
}
return 0;
// Jedis jedis = JedisPoolUtils.getJedis();
// try {
// return jedis.hset(key, field, value);
// } finally {
// JedisPoolUtils.returnResource(jedis);
// }
}
/**
* 获取redis中的Map
*
* @param key
* @return
*/
protected Map<String, String> getMap(String key) {
getValidJedis();
return jedis.hgetAll(key);
}
/**
* 获取指定Map中的某键对应的值
*
* @param key Map对应的key值
* @param field Map中键值对中的键
* @return
*/
protected String getMapValue(String key, String field) {
getValidJedis();
String value = jedis.hget(key, field);
return value;
}
/**
* 删除已有Map中的键值对
*
* @param key
* @param fields
* @return
*/
protected long delMapValues(String key, String... fields) {
getValidJedis();
return jedis.hdel(key, fields);
}
/**
* 删除Map
*
* @param key
* @return
*/
protected long delMap(String key) {
getValidJedis();
return jedis.del(key);
}
/**
* 设置键的超时时间
*
* @param key 键
* @param seconds 秒
* @return
*/
protected long setExpire(String key, int seconds) {
getValidJedis();
return jedis.expire(key, seconds);
}
/**
* 向集合中添加元素
*
* @param key
* @param members
* @return
*/
protected long setList(String key, String... members) {
getValidJedis();
return jedis.sadd(key, members);
}
/**
* 取出集合中所有的元素
*
* @param key
* @return
*/
protected Set<String> getList(String key) {
getValidJedis();
return jedis.smembers(key);
}
/**
* 移除集合中指定元素
*
* @param key
* @param members
* @return
*/
protected long delList(String key, String... members) {
getValidJedis();
return jedis.srem(key, members);
}
}