分布式锁
HanGR 于 2024-09-16 发布
背景
- 多线程并发场景下, 如果多个线程同时对一个共享资源进行操作, 可能会造成数据不一致的问题.
- 比如: 之前提到的定时任务, 多个线程同时执行同一个定时任务, 可能会导致任务执行时间错乱.
解决方案
- 控制在同一时刻, 只有一个线程对共享资源进行操作.
具体方法
- 分离定时任务程序和主程序, 只在 1 个服务器运行定时任务
- 存在问题: 定时任务程序和主程序耦合在一起, 定时任务程序的修改需要同时修改主程序, 增加了维护成本.
- 写死配置
- 每个服务器都执行定时任务 但是只有 ip 符合配置的服务器才真实执行业务逻辑 其他的直接返回.
- 存在问题: 服务器 IP 可能是不固定的,配置管理起来比较麻烦.
- 动态配置
- 定时任务程序从配置中心 (Nacos, Apollo, Spring Cloud Config) 获取执行的服务器列表, 然后只在这些服务器上执行定时任务.
- 存在问题: 配置中心需要实现动态获取服务器列表的功能, 并通知定时任务程序, 增加了系统复杂度.
- 分布式锁
- 定时任务程序获取分布式锁, 只有获得锁的线程才可以执行定时任务.
- 存在问题: 锁的获取和释放需要考虑超时和异常情况, 增加了系统复杂度.
锁
- 介绍
- 锁是一种同步机制, 用于控制对共享资源的访问, 防止多个线程同时访问共享资源, 造成数据不一致的问题.
- java 实现锁的方式
- ReentrantLock 和 synchronized 关键字、并发包中的锁类。
- 问题
- 死锁: 多个线程互相等待对方释放锁, 导致一直阻塞, 无法继续运行.
- 性能问题: 锁的获取和释放会影响性能, 降低并发度.
- 只对单个JVM有效: 无法在分布式环境下使用。
- 解决方案
分布式锁
- 介绍
- 分布式锁是一种基于数据库实现的锁, 用于控制对共享资源的访问, 防止多个进程或线程同时访问共享资源, 造成数据不一致的问题.
- 分布式锁的实现依赖于数据库的原子性, 能够保证在分布式环境下, 多个进程或线程对共享资源的访问是原子性的.
- 核心思想
- 所有进程或线程都能获取锁, 只有一个进程或线程能获取到锁, 其他进程或线程只能等待.
- 先来的进程或线程先抢占锁, 先把数据改成自己的标识, 后来的进程或线程发现表示已存在, 就抢锁失败, 直到锁被释放.
- 实现方式
- Mysql: select for update 语句 行级锁(最简单, 但效率低, 不常用)
- Redis: 内存数据库, 读写速度快, 常用
- redis中有 setnx 命令, 可以实现分布式锁, setnx:set if not exists 如果不存在,则设置;只有设置成功才会返回 true,否则返回
false。
- 注意事项
- 及时释放锁: 避免死锁, 避免进程或线程长时间占用锁, 避免进程或线程异常退出导致锁一直被占用.
- 设置过期时间: 避免锁一直被占用, 导致其他进程或线程无法获取锁.
- 及时续期: 避免方法执行时间过长, 锁提前过期(会产生连锁效应, 释放掉其他进程的锁, 会存在多个方法同时执行的情况)
- 保证原子性: 避免多线程并发时, 出现数据不一致的问题.