import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate; import java.util.Random;
import java.util.concurrent.TimeUnit; public class RedisLock implements AutoCloseable {
private static final Logger LOGGER = LoggerFactory.getLogger(RedisLock.class);
public static final String REDIS_LOCK = "RedisLock:"; private static final long DEFAULT_WAIT_LOCK_TIME_OUT = 60;//60s 有慢sql,超时时间设置长一点
private static final long DEFAULT_EXPIRE = 80;//80s 有慢sql,超时时间设置长一点
private String key;
private RedisTemplate redisTemplate; public RedisLock(RedisTemplate redisTemplate,String key) {
this.redisTemplate = redisTemplate;
this.key = key;
} /**
* 等待锁的时间,单位为s
*
* @param key
* @param timeout s
* @param seconds
*/
public boolean lock(String key, long timeout, TimeUnit seconds) {
String lockKey = generateLockKey(key);
long nanoWaitForLock = seconds.toNanos(timeout);
long start = System.nanoTime(); try {
while ((System.nanoTime() - start) < nanoWaitForLock) {
if (redisTemplate.getConnectionFactory().getConnection().setNX(lockKey.getBytes(), new byte[0])) {
redisTemplate.expire(lockKey, DEFAULT_EXPIRE, TimeUnit.SECONDS);//暂设置为80s过期,防止异常中断锁未释放
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("add RedisLock[{}].{}", key, Thread.currentThread());
}
return true;
}
TimeUnit.MILLISECONDS.sleep(1000 + new Random().nextInt(100));//加随机时间防止活锁
}
} catch (Exception e) {
LOGGER.error("{}", e.getMessage(), e);
unlock();
}
return false;
} public void unlock() {
try {
String lockKey = generateLockKey(key);
RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
connection.del(lockKey.getBytes());
connection.del(key.getBytes());
connection.close();
} catch (Exception e) {
LOGGER.error("{}", e.getMessage(), e);
}
} private String generateLockKey(String key) {
return String.format(REDIS_LOCK + "%s", key);
} public boolean lock() {
return lock(key, DEFAULT_WAIT_LOCK_TIME_OUT, TimeUnit.SECONDS);
} @Override
public void close(){
try {
String lockKey = generateLockKey(key);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("release RedisLock[" + lockKey + "].");
}
RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
connection.del(lockKey.getBytes());
connection.close();
} catch (Exception e) {
LOGGER.error("{}", e.getMessage(), e);
}
}
}

在高并发的使用场景下,如何让redis里的数据尽量保持一致,可以采用分布式锁。以分布式锁的方式来保证对临界资源的互斥读写。

redis使用缓存作为分布式锁,性能非常强劲,在一些不错的硬件上,redis可以每秒执行10w次,内网延迟不超过1ms,足够满足绝大部分应用的锁定需求。

redis常用的分布式锁的实现方式:

一、setbit / getbit

用索引号为0的第一个比特位来表示锁定状态,其中:0表示未获得锁,1表示已获得锁。

优势:简单;

劣势:竞态条件(race condition),死锁。

获得锁的过程至少需要两步:先getbit判断,后setbit上锁。由于不是原子操作,因此可能存在竞态条件;如果一个客户端使用setbit获取到锁,然后没来得及释放crash掉了,那么其他在等待的客户端将永远无法获得该锁,进而形成了死锁。所以这种形式不太适合实现分布式锁。

二、setnx / del / getset

redis官网有一篇文章专门谈论了实现分布式锁的话题。基本的原则是:采用setnx尝试获取锁并判断是否获得了锁,setnx设置的值是它想占用锁的时间(预估):

  • 如返回1,则该客户端获得锁,把lock.foo的键值设置为时间值表示该键已被锁定,该客户端最后可以通过DEL lock.foo来释放该锁。
  • 如返回0,表明该锁已被其他客户端取得,这时我们可以先返回或进行重试等对方完成或等待锁超时。

通过del删除key来释放锁。某个想获得锁的客户端,先采用setnx尝试获取锁,如果获取失败了,那么会通过get命令来获得锁的过期时间以判断该锁的占用是否过期。如果跟当前时间对比,发现过期,那么先执行del,然后执行setnx获取锁。如果整个流程就这样,可能会产生死锁,请参考下面的执行序列:

所以,在高并发的场景下,如果检测到锁过期,不能简单地进行del并尝试通过setnx获得锁。我们可以通过getset命令来避免这个问题。来看看,如果存在一个用户user4,它通过调用getset命令如何避免这种情况的发生:

getset设置的过期时间跟上面的setnx设置的相同:

如果该命令返回的结果跟上一步通过get获得的过期时间一致,则说明这两步之间,没有新的客户端抢占了锁,则该客户端即获得锁。如果该命令返回的结果跟上一步通过get获得的过期时间不一致,则该锁可能已被其他客户端抢先获得,则本次获取锁失败。

