分布式锁

HanGR 于 2024-09-16 发布


背景


解决方案


具体方法

  1. 分离定时任务程序和主程序, 只在 1 个服务器运行定时任务
    • 存在问题: 定时任务程序和主程序耦合在一起, 定时任务程序的修改需要同时修改主程序, 增加了维护成本.
  2. 写死配置
    • 每个服务器都执行定时任务 但是只有 ip 符合配置的服务器才真实执行业务逻辑 其他的直接返回.
    • 存在问题: 服务器 IP 可能是不固定的,配置管理起来比较麻烦.
  3. 动态配置
    • 定时任务程序从配置中心 (Nacos, Apollo, Spring Cloud Config) 获取执行的服务器列表, 然后只在这些服务器上执行定时任务.
    • 存在问题: 配置中心需要实现动态获取服务器列表的功能, 并通知定时任务程序, 增加了系统复杂度.
  4. 分布式锁
    • 定时任务程序获取分布式锁, 只有获得锁的线程才可以执行定时任务.
    • 存在问题: 锁的获取和释放需要考虑超时和异常情况, 增加了系统复杂度.


  1. 介绍
    • 锁是一种同步机制, 用于控制对共享资源的访问, 防止多个线程同时访问共享资源, 造成数据不一致的问题.
  2. java 实现锁的方式
    • ReentrantLock 和 synchronized 关键字、并发包中的锁类。
  3. 问题
    • 死锁: 多个线程互相等待对方释放锁, 导致一直阻塞, 无法继续运行.
    • 性能问题: 锁的获取和释放会影响性能, 降低并发度.
    • 只对单个JVM有效: 无法在分布式环境下使用。
  4. 解决方案
    • 分布式锁


分布式锁

  1. 介绍
    • 分布式锁是一种基于数据库实现的锁, 用于控制对共享资源的访问, 防止多个进程或线程同时访问共享资源, 造成数据不一致的问题.
    • 分布式锁的实现依赖于数据库的原子性, 能够保证在分布式环境下, 多个进程或线程对共享资源的访问是原子性的.
  2. 核心思想
    • 所有进程或线程都能获取锁, 只有一个进程或线程能获取到锁, 其他进程或线程只能等待.
    • 先来的进程或线程先抢占锁, 先把数据改成自己的标识, 后来的进程或线程发现表示已存在, 就抢锁失败, 直到锁被释放.
  3. 实现方式
    • Mysql: select for update 语句 行级锁(最简单, 但效率低, 不常用)
    • Redis: 内存数据库, 读写速度快, 常用
    • redis中有 setnx 命令, 可以实现分布式锁, setnx:set if not exists 如果不存在,则设置;只有设置成功才会返回 true,否则返回 false。
  4. 注意事项
    • 及时释放锁: 避免死锁, 避免进程或线程长时间占用锁, 避免进程或线程异常退出导致锁一直被占用.
    • 设置过期时间: 避免锁一直被占用, 导致其他进程或线程无法获取锁.
    • 及时续期: 避免方法执行时间过长, 锁提前过期(会产生连锁效应, 释放掉其他进程的锁, 会存在多个方法同时执行的情况)
    • 保证原子性: 避免多线程并发时, 出现数据不一致的问题.