Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。其中包括(BitSet
, Set
, Multimap
, SortedSet
, Map
, List
, Queue
, BlockingQueue
, Deque
, BlockingDeque
, Semaphore
, Lock
, AtomicLong
, CountDownLatch
, Publish / Subscribe
, Bloom filter
, Remote service
, Spring cache
, Executor service
, Live Object service
, Scheduler service
) Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。
地址https://github.com/redisson/redisson/wiki/
使用场景简介:多实例部署的应用,为避免定时任务的重复执行,在同一个定时任务设定的时间节点要保证只有一个实例在执行,这时就需要用到分布式锁,Redisson提供了分布式锁的实现,本文Redis单机部署。
依赖如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>distlock-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>distlock-demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--MyBatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<!-- MyBatis 生成器 -->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.3</version>
</dependency>
<!--MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--Redisson -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.11.1</version>
</dependency>
<!-- Druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.16</version>
</dependency>
<!-- Swagger-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置文件
server:
port: 9010
spring:
application:
name: demo
redis:
database: 0
host: 192.168.135.162
port: 6379
password:
datasource:
url: jdbc:mysql://localhost:3306/dist?serverTimezone=Asia/Shanghai&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapper-locations: classpath:mapper/*.xml
使用MySQL记录锁的获取情况
表的SQL脚本
CREATE TABLE `count` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`count` int(11) NOT NULL DEFAULT 0,
`create_time` datetime(0) NULL DEFAULT NULL,
`modify_time` datetime(0) NULL DEFAULT NULL,
`app_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
Redisson配置类
package com.example.distlockdemo.config;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RedissonConfig {
@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.port}")
private String redisPort;
@Value("${spring.redis.database}")
private int dataBase;
@Bean
public RedissonClient redissonClient(){
Config config = new Config();
config.useSingleServer().setAddress("redis://" + redisHost + ":" + redisPort)
.setDatabase(dataBase);
RedissonClient redissonClient = Redisson.create(config);
return redissonClient;
}
}
对Redisson提供的分布式锁做简单的封装,这里使用Redisson的公平锁
package com.example.distlockdemo.lock;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
@Component
public class RedissonLock {
private static final Logger log = LoggerFactory.getLogger(RedissonLock.class);
private static final String PREFIX = "DIST_LOCK_";
@Autowired
private Environment environment;
@Autowired
private RedissonClient redissonClient;
/**
* 获取锁
* @param suffixName
* @return
*/
public boolean lock(String suffixName){
String name = PREFIX + suffixName;
RLock lock = redissonClient.getFairLock(name);
boolean flag = lock.tryLock();
if(flag == true){
log.info("实例{}获取分布式锁{}成功", environment.getProperty("spring.application.name"), name);
}
return flag;
}
/**
* 释放锁
* @param suffixName
*/
public void unlock(String suffixName){
String name = PREFIX + suffixName;
RLock lock = redissonClient.getFairLock(name);
//只有持有该锁的线程才能释放该锁,否则抛出IllegalMonitorStateException异常
if(lock.isHeldByCurrentThread()){
lock.unlock();
log.info("分布式锁{}已被实例{}释放", name, environment.getProperty("spring.application.name"));
}
}
}
使用定时任务进行测试
package com.example.distlockdemo.schedule;
import com.example.distlockdemo.lock.RedissonLock;
import com.example.distlockdemo.service.CountService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class ScheduledTask {
private static final Logger log = LoggerFactory.getLogger(ScheduledTask.class);
private static String LOCK_PREFIX = "TASK_SCHEDULE";
@Autowired
private RedissonLock redissonLock;
@Autowired
private CountService countService;
@Scheduled(cron = "0/10 * * * * ?")
public void Scheduled() {
try {
boolean flag = redissonLock.lock(LOCK_PREFIX);
if (flag) {
Integer count = countService.getMaxCount() + 1;
log.info("第{}次定时任务开始执行", count);
TimeUnit.SECONDS.sleep(60);
log.info("第{}次定时任务执行完毕", count);
countService.addCount(count);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
redissonLock.unlock(LOCK_PREFIX);
}
}
}
使用IDEA开启该程序的两个实例,观察运行结果
实例1
实例2
运行结果符合预期,每一个定时任务的执行时间几点只有一个实例在执行