一、簡介
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);
}
}