由于使用for update来实现分布式锁在高并发时,会造成数据库压力过大,所以可以使用redis的setNX特性来实现分布式锁。
首先,分布式锁和我们平常讲到的锁原理基本一样,目的就是确保在多个线程并发时,只有一个线程在同一刻操作这个业务或者说方法、变量。
在一个进程中,也就是一个jvm或者说应用中,我们很容易去处理控制,在java.util并发包中已经为我们提供了这些方法去加锁,比如synchronized关键字或者Lock锁,都可以处理。
但是如果在分布式环境下,要保证多个线程同时只有1个能访问某个资源,就需要用到分布式锁。这里我们将介绍用Redis的setnx命令来实现分布式锁。
其实目前通常所说的setnx命令,并非单指redis的setnx key value这条命令,这条命令可能会在后期redis版本中删除。
一般代指redis中对set命令加上nx参数进行使用,set这个命令,目前已经支持这么多参数可选:
SET key value [EX seconds] [PX milliseconds] [NX|XX]
从 Redis 2.6.12 版本开始, SET 命令的行为可以通过一系列参数来修改:
- EX second :设置键的过期时间为 second 秒。 SET key value EX second 效果等同于 SETEX key second value 。
- PX millisecond :设置键的过期时间为 millisecond 毫秒。 SET key value PX millisecond 效果等同于 PSETEX key millisecond value 。
- NX :只在键不存在时,才对键进行设置操作。 SET key value NX 效果等同于 SETNX key value 。
- XX :只在键已经存在时,才对键进行设置操作。
实战
根据上述原理,编写分布式锁
@Slf4jpublic class RedisLock implements AutoCloseable {private RedisTemplate redisTemplate;private String key;private String value;//单位:秒private int expireTime;public RedisLock(RedisTemplate redisTemplate,String key,int expireTime){this.redisTemplate = redisTemplate;this.key = key;this.expireTime=expireTime;this.value = UUID.randomUUID().toString();}/*** 获取分布式锁* @return*/public boolean getLock(){RedisCallback<Boolean> redisCallback = connection -> {//设置NXRedisStringCommands.SetOption setOption = RedisStringCommands.SetOption.ifAbsent();//设置过期时间Expiration expiration = Expiration.seconds(expireTime);//序列化keybyte[] redisKey = redisTemplate.getKeySerializer().serialize(key);//序列化valuebyte[] redisValue = redisTemplate.getValueSerializer().serialize(value);//执行setnx操作Boolean result = connection.set(redisKey, redisValue, expiration, setOption);return result;};//获取分布式锁Boolean lock = (Boolean)redisTemplate.execute(redisCallback);return lock;}public boolean unLock() {String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +" return redis.call(\"del\",KEYS[1])\n" +"else\n" +" return 0\n" +"end";RedisScript<Boolean> redisScript = RedisScript.of(script,Boolean.class);List<String> keys = Arrays.asList(key);Boolean result = (Boolean)redisTemplate.execute(redisScript, keys, value);log.info("释放锁的结果:"+result);return result;}@Overridepublic void close() throws Exception {unLock();}}
@RequestMapping("redisLock")public String redisLock(){log.info("我进入了方法!");try (RedisLock redisLock = new RedisLock(redisTemplate,"redisKey",30)){if (redisLock.getLock()) {log.info("我进入了锁!!");Thread.sleep(15000);}} catch (InterruptedException e) {e.printStackTrace();} catch (Exception e) {e.printStackTrace();}log.info("方法执行完成");return "方法执行完成";}
通过定时任务(spring-task)集群部署校验编写的分布式锁

说明:哪个服务获取锁,就哪个服务执行任务A,来解决任务A重复执行的问题。
@Service@Slf4jpublic class SchedulerService {@Autowiredprivate RedisTemplate redisTemplate;@Scheduled(cron = "0/5 * * * * ?")public void sendSms(){try(RedisLock redisLock = new RedisLock(redisTemplate,"autoSms",30)) {if (redisLock.getLock()){log.info("向138xxxxxxxx发送短信!");}} catch (Exception e) {e.printStackTrace();}}}

