本文共 11359 字,大约阅读时间需要 37 分钟。
介绍三种方式实现分布式锁:
Redis原生方式实现分布式锁
Redisson实现分布式锁
注解aop的方式加Redisson实现分布式锁(推荐)
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"; }
org.redisson redisson 3.12.0
@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版本可不加// ListclusterNodes = 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
// 实现分布式锁
@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"; }
// 定义注解类
@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默认值是2147483647public 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"; }
转载地址:http://nexxi.baihongyu.com/