当前位置:首页 > 问答 > 正文

Redis死锁怎么查啊,还有那些办法能解决,真头疼

朋友,听你说Redis死锁头疼,我特别能理解,这东西就像两个人挤一扇门,你等我先出,我等你先出,结果谁都动不了,服务器上的应用就卡在那里了,下面我直接跟你说说怎么查,还有能试试的解决办法。

第一部分:怎么判断是不是Redis死锁了?

你别急着认定就是Redis的死锁,很多时候,感觉“卡住了”其实是别的原因,你得先排查一圈。

  1. 先看Redis本身还活着吗? 这是第一步,你用 redis-cli 连上Redis服务器,打个 PING 命令,看它回不回 PONG,如果连不上或者没反应,那问题可能不是死锁,而是Redis服务挂掉了,你得先去看Redis的日志文件,或者检查服务器内存、CPU是不是爆了。

  2. 检查Redis的慢查询日志。 Redis有个功能叫慢查询日志,它会记录执行时间超过设定阈值的命令,你可以通过 CONFIG GET slowlog-log-slower-than 看看阈值设了多少微秒(比如10000就是10毫秒),然后用 SLOWLOG GET [数量] 命令看最近的一些慢查询,如果发现某些键被某个命令长时间占用,比如一个 BRPOP 阻塞了特别久,或者一个复杂的Lua脚本一直没执行完,那可能就是线索,根据Redis官方文档,慢查询日志是排查性能问题的首要工具。

  3. INFO 命令看关键指标。redis-cli 里输入 INFO,会出来一大堆信息,你重点看几块:

    • clients 部分:connected_clients(连接数)是不是异常的高?blocked_clients(被阻塞的客户端数)是不是大于0并且一直不减少?如果有客户端一直被阻塞,那很可能就是陷入某种等待了。
    • memory 部分:used_memory 是不是快满了?如果内存满了,Redis可能会开始逐出数据或者拒绝写入,这也会导致客户端操作失败或等待,但这不是死锁。
    • stats 部分: 看看 keyspace_hitskeyspace_misses 的比例,如果miss很高,可能只是缓存穿透,大量请求直接打到数据库,感觉像卡顿。
  4. 检查连接和命令。 可以用 CLIENT LIST 命令列出所有客户端连接,你会看到每个连接的详细信息,idle(空闲时间)、cmd(最后执行的命令)、flags(标志,比如b表示阻塞),如果你发现好几个连接的 cmd 都停在像 BRPOPBLPOPBRPOPLPUSH 这类阻塞式命令上,idle 时间很长,那它们很可能就是在互相等待,形成了死锁的局面。

    Redis死锁怎么查啊,还有那些办法能解决,真头疼

  5. 监控键空间。 如果怀疑是某个特定的键被锁住了,你可以用 redis-cli--bigkeys 选项找找有没有特别大的键(大键操作耗时可能长),或者用 DEBUG OBJECT key名 命令查看某个键的详细信息(慎用,生产环境别乱来),更安全的做法是,如果你知道锁的键名模式(比如都以 _lock ,可以用 KEYS pattern 命令找出来看看(注意 KEYS 命令会阻塞其他请求,最好在从库或用 SCAN 命令替代)。

第二部分:有哪些办法可以解决和避免?

查出来问题所在后,就得想办法解决和预防了。

  1. 紧急情况:强制解锁。 如果确认是死锁,线上业务已经受影响,需要立刻恢复,最直接的办法就是删除那个造成死锁的键,一个分布式锁的键叫 order_lock_123,它一直不释放,你可以直接用 DEL order_lock_123 命令把它删掉。但这招是杀手锏,有副作用的,因为你删掉的锁可能正在被另一个业务逻辑使用,会导致数据不一致,所以这只能是救急,救完急一定要查根本原因。

    Redis死锁怎么查啊,还有那些办法能解决,真头疼

  2. 给锁设置过期时间。 这是最重要也是最有效的预防措施,Redis死锁很多时候是因为客户端拿到锁后,还没等到释放锁(比如程序崩溃、网络中断),锁就永远留在那了,在加锁的时候,一定要设置一个过期时间,比如用 SET key value NX PX 30000 命令,NX 表示只在键不存在时设置,PX 30000 表示30秒后自动过期,这样即使客户端挂了,锁最多锁30秒,不会永久死锁,根据《Redis设计与实现》这本书里的建议,设置过期时间是实现分布式锁的黄金法则。

  3. 避免使用非常长的阻塞操作。 尽量少用 BRPOP 这类会长时间阻塞连接的命令,如果一定要用,务必设置一个合理的超时时间(比如5秒),而不是0(无限等待),超时后即使没拿到数据,也释放连接,进行错误处理,然后重试,这比永远卡死要好。

  4. 检查Lua脚本。 Lua脚本在Redis中是原子执行的,执行期间会阻塞所有其他命令,如果你的Lua脚本写得特别复杂,逻辑里有死循环,或者操作了大量数据,就会导致Redis像死锁一样不响应,所以一定要保证Lua脚本的轻量和高效,并且要有严格的测试。

  5. 使用更成熟的分布式锁算法或库。 自己基于Redis实现一个健壮的分布式锁其实挺麻烦的,要考虑很多边界情况(比如锁过期了但业务还没执行完,另一个客户端又加锁成功),社区里已经有很成熟的方案,Redlock 算法(Redis官方提出的),或者直接使用像 Redisson(Java)这样的客户端库,它里面已经封装好了分布锁的实现,解决了大部分你可能想不到的坑,根据Redisson的官方文档,其内置的分布式锁提供了自动续期、可重入等高级特性,能大大降低死锁风险。

  6. 规范和监控。 制定开发规范,要求所有使用Redis锁的地方必须设置过期时间,建立监控告警,持续监控Redis的 blocked_clients 指标,一旦有长时间阻塞的客户端,立刻报警,让你能第一时间介入处理,而不是等业务方投诉才发现。

查死锁就是一步步排除,从Redis健康状态、慢查询、客户端列表里找蛛丝马迹,解决和预防的核心就是给锁加个“保质期”,并且尽量使用久经考验的库和算法,别自己从头造轮子,希望这些实实在在的步骤能帮你解决这个头疼的问题。