问题:
在实际项目中,我采用redis做资源锁。获取锁的线程才允许执行。然后再最后释放锁的时候。线程堵塞了16分钟后才能释放资源。
尝试解决:
我试着加大redispool的数量也无法解决。然后又查看了jedispool 的部分源码。查看默认属性中无法找到 16
分钟 或 960
秒 或 960000
毫秒 965
或965000
的配置。
我释放资源的地方
try {
xxxxxxxxxxx
}
} catch (Exception e) {
} finally {
//释放锁
log.info(targetPar + "." + methodPar + ":释放资源锁");
if (StringUtil.isNotEmpty(userId)) {
redisLock.unLock(userId);
}
}
加锁的代码
/**
*
* @Description
* @author lxlong
* @param lockKey 键
* @param expiretime 超时时间,默认为 30*1000 (单为毫秒)
* @return
* @throws LockOccupiedException 加锁失败
*/
public boolean lockKey(String lockKey, Integer expiretime) throws LockOccupiedException {
//添加锁前缀,避免相同key 被误删。
key = LOCK_PREFIX.concat(lockKey);
//设置默认超时时间
if(expiretime == null || expiretime <= 0){
expiretime = defaultExpireTime;
}
String expiresStr = String.valueOf(System.currentTimeMillis() + expiretime);
if(redisService.setNX(key, expiresStr)){
log.info("---------------" + key + "加锁成功-------------------");
myExpireTime = expiresStr;
isLock = true;
return true;
}
String currentValueStr = redisService.get(key); //redis里的时间
if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
log.info("===================前一锁已超时========================");
//判断是否为空,不为空的情况下,如果被其他线程设置了值,则第二个条件判断是过不去的
//锁超时,设置新锁
String oldValueStr = redisService.getAndSet(key, expiresStr);
//获取上一个锁到期时间,并设置现在的锁到期时间,
//只有一个线程才能获取上一个线上的设置时间,因为jedis.getSet是同步的
if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
myExpireTime = expiresStr;
isLock = true;
log.info("---------------" + key + "加锁成功-------------------");
return true;
}
}
throw new LockOccupiedException();
}
释放资源的代码如下
/**
* 释放锁
* @Description
* @author lxlong
* @param lockKey
*/
public void unLock(String lockKey) {
if(isLock){
lockKey = LOCK_PREFIX.concat(lockKey);
String expiresStr = redisService.get(lockKey);
//判断是否超时,若时间值不一致说明锁已被其他线程获取,不用释放
if(expiresStr.equals(myExpireTime)){
redisService.delete(lockKey);
log.info("---------------" + lockKey + "释放成功-------------------");
} else {
log.info("---------------" + lockKey + "已被其他线程获取/未获取到锁-------------------");
}
}
}
/**
* 删除 (non-Javadoc)
*
* @see com.leyizhu.dao.redis.RedisDao#delete(java.lang.String)
*/
public boolean delete(final String key) {
boolean result = redisTemplate.execute(new RedisCallback<Boolean>() {
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
RedisSerializer<String> serializer = getRedisSerializer();
byte[] jkey = serializer.serialize(key);
if (connection.exists(jkey)) {
connection.del(jkey);
return true;
} else {
return false;
}
}
});
return result;
}
实际中出现的问题如下:
[INFO] 2019-01-23 06:20:32 ---> ---------------LOCK_PREFIX_7170e8b3ed2144b48bf9ac8bebd29733加锁成功-------------------
[INFO] 2019-01-23 06:20:32 ---> =====================用户签到=====================
[INFO] 2019-01-23 06:20:32 ---> ------------------sign-----end
[INFO] 2019-01-23 06:20:32 ---> apiUserInfoTokenControl.sign:释放资源锁
[INFO] 2019-01-23 06:36:37 ---> ---------------LOCK_PREFIX_7170e8b3ed2144b48bf9ac8bebd29733释放成功-------------------
apiUserInfoTokenControl.sign:释放资源锁
和-LOCK_PREFIX_7170e8b3ed2144b48bf9ac8bebd29733释放成功
之间时间差为 16分05秒
,965 millisecond
。这段时间不知到发生了什么?
而且这段时间之内,对其他线程没有影响,可以正常操作redis。
这种问题基本上2-3天出现一次,没有规律可循。
环境配置
- 服务器配置
CPU: 2核
内存: 16 GB
实例类型: I/O优化
操作系统: Aliyun Linux 17.1 64位
- redis配置
redis 4.0
redis.host=127.0.0.1
redis.port=6379
redis.pass=123456
redis.minIdle=5
redis.maxIdle=10
redis.maxTotal=50
redis.timeout=2000
redis.maxWait=6000
redis.testOnBorrow=true
spring-redis.xml
<bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
<property name="connectionFactory" ref="connectionFactory" />
</bean>
<!-- Spring-redis连接池管理工厂 -->
<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="${redis.host}" />
<property name="port" value="${redis.port}" />
<property name="password" value="${redis.pass}" />
<property name="timeout" value="${redis.timeout}" />
<property name="poolConfig" ref="poolConfig" />
<property name="usePool" value="true"/>
</bean>
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="${redis.maxTotal}" />
<property name="minIdle" value="${redis.minIdle}" />
<property name="maxIdle" value="${redis.maxIdle}" />
<property name="maxWaitMillis" value="${redis.maxWait}" />
<property name="testOnBorrow" value="${redis.testOnBorrow}" />
</bean>
然后现在我删除key的方法改了一下下 然后效果还是一样。删除的时候会拥塞16分钟零4秒
/**
* 删除 (non-Javadoc)
*
* @see com.leyizhu.dao.redis.RedisDao#delete(java.lang.String)
*/
public boolean delete(final String key) {
redisTemplate.delete(key);
return true;
// boolean result = redisTemplate.execute(new RedisCallback<Boolean>() {
// public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
// RedisSerializer<String> serializer = getRedisSerializer();
// byte[] jkey = serializer.serialize(key);
// if (connection.exists(jkey)) {
// connection.del(jkey);
// return true;
// } else {
// return false;
// }
// }
// });
// return result;
}
这是现在的日志,我在删除之前加了一段日志 准备释放 XXXXXXX
。用于确定是删除的时候拥塞的
[INFO] 2019-01-29 04:00:48 ---> ---------------LOCK_PREFIX_c3372edaae224b409ce87684d6abc125加锁成功-------------------
[INFO] 2019-01-29 04:00:48 ---> -----------------------------准备释放LOCK_PREFIX_c3372edaae224b409ce87684d6abc125
[INFO] 2019-01-29 04:16:52 ---> ---------------LOCK_PREFIX_c3372edaae224b409ce87684d6abc125释放成功-------------------
最近发现只要有调用redis删除key的地方都有可能出现拥塞。拥塞时间也是在16分钟左右。基本上可确定是 redis.delete(key) 这一块的问题。但是还是不知道具体原因,以及解决办法。