博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
分布式锁原理 --及常见实现方式的优劣势分析
阅读量:4041 次
发布时间:2019-05-24

本文共 5183 字,大约阅读时间需要 17 分钟。

分布式锁

  • 什么是锁
  • 背景介绍
  • 分布式锁的基本原则
  • 常见的分布式锁实现方式

什么是锁

简单的来说就是对资源操作时的一种控制策略	何为资源:能被程序访问的所有信息或是媒介;比如常见的文件,数据库,内存中的数据等何为操作:像读,写,修改等

背景介绍

以Java开发为例子说明	在单体应用或是单实例部署的情况下,所有的请求处理都是在同一个JVM中。可借助java 中的锁机制来控制对资源的访问比如:synchronized关键字,java.util.concurrent.* 中的api但是如果应用程序是分布式部署或是集群部署,请求会别分发到不同的实例上来处理。这样就会带来一个问题:如果资源是被一个JVM给锁定了(比如说通过synchronized),那么其它的JVM是如何知道资源的锁定状态呢?	实际上java在处理请求时没有办法直接跨越JVM的,这个时候只能是借用一个"第三方"来饰演资源的公共角色?这个“第三方”,就可以是我们常见的文件,数据库等外部的一切可访问资源

分布式锁的基本原则

那么在设计分布式锁的时候,需要考虑哪些因素呢:1.互斥性:在同一时刻只能由一个客户端持久2.独立性:在正常情况下,只有锁的拥有者才可以释放锁即可识别锁拥有者的身份3.可用性:即在使用端出现异常的情况下,锁还是可以正常被使用的,即不会存在死锁或是长时间无资源可用的状态

常见的分布式锁实现方式

常见实现有三种1.基于数据库2.基于Redis3.基于Zookeeper4.其它第三实现

1.基本数据库

不做详细的说明,大体的思路就是一张表来记录资源的各种状态

2.基于Redis

