在分布式系统中,为了保证数据的一致性,我们通常需要很多的技术方案支持,比如分布式事务、分布式锁等。其中分布式锁主要是为了解决多线程下资源抢占的问题,原理和平常所讲的锁原理基本一致,目的就是确保在多个线程、进程(服务)并发时,只有一个线程、进程(服务)在同一刻操作这个业务。
分布式锁一般有以下三种实现:
基于数据库实现分布式锁;
基于缓存(Redis等)实现分布式锁;
基于Zookeeper实现分布式锁
本次我们重点讨论基于redis的分布式锁实现。
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),从而让使用者能够将精力更集中地放在处理业务逻辑上。
关于Redisson项目的详细介绍可以在官方网站找到。
以下是Redisson的结构:
Redisson作为独立节点 可以用于独立执行其他节点发布到分布式执行服务 和 分布式调度任务服务 里的远程任务。
Redisson的功能非常强大,这里仅仅用于实现分布式锁,后续我将进一步学习这个框架。
关于Redisson的分布式锁原理(这里借用他人的一张图说明一下):
1、锁机制
线程去获取锁,获取成功, 执行lua脚本,保存数据到redis数据库。
线程去获取锁,获取失败,一直通过while循环尝试获取锁,直到超时获取成功后,执行lua脚本,保存数据到redis数据库。
2、watch dog自动延期机制
如果负责储存这个分布式锁的Redisson节点宕机以后,而且这个锁正好处于锁住的状态时,这个锁会出现锁死的状态。为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定。
3、lua脚本
将业务逻辑进行封装在lua脚本中,然后发送给redis,而且由于redis是单线程的,从而保证业务逻辑执行的原子性。
为了在项目中可以自由引用,我创建了一个独立的common-lock项目,封装分布式锁相关的全部逻辑,目录如下:
DistributedLock和DistributedLockAspect实现了分布式锁注解封装,简化使用
config.*目录实现了自定义配置,由于Redisson的配置略显繁琐,所以进行二次封装,只保留基本配置项
strategy.*目录实现四种Redis的部署连接方式,分别为:单机模式、集群模式、主从模式和哨兵模式
DistributedLockClient为分布式锁工具类
org.redisson
redisson
3.15.5
org.springframework.boot
spring-boot-starter-aop
RedissonProperties
/**
* @description: Redisson配置
*/
@Data
@ConfigurationProperties(prefix = "spring2go.redisson")
public class RedissonProperties {
/**
* redis主机地址,ip:port,多个用逗号(,)分隔
*/
private String address;
/**
* 连接类型
*/
private RedisConnectionType type;
/**
* 密码
*/
private String password;
/**
* 数据库(默认0)
*/
private int database;
/**
* 是否装配redisson配置
*/
private Boolean enabled = true;
}
/**
* @description: Redis连接方式
*/
@Getter
@AllArgsConstructor
public enum RedisConnectionType {
/**
* 单机部署方式(默认)
*/
STANDALONE("standalone", "单机部署方式"),
/**
* 哨兵部署方式
*/
SENTINEL("sentinel", "哨兵部署方式"),
/**
* 集群部署方式
*/
CLUSTER("cluster", "集群方式"),
/**
* 主从部署方式
*/
MASTERSLAVE("masterslave", "主从部署方式");
/**
* 编码
*/
private final String code;
/**
* 名称
*/
private final String name;
}
/**
* @description: Redisson配置构建接口
*/
public interface RedissonConfigStrategy {
/**
* 根据不同的Redis配置策略创建对应的Config
*
* @param redissonProperties
* @return Config
*/
Config createRedissonConfig(RedissonProperties redissonProperties);
/**
* @description 获取redis连接类型
*/
RedisConnectionType getType();
}
/**
* @description: 单例模式
*/
@Slf4j
@Component
public class StandaloneRedissonConfigStrategy implements RedissonConfigStrategy {
@Override
public Config createRedissonConfig(RedissonProperties redissonProperties) {
Config config = new Config();
try {
String address = redissonProperties.getAddress();
String password = redissonProperties.getPassword();
int database = redissonProperties.getDatabase();
String redisAddr = RedissonConstant.REDIS_CONNECTION_PREFIX + address;
config.useSingleServer().setAddress(redisAddr);
config.useSingleServer().setDatabase(database);
if (StringUtils.isNotEmpty(password)) {
config.useSingleServer().setPassword(password);
}
log.info("初始化Redisson单机配置,连接地址:" + address);
} catch (Exception e) {
log.error("单机Redisson初始化错误", e);
e.printStackTrace();
}
return config;
}
@Override
public RedisConnectionType getType() {
return RedisConnectionType.STANDALONE;
}
}
/**
* @description: 哨兵方式Redis连接配置
*/
@Slf4j
@Component
public class SentinelRedissonConfigStrategy implements RedissonConfigStrategy {
@Override
public Config createRedissonConfig(RedissonProperties redissonProperties) {
Config config = new Config();
try {
String address = redissonProperties.getAddress();
String password = redissonProperties.getPassword();
int database = redissonProperties.getDatabase();
String[] addrTokens = address.split(",");
String sentinelAliasName = addrTokens[0];
// 设置redis配置文件sentinel.conf配置的sentinel别名
config.useSentinelServers().setMasterName(sentinelAliasName);
config.useSentinelServers().setDatabase(database);
if (StringUtils.isNotEmpty(password)) {
config.useSentinelServers().setPassword(password);
}
// 设置哨兵节点的服务IP和端口
for (int i = 1; i < addrTokens.length; i++) {
config.useSentinelServers().addSentinelAddress(RedissonConstant.REDIS_CONNECTION_PREFIX + addrTokens[i]);
}
log.info("初始化哨兵方式Config,redisAddress:" + address);
} catch (Exception e) {
log.error("哨兵Redisson初始化错误", e);
e.printStackTrace();
}
return config;
}
@Override
public RedisConnectionType getType() {
return RedisConnectionType.SENTINEL;
}
}
/**
* @description: 主从方式Redisson配置
*/
@Slf4j
@Component
public class MasterslaveRedissonConfigStrategy implements RedissonConfigStrategy {
@Override
public Config createRedissonConfig(RedissonProperties redissonProperties) {
Config config = new Config();
try {
String address = redissonProperties.getAddress();
String password = redissonProperties.getPassword();
int database = redissonProperties.getDatabase();
String[] addrTokens = address.split(",");
String masterNodeAddr = addrTokens[0];
// 设置主节点ip
config.useMasterSlaveServers().setMasterAddress(masterNodeAddr);
if (StringUtils.isNotEmpty(password)) {
config.useMasterSlaveServers().setPassword(password);
}
config.useMasterSlaveServers().setDatabase(database);
// 设置从节点,移除第一个节点,默认第一个为主节点
List slaveList = new ArrayList<>();
for (String addrToken : addrTokens) {
slaveList.add(RedissonConstant.REDIS_CONNECTION_PREFIX + addrToken);
}
slaveList.remove(0);
config.useMasterSlaveServers().addSlaveAddress((String[]) slaveList.toArray());
log.info("初始化主从方式Config,redisAddress:" + address);
} catch (Exception e) {
log.error("主从Redisson初始化错误", e);
e.printStackTrace();
}
return config;
}
@Override
public RedisConnectionType getType() {
return RedisConnectionType.MASTERSLAVE;
}
}
/**
* @description: 集群方式Redisson配置
*/
@Slf4j
@Component
public class ClusterRedissonConfigStrategy implements RedissonConfigStrategy {
@Override
public Config createRedissonConfig(RedissonProperties redissonProperties) {
Config config = new Config();
try {
String address = redissonProperties.getAddress();
String password = redissonProperties.getPassword();
String[] addrTokens = address.split(",");
// 设置集群(cluster)节点的服务IP和端口
for (int i = 0; i < addrTokens.length; i++) {
config.useClusterServers().addNodeAddress(RedissonConstant.REDIS_CONNECTION_PREFIX + addrTokens[i]);
if (StringUtils.isNotEmpty(password)) {
config.useClusterServers().setPassword(password);
}
}
log.info("初始化集群方式Config,连接地址:" + address);
} catch (Exception e) {
log.error("集群Redisson初始化错误", e);
e.printStackTrace();
}
return config;
}
@Override
public RedisConnectionType getType() {
return RedisConnectionType.CLUSTER;
}
}
自定义策略接口,分别实现四种Redis连接方式:
单机模式部署
集群模式部署
主从模式部署
哨兵模式部署
然后通过工厂模式动态初始化。
/**
* @description: Redisson连接方式配置工厂
*/
public class RedissonConfigFactory {
private Map redissonConfigStrategys;
public RedissonConfigFactory() {
redissonConfigStrategys = new HashMap<>();
Map strategys = SpringContextHolder.getApplicationContext().getBeansOfType(RedissonConfigStrategy.class);
strategys.forEach((k, v) -> {
redissonConfigStrategys.put(v.getType().getCode(), v);
});
}
public RedissonClient createRedissonClient(RedissonProperties redissonProperties) {
RedisConnectionType connectionType = redissonProperties.getType();
RedissonConfigStrategy redissonConfigStrategy = redissonConfigStrategys.get(connectionType.getCode());
Config config = redissonConfigStrategy.createRedissonConfig(redissonProperties);
RedissonClient redissonClient = Redisson.create(config);
return redissonClient;
}
}
/**
* @description: 基于Redisson实现分布式锁
*/
@Slf4j
@RequiredArgsConstructor
public class DistributedLockClient {
private final RedissonClient redissonClient;
/**
* 获取锁
*/
public RLock getLock(String lockKey) {
return redissonClient.getLock(lockKey);
}
/**
* 加锁操作
*
* @return boolean
*/
public boolean tryLock(String lockName, long expireSeconds) {
return tryLock(lockName, 0, expireSeconds);
}
/**
* 加锁操作
*
* @return boolean
*/
public boolean tryLock(String lockName, long waitTime, long expireSeconds) {
RLock rLock = getLock(lockName);
boolean getLock = false;
try {
getLock = rLock.tryLock(waitTime, expireSeconds, TimeUnit.SECONDS);
if (getLock) {
log.info("获取锁成功,lockName={}", lockName);
} else {
log.info("获取锁失败,lockName={}", lockName);
}
} catch (InterruptedException e) {
log.error("获取式锁异常,lockName=" + lockName, e);
getLock = false;
}
return getLock;
}
/**
* 锁lockKey
*
* @param lockKey
* @return
*/
public RLock lock(String lockKey) {
RLock lock = getLock(lockKey);
lock.lock();
return lock;
}
/**
* 锁lockKey
*
* @param lockKey
* @param leaseTime
* @return
*/
public RLock lock(String lockKey, long leaseTime) {
RLock lock = getLock(lockKey);
lock.lock(leaseTime, TimeUnit.SECONDS);
return lock;
}
/**
* 解锁
*
* @param lockName 锁名称
*/
public void unlock(String lockName) {
redissonClient.getLock(lockName).unlock();
}
}
/**
* @description: Redisson分布式锁注解
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DistributedLock {
/**
* 锁Key
*
* @return
*/
String lockKey() default "";
/**
* 锁超时时间,默认30000毫秒
*
* @return int
*/
long expireSeconds() default 30000L;
/**
* 等待加锁超时时间,默认10000毫秒 -1 则表示一直等待
*
* @return int
*/
long waitTime() default 10000L;
}
/**
* @description: 分布式锁解析器
*/
@Slf4j
@Aspect
@RequiredArgsConstructor
public class DistributedLockAspect {
private final RedissonClient redissonClient;
@SneakyThrows
@Around("@annotation(distributedLock)")
public Object around(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) {
Object obj = null;
log.info("进入RedisLock环绕通知...");
String lockKey = distributedLock.lockKey();
RLock rLock = redissonClient.getLock(lockKey);
boolean res = false;
//获取超时时间
long expireSeconds = distributedLock.expireSeconds();
//等待多久,n秒内获取不到锁,则直接返回
long waitTime = distributedLock.waitTime();
//执行aop
if (rLock != null) {
try {
if (waitTime == -1) {
res = true;
//一直等待加锁
rLock.lock(expireSeconds, TimeUnit.MILLISECONDS);
} else {
res = rLock.tryLock(waitTime, expireSeconds, TimeUnit.MILLISECONDS);
}
if (res) {
obj = joinPoint.proceed();
} else {
log.error("获取锁异常");
}
} finally {
if (res) {
rLock.unlock();
}
}
}
log.info("结束RedisLock环绕通知...");
return obj;
}
}
/**
* @description: Redisson自动配置
*/
@Configuration
@ConditionalOnClass(RedissonProperties.class)
@EnableConfigurationProperties(RedissonProperties.class)
@Slf4j
//自动装载连接策略
@ComponentScan(basePackages = {"com.spring2go.common.lock.strategy"})
public class RedissonAutoConfiguration {
@Bean
@ConditionalOnMissingBean(RedissonClient.class)
public RedissonClient redissonClient(RedissonProperties redissonProperties) {
RedissonConfigFactory redissonConfigFactory = new RedissonConfigFactory();
log.info("RedissonManager初始化完成,当前连接方式:" + redissonProperties.getType() + ",连接地址:" + redissonProperties.getAddress());
return redissonConfigFactory.createRedissonClient(redissonProperties);
}
@Bean
public DistributedLockClient distributeLockClient(RedissonClient redissonClient) {
return new DistributedLockClient(redissonClient);
}
@Bean
public DistributedLockAspect distributedLockAspect(RedissonClient redissonClient) {
return new DistributedLockAspect(redissonClient);
}
}
以上就是整个模块的代码逻辑,代码就是最好的说明。
主动声明lock
public void lockDecreaseStock() throws InterruptedException {
distributedLockClient.tryLock("lock", 10);
if (stock > 0) {
stock--;
}
Thread.sleep(50);
log.info("当前库存:" + stock);
distributedLockClient.unlock("lock");
}
使用注解
@DistributedLock(lockKey="lock",expireSeconds = 10)
public void lockDecreaseStock() {
if (stock > 0) {
stock--;
}
Thread.sleep(50);
log.info("当前库存:" + stock);
}
redisson wiki
Redisson实现分布式锁