实体
用于记录锁执行情况
package cn.picclife.authen.system.bean.bo;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Date;
@Data
@Accessors(chain = true)
public class TaskBo {
private String id;
private String name;
private String systemInfo;
private String redisException;
private String mysqlException;
private String otherException;
private Date createTime;
/**
* 3:获取redis锁失败
* 4:获取mysql锁失败
* 1:获取redis锁成功
* 2获取mysql锁成功
* 5:释放redis锁失败
* 6:释放mysql锁失败
* 7:释放redis锁成功
* 8: 释放mysql锁成功
*/
private String status;
public static void main(String[] args) {
try {
Integer j=null;
int i=1/j;
} catch (Exception e) {
System.out.println(e.toString());
}
}
}
基于springboot redis setIfAbsent() 方法的扩展
package cn.picclife.authen.system.distributed;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
@Component
public class CustomRedisUtil {
@Autowired
private StringRedisTemplate stringRedisTemplate;
public boolean setIfAbsent(final String key, final Serializable value, final long exptime) {
Boolean result = (Boolean) stringRedisTemplate.execute(new RedisCallback<Boolean>() {
@Override
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
RedisSerializer valueSerializer = stringRedisTemplate.getValueSerializer();
RedisSerializer keySerializer = stringRedisTemplate.getKeySerializer();
Object obj = connection.execute("set", keySerializer.serialize(key),
valueSerializer.serialize(value),
"NX".getBytes(StandardCharsets.UTF_8),
"EX".getBytes(StandardCharsets.UTF_8),
String.valueOf(exptime).getBytes(StandardCharsets.UTF_8));
return obj != null;
}
});
return result;
}
}
redis获取锁与释放锁的类
package cn.picclife.authen.system.distributed;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.UUID;
/**
* 任务开始结束时间 保存任务记录以及异常,
*/
@Slf4j
@Component
public class RedisDistribute {
@Autowired
private CustomRedisUtil redisUtil;
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
*
*
* @param lockName 锁名称
* @param acquireTimeout redis重试时间 (这个参数可以不用 当去掉while循环和睡眠的时候)
* @param expireTime redis过期时间
* @return
*/
public String lockWithTimeOut(String lockName,long acquireTimeout,long expireTime){
String retIdentifier=null;
String identifier= UUID.randomUUID().toString();
String lockKey="lock:"+lockName;
int expire=(int)expireTime/1000;
long end=System.currentTimeMillis()+acquireTimeout;
while (System.currentTimeMillis()<end){
if(redisUtil.setIfAbsent(lockKey,identifier,expire) ){
retIdentifier=identifier;
return retIdentifier;
}
try{
Thread.sleep(10*1000);
}catch (InterruptedException interruptedException){
Thread.currentThread().interrupt();
}
}
return retIdentifier;
}
/**
* 释放锁 如果上面redis添加了失效时间,其实锁也就不需要主动释放了,不过也可以主动释放
* @param lockName
* @param identify
* @return
*/
public boolean releaseLock(String lockName,String identify){
String lockKey="lock:"+lockName;
boolean retFlag=false;
stringRedisTemplate.watch(lockKey);
while (true){
if(identify.equals(stringRedisTemplate.opsForValue().get(lockKey))){
stringRedisTemplate.delete(lockKey);
retFlag=true;
}
stringRedisTemplate.unwatch();
break;
}
return retFlag;
}
}
package cn.picclife.authen.system.bean.bo;
import cn.picclife.authen.common.utils.SystemUtils;
import lombok.Data;
import java.util.Date;
/**
* 数据库分布式锁 khzsx_lock
*/
@Data
public class LockBo {
public final static String LOCKED_STATUS = "1";
public final static String UNLOCKED_STATUS = "0";
private String id;
/**
* 保证lockname唯一 加主键是一种办法
*/
private String lockName;
private String expireTime;
private String status;
public LockBo(String lockName, String expireTime, String status) {
this.id= SystemUtils.getCompanyTransNo();
this.lockName = lockName;
this.expireTime = expireTime;
this.status = status;
}
}
//mapper 接口
package cn.picclife.authen.system.mapper;
import cn.picclife.authen.system.bean.bo.LockBo;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface LockMapper {
LockBo findByLockName(String lockName);
int deleteByLockName(String lockName);
int updateByLockName(String lockName);
int saveLock(LockBo lockBo);
}
//mapper.xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="cn.picclife.authen.system.mapper.LockMapper">
<select id="findByLockName" resultType="cn.picclife.authen.system.bean.bo.LockBo" >
SELECT id as id, lock_name as lockName,expire_time as expireTime,status as status
FROM khzsx_lock WHERE lock_name= #{lockName} LIMIT 1 for update;
</select>
<delete id="deleteByLockName" parameterType="string" >
DELETE FROM khzsx_lock WHERE lock_name=#{lockName}
</delete>
<update id="updateByLockName" parameterType="string" >
<!--DELETE FROM khzsx_lock WHERE lock_name=#{lockName}-->
update khzsx_lock
<set>
<if test="status!=null and status!='' ">
status = '0'
</if>
</set>
WHERE lock_name=#{lockName}
</update>
<insert id="saveLock" parameterType="cn.picclife.authen.system.bean.bo.LockBo">
INSERT INTO khzsx_lock
(
id,
lock_name,
expire_time,
status
)
values (
#{id},
#{lockName},
#{expireTime},
#{status}
)
on duplicate key update id=id
</insert>
</mapper>
实现mysql锁
package cn.picclife.authen.system.distributed;
import cn.picclife.authen.common.utils.DateUtil;
import cn.picclife.authen.system.bean.bo.LockBo;
import cn.picclife.authen.system.mapper.LockMapper;
import com.alibaba.druid.util.StringUtils;
import com.google.inject.internal.cglib.proxy.$Factory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.Calendar;
import java.util.Date;
import java.util.Objects;
@Component
public class SqlDistribute {
@Autowired
private LockMapper lockMapper;
@Transactional
public String lock(String lockName,Integer expiredSeconds){
if (StringUtils.isEmpty(lockName)) {
throw new NullPointerException();
}
LockBo lockBo=lockMapper.findByLockName(lockName);
/** try{
Thread.sleep(1*1000);
}catch (InterruptedException interruptedException){
Thread.currentThread().interrupt();
}**/
if(Objects.isNull(lockBo)){
lockBo=new LockBo(lockName, DateUtil.parseDateToStr(this.addSeconds(new Date(),expiredSeconds),DateUtil.DATE_TIME_FORMAT_YYYY_MM_DD_HH_MI_SS),LockBo.LOCKED_STATUS);
//此处保证lockname 唯一
if(lockMapper.saveLock(lockBo)>0){
return lockBo.getLockName();
}
}
else{
if(lockBo.getStatus()==LockBo.UNLOCKED_STATUS&&DateUtil.parseStrToDate(lockBo.getExpireTime(),DateUtil.DATE_TIME_FORMAT_YYYY_MM_DD_HH_MI_SS).after(new Date())){
return null;
}
String expiredTime = lockBo.getExpireTime();
Date now = new Date();
if (DateUtil.parseStrToDate(expiredTime,DateUtil.DATE_TIME_FORMAT_YYYY_MM_DD_HH_MI_SS).before(now)) {
lockMapper.deleteByLockName(lockName);
lockBo.setExpireTime(DateUtil.parseDateToStr(this.addSeconds(now, expiredSeconds),DateUtil.DATE_TIME_FORMAT_YYYY_MM_DD_HH_MI_SS));
lockBo.setStatus(LockBo.LOCKED_STATUS);
lockMapper.saveLock(lockBo);
return lockBo.getLockName();
}
}
return null;
}
public boolean releaseLock(String lockName){
if (StringUtils.isEmpty(lockName)) {
throw new NullPointerException();
}
int i= lockMapper.updateByLockName(lockName);
return i==1?true:false;
}
private Date addSeconds(Date date, Integer seconds) {
if (Objects.isNull(seconds)){
seconds = DEFAULT_EXPIRED_SECONDS;
}
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.add(Calendar.SECOND, seconds);
return calendar.getTime();
}
}
注解
package cn.picclife.authen.system.annotation;
import java.lang.annotation.*;
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DistributedLock {
String name () default "default";
long expireTime () default 20*1000;
}
切面
package cn.picclife.authen.system.aspect;
import cn.picclife.authen.common.annotation.DatasourceChange;
import cn.picclife.authen.common.utils.SystemUtils;
import cn.picclife.authen.system.annotation.DistributedLock;
import cn.picclife.authen.system.bean.bo.TaskBo;
import cn.picclife.authen.system.distributed.RedisDistribute;
import cn.picclife.authen.system.distributed.SqlDistribute;
import cn.picclife.authen.system.mapper.TaskMapper;
import com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Objects;
@Component
@Aspect
@Slf4j
public class DistributeAspect {
@Autowired
private RedisDistribute redisDistribute;
@Autowired
private SqlDistribute sqlDistribute;
@Autowired
private TaskMapper taskMapper;
@Before ("@annotation(cn.picclife.authen.system.annotation.DistributedLock)")
public void hh(JoinPoint point){
System.out.println("======sssssssssssssssss=======================");
}
@Around("@annotation(cn.picclife.authen.system.annotation.DistributedLock)")
public void beforeStartTask(ProceedingJoinPoint pjp) throws NoSuchMethodException {
InetAddress address=null;
String ipAddr="";
try {
address= InetAddress.getLocalHost();
ipAddr= address.getHostAddress();
} catch (UnknownHostException e) {
ipAddr="获取ip失败";
log.error("获取系统ip异常"+e.getStackTrace());
}
TaskBo taskBo=new TaskBo();
//获取该注解所在类的类名(此处指的是TestTask )
Class<?> className = pjp.getTarget().getClass();
//获取该注解所在类的方法名(此处指的是TestTask中的 test)
String methodName = pjp.getSignature().getName();
Class<?>[] argClass = ((MethodSignature) pjp.getSignature()).getParameterTypes();
Method method = className.getMethod(methodName, argClass);
boolean sqlflag=false;boolean redisflag=false;
//Annotation[] annotations = className;
if (method.isAnnotationPresent(DistributedLock.class)) {
for (Annotation anno : method.getDeclaredAnnotations()) {{
if(anno.annotationType().equals(DistributedLock.class)){
DistributedLock distributedLock = (DistributedLock)anno;
String name=distributedLock.name();
long expireTime=distributedLock.expireTime();
taskBo.setId(SystemUtils.getCompanyTransNo()).setName(name).setSystemInfo(ipAddr);
//taskMapper.saveTask(taskBo);
String isLock= null;
try {
isLock = redisDistribute.lockWithTimeOut(name,2000,expireTime);
} catch (Exception e) {
log.error("redis异常"+e );
log.info("<========使用mysql获取锁=======>");
taskBo.setRedisException(e.toString());
redisflag=true;
taskBo.setStatus("3");
// taskMapper.saveTask(taskBo);
try {
sqlflag=true;
isLock=sqlDistribute.lock(name,(int)expireTime/1000);
} catch (Exception exception) {
taskBo.setMysqlException(exception.toString());
taskBo.setStatus("4");
// taskMapper.updateTask(taskBo);
log.error("mysql异常"+exception );
}
}
if(Objects.isNull(isLock)) {
log.info("redis或者mysql获取锁失败,不向下执行方法");
return ;
}else {
log.info("获取到锁,执行定时任务");
if(!redisflag){
taskBo.setStatus("1");
taskMapper.saveTask(taskBo);
}if(sqlflag){
taskBo.setStatus("2");
taskMapper.saveTask(taskBo);
}
try {
Object result = pjp.proceed();
} catch (Throwable throwable) {
log.info("定时任务执行有误"+throwable);
taskBo.setOtherException(throwable.toString());
taskMapper.updateTask(taskBo);
}
try {
if(redisDistribute.releaseLock(name,isLock)){
log.info("redis释放锁成功");
taskBo.setStatus("7");
taskMapper.updateTask(taskBo);
}
} catch (Exception e) {
log.error("redis异常"+e );
taskBo.setRedisException(e.toString());
taskBo.setStatus("5");
taskMapper.updateTask(taskBo);
log.info("<========使用mysql释放锁=======>");
try {
if(sqlDistribute.releaseLock(name)){
taskBo.setStatus("8");
taskMapper.updateTask(taskBo);
log.info("<========mysql释放锁成功=======>");
}
} catch (Exception exception) {
log.info("<========mysql释放锁失败联系管理员=======>");
log.error("mysql异常"+e);
taskBo.setStatus("6");
taskBo.setMysqlException(exception.toString());
taskMapper.updateTask(taskBo);
}
}
}
}
}
}
}
}
/* @After ("@annotation(cn.picclife.authen.system.annotation.DistributedLock)")
public void afterEndTask(JoinPoint point) {
Class<?> className = point.getTarget().getClass();
String methodName = point.getSignature().getName();
}*/
}
main方法
package cn.picclife.authen.system.distributed;
import cn.picclife.authen.common.utils.SystemUtils;
import cn.picclife.authen.system.annotation.DistributedLock;
import cn.picclife.authen.system.bean.bo.LockBo;
import cn.picclife.authen.system.bean.bo.TaskBo;
import cn.picclife.authen.system.mapper.LockMapper;
import cn.picclife.authen.system.mapper.TaskMapper;
import com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.TransientDataAccessResourceException;
import org.springframework.scheduling.annotation.Scheduled;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Configuration
@Slf4j
public class TestTask {
@Autowired
private RedisDistribute redisDistribute;
@Autowired
private SqlDistribute sqlDistribute;
@Autowired
private TaskMapper taskMapper;
//@Scheduled(cron = "0 */1 * * * * ")
public void test(){
log.info("==========开始执行test方法===============");
CountDownLatch countDownLatch = new CountDownLatch(1);
ExecutorService executorService = Executors.newFixedThreadPool(10);
for(int i=0;i<10;i++){
executorService.submit(()->{
try {
countDownLatch.await();
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
//test1();
{ boolean sqlflag=false;boolean redisflag=false;
TaskBo taskBo=new TaskBo();
String name="distributedLock";
long expireTime=300*1000;
taskBo.setId(SystemUtils.getCompanyTransNo()).setName(name).setSystemInfo("ipAddr");
// taskMapper.saveTask(taskBo);
String isLock= null;
try {
//TransientDataAccessResourceException: Error attempting to get column 'lock_name' from result set. Cause: java.sql.SQLException: Cannot convert value 'distributedLock.name()' from column 2 to TIMESTAMP.
isLock = redisDistribute.lockWithTimeOut(name,2000,expireTime);
} catch (Exception e) {
log.error("redis异常"+e );
log.info("<========使用mysql获取锁=======>");
taskBo.setRedisException(e.toString());
redisflag=true;
try {
sqlflag=true;
isLock=sqlDistribute.lock(name,(int)expireTime/1000);
} catch (Exception exception) {
taskBo.setMysqlException(exception.toString());
taskBo.setStatus("4");
// taskMapper.updateTask(taskBo);
log.error("mysql异常"+exception );
}
}
if(Objects.isNull(isLock)) {
log.info("redis或者mysql获取锁失败,不向下执行方法");
return ;
}else {
log.info("获取到锁,执行定时任务");
if(!redisflag){
taskBo.setStatus("1");
taskMapper.saveTask(taskBo);
}if(sqlflag){
taskBo.setStatus("2");
taskMapper.saveTask(taskBo);
}
try {
test1();
} catch (Throwable throwable) {
log.info("定时任务执行有误"+throwable);
taskBo.setOtherException(throwable.toString());
taskMapper.updateTask(taskBo);
}
try {
if(redisDistribute.releaseLock(name,isLock)) log.info("redis释放锁成功");
} catch (Exception e) {
log.error("redis异常"+e );
taskBo.setRedisException(e.toString());
taskBo.setStatus("5");
taskMapper.updateTask(taskBo);
log.info("<========使用mysql释放锁=======>");
try {
if(sqlDistribute.releaseLock(name)){
log.info("<========mysql释放锁成功=======>");
taskBo.setStatus("8");
taskMapper.updateTask(taskBo);
}
} catch (Exception exception) {
log.info("<========mysql释放锁失败联系管理员=======>");
log.error("mysql异常"+e);
taskBo.setStatus("6");
taskBo.setMysqlException(exception.toString());
taskMapper.updateTask(taskBo);
}
}
}
}
log.info(">>>>>>>>>>>"+Thread.currentThread().getName()+">>>>>>>>>>>>"+ ""+"<<<<<<<<<<<<<<<<");
});
countDownLatch.countDown();
}
}
@Autowired
private LockMapper lockMapper;
@DistributedLock
//@Scheduled(cron = "0 */1 * * * * ")
public void test1(){
log.info("==========开始执行test方法===============");
LockBo lockBo=lockMapper.findByLockName("distributedLock");
}
}