文章目录
redis 实现分布式锁
介绍三种方式实现分布式锁:
-
Redis原生方式实现分布式锁
-
Redisson实现分布式锁
-
注解aop的方式加Redisson实现分布式锁(推荐)
一、redis原生方式实现分布式锁
Redis 单线程
SETNX (set if not exists)
setnx key value 若key存在则添加失败,若key不存在才会添加存在
redisTempalte.setIfAbsetn(key,value,time) 设置 key 和 value 和 超时时间
try {
// todo 业务
} finally {
// get 值,若 value 与 设置的value相等 则 删除key
}
简单的实现分布式锁:
// 这里使用到了 springboot集成Redis 用到了springboot的 StringRedisTemplate
@GetMapping("/transaction")
public String transaction() {
// 锁的键值
String lockKey = "orderLock";
// 设置客户端id
String clientId = UUID.randomUUID().toString();
try {
// 设置锁 如果redis中已经存在 lockKey 则会添加失败
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS);
if (result == null || !result) {
return "error";
}
String stock = stringRedisTemplate.opsForValue().get("stock");
if (StrUtil.isEmpty(stock)) {
return ">>>>>>>> 数据异常请稍后重试";
}
int number = Integer.parseInt(stock);
if (number > 0) {
Long lastNumber = stringRedisTemplate.opsForValue().decrement("stock");
System.out.println(">>>>>>>>>>> 扣除库存成功,剩余:" + lastNumber);
} else {
System.out.println(">>>>>>>>>>> 扣除库存失败,库存不足");
}
} finally {
// 如果客户端id相等
if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
stringRedisTemplate.delete(lockKey);
}
}
return "success";
}
二、Redisson实现分布式锁
1.导入jar包
<!-- redisson -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.12.0</version>
</dependency>
2.设置连接配置
@Getter
@Setter
@ConfigurationProperties(prefix="redisson")
public class RedissonProperties {
private int timeout = 3000;
private String address;
private String password;
private int connectionPoolSize = 64;
private int connectionMinimumIdleSize=10;
private int slaveConnectionPoolSize = 250;
private int masterConnectionPoolSize = 250;
private String[] sentinelAddresses;
private String masterName;
}
// 自动装配
@Configuration
@ConditionalOnClass(Config.class)
@EnableConfigurationProperties(RedissonProperties.class)
public class RedissonAutoConfiguration {
@Autowired
private RedissonProperties redssionProperties;
// import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
// @Autowired
// private RedisProperties redisProperties;
/**
* 哨兵模式自动装配
* # 哨兵模式
* #redisson:
* # master-name: mymaster
* # password:
* # sentinel-addresses: 10.47.91.83:26379,10.47.91.83:26380,10.47.91.83:26381
* @return
*/
// @Bean
// @ConditionalOnProperty(name="redisson.master-name")
// RedissonClient redissonSentinel() {
// Config config = new Config();
// SentinelServersConfig serverConfig = config.useSentinelServers().addSentinelAddress(redssionProperties.getSentinelAddresses())
// .setMasterName(redssionProperties.getMasterName())
// .setTimeout(redssionProperties.getTimeout())
// .setMasterConnectionPoolSize(redssionProperties.getMasterConnectionPoolSize())
// .setSlaveConnectionPoolSize(redssionProperties.getSlaveConnectionPoolSize());
//
// if(StrUtil.isNotBlank(redssionProperties.getPassword())) {
// serverConfig.setPassword(redssionProperties.getPassword());
// }
// return Redisson.create(config);
// }
/**
* 单机模式自动装配
* @return
*/
@Bean
@ConditionalOnProperty(name="redisson.address")
RedissonClient redissonClient() {
Config config = new Config();
SingleServerConfig serverConfig = config.useSingleServer()
.setAddress(redssionProperties.getAddress())
.setTimeout(redssionProperties.getTimeout())
.setDatabase(0)
.setConnectionPoolSize(redssionProperties.getConnectionPoolSize())
.setConnectionMinimumIdleSize(redssionProperties.getConnectionMinimumIdleSize());
if(StrUtil.isNotBlank(redssionProperties.getPassword())) {
serverConfig.setPassword(redssionProperties.getPassword());
}
return Redisson.create(config);
}
}
/**
* redis集群下的配置 cluster
* @return
*/
// @Bean
// public RedissonClient redisson() {
// //redisson版本是3.5,集群的ip前面要加上“redis://”,不然会报错,3.2版本可不加
// List<String> clusterNodes = new ArrayList<>();
// for (int i = 0; i < redisProperties.getCluster().getNodes().size(); i++) {
// clusterNodes.add("redis://" + redisProperties.getCluster().getNodes().get(i));
// }
// Config config = new Config();
// // 采用集群模式
// ClusterServersConfig clusterServersConfig = config.useClusterServers()
// .addNodeAddress(clusterNodes.toArray(new String[clusterNodes.size()]));
// //设置密码 注意这里集群的 redis 密码都必须一致
// clusterServersConfig.setPassword(redisProperties.getPassword());
// return Redisson.create(config);
// }
yaml中的配置文件
# 哨兵模式
#redisson:
# master-name: mymaster
# password:
# sentinel-addresses: 10.47.91.83:26379,10.47.91.83:26380,10.47.91.83:26381
# redis单机模式
redisson:
address: redis://81.69.43.66:6379
password: root
3.编写代码实现分布式锁
// 实现分布式锁
@GetMapping("/redisson")
public String redisson() {
String lockKey = "orderLock";
// 获取锁
RLock lock = redissonClient.getLock(lockKey);
try {
// 加锁
lock.lock();
String stock = stringRedisTemplate.opsForValue().get("stock");
if (StrUtil.isEmpty(stock)) {
return ">>>>>>>> 数据异常请稍后重试";
}
int number = Integer.parseInt(stock);
if (number > 0) {
Long lastNumber = stringRedisTemplate.opsForValue().decrement("stock");
System.out.println(">>>>>>>>>>> 扣除库存成功,剩余:" + lastNumber);
} else {
System.out.println(">>>>>>>>>>> 扣除库存失败,库存不足");
}
} finally {
System.out.println(">>>>>>>>>>>>>>>>> 解除锁定");
// 解锁
lock.unlock();
}
return "success";
}
三、注解aop的方式加Redisson实现分布式锁(推荐)
// 定义注解类
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LockAction {
/**
* 锁的名称
*/
String value() default "";
/**
* 描述
*/
String description() default "";
/**
* 等待锁超时时间,默认 30
* @return
*/
long waitTime() default 30;
/**
* 自动解锁时间,自动解锁时间一定得大于方法执行时间,否则会导致锁提前释放,默认100
* @return
*/
long leaseTime() default 100;
/**
* 时间单位,默认为 秒
* @return
*/
TimeUnit timeUnit() default TimeUnit.SECONDS;
/**
* 默认:可重入锁
* @return
*/
LockTypeEnum lockType() default LockTypeEnum.REENTRANT_LOCK;
}
// 定义AOP
@Slf4j
@Aspect
@Component
@Order(1) //order越小越是最先执行,但更重要的是最先执行的最后结束。order默认值是2147483647
public class LockAspect implements ApplicationContextAware {
private final String LOCK_CODE = "lockCode";
private final RedissonClient redissonClient;
private ApplicationContext applicationContext;
public LockAspect(RedissonClient redissonClient) {
this.redissonClient = redissonClient;
}
/**
* 定义公共的切点
*/
@Pointcut("@annotation(LockAction)")
public void log(){
}
/**
* 环绕通知
* @param joinPoint
* @return
* @throws Throwable
*/
@Around(value = "@annotation(lockAction)")
public Object around(ProceedingJoinPoint joinPoint,LockAction lockAction) throws Throwable {
String code = IdUtil.fastSimpleUUID();
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
String remoteUser = request.getRemoteUser();
log.info(">>>>>>>>>>>> remoteUser 远程用户:{}",remoteUser);
String requestURI = request.getRequestURI();
log.info(">>>>>>>>>>>> requestURI 请求地址:{}",requestURI);
request.setAttribute(LOCK_CODE, code);
RLock lock = redissonClient.getLock(lockAction.value());
Object proceed = null;
try {
// lock.lock(); // 这种方式最稳妥,框架自动续期
// 采用自定义时间的方式,注意时间长短
boolean b = lock.tryLock(lockAction.waitTime(), lockAction.leaseTime(), lockAction.timeUnit());
if (b) {
// 类似于 method.invoke 方法
System.out.println(">>>>>>>>>>>>>>>>>>> 加锁成功");
proceed = joinPoint.proceed();
}
} finally {
lock.unlock();
System.out.println(">>>>>>>>>>>>>>>>>>> 解锁成功");
}
// 第一个参数
// String seckillId = joinPoint.getArgs()[0].toString();
// 方法名
// String name = joinPoint.getSignature().getName();
// Class declaringType = joinPoint.getSignature().getDeclaringType();
// Method[] methods = declaringType.getMethods();
// Object proceed = null;
// for (Method method : methods) {
// if(name.equals(method.getName())){
// // 获得方法上面的注解
// String value = AnnotationUtil
// .getAnnotationValue(method, LockAction.class, "value");
// if (StrUtil.isEmpty(value)){
// throw new RuntimeException("运行时发生异常");
// }
// // 获取锁
// RLock lock = redissonClient.getLock(value);
// try {
// // 加锁,加锁结果
// lock.lock();
// System.out.println(">>>>>>>>>>>>>>>>>>> 枷锁成功");
// // 类似于 method.invoke 方法
// proceed = joinPoint.proceed();
// } finally {
// System.out.println(">>>>>>>>>>>>>>>> 解除锁定");
// lock.unlock();
// }
// }
// }
return proceed;
}
/**
* 异常通知,当目标方法抛出异常时,该方法会被触发
* @param joinPoint
*/
@AfterThrowing(value = "log()",throwing = "e")
public void afterThrowing(JoinPoint joinPoint, Exception e){
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
String code = (String) request.getAttribute(LOCK_CODE);
log.info(">>>>>>>>>> 错误码:{}", code);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
// 分布式锁 注解实战使用
@LockAction(value = "lockey")
@GetMapping("/order/{parameter}")
public String order(@PathVariable("parameter") String parameter) {
String stock = stringRedisTemplate.opsForValue().get("stock");
int number = Integer.parseInt(stock);
if (number > 0) {
Long lastNumber = stringRedisTemplate.opsForValue().decrement("stock");
System.out.println(">>>>>>>>>>> 扣除库存成功,剩余:" + lastNumber);
} else {
System.out.println(">>>>>>>>>>> 扣除库存失败,库存不足");
}
return "success";
}