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

起因

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

相关推荐
Csvn11 小时前
📊 SQL 入门 Day 8:集合操作 — 用 SQL 做数学里的"并交差"
后端·sql
码事漫谈14 小时前
告别数据孤岛与AI“水土不服”:金仓多模融合时序库如何让数据真正服务于业务
后端
IT_陈寒14 小时前
Redis的持久化配置把我坑惨了:你以为数据安全了?
前端·人工智能·后端
星栈14 小时前
Node 接口该写同步还是异步?
后端·node.js
红烧大青虫14 小时前
HarmonyOS应用开发实战:小事记 - UIAbility 的冷启动/热启动/后台启动三种场景与 launchParam 解析
后端·华为·harmonyos·鸿蒙系统
码事漫谈14 小时前
AI Token 缓存:命中省 10 倍,不命中白扔钱
后端
65岁退休Coder14 小时前
LangChain v1.3.4 笔记 - 04 Agent 中间件
后端
神奇小汤圆15 小时前
一个接口多个实现,Spring 怎么"适配多场景"?
后端
花开彼岸天~15 小时前
鸿蒙原生开发手记:徒步迹 - 自定义组件开发规范
后端·华为·harmonyos·鸿蒙系统
songroom17 小时前
Kimi K3:Rust封装XTP接口详细教程实践
开发语言·后端·rust