这种实现方式得益于getset命令的原子性,从而有效得避免了竞态条件。并且,通过将比对锁的过期时间作为获取锁逻辑的一部分,从而避免了死锁。

三、setnx / del / expire

这是使用最多的实现方式:setnx的目的同上,用来实现尝试获取锁以及判断是否获取到锁的原子性,del删除key来释放锁,与上面不同的是,使用redis自带的expire命令来防止死锁(可能出现某个客户端获得了锁,但是crash了,永不释放导致死锁)。这算是一种比较简单但粗暴的实现方式:因为,不管实际的情况如何,当你设置expire之后,它一定会在那个时间点删除key。如何当时某个客户端已获得了锁,正在执行临界区内的代码,但执行时间超过了expire的时间,将会导致另一个正在竞争该锁的客户端也获得了该锁,这个问题下面还会谈到。

我们来看一下宿舍锁的简单实现很简单:

通过一个while(true),在当前线程上进行阻塞等待,并通过一个计数器进行自减操作,防止永久等待。

http://www.cnblogs.com/moonandstar08/p/5682822.html

多节点的部署中,对锁的控制,参考:

http://www.jeffkit.info/2011/07/1000/

直接贴上代码实现,同上一篇文章一样,都是基于AOP

定义注解,标志切入点:

package com.ns.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; @Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface RedisLock {
/**
* redis的key
* @return
*/
String value();
/**
* 持锁时间,单位毫秒,默认一分钟
*/
long keepMills() default 60000;
/**
* 当获取失败时候动作
*/
LockFailAction action() default LockFailAction.GIVEUP; public enum LockFailAction{
/**
* 放弃
*/
GIVEUP,
/**
* 继续
*/
CONTINUE;
}
/**
* 睡眠时间,设置GIVEUP忽略此项
* @return
*/
long sleepMills() default 1000;
}

切面实现:

package com.redis.aop;
import java.lang.reflect.Method;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import com.ns.annotation.RedisLock;
import com.ns.annotation.RedisLock.LockFailAction;
import com.ns.redis.dao.base.BaseRedisDao;
@Aspect
public class RedisLockAspect extends BaseRedisDao<String, Long>{
private static final Logger log = LoggerFactory.getLogger(RedisLockAspect.class);
//execution(* com.ns..*(*,..)) and @within(com.ns.annotation.RedisLock) @Pointcut("execution(* com.ns..*(..)) && @annotation(com.ns.annotation.RedisLock)")
private void lockPoint(){}
@Around("lockPoint()")
public Object arround(ProceedingJoinPoint pjp) throws Throwable{
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
Method method = methodSignature.getMethod();
RedisLock lockInfo = method.getAnnotation(RedisLock.class);
boolean lock = false;
Object obj = null;
while(!lock){
long timestamp = System.currentTimeMillis()+lockInfo.keepMills();
lock = setNX(lockInfo.value(), timestamp);
//得到锁,已过期并且成功设置后旧的时间戳依然是过期的,可以认为获取到了锁(成功设置防止锁竞争)
long now = System.currentTimeMillis();
if(lock || ((now > getLock(lockInfo.value())) && (now > getSet(lockInfo.value(), timestamp)))){
//得到锁,执行方法,释放锁
log.info("得到锁...");
obj = pjp.proceed();
//不加这一行,对于只能执行一次的定时任务,时间差上不能保证另一个一定正好放弃
if(lockInfo.action().equals(LockFailAction.CONTINUE)){
delete(lockInfo.value());
}
}else{
if(lockInfo.action().equals(LockFailAction.CONTINUE)){
log.info("稍后重新请求锁...");
Thread.currentThread().sleep(lockInfo.sleepMills());
}else{
log.info("放弃锁...");
break;
}
}
}
return obj;
}
public boolean setNX(String key,Long value){
return valueOperations.setIfAbsent(key, value);
}
public long getLock(String key){
return valueOperations.get(key);
}
public Long getSet(String key,Long value){
return valueOperations.getAndSet(key, value);
}
public void releaseLock(String key){
delete(key);
}
}

Python的一个实现

LOCK_TIMEOUT = 3
lock = 0
lock_timeout = 0
lock_key = 'lock.foo' # 获取锁
while lock != 1:
now = int(time.time())
lock_timeout = now + LOCK_TIMEOUT + 1
lock = redis_client.setnx(lock_key, lock_timeout)
if lock == 1 or (now > int(redis_client.get(lock_key))) and now > int(redis_client.getset(lock_key, lock_timeout)):
break
else:
time.sleep(0.001) # 已获得锁
do_job() # 释放锁
now = int(time.time())
if now < lock_timeout:
redis_client.delete(lock_key)

http://blog.csdn.net/lihao21/article/details/49104695

