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

起因

如图所示,客户问为什么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 深藏功与名吧

相关推荐
用户990450177800915 分钟前
JeecgFlow之Camunda开发脚手架介绍,让天下没有难用的工作流
后端
敖行客 Allthinker15 分钟前
Go 语言中 panic 和 recover 的代价:性能与设计的权衡
开发语言·后端·golang
谦行1 小时前
前端视角 Java Web 入门手册 4.4:Web 开发基础—— Listener
java·后端
非优秀程序员2 小时前
使用Python给自己网站生成llms.txt
人工智能·后端·架构
尘鹄2 小时前
一文讲懂Go语言如何使用配置文件连接数据库
开发语言·数据库·后端·golang
benben0442 小时前
Django小白级开发入门
后端·python·django
qw9493 小时前
SpringBoot3—场景整合:环境准备
java·后端
孟and平5 小时前
Flask 打包为exe 文件
后端·python·flask
大只因bug7 小时前
基于Django的协同过滤算法养老新闻推荐系统的设计与实现
后端·python·django·协同过滤算法推荐系统·新闻推荐网站系统·养老新闻推荐系统·个性化新闻推荐网站系统
_TokaiTeio11 小时前
JVM面试题100
java·开发语言·jvm·后端·虚拟机