节前的最后一天,sso登陆出现问题,多数用户无法正常登陆。经查询日志,报错如下:
错误日志截取如下
2015-02-15 08:40:30,536 INFO [org.jasig.cas.authentication.AuthenticationManagerImpl] - <com.gaochao.oa.module.sso.authentication.handler.FxLdapMixAuthenticationHandler successfully authenticated[username: wei.guo]>
2015-02-15 08:40:30,536 INFO [org.jasig.cas.authentication.AuthenticationManagerImpl] - <Resolved principal wei.guo>
2015-02-15 08:40:30,536 INFO [org.jasig.cas.authentication.AuthenticationManagerImpl] - <com.gaochao.oa.module.sso.authentication.handler.FxLdapMixAuthenticationHandler@7952dda7 authenticated wei.guo with credential [username: wei.guo].>
2015-02-15 08:40:30,537 INFO [com.github.inspektr.audit.support.Slf4jLoggingAuditTrailManager] - <Audit trail record BEGIN
2015-02-15 08:40:30,576 ERROR [com.gaochao.oa.module.sso.ticket.registry.RedisTicketRegistry] - <Failed adding TGT-18015-B0igJB5vgG03iiVqXKl5VSO0tnBtYipfauzcKLCfD2MvhVYTEP-cas01.example.org, error message :Could not get a resource from the pool>
2015-02-15 08:40:30,576 INFO [com.github.inspektr.audit.support.Slf4jLoggingAuditTrailManager] - <Audit trail record BEGIN
2015-02-15 08:40:30,955 ERROR [com.gaochao.oa.module.sso.ticket.registry.RedisTicketRegistry] - <Failed adding TGT-18016-nMaZefSqRG393dcANeR4S1Tc2ff1WWdCafrNGwESgXkwLkCfN3-cas01.example.org, error message :Could not get a resource from the pool>
通过以上日志可以看出问题代码在sso中,红色加粗部分可以看出sso服务器证书以及用户鉴权是成功的。
if (!authenticationHandler.authenticate(credentials)) {
log.info("{} failed to authenticate {}", handlerName, credentials);
} else {
log.info("{} successfully authenticated {}", handlerName,
credentials);
authenticatedClass = authenticationHandler;
authenticated = true;
break;
}但是生成认证服务凭据ticket并且获得资源时失败
try {
jedis = getResource();
byte[] b = jedis.hget(SSO_HASH_ID, ticketId.getBytes());
if (b == null || b.length == 0) {
return null;
}
ByteArrayInputStream bais = new ByteArrayInputStream(b);
ois = new ObjectInputStream(bais);
ticket = (Ticket) ois.readObject();
} catch (JedisException e) {
borrowOrOprSuccess = false;
returnBrokenResource(jedis);
log.error("Failed getTicket {}, error message :{}", ticketId,
e.getMessage());
} catch (Exception e) {
log.error("Failed getTicket {}, error message :{}", ticketId,
e.getMessage());
}这段代码中可能报出Redis异常和其他异常,通过Could not get a resource from the pool,可以认为是redis异常。再进一步跟踪报出异常的地方,见如下代码:
@SuppressWarnings("unchecked")
public T getResource() {
try {
return (T) internalPool.borrowObject();
} catch (Exception e) {
throw new JedisConnectionException(
"Could not get a resource from the pool", e);
}
}在网上查找资料,一般认为这种异常都与redis的池连接配置有关:
产生红色部分的可能原因之一是客户端去redis服务器拿连接(代码描述的是租用对象borrowObject)的时候,池中无可用连接,即池中所有连接被占用,且在等待时候设定的超时时间后还没拿到时,会报出此异常。
解决办法:
1.调整JedisPoolConfig中maxActive为适合自己系统的阀值,当然这种情况下重启服务并将重建池连接是必然可行的。
我在查看我们系统的redis配置时,redis.properties中配置为redis.maxIdle=5000,
# Redis settings redis.maxIdle=5000 redis.maxActive=5000 redis.maxWait=10000 redis.testOnBorrow=true
理论上来说5000的并发量是足够的,不会出现拿不到资源的情况,但是我们的系统却出现了,继续查资料发现产生这问题的原因还可能是我们使用的redis2.1.0(redis自2.4.0后不需要手动归还)需要手动归还redis,而我们有的调用未归还,导致连接被长期占用;比如我们自己重写的类com.gaochao.oa.module.sso.ticket.registry.RedisTicketRegistry.java的getTickets只有获取资源,并没有归还的地方。如这里被频繁调用,在不长的时间内就很有可能在5000的情况下即出现资源连接不够用的情况。如下:
/**
* @author **
* @date 2013-7-15 下午10:13:19
* @see org.jasig.cas.ticket.registry.TicketRegistry#getTickets()
* @return
*/
public Collection<Ticket> getTickets() {
Jedis jedis = null;
jedis = getResource();
Map<byte[], byte[]> map = jedis.hgetAll(SSO_HASH_ID);
if(CollectionUtils.isEmpty(map)) {
return new ArrayList<Ticket>();
}
Map<String, Ticket> result = new HashMap(map.size());
for(Iterator<Map.Entry<byte[], byte[]>> iterator = map.entrySet().iterator(); iterator.hasNext();) {
Map.Entry<byte[], byte[]> entry = iterator.next();
// Object jsonValue = redisSerializer.parseObject(entry.getValue());
String ticketId = new String(entry.getKey());
Ticket ticket = getTicket(ticketId);
if(ticket != null) {
result.put(ticketId, ticket);
}
}
return result.values();
}如是以上问题,建议
1.redis换用redis 2.4.0以上版本;
2.对我们自己写的类进行走读,凡获取redis资源的地方必须需释放(一般使用redis的地方都有一个finally即最终必须执行的代码负责归还连接)
如下:
finally {
IOUtils.close(ois);
if (borrowOrOprSuccess) {
//返回到资源池
returnResource(jedis);
}
}另外,其他的原因还可能是网络异常、redis服务器异常等。
节前初步解决方案是每两天重启sso、redis服务器,另外节后对代码进行走读,将获取资源后未释放的代码全部进行修复。
本文出自 “南湖矿工技术空间” 博客,请务必保留此出处http://jncumter.blog.51cto.com/812546/1615620
Redis2.1.0:Could not get a resource from the pool的分析
原文地址:http://jncumter.blog.51cto.com/812546/1615620