问题: 我们的核心系统业务突现大批量告警,某个业务校验类token大量出现客户端和服务端的不一致,业务流程如下:
1、此token是APP调用接口从后端获取,后端接口是生成32位随机数存储redis然后返回给前端的。返回时一并返回token的有效时间,对应的redis的TTL设定。
2、APP访问某业务,调用请求接口传入token给后台,后台校验token一致性然后处理后台业务。
之前业务稳定器只有极少部分用户由于未频繁使用APP,导致token失效未更新,会出现校验发现数据过期的情况。故此情况一定是有哪个环境出现问题。从业务逻辑分析上,不可能出现其他流程会操作token.
问题分析:
1、打开一台生产机器Info日志,观察此场景业务的异常数据现象;
跟踪了几个用户出现情况“
1、小A用户 前几分钟获取了最新token,然后使用业务校验token,redis打印token获取不到;
2、小B用户 前几分钟获取了最新token,校验token,redis获取不到token,几小时后再次尝试redis校验正常;后面又校验又失败;
从现象来看,数据的异常一定和redis相关:
1、redis服务端异常,造成redis响应延迟,导致token写入延迟(疑问:但是查询有没有相应延迟问题);
先确定(1)redis整个期间是否正常,可能引起redis阻塞的情况:包含请求相应数量暴增导致请求的队列堆积,后面的数据相应延迟;几个慢操作导致阻塞,但是通过slowlog&redis请求监控 并未发现redis异常。
2、程序逻辑异常(只能从业务相关的redis使用代码处进行分析)
走查代码发现一处从redis连接池获取了资源操作完之后,资源回收实际调用了系统里面的另外一个回收接口。相当于从A资源池获取资源处理完业务,然后将资源会受到B池。其中B池就是目前宜昌token使用的资源池。走查到这里大家松了一口气~
此处代码可能导致的影响:
A池资源频繁被回收到B池,导致A池可用资源减少,可能导致大并发情况下池资源不足;
B池资源拥有了A池资源,导致从B池业务token会写入到A资源库(实际获取从池获取的是A连接,导致数据写错库),这样当下一个查询操作时可能又正好获取到了B池的资源,导致查询不到数据;
下面我们看下jedis的连接池回收源码
/**
*
* @param wxsessionId key
* @param overtime rediskey过期时间,单位秒
*/
public void setWXSessionidToRedis(String wxsessionId,int overtime){
ShardedJedis jedis = null;
String redisKey = Constants.REDIS_PREFIX_WXSESSION_ID + wxsessionId;
//是否Redis连接异常
boolean isRedisE = false;
try {
jedis = LoginRedisPoolUtil.getJedisPool().getResource();
jedis.expire(redisKey, overtime);
} catch (Exception e) {
logger.error("setJsessionidToRedis() is failed. 设置redisKey:"
+ redisKey + "到redis服务器,失败原因:", e);
isRedisE = true;
} finally {
if (null != jedis) {
RedisPoolUtil.returnResource(jedis, isRedisE); //此处回收使用了其它另一个资源池的回收
}
}
}
/**
* jedis 连接回收
* @param jedis
* @param isFalg
*/
public static void returnResource(ShardedJedis jedis, boolean isFalg) {
if (null == jedis) {
return;
}
if (isFalg) {
sharedJedisPool.returnBrokenResource(jedis);
} else {
sharedJedisPool.returnResource(jedis); //sharedjedispool实际是使用了commons-pool.jar里面的pool来进行回收
}
}
源码关系图如下
Pool源码
public abstract class Pool {
private final GenericObjectPool internalPool;
public Pool(final GenericObjectPool.Config poolConfig,
PoolableObjectFactory factory) {
this.internalPool = new GenericObjectPool(factory, poolConfig);
}
@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);
}
}
public void returnResourceObject(final Object resource) {
try {
internalPool.returnObject(resource); //实际是GenericObjectPool 的接口回收
} catch (Exception e) {
throw new JedisException(
"Could not return the resource to the pool", e);
}
}
public void returnBrokenResource(final T resource) {
returnBrokenResourceObject(resource);
}
public void returnResource(final T resource) {
returnResourceObject(resource);
}
protected void returnBrokenResourceObject(final Object resource) {
try {
internalPool.invalidateObject(resource);
} catch (Exception e) {
throw new JedisException(
"Could not return the resource to the pool", e);
}
}
public void destroy() {
try {
internalPool.close();
} catch (Exception e) {
throw new JedisException("Could not destroy the pool", e);
}
}
}
GenericObjectPool
public void returnObject(Object obj)
throws Exception
{
try
{
addObjectToPool(obj, true);
}
catch (Exception e)
{
if (this._factory != null)
{
try
{
this._factory.destroyObject(obj);
}
catch (Exception e2) {}
synchronized (this)
{
this._numActive -= 1;
allocate();
}
}
}
}
private void addObjectToPool(Object obj, boolean decrementNumActive)
throws Exception
{
boolean success = true;
if ((this._testOnReturn) && (!this._factory.validateObject(obj))) {
success = false;
} else {
this._factory.passivateObject(obj);
}
boolean shouldDestroy = !success;
synchronized (this)
{
if (isClosed())
{
shouldDestroy = true;
}
else if ((this._maxIdle >= 0) && (this._pool.size() >= this._maxIdle))
//判断idle情况 所以回收错资源池 可能导致B正常的资源回收时正好碰上超过了最大idle 导致B正常的资源池被关闭
{
shouldDestroy = true;
}
else if (success)
{
if (this._lifo) {
this._pool.addFirst(new GenericKeyedObjectPool.ObjectTimestampPair(obj));
} else {
this._pool.addLast(new GenericKeyedObjectPool.ObjectTimestampPair(obj));
}
if (decrementNumActive) {
this._numActive -= 1;//由于回收了资源 活跃数-1
}
allocate();
}
}
if (shouldDestroy)
{
try
{
this._factory.destroyObject(obj);
}
catch (Exception e) {}
if (decrementNumActive) {
synchronized (this)
{
this._numActive -= 1;
allocate();
}
}
}
}