redis lock java_Redis实现分布式锁

Redis 实现分布式锁

一、背景:

业务架构中没有使用 Zookeeper,只使用了 Redis,但是业务中又需要使用到分布式锁,还好 redis 提供了很多原子操作,可以利用这些原子操作来实现分布式锁

二、设计分布式锁需要考虑的点:

互斥性

在任意时刻,只有一个客户端能持有锁。

不会发生死锁

即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。

具有容错性

只要大部分的 Redis 节点正常运行,客户端就可以加锁和解锁。

解铃还须系铃人

加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。

三、流程:

a8667bc485f27eb8587da1630bf03da8.pngredis分布式锁流程

1.两个进程同时获取锁,但是 A 进程得到了锁,所以 B 进程阻塞;

2.B 进程等待超时重试,失败,继续等待;

3.A 进程执行完毕,释放锁;

4.B 进程等待超时继续获取锁,获得锁,执行逻辑。

四、Java 实现:

bfeef52a9ccc67541af6bca3df7e3fd2.pngredis包结构

DistributedRedisLock.java

package com.gameboys;

import java.util.Arrays;

import java.util.Collections;

import java.util.UUID;

import redis.clients.jedis.Jedis;

/**

*

*

*

*

* redis版本必须高于 Redis 2.6.12 版本

*

* jedis版本也对应的需要升级,目前使用的是2.8.1

*

* redis官方说明 http://doc.redisfans.com/script/eval.html

*

* @author sniper

* @date 2019年10月30日

*/

public class DistributedRedisLock {

// 超时时间

private static final int DEFAULT_TIME_OUT_MILLIONS = 3000;

private static final int DEFAULT_RETRY_TIMES = 3;// 重试次数

private static final int DEFAULT_RETRY_TIME_OUT_MILLIONS = 100;// 重试超时时间

private static final String LOCK_SUCCESS = "OK";

private static final String SET_IF_NOT_EXIST = "NX";

private static final String SET_WITH_EXPIRE_TIME = "PX";

public DistributedRedisLock() {

}

/**

* 这个方法会阻塞线程,

*

* @param key

* @param lockLogic

* @param successParams

* @param failParams

* @param retryTimes

* 重试次数

* @param tryTime

* 重试时间

*/

public void lock(Jedis jedis,String key, ILockLogic lockLogic, Object[] successParams, Object[] failParams, int retryTimes, int retryTimeOut) {

final String reqID = UUID.randomUUID().toString();

boolean lock = this.tryLockAndDoAction(jedis,key, reqID, DEFAULT_TIME_OUT_MILLIONS, lockLogic, successParams);

if (lock) {

return;

}

// 获取失败,阻塞一下再次获取

for (int i = 0; i < retryTimes; i++) {

synchronized (this) {

try {

this.wait(retryTimeOut);

lock = this.tryLockAndDoAction(jedis,key, reqID, DEFAULT_TIME_OUT_MILLIONS, lockLogic, successParams);

if (lock) {

return;

}

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

// 如果到这里还没有拿到锁,执行失败逻辑

lockLogic.onFailDo(failParams);

}

/**

* 这个方法会阻塞线程,默认重试3次,每次100ms

*

*

* @param key

* @param lockLogic

* @param successParams

* @param failParams

*/

public void lock(Jedis jedis,String key, ILockLogic lockLogic, Object[] successParams, Object[] failParams) {

this.lock(jedis,key, lockLogic, successParams, failParams, DEFAULT_RETRY_TIMES, DEFAULT_RETRY_TIME_OUT_MILLIONS);

}

private boolean tryLockAndDoAction(Jedis jedis, String lockKey, String requestId, int expireTime, ILockLogic lockLogic, final Object[] successParams) {

String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);

if (LOCK_SUCCESS.equals(result)) {

lockLogic.onSuccessDo(successParams);

this.unlock(jedis, lockKey, requestId);

return true;

}

return false;

}

private static final Long RELEASE_SUCCESS = 1L;

/**

* 释放分布式锁,这个可看成原子操作

*

* @param jedis

* Redis客户端

* @param lockKey

* 锁

* @param requestId

* 请求标识

* @return 是否释放成功

*/

private boolean unlock(Jedis jedis, String lockKey, String requestId) {

String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

Object result = jedis.eval( script, Collections.singletonList(lockKey), Collections.singletonList(requestId));

if (RELEASE_SUCCESS.equals(result)) {

return true;

}

return false;

}

public static void main(String[] args) {

//redis按照自己的项目初始化

Jedis jedis = null;

DistributedRedisLock redisLock = new DistributedRedisLock();

redisLock.lock(jedis, "gameboysTest", new ILockLogic() {

@Override

public void onSuccessDo(Object[] params) {

System.out.println(Thread.currentThread().getName() + "成功执行:" + Arrays.toString(params));

}

@Override

public void onFailDo(Object[] params) {

System.out.println(Thread.currentThread().getName() + "失败执行:" + Arrays.toString(params));

}

}, new String[] { "gameboys", "nice" }, new String[] { "lisa", "nice" });

}

}

ILockLogic.java

package com.gameboys;

/**

* Description:

*

* @author gameboys

* @date 2019年10月30日

*/

public interface ILockLogic {

void onSuccessDo(Object[] params);

void onFailDo(Object[] params);

}

ParamsRunnable.java

package com.gameboys;

/**

* Description:

*

* @author gameboys

* @date 2019年9月30日

*/

public abstract class ParamsRunnable implements Runnable {

// 参数

protected Object[] params;

public ParamsRunnable(Object... params) {

this.params = params;

}

@Override

public void run() {

try {

this.doAction(params);

} catch (Exception e) {

}

}

public abstract void doAction(Object[] params);

public Object[] getParams() {

return params;

}

}

五、总结:

Redis 能实现分布式锁,得益于 redis 官方提供的 set 和 eval 命令,具体解释如下:

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 ** :只在键已经存在时,才对键进行设置操作。

eval 命令:

EVAL script numkeys key [key ...] arg [arg ...]

从 Redis 2.6.0 版本开始,通过内置的 Lua 解释器,可以使用 EVAL 命令对 Lua 脚本进行求值。

详情查看官网:http://doc.redisfans.com/script/eval.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值