记一次redis异常定位

问题:  我们的核心系统业务突现大批量告警,某个业务校验类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();

        }

      }

    }

  }

你可能感兴趣的:(记一次redis异常定位)