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

起因

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

相关推荐
计算机学姐20 分钟前
基于微信小程序的民宿预订管理系统
java·vue.js·spring boot·后端·mysql·微信小程序·小程序
Code侠客行1 小时前
Scala语言的编程范式
开发语言·后端·golang
moton20172 小时前
云原生:构建现代化应用的基石
后端·docker·微服务·云原生·容器·架构·kubernetes
何中应2 小时前
Spring Boot中选择性加载Bean的几种方式
java·spring boot·后端
web2u3 小时前
MySQL 中如何进行 SQL 调优?
java·数据库·后端·sql·mysql·缓存
michael.csdn3 小时前
Spring Boot & MyBatis Plus 版本兼容问题(记录)
spring boot·后端·mybatis plus
Ciderw4 小时前
Golang并发机制及CSP并发模型
开发语言·c++·后端·面试·golang·并发·共享内存
Мартин.4 小时前
[Meachines] [Easy] Help HelpDeskZ-SQLI+NODE.JS-GraphQL未授权访问+Kernel<4.4.0权限提升
后端·node.js·graphql
程序员牛肉4 小时前
不是哥们?你也没说使用intern方法把字符串对象添加到字符串常量池中还有这么大的坑啊
后端
烛阴4 小时前
Go 语言进阶必学:&^ 操作符,高效清零的秘密武器!
后端·go