目录
1. 控制定时任务执行
2. 买票场景
3. 需求:控制定时任务 / 需要加锁的任务在同一时间只能有一台服务器执行
以定时任务为例
1.?分离定时任务程序和主程序,只在一台服务器运行定时任务=>成本太大
2.?写死配置,每个服务器都执行定时任务,但是只有 IP?符合配置的服务器才真实执行业务逻辑,其他的直接返回。成本最低;但是我们的 IP 可能是不固定的,把 IP 写的太死了
3.?动态配置,配置是可以轻松的、很方便地更新的(代码无需重启,项目无需重新部署),但是只有 IP?符合配置的服务器才真实执行业务逻辑。
读取数据库中的配置
Redis
配置中心(Nacos、Apollo、Spring Cloud Config)
问题:服务器多了、IP 不可控还是很麻烦,还是要人工修改
4.?分布式锁:只有抢到锁的服务器才能执行业务逻辑
1. Java 实现同步锁:synchronized 关键字
2. 锁存在 JVM 中,每台 JVM 独立,不共享锁,多机部署锁会失效(多个线程都会获取到不同 JVM 中的同一名称的锁)
3. 单机就会存在单点故障
1. 为什么需要分布式锁?
2. 如何实现分布式锁?
实现分布式锁的核心思想 /?怎么保证同一时间只有一台服务器能抢到锁?
- 先来的人先把数据改成自己的标识(服务器 IP),后来的人发现标识已存在,就抢锁失败,继续等待
- 等先来的人执行方法结束,把标识清空(释放锁),其他的人继续抢锁
1. set nx ex
2. 释放锁
3. 误删锁
4. 解决误删锁
5. 改进锁之后仍然存在问题:判断锁和释放锁的原子性问题
Github:https://github.com/redisson/redisson
官网:Redisson: Easy Redis Java client with features of In-Memory Data Grid
1. 定义
2. 自己编写 Redisson 的配置,创建 RedissonClient
/**
* Redisson 配置
* @author 乐小鑫
* @version 1.0
* @Date 2024-01-21-15:44
*/
@Configuration
@ConfigurationProperties(prefix = "spring.redis")
@Data
public class RedissonConfig {
private String host;
private String port;
private String password;
@Bean
public RedissonClient getRedissonClient() {
// 1. 创建配置
Config config = new Config();
String redisAddress = String.format("redis://%s:%s", host, port);
config.useSingleServer().setAddress(redisAddress).setPassword(password).setDatabase(3);
// 2. 创建 Redisson 客户端实例并返回
RedissonClient redisson = Redisson.create(config);
return redisson;
}
}
3. 测试 Redisson 的功能实现
/**
* @author 乐小鑫
* @version 1.0
* @Date 2024-01-21-15:53
*/
@SpringBootTest
public class RedissonTest {
@Resource
private RedissonClient redissonClient;
@Test
void test() {
// list
List<String> list = new ArrayList<>();
list.add("ghost");
System.out.println("List:" + list.get(0));
RList<Object> rList = redissonClient.getList("test-list");
rList.add("ghost");
System.out.println("rList:" + rList.get(0));
}
}
?
1. getLock():获取 Redisson 的锁对象,需要指定锁的名称
2. tryLock():尝试获取锁(分布式锁),获取成功返回 true,可以指定重试获取锁的等待时间和锁的释放时间
3. unlock():释放锁,放到 finally 语句块中执行,如果 try 语句块中的内容出现异常,也会释放锁,避免发生死锁的情况
/**
* 缓存预热定时任务
* @author 乐小鑫
* @version 1.0
*/
@Component
@Slf4j
public class PreCacheUser {
@Resource
private RedisTemplate redisTemplate;
@Resource
private UserService userService;
@Resource
private RedissonClient redissonClient;
List<Long> mainUserList = Arrays.asList(3L);// 重要用户列表,为该列表的用户开启缓存预热
@Scheduled(cron = "0 59 21 ? * * ")// 每天 21:59 执行定时任务进行用户数据缓存预热
public void doPreCacheUser() {
// 获取锁对象
RLock lock = redissonClient.getLock("langhua:precachejob:doprecache:lock");
try {
if (lock.tryLock(0,30000L,TimeUnit.MILLISECONDS)) {
log.info("get redisson lock" + Thread.currentThread().getId());
// 查出用户存到 Redis 中
for (Long userId : mainUserList) {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
Page<User> userPage = userService.page(new Page<>(1, 20), queryWrapper);// 查询所有用户
String key = String.format("langhua:user:recommend:%s", userId);
ValueOperations valueOperations = redisTemplate.opsForValue();
// 将查询出来的数据写入缓存
try {
valueOperations.set(key,userPage,24, TimeUnit.HOURS);
} catch (Exception e) {
log.error("redis key set error", e);
}
}
}
} catch (InterruptedException e) {
log.error("redisson precache user error", e);
} finally {
// 释放锁
log.info("redisson unlock" + Thread.currentThread().getId());
lock.unlock();
}
}
}