记录一个看门狗一直狗叫的问题

起因

如图所示,客户问为什么redis的 pexpire,hexists,wait,eval,replconf 这几个指标每天持续增加?

分析

我赶紧gtp一下这五个指标是什么含义

  • pexpire:用于获取指定键的过期时间。
  • hexists:用于检查指定哈希表中是否存在给定字段。
  • wait:用于等待指定数量的从节点复制指定数量的数据集。
  • eval:用于在Redis服务器端执行Lua脚本。
  • replconf:用于在Redis复制中配置参数。

这看着怎么这么熟悉呢?是不是分布式锁一直在操作导致的呢?但是正常逻辑就是加锁-解锁,不顺利的话看门狗添加下时间,但是没道理越来越多的啊!!!

然后我去翻了翻redssion的分布式锁的逻辑

java 复制代码
<T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
    return evalWriteAsync(getRawName(), LongCodec.INSTANCE, command,
            "if (redis.call('exists', KEYS[1]) == 0) then " +
                    "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
                    "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                    "return nil; " +
                    "end; " +
                    "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
                    "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
                    "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                    "return nil; " +
                    "end; " +
                    "return redis.call('pttl', KEYS[1]);",
            Collections.singletonList(getRawName()), unit.toMillis(leaseTime), getLockName(threadId));
}

tryLock中调用的这个方法中 pexpire、hexists、eval 都严重符合当前的场景啊,至于wait、replconf稍微脑补一下,肯定是因为集群的时候,调用锁的逻辑会做主从同步产生的,所以问题很可能就是在使用redis分布式锁的时候。

下面这块是redssion续约的逻辑,未设置超时时间的加锁,会每10s执行续约,也就是说如果没有解锁的操作、且服务一直正常运行的情况下,这个锁是每10秒无限续约的。

java 复制代码
/**
 * org.redisson.config.Config 类部分参数
 */

private long lockWatchdogTimeout = 30 * 1000;//在Config.class中默认是30s

public long getLockWatchdogTimeout() {
    return lockWatchdogTimeout;
}
java 复制代码
/**
 * org.redisson.RedissonBaseLock 类部分方法
 */

public RedissonBaseLock(CommandAsyncExecutor commandExecutor, String name) {
    super(commandExecutor, name);
    this.commandExecutor = commandExecutor;
    this.id = commandExecutor.getConnectionManager().getId();
    this.internalLockLeaseTime = commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout();
    this.entryName = id + ":" + name;
}

private void renewExpiration() {
    ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());
    if (ee == null) {
        return;
    }
    
    Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
        @Override
        public void run(Timeout timeout) throws Exception {
            ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName());
            if (ent == null) {
                return;
            }
            Long threadId = ent.getFirstThreadId();
            if (threadId == null) {
                return;
            }
            
            CompletionStage<Boolean> future = renewExpirationAsync(threadId);
            future.whenComplete((res, e) -> {
                if (e != null) {
                    log.error("Can't update lock " + getRawName() + " expiration", e);
                    EXPIRATION_RENEWAL_MAP.remove(getEntryName());
                    return;
                }
                
                if (res) {
                    // reschedule itself
                    renewExpiration();
                } else {
                    cancelExpirationRenewal(null);
                }
            });
        }
    }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);//每30/3=10秒 触发续约逻辑
    
    ee.setTimeout(task);
}

也就是说,如果有地方一直未能成功解锁,那么redis监控到的数据就会是图像这个样子。

让我康康又是哪个大神埋下的种子。

好在前一天刚好排查到这个流量骤降是因为重启过我们其中一个微服务导致的,所以问题大概率是在这个服务的某个地方。

全局搜索后倒数第三行成功引起了我的注意,普通的加锁,如果未释放,那么其它线程是加不上锁的,不会导致操作数持续增加。

嗯 。。。。那就是这里了,每条数据都加了锁,而且没有在finally里面解锁,抛开业务,只要是异常(其实这方法里面也有很多return,这里业务就不展示了),那肯定是服务不停狗不停,越来越多狗狂吠,那场面真是 锣鼓喧天 鞭炮齐鸣 啊。

题外话 : 听说有其他客户遇到了短信发送越来越慢的问题,重启之后就好了,我估计也是这个问题导致的了。

默默加个finally 深藏功与名吧

相关推荐
啦啦右一1 小时前
Spring Boot | (一)Spring开发环境构建
spring boot·后端·spring
森屿Serien1 小时前
Spring Boot常用注解
java·spring boot·后端
盛派网络小助手3 小时前
微信 SDK 更新 Sample,NCF 文档和模板更新,更多更新日志,欢迎解锁
开发语言·人工智能·后端·架构·c#
∝请叫*我简单先生3 小时前
java如何使用poi-tl在word模板里渲染多张图片
java·后端·poi-tl
zquwei4 小时前
SpringCloudGateway+Nacos注册与转发Netty+WebSocket
java·网络·分布式·后端·websocket·网络协议·spring
dessler5 小时前
Docker-run命令详细讲解
linux·运维·后端·docker
Q_19284999065 小时前
基于Spring Boot的九州美食城商户一体化系统
java·spring boot·后端
ZSYP-S6 小时前
Day 15:Spring 框架基础
java·开发语言·数据结构·后端·spring
Yuan_o_7 小时前
Linux 基本使用和程序部署
java·linux·运维·服务器·数据库·后端