更新于:2024-04-07 22:10
一、分布式锁常见需求
锁的功能在于确保资源的合理竞争,而分布式锁则是为了处理不同服务器之间进程对资源的竞争情况。功能场景有避免重复操作(订单多次支付)、防止并发竞态条件(账户进行余额扣除)、保证数据一致性(控制业务顺序流程)、定时任务调度(执行一次)等等。这里我们通过介绍“定时任务调度”场景来介绍分布锁的常见方案。
二、分布式锁常见方案
不知道大家第一次写定时任务的时候,会不会犯个错误。就是定时任务被部署在不同的服务器上的服务一起执行(被多分片服务执行)。如果是查询类型的任务可能影响不大,但是如果是修改类型任务(涉及到文件、数据库等)时,可能就有引起较为严重的问题。
1、redis/zookeeper
一般推荐使用中间件提供的分布式锁,来保证只有一个服务去执行定时任务,例如使用redis
,在执行任务前先去获取设置一个key
,谁设置成功让谁继续执行下去,失败则直接结束当前任务。
set key value nx ex duration
# nx 表示只有在键不存在时才设置成功,ex 表示要给键的设置过期时间
但是可能会有一些问题,如果定时任务准备启动时,redis
出现故障如何处理?简单方案是将key
的过期时间设置为半天或者一天,然后定时任务设置@Scheduled(cron = "0 0 1,2,3 * * ?")
成当天执行多次,如果前面有执行成功的话,因为key
的存在就不会再执行了。如果redis
宕机一夜都没处理好,那确实尴尬了。
同理用redis
设置分布式锁,也能用zookeeper
设置,客户端通过zookeeper
在指定路径下创建一个临时顺序节点,并获取所有子节点列表。如果客户端创建的节点是当前节点列表中序号最小的节点,则代表该客户端获取到了锁。
//<dependency>
// <groupId>org.apache.curator</groupId>
// <artifactId>curator-recipes</artifactId>
// <version>5.3.0</version>
//</dependency>
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
public class ZookeeperLockExample {
private static final String ZK_CONNECTION_STRING = "127.0.0.1:2181";
private static final String LOCK_PATH = "/mylock";
public static void main(String[] args) throws Exception {
CuratorFramework client = CuratorFrameworkFactory.newClient(ZK_CONNECTION_STRING, new ExponentialBackoffRetry(1000, 3));
client.start();
InterProcessMutex lock = new InterProcessMutex(client, LOCK_PATH);
try {
// 尝试获取锁
lock.acquire();
// 任务处理
} finally {
// 释放锁
lock.release();
}
client.close();
}
}
2、绑定机器
也有一些其他的特殊需求,可能需要在特定的机器上面运行对应的定时任务(仅授权某个机器有权限访问相关服务等)。此时就需要硬编码(后续可通过Apollo或Nacos等配置中心控制),定时任务增加判断读取服务器特殊标识符(ip地址等),符合条件才可以执行。保证服务器上运行的服务实例(分片)只有一个,保证在定时任务执行时对应的机器不会出现故障。
3、数据库锁
不使用redis
或zk
的分布式锁,而是用数据库的行锁帮忙。一般不推荐使用这个方案,除非是没有外部中间件可用的话才考虑。建一张表,只有一行数据。switch 字段表示开关,决定是否开启定时任务,start_time 字段表示最近一次定时任务开始执行的时间,status 字段表示是否加锁防止其他服务器再执行,表结构如下。
table_name: schedule_status
id switch start_time status
1 1 2020/10/11 21:08:00 1
假设有两个事务,分别是A和B
事务A:
开始事务;
select * from schedule_status where id=1 for update;
update schedule_status set status=0 where id=1;
提交事务;
事务B:
开始事务;
select * from schedule_status where id=1 for update;
update schedule_status set status=0 where id=1;
提交事务;
如果事务A先执行了 select,即使事务B也 select 到了 status=1,但是事务A通过for update
把该行数据锁住(排他锁),事务B只能查不能改。等事务A把status改为0,事务B则不能执行本次定时任务,等待下次时间点再争夺。至于start_time字段是防止某台服务器在执行完定时任务之后在恢复status为1时出现了故障,所以需要每次执行定时任务时,遇到status为0之后,再通过当前时间-start_time是否超时30min左右,如果超时我们可以认为上次执行定时任务的服务器宕机之类的,此时我们接着返回true让先发现这个故障的服务器先执行,并且同时更新一下对应start_time。这样即使节点挂掉,也不影响下一次定时任务的执行。
三、小结
本篇文件通过“定时任务调度”的业务场景来介绍分布式锁的常用方案,希望大家在开发过程中遇到分布式场景时能做好应对策略。
北部白犀牛🦏
北部白犀牛和南部白犀牛同属白犀亚种,与非洲南部的白犀在基因上存在较大差异。2018年3月19日,世界上最后一
头雄性北方白犀牛“苏丹”在肯尼亚去世,终年45岁。尽管犀牛角的交易在全球范围内被禁止,但在黑市内仍然热火朝
天,在也门就有专门的犀牛角市场,在那里以犀牛角制成手柄的匕首是众多买家和卖家关注的焦点,是身份的象征。
利益熏心的偷猎者每年都大量猎杀这些珍贵的白犀,而面对偷猎猖獗,非洲国家由于落后的经济技术无暇应对,这些
问题已经导致北白犀成为即将灭绝在现代文明面前的大型动物。