分布式锁
1: 基于数据库实现分布式锁的步骤
多个进程,多个线程 访问共同组件数据库
通过select.........for update 访问同一条数据
for update 锁定数据 其他线程只能等待
优缺点:
优点: 简单方便 易于理解 易于操作
缺点: 并发量大时 对数据库压力较大
建议: 作为锁的数据库与业务数据库分开
代码示例:
1: 端口8081 8080启动两个程序
2: 访问8080 接口 日志--我进入了方法!我进入了锁!
3: 访问8081 接口 日志--我进入了方法!再往下需要等待8080释放锁
package com.example.distributelock.controller; import com.example.distributelock.dao.DistributeLockMapper; import com.example.distributelock.model.DistributeLock; import com.example.distributelock.model.DistributeLockExample; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @RestController @Slf4j public class DemoController { @Resource private DistributeLockMapper distributeLockMapper; @RequestMapping("singleLock")
// 必须加上事务 否则第一个线程进去了 访问完直接就提交了 第二个线程依旧能进去 需要加上事物 在方法执行完成之后再提交 @Transactional(rollbackFor = Exception.class) public String singleLock() throws Exception { log.info("我进入了方法!"); DistributeLock distributeLock = distributeLockMapper.selectDistributeLock("demo"); if (distributeLock==null) throw new Exception("分布式锁找不到"); log.info("我进入了锁!"); try { Thread.sleep(20000); } catch (InterruptedException e) { e.printStackTrace(); } return "我已经执行完成!"; } }
-- sql中加锁的语句 selectDistributeLock
<select id="selectDistributeLock" resultType="com.example.distributelock.model.DistributeLock">
select * from distribute_lock
where business_code = #{businessCode,jdbcType=VARCHAR}
for update
</select>
2: 基于Redis的Setnx实现分布式锁的步骤
my_random_value: 随机值,每个线程的随机值都不同,用于释放锁时的校验
nx: key不存在时设置成功,key存在时设置不成功
px:自动失效时间,出现一场情况,锁可以过期失效
实现原理:
利用nx的原子性,多个线程并发时,只有一个线程可以设置成功 -- Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系
设置成功即获得锁,可以执行后续的业务处理
如果出现异常,过了锁的有效期,锁自动释放
释放锁采用dedis的delete命令
释放锁时校验之前设置的随机数,相同才释放
释放锁的lua校本
package com.example.distributelock.controller; import com.example.distributelock.lock.RedisLock; import com.example.distributelock.lock.ZkLock; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @Slf4j public class RedisLockController { @Autowired private RedisTemplate redisTemplate; @RequestMapping("redisLock") public String redisLock(){ log.info("我进入了方法!"); try (RedisLock redisLock = new RedisLock(redisTemplate,"redisKey",30)){ if (redisLock.getLock()) { log.info("我进入了锁!!"); Thread.sleep(15000); } } catch (InterruptedException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); }
//unlock();释放锁放在了RedisLocl的自动close下面 jdk的AutoCloseable特性 log.info("方法执行完成"); return "方法执行完成"; } } package com.example.distributelock.lock; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.connection.RedisStringCommands; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.script.RedisScript; import org.springframework.data.redis.core.types.Expiration; import java.util.Arrays; import java.util.List; import java.util.UUID; @Slf4j public class RedisLock implements AutoCloseable { private RedisTemplate redisTemplate; private String key; private String value; //单位:秒 private int expireTime; public RedisLock(RedisTemplate redisTemplate,String key,int expireTime){ this.redisTemplate = redisTemplate; this.key = key; this.expireTime=expireTime; this.value = UUID.randomUUID().toString(); } /** * 获取分布式锁 * @return */ public boolean getLock(){ RedisCallback<Boolean> redisCallback = connection -> { //设置NX RedisStringCommands.SetOption setOption = RedisStringCommands.SetOption.ifAbsent(); //设置过期时间 Expiration expiration = Expiration.seconds(expireTime); //序列化key byte[] redisKey = redisTemplate.getKeySerializer().serialize(key); //序列化value byte[] redisValue = redisTemplate.getValueSerializer().serialize(value); //执行setnx操作 Boolean result = connection.set(redisKey, redisValue, expiration, setOption); return result; }; //获取分布式锁 Boolean lock = (Boolean)redisTemplate.execute(redisCallback); return lock; } public boolean unLock() { String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" + " return redis.call(\"del\",KEYS[1])\n" + "else\n" + " return 0\n" + "end"; RedisScript<Boolean> redisScript = RedisScript.of(script,Boolean.class); List<String> keys = Arrays.asList(key); Boolean result = (Boolean)redisTemplate.execute(redisScript, keys, value); log.info("释放锁的结果:"+result); return result; } @Override public void close() throws Exception { unLock(); } }
为什么释放锁时候需要lua脚本
答:
在多线程情况下,你的线程A 执行了unlock,也判断完,这个时候恰恰在你执行到redisDao.delete(key),还未执行的时候,失去了CPU执行权。这个时间 + 你执行业务代码的时间大于5秒。也就是说,这个时候线程A拿到的锁已经被释放掉。 这个时候线程B拿到CPU执行权,并且执行了lock的逻辑(线程A设置的过期时间过期了,线程B获取到了锁),并且成功,然后恰巧,线程B在这个时候失去了CPU执行权,线程A又获得了执行权(线程A锁过期了,但是程序还需要继续执行,执行到最后再次释放锁),并且成功的执行了redisDao.delete(key),(线程A和线程B的key相同), 这个时候线程A就把线程B获取的锁删除了,这个时候就会出现坏情况啦。
没错,你看似有这么多凑巧,但是在多线程情况下都是可能出现的。 如果你在执行redisDao.delete(key)的时候,给上value,redisDao.delete(key,uniqueValue),在删除的时候就会判断value是否相等, 就不会出现线程A删除线程B锁的情况了。 (每次代码执行结束释放锁的时候,判断redis里面的value是否与前面设置的value值是否相等,相等在释放,如果查询到的value是空,说明本次锁已经超时了,但是为了防止代码异常,释放锁操作应该放到finnlly里面)
在执行redisDao.delete(key,uniqueValue)的时候,delete命令不是原子性的,比如在delete的过程中,在此过来一个线程c,重新设置了一个redis锁,会直接将线程c的锁删除掉,而LUA执行脚本的时候就一个线程,本身是具有原子性的.(将判断value值相等,delete操作都放在lua脚本中)
推荐文章: https://www.jianshu.com/p/2a2f3d133e8d
3: 基于Zookeeper实现分布式锁的步骤
设置观察器的三个方法: getData(),getChildren(),exists();
节点数据发生变化,发送给客户端
观察器只能观察一次,在监控需重新设置
实现原理:
利用zookeeper的瞬时有序节点的特性
多线程并发创建瞬时阶段节点时,得到有序的序列
序号最小的线程获得锁
其他线程监听自己序号的前一个序号节点
前一个线程执行完成,删除自己的序号节点
下一个序号的线程得到通知,继续执行
创建节点时,就已经决定了线程的执行顺序
代码示例:
package com.example.distributezklock.controller; import com.example.distributezklock.lock.ZkLock; import lombok.extern.slf4j.Slf4j; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.recipes.locks.InterProcessMutex; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.io.IOException; import java.util.concurrent.TimeUnit; @RestController @Slf4j public class ZookeeperController { @Autowired private CuratorFramework client; @RequestMapping("zkLock") public String zookeeperLock(){ log.info("我进入了方法!"); try (ZkLock zkLock = new ZkLock()) { if (zkLock.getLock("order")){ log.info("我获得了锁"); Thread.sleep(10000); } } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } log.info("方法执行完成!"); return "方法执行完成!"; } } package com.example.distributezklock.lock; import lombok.extern.slf4j.Slf4j; import org.apache.zookeeper.*; import org.apache.zookeeper.data.Stat; import java.io.IOException; import java.util.Collections; import java.util.List; @Slf4j public class ZkLock implements AutoCloseable, Watcher { private ZooKeeper zooKeeper; private String znode; public ZkLock() throws IOException { this.zooKeeper = new ZooKeeper("localhost:2181", 10000,this); } public boolean getLock(String businessCode) { try { //创建业务 根节点 Stat stat = zooKeeper.exists("/" + businessCode, false); if (stat==null){ zooKeeper.create("/" + businessCode,businessCode.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } //创建瞬时有序节点 /order/order_00000001 znode = zooKeeper.create("/" + businessCode + "/" + businessCode + "_", businessCode.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); //获取业务节点下 所有的子节点 List<String> childrenNodes = zooKeeper.getChildren("/" + businessCode, false); //子节点排序 Collections.sort(childrenNodes); //获取序号最小的(第一个)子节点 String firstNode = childrenNodes.get(0); //如果创建的节点是第一个子节点,则获得锁 if (znode.endsWith(firstNode)){ return true; } //不是第一个子节点,则监听前一个节点 String lastNode = firstNode; for (String node:childrenNodes){ if (znode.endsWith(node)){ zooKeeper.exists("/"+businessCode+"/"+lastNode,true); break; }else { lastNode = node; } } synchronized (this){ wait(); } return true; } catch (Exception e) { e.printStackTrace(); } return false; } @Override public void close() throws Exception { zooKeeper.delete(znode,-1); zooKeeper.close(); log.info("我已经释放了锁!"); } @Override public void process(WatchedEvent event) { if (event.getType() == Event.EventType.NodeDeleted){ synchronized (this){ notify(); } } } }
基于zookeeper下提供的curator实现分布式锁 --curator已经实现了分布式锁 直接调用即可
package com.example.distributezklock.controller; import com.example.distributezklock.lock.ZkLock; import lombok.extern.slf4j.Slf4j; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.recipes.locks.InterProcessMutex; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.io.IOException; import java.util.concurrent.TimeUnit; @RestController @Slf4j public class ZookeeperController { @Autowired private CuratorFramework client; @RequestMapping("curatorLock") public String curatorLock(){ log.info("我进入了方法!"); InterProcessMutex lock = new InterProcessMutex(client, "/order"); try{ if (lock.acquire(30, TimeUnit.SECONDS)){ log.info("我获得了锁!!"); Thread.sleep(10000); } } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); }finally { try { log.info("我释放了锁!!"); lock.release(); } catch (Exception e) { e.printStackTrace(); } } log.info("方法执行完成!"); return "方法执行完成!"; } } //需要初始化CuratorFrameword这个bean @Bean(initMethod="start",destroyMethod = "close") public CuratorFramework getCuratorFramework() { RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3); CuratorFramework client = CuratorFrameworkFactory.newClient("localhost:2181", retryPolicy); return client; }
4: 基于Redisson实现分布式锁
https://zhuanlan.zhihu.com/p/135864820
--引入其他的人博客 感觉写的不错

浙公网安备 33010602011771号