在多机环境下要想控制某段程序所需要的执行机子需要给定义一个或者多个锁。来让不同的机子去抢锁只有抢到锁的程序才执行。例如定时任务插入数据,如果没有锁那么原本只需要插入100条数据在3台服务器上就会插入300条。

方法类似于多线程的Lock但是多线程实在本地一台服务器执行,分布式锁是在多台服务器执行。本地一台的Lock无法被其他的服务器识别到

解决方法:用数据库设置一个字段例如lock,当一个程序进入判断这个字段是否为空,如果为空那么就填入字段执行完毕后释放。

用Redis实现

注意事项:

1.用完锁要释放

2.锁要加过期时间(如果程序异常终止了,那么字段就会一直存在)。

3.如果方法执行时间太长超过了锁设置的过期时间

​ 问题:

​ 1.提前释放之后下一个服务提前进来,但程序还未执行结束.

​ 2.连锁效应:第二个方法进来运行时第一个方法执行结束delete了锁此时锁的value为空,第 三个服务也进来了。

​ 解决方案:

​ 续期 :在方法执行过程中给其续期。

4.释放锁时,会先判断是否为自己的锁,再进行删除,但是判断完是否是自己的锁时锁过期,此时再进来一个B方法的锁,执行delete方法会把B删除。

​ 解决方法:使用原子操作:Redis+lua脚本。

可以使用Redisson解决以上问题

Redisson 实现分布锁

Redisson 是一个java操作redis的客户端,提供了大量的分布式数据集来简化对Redis的操作和使用,让开发者像使用本地集合一样使用Redis,完全感受不到Redis存在

Redisson配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.example.demo.configuration;

import lombok.Data;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* @className: RedissonConfig
* @author: ZH
* @date: 2024/12/1 16:32
* @Version: 1.0
* @description:
*/
@Data
@Configuration
@ConfigurationProperties("spring.redis")
public class RedissonConfig {
private String host;
private String port;
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
String redisAddress = String.format("redis://%s:%s", host, port);
config.useSingleServer().setAddress(redisAddress).setDatabase(3);
// Sync and Async API
RedissonClient redisson = Redisson.create(config);
return redisson;
}
}

点击并拖拽以移动

看门狗 —watch dog

redisson解决了程序运行时间过长导致value过期问题,利用watch dog看门狗机制隔一段时间重新生成一段新的锁

Redisson 的 Watch Dog机制_redisson 检查锁的间隔-CSDN博客

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
    @Scheduled(cron = "0 25 * * * *")
public void doCacheRecommendUser(){
RLock lock = redissonClient.getLock("zh:preCacheJob:doCache:lock");

try {
/*-1代表releasetime 默认30s时效每隔10s检测一次检测到还在执行就重置回30s如果加了时间,那么他不会自动刷新*/
if(lock.tryLock(0,-1,TimeUnit.MILLISECONDS)){
System.out.println("定时任务执行");
// sleep(1000000);
importantUserIds.forEach(id->{

String strKey = String.format("user:recommend:%d",id);
ValueOperations valueOperations = redisTemplate.opsForValue();
QueryWrapper queryWrapper = new QueryWrapper<>();
Page<User> userList1 = (Page<User>) userService.page(new Page<>( 1,20),queryWrapper);
try {
valueOperations.set(strKey,userList1,300000, TimeUnit.MILLISECONDS);

}catch (Exception e) {
log.error("redis存储失败",e);
}
});
}
} catch (InterruptedException e) {
log.error("doCacheRecommendUser error:",e);
throw new RuntimeException(e);
}finally {
//释放自己的锁
if(lock.isHeldByCurrentThread()){
lock.unlock();
}
}
}

点击并拖拽以移动