由于使用for update来实现分布式锁在高并发时,会造成数据库压力过大,所以可以使用redis的setNX特性来实现分布式锁。
封装redis分布式锁
package com.example.distributelock.lock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.core.types.Expiration;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
@Slf4j
public class RedisLock implements AutoCloseable {
private RedisTemplate redisTemplate;
private String key;
private String value;
//单位:秒
private int expireTime;
/**
*
* @param redisTemplate
* @param key
* @param 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 -> {
//设置NX
RedisStringCommands.SetOption setOption = RedisStringCommands.SetOption.ifAbsent();
//设置过期时间
Expiration expiration = Expiration.seconds(expireTime);
//序列化key
byte[] redisKey = redisTemplate.getKeySerializer().serialize(key);
//序列化value
byte[] redisValue = redisTemplate.getValueSerializer().serialize(value);
//执行setnx操作(获取锁的操作)
Boolean result = connection.set(redisKey, redisValue, expiration, setOption);
// 也可以通过这种方式:
// Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(redisKey, redisValue);
return result;
};
//获取分布式锁
Boolean lock = (Boolean)redisTemplate.execute(redisCallback);
return lock;
}
/**
* 释放分布式锁
* @return
*/
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;
}
@Override
public void close() throws Exception {
unLock();
}
}
实现AutoCloseable接口并重写close()方法实现释放锁的操作,这样就不需要在finally中去释放锁。
在controller中使用redis分布式锁
@Autowired
private RedisTemplate redisTemplate;
@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 "方法执行完成";
}
直接将实例化RedisLock写在try的括号中,这是java1.7之后的特性,如果获得锁就进入锁,然后sleep模拟业务操作。
解决定时任务重复执行的问题
package com.example.distributelock.service;
import com.example.distributelock.lock.RedisLock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class SchedulerService {
@Autowired
private 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();
}
}
}