查看代码实例:public class CustomRedisLock implements  java.util.concurrent.locks.Lock {private StringRedisTemplate stringRedisTemplate;//资源的唯一keyprivate String lockKey=null;//默认锁时间private int defaultLockTimeout;//锁ID标识private AtomicReference
lockedID = new AtomicReference<>();//本机JVM锁private Lock lock=null;//ThreadLocal
threadLocal=new ThreadLocal();/** stringRedisTemplate: lockKey:锁资源标识 defaultLockTimeout:锁超时时间*/public CustomRedisLock(StringRedisTemplate stringRedisTemplate,String lockKey,int defaultLockTimeout){ this.stringRedisTemplate=stringRedisTemplate; this.lockKey="Lock-"+lockKey; this.defaultLockTimeout=defaultLockTimeout; this.lock=new ReentrantLock();}@Overridepublic void lock() {}@Overridepublic void unlock() { String findRequestId=this.stringRedisTemplate.opsForValue().get(this.lockKey); //是当前线程获取到的锁 if(!StringUtils.isEmpty(findRequestId) && findRequestId.equals(threadLocal.get())){ //删除redis锁标识,只能释放锁定状态是当前线程操作的Key.即要满足原则的第三点 String scriptText = "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)); List
keys=Collections.singletonList(lockKey); DefaultRedisScript
script =new DefaultRedisScript<>(); script.setScriptText(scriptText); script.setResultType(Integer.class); Object result=stringRedisTemplate.execute(script,keys,findRequestId); log.info("Delete Result ="+result.getClass().getName()+",value="+((Long)result).longValue()); } /* log.info("release lock:{key:{},uuid:{}}", key, uuid); return redisTemplate.execute( (RedisConnection connection) -> connection.eval( RELEASE_LOCK_LUA_SCRIPT.getBytes(), ReturnType.INTEGER, 1, key.getBytes(), uuid.getBytes()) ).equals(RELEASE_LOCK_SUCCESS_RESULT); */}@Overridepublic void lockInterruptibly() throws InterruptedException {}@Overridepublic boolean tryLock() { try{ lock.tryLock();//JVM的锁 String requestId=UUID.randomUUID().toString().replace("-","")+Thread.currentThread().getId(); Boolean execute = this.stringRedisTemplate.execute(new RedisCallback
() { @Override public Boolean doInRedis(RedisConnection connection) throws DataAccessException { //??蛋疼: 1.5.12.RELEASE 版本的这个Set操作没有返回r操作结果 //其它版本的,如果此RedisConnection接口可以返回操作结果,则无需要后的比较确认步骤 //设置key 与 设置过滤时间必须是原子性 connection.set(lockKey.getBytes(Charset.forName("UTF-8")),requestId.getBytes(Charset.forName("UTF-8")), Expiration.seconds(defaultLockTimeout),RedisStringCommands.SetOption.SET_IF_ABSENT); log.info("------------------------------"); threadLocal.set(requestId); return true; } }); //鉴于上述的蛋疼点,比较redis中锁身份标识是不是当前线程的,如果是则表明当前线程获取到了锁 String findRequestId=this.stringRedisTemplate.opsForValue().get(this.lockKey); log.info("execute result="+execute.booleanValue()+", value="+findRequestId); if(requestId.equals(findRequestId)){ return true; }else { threadLocal.remove(); return false; } }finally { lock.unlock(); }}@Overridepublic boolean tryLock(long time, TimeUnit unit) throws InterruptedException { return false;}@Overridepublic Condition newCondition() { return null;}}缺点:为什么一定要设置Key的超时时间呢? 原因:是防止客户端在获取到了锁之后没有释放锁或是客户端在获致到锁之后宕机了,锁不能被释放。 加入超时时间设置,也是一种被动的检测机制 虽然引入了超时时间的检测机制,但会引发另外一个问题就是:这个超时时间该设置多长时间的问题? 有方案说可以自动延续key的超时时间,但是这种方法也不能全面覆盖拟所有的场景(比如:java在触发了GC动作后) 也就是说这个问题最终还是由业务使用方来确定一个合理的时间值

3.基于Zookeeper

实现方案: 	第三方实现->curator-recipes	 
org.apache.curator
curator-client
2.11.1
org.apache.curator
curator-recipes
2.11.1
其中有几种锁的实现方式: InterProcessMutex InterProcessReadWriteLock InterProcessSemaphoreMutex InterProcessMultiLock 实现原理为: 在Lock-Key节点下创建临时有序列节点 获致Lock-key下所有的子节点,按照有序规则排序,如果最小的节点是当前连接创建的则代表获取到了 监听Lock-Key下所有比自己大的节点的删除事件 备注:因为是临时节点,连接使用完/断开之后节点就会被自动清除

在这里插入图片描述

4.其它第三方实现

redisson  / https://github.com/redisson/redisson

转载地址:http://vwadi.baihongyu.com/

你可能感兴趣的文章
动态规划-找零钱
查看>>
动态规划-跳台阶
查看>>
动态规划-01背包问题
查看>>
动态规划-优化编辑器问题
查看>>
堆排序(C++实现)
查看>>
图的俩种遍历方式(DFS,BFS)C++代码实现
查看>>
Hibernate设置主键自增,执行HQL语句
查看>>
设置MYSQL最大连接数与WAIT_TIMEOUT
查看>>
java根据ip地址获取详细地域信息
查看>>
解决s:iterator嵌套s:radio的传值问题
查看>>
位运算-不用加减乘除做加法。
查看>>
C++继承的三种方式(公有,私有,保护)
查看>>
待修改:C++多线程编程学习笔记
查看>>
冒泡、选择、插入、归并
查看>>
QTextEdit显示超链接
查看>>
使用socket下载文件(C++)
查看>>
cent os6.5静默安装oracle
查看>>
cent os6.5搭建oracle-dataguard
查看>>
使easyui-tree显示到指定层次
查看>>
给easyui-input元素添加js原生方法
查看>>