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

起因

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

相关推荐
JustHappy6 小时前
古法编程秘籍(二):什么是代码模块化?别背概念,把房间收拾明白就够了
前端·后端
小江的记录本7 小时前
【JVM虚拟机】堆内存分代模型:年轻代(Eden+Survivor)、老年代、元空间Metaspace(附《思维导图》+《面试高频考点清单》)
java·前端·jvm·后端·python·spring·面试
IT_陈寒10 小时前
Python闭包里藏的这个坑,差点让我加班到凌晨
前端·人工智能·后端
IT_陈寒10 小时前
Java注解空指针?这个坑我踩得莫名其妙
前端·人工智能·后端
土狗TuGou11 小时前
SQL内功笔记 · 第8篇:事务的四大特性与隔离级别
数据库·笔记·后端·sql·mysql·oracle
ZengLiangYi11 小时前
React Query + REST API 最佳实践
javascript·后端·react.js
星浩AI11 小时前
项目实战:合同智能审批 · LangGraph + HITL 人机协同方案 [有源码]
后端·langchain·agent
JavaGuide11 小时前
Codex 接入第三方模型 DeepSeek、GLM、Kimi 教程:CC-Switch 和 Codex++ 两种方案对比
后端·ai编程
ZengLiangYi11 小时前
Fastify 加 Electron:把 Web 服务嵌进桌面应用
前端·javascript·后端
李白你好12 小时前
页面资产梳理 · 技术指纹识别 · Spring 端点探测
java·后端·spring