基于redis作为工具做分布式锁
推荐文章:Java分布式锁看这篇就够了
思路:利用主键唯一的特性,如果有多个请求同时提交到数据库的话,数据库会保证只有一个操作可以成功,那么我们就可以认为操作成功的那个线程获得了该方法的锁,当方法执行完毕之后,想要释放锁的话,删除这条数据库记录即可。
创建task_lock表,注意key作为唯一主键
基于数据库 乐观锁的 分布式锁工具类 实现如下
/**
* 基于数据库 乐观锁的 分布式锁
*
* 利用主键唯一的特性,如果有多个请求同时提交到数据库的话,
* 数据库会保证只有一个操作可以成功,那么我们就可以认为操作成功的那个线程获得了该方法的锁,
* 当方法执行完毕之后,想要释放锁的话,删除这条数据库记录即可。
* Created by xjb on 2019/6/24
*/
@Component
public class SynTaskLockUtils {
private static Logger logger = LoggerFactory.getLogger(SynTaskLockUtils.class);
@Autowired
TaskLockMapper taskLockMapper;
//每个小时第52分钟执行一次
@Scheduled(cron = "0 52 * * * ?")
private void checkSynTaskKeyIsExpire() {
logger.info("=========开始检查数据库分布式锁的过期时间=======");
List<TaskLock> taskLocks = taskLockMapper.findAll();
//筛选出过期的key
List<TaskLock> taskLockList = taskLocks.stream().
filter(taskLock -> new Date().getTime() - taskLock.getUtime().getTime()
- taskLock.getTimeout() * 1000 > 0).collect(Collectors.toList());
taskLockList.forEach(taskLock -> {
taskLockMapper.deleteByPrimaryKey(taskLock.getKey());
});
logger.info("=========trs_task_lock 删除{}条 过期的key=======",taskLockList.size());
}
/**
* 获取分布式锁
* 根据插入语句中的主键冲突,相同主键的多次插入操作,只会有一次成功,成功的就获得分布式锁执行任务的权利
* @param key
* @param ip
* @param desc
* @param timeout 单位秒
* @return
*/
public boolean tryLock(String key, String ip, String desc, int timeout) {
TaskLock taskLock = new TaskLock();
taskLock.setKey(key);
taskLock.setTimeout(timeout);
taskLock.setIp(ip);
taskLock.setDescription(desc);
int sum = 0;
try {
sum = taskLockMapper.insertSelective(taskLock);
} catch (Exception e) {
logger.info("{} 其他机器已经在执行", key);
}
return sum == 1;
}
/**
* 删除分布式锁
*
* @param redisKey
* @return
*/
public boolean del(String redisKey) {
int sum = taskLockMapper.deleteByPrimaryKey(redisKey);
return sum == 1;
}
/**
* 获得key中对象
* @param redisKey
* @return
*/
public TaskLock get(String redisKey) {
return taskLockMapper.selectByPrimaryKey(redisKey);
}
/**
*
* @param key
* @param value
* @param ip
*/
public void set(String key, String value,String ip) {
TaskLock taskLock = new TaskLock();
taskLock.setKey(key);
taskLock.setTimeout(-1);
taskLock.setIp(ip);
taskLock.setDescription(value);
taskLockMapper.insertOrUpdate(taskLock);
}
public boolean hasKey(String queryEndTimeRedisKey) {
return taskLockMapper.queryCount(queryEndTimeRedisKey)==1;
}
}
TaskLock 实体类
/**
* 分布式锁
* 基于mysql数据库做的分布式锁 类redis的 key-value
*
*/
public class TaskLock {
/**
* 键名 key-value的 key
*/
private String key;
/**
* 过期时间 避免死锁
*/
private Integer timeout;
/**
* 运行机器ip
*/
private String ip;
/**
* 描述 key-value的 value
*/
private String description;
/**
* 修改时间
*/
private Date utime;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key == null ? null : key.trim();
}
public Integer getTimeout() {
return timeout;
}
public void setTimeout(Integer timeout) {
this.timeout = timeout;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip == null ? null : ip.trim();
}
public String getDesc() {
return description;
}
public void setDescription(String description) {
this.description = description == null ? null : description.trim();
}
public Date getUtime() {
return utime;
}
public void setUtime(Date utime) {
this.utime = utime;
}
}
TaskLockMapper.java
public interface TaskLockMapper {
int deleteByPrimaryKey(String key);
int insertSelective(TaskLock record);
TaskLock selectByPrimaryKey(String key);
List<TaskLock> findAll();
int queryCount(String key);
void insertOrUpdate(TaskLock taskLock);
}
TaskLockMapper.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="com.xjb.data.mapper.cluster.TaskLockMapper">
<resultMap id="BaseResultMap" type="com.xjb.data.lock.TaskLock">
<id column="key" property="key" jdbcType="VARCHAR"/>
<result column="timeout" property="timeout" jdbcType="INTEGER"/>
<result column="ip" property="ip" jdbcType="VARCHAR"/>
<result column="description" property="description" jdbcType="VARCHAR"/>
<result column="utime" property="utime" jdbcType="TIMESTAMP"/>
</resultMap>
<sql id="Base_Column_List">
key, timeout, ip, description, utime
</sql>
<select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.String">
select
<include refid="Base_Column_List"/>
from trs_task_lock
where key = #{
key,jdbcType=VARCHAR}
</select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.String">
delete from trs_task_lock
where key = #{
key,jdbcType=VARCHAR}
</delete>
<insert id="insertSelective" parameterType="com.xjb.data.lock.TaskLock">
insert into trs_task_lock
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="key != null">
key,
</if>
<if test="timeout != null">
timeout,
</if>
<if test="ip != null">
ip,
</if>
<if test="description != null">
description,
</if>
<if test="utime != null">
utime,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="key != null">
#{
key,jdbcType=VARCHAR},
</if>
<if test="timeout != null">
#{
timeout,jdbcType=INTEGER},
</if>
<if test="ip != null">
#{
ip,jdbcType=VARCHAR},
</if>
<if test="description != null">
#{
description,jdbcType=VARCHAR},
</if>
<if test="utime != null">
#{
utime,jdbcType=TIMESTAMP},
</if>
</trim>
</insert>
<select id="findAll" resultMap="BaseResultMap">
select * from trs_task_lock where timeout <![CDATA[ > ]]> 0
</select>
<select id="queryCount" resultType="java.lang.Integer" parameterType="java.lang.String">
select count(*) from trs_task_lock where key=#{
key}
</select>
<insert id="insertOrUpdate">
insert into trs_task_lock (
key,
timeout,
ip,
description
)
values
(
#{
key,jdbcType=VARCHAR},
#{
timeout,jdbcType=VARCHAR},
#{
ip,jdbcType=VARCHAR},
#{
description,jdbcType=VARCHAR}
)
ON CONFLICT(key)
do UPDATE
set
key=excluded.key,
timeout=excluded.timeout,
ip=excluded.ip,
description=excluded.description
</insert>
</mapper>
测试代码:
@Component
public class Task2 {
private static Logger logger = LoggerFactory.getLogger(Task2.class);
@Autowired
SynTaskLockUtils synTaskLockUtils;
String redisKey = "java-Task-isRun";
//单位为秒 默认十分钟
private int redis_default_expire_time = 60 * 3;
@Scheduled(cron = "0 */1 * * * ?")
public void run() throws InterruptedException {
//-------------上分布式锁开始-----------------
InetAddress addr = null;
try {
addr = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
}
//获取本机ip
String ip = addr.getHostAddress();
//默认上锁时间为五小时
//此key存放的值为任务执行的ip,
// redis_default_expire_time 不能设置为永久,避免死锁
boolean lock = synTaskLockUtils.tryLock(redisKey, ip, "1", redis_default_expire_time);
logger.info("============本次聚类定时任务开始==============");
if (lock) {
logger.info("============获得分布式锁成功=======================");
//TODO 开始执行任务 执行结束后需要释放锁
ccc();
synTaskLockUtils.del(redisKey);
logger.info("============释放分布式锁成功=======================");
} else {
logger.info("============获得分布式锁失败=======================");
ip = synTaskLockUtils.get(redisKey).getIp();
logger.info("============{}机器上占用分布式锁,聚类任务正在执行=======================", ip);
logger.info("============本次聚类定时任务结束==============");
return;
}
}
public void ccc() throws InterruptedException {
System.out.println("执行中");
Thread.sleep(1000 * 60 * 2);
System.out.println("执行结束");
}
}