以上有些代码只符合我现在的项目场景,根据实际需要进行调整

http://www.tuicool.com/articles/EzaM7by

一个Redis实现的分布式锁的更多相关文章

  1. Redis系列文章总结:ASP.Net Core 中如何借助CSRedis实现一个安全高效的分布式锁

    引言:最近回头看了看开发的.Net Core 2.1项目的复盘总结,其中在多处用到Redis实现的分布式锁,虽然在OnResultExecuting方法中做了防止死锁的处理,但在某些场景下还是会发生死 ...

  2. 基于redis实现的分布式锁

    基于redis实现的分布式锁 我们知道,在多线程环境中,锁是实现共享资源互斥访问的重要机制,以保证任何时刻只有一个线程在访问共享资源.锁的基本原理是:用一个状态值表示锁,对锁的占用和释放通过状态值来标 ...

  3. 基于Redis的简单分布式锁的原理

    参考资料:https://redis.io/commands/setnx 加锁是为了解决多线程的资源共享问题.Java中,单机环境的锁可以用synchronized和Lock,其他语言也都应该有自己的 ...

  4. redis客户端、分布式锁及数据一致性

    Redis Java客户端有很多的开源产品比如Redission.Jedis.lettuce等. Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持:Redis ...

  5. Redis系列(二)--分布式锁、分布式ID简单实现及思路

    分布式锁: Redis可以实现分布式锁,只是讨论Redis的实现思路,而真的实现分布式锁,Zookeeper更加可靠 为什么使用分布式锁: 单机环境下只存在多线程,通过同步操作就可以实现对并发环境的安 ...

  6. 如何用redis正确实现分布式锁?

    先把结论抛出来:redis无法正确实现分布式锁!即使是redis单节点也不行!redis的所谓分布式锁无法用在对锁要求严格的场景下,比如:同一个时间点只能有一个客户端获取锁. 首先来看下单节点下一般r ...

  7. redis中的分布式锁

    分布式锁的实现场景 在平时的开发中,对于高并发的开发场景,我们不可避免要加锁进行处理,当然redis中也是不可避免的,下面是我总结出来的几种锁的场景 Redis分布式锁方案一 使用Redis实现分布式 ...

  8. redis系列:分布式锁

    redis系列:分布式锁 1 介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分 ...

  9. 一般实现分布式锁都有哪些方式?使用redis如何设计分布式锁?使用zk来设计分布式锁可以吗?这两种分布式锁的实现方式哪种效率比较高?

    #(1)redis分布式锁 官方叫做RedLock算法,是redis官方支持的分布式锁算法. 这个分布式锁有3个重要的考量点,互斥(只能有一个客户端获取锁),不能死锁,容错(大部分redis节点创建了 ...

随机推荐

  1. 将多维数组转换为支持curl提交的一维数组格式

    /** * @desc 多维数组转化为支持curl提交数组 * @author lytian 2013-06-29 */ public function toPost(array $params = ...

  2. java多线程总结五:线程池的原理及实现

    1.线程池简介:     多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力.        假设一个服务器完成一项任务所需时间为:T1 创 ...

  3. selenium2.0处理case实例(二)

    本文通过具体代码处理过程, 来展示selenium中一些比较不常用的类的用法 1.javascriptExcutor,通过将driver强转成JavascriptExecutor类型, 调用execu ...

  4. iOS开发——图片轮播图+单选选项

    由于公司开发需要,需要滚动每道评测题, 并且一道评测题单项选择,按钮和文字都可点击选中 (单选比多选复杂一点,但是原理差不多) 1.当初任务紧,代码也没有优化,仅供思路参考,先放几张图 2.代码部分 ...

  5. Windows下lex 与 yacc的使用

     Windows下lex 与 yacc的使用 首先 下载下载flex和bison.网址是http://pan.baidu.com/s/1dDlfiW5 选择下载就好了,下载后解压到你电脑中的任一盘中. ...

  6. Kafka-0.10.0.0入门

    搭建环境略(伪集群即可以),但要注意Kafka的配置必须配置的,少配了也一样可以用,但是只能单机使用,外部机器无法连接,网上也有说. host.name=192.168.1.30 advertised ...

  7. dorado基本事件样例

    var info = self.get("returnValue"); view.set("#labelProduct.text","产品:" ...

  8. strcpy(),string使用问题

    两个CString,把一个赋值给另外一个,用strncpy出现问题,直接=赋值正确了,不知道为什么?

  9. Skia

    1 What is SKIA. Skia is an open source 2D graphics library which provides common APIs that work acro ...

  10. Struts2文件下载浅析

    Struts2极大的简化了文件上传和下载,本文将主要介绍一下Struts2文件下载的实现1.功能主要是,在下载页面点击下载后,则下载相应的文件 2.代码部分jsp页面downloadPage:< ...