分享一个大牛的人工智能教程。零基础!通俗易懂!风趣幽默!希望你也加入到人工智能的队伍中来!请轻击http://www.captainbed.net

分布式锁的三种实现方式:

  1. 基于数据库实现分布式锁;
  2. 基于缓存(Redis等)实现分布式锁;
  3. 基于Zookeeper实现分布式锁。

一、基于数据库实现分布式锁

1、悲观锁

利用 select … where … for update 排他锁。

注意:其他附加功能与实现基本一致,这里需要注意的是“where name=lock”,name字段必须要走索引,否则会锁表。有些情况下,比如表不大,MySQL优化器会不走这个索引,导致锁表问题。

2、乐观锁

所谓乐观锁与前边最大的区别在于其基于CAS思想,是不具有互斥性,不会产生锁等待而消耗资源,操作过程中认为不存在并发冲突,只有update version失败后才能觉察到。我们的抢购、秒杀就是用了这种实现以防止超卖。
通过增加递增的版本号字段实现乐观锁。

二、基于缓存(Redis等)实现分布式锁

1、使用命令介绍:
(1)SETNX
SETNX key val:当且仅当key不存在时,set一个key为val的字符串,返回1;若key存在,则什么都不做,返回0。
(2)expire
expire key timeout:为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁。
(3)delete
delete key:删除key。

在使用Redis实现分布式锁的时候,主要就会使用到这三个命令。

2、实现思想:
(1)获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁。锁的value值为一个随机生成的UUID,通过此UUID在释放锁的时候进行判断。
(2)获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
(3)释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。

3、分布式锁的简单实现代码:

/* 分布式锁的简单实现代码 */
public class DistributedLock {private final JedisPool jedisPool;public DistributedLock(JedisPool jedisPool) {this.jedisPool = jedisPool;}/*** 加锁* @param lockName       锁的key* @param acquireTimeout 获取超时时间* @param timeout        锁的超时时间* @return 锁标识*/public String lockWithTimeout(String lockName, long acquireTimeout, long timeout) {Jedis conn = null;String retIdentifier = null;try {// 获取连接conn = jedisPool.getResource();// 随机生成一个valueString identifier = UUID.randomUUID().toString();// 锁名,即key值String lockKey = "lock:" + lockName;// 超时时间,上锁后超过此时间则自动释放锁int lockExpire = (int) (timeout / 1000);// 获取锁的超时时间,超过这个时间则放弃获取锁long end = System.currentTimeMillis() + acquireTimeout;while (System.currentTimeMillis() < end) {if (conn.setnx(lockKey, identifier) == 1) {conn.expire(lockKey, lockExpire);// 返回value值,用于释放锁时间确认retIdentifier = identifier;return retIdentifier;}// 返回-1代表key没有设置超时时间,为key设置一个超时时间if (conn.ttl(lockKey) == -1) {conn.expire(lockKey, lockExpire);}try {Thread.sleep(10);} catch (InterruptedException e) {Thread.currentThread().interrupt();}}} catch (JedisException e) {e.printStackTrace();} finally {if (conn != null) {conn.close();}}return retIdentifier;}/*** 释放锁* @param lockName   锁的key* @param identifier 释放锁的标识* @return*/public boolean releaseLock(String lockName, String identifier) {Jedis conn = null;String lockKey = "lock:" + lockName;boolean retFlag = false;try {conn = jedisPool.getResource();while (true) {// 监视lock,准备开始事务conn.watch(lockKey);// 通过前面返回的value值判断是不是该锁,若是该锁,则删除,释放锁if (identifier.equals(conn.get(lockKey))) {Transaction transaction = conn.multi();transaction.del(lockKey);List<Object> results = transaction.exec();if (results == null) {continue;}retFlag = true;}conn.unwatch();break;}} catch (JedisException e) {e.printStackTrace();} finally {if (conn != null) {conn.close();}}return retFlag;}}

4、测试刚才实现的分布式锁:

例子中使用50个线程模拟秒杀一个商品,使用–运算符来实现商品减少,从结果有序性就可以看出是否为加锁状态。

模拟秒杀服务,在其中配置了jedis线程池,在初始化的时候传给分布式锁,供其使用。

public class Service {private static JedisPool pool = null;private DistributedLock lock = new DistributedLock(pool);int n = 500;static {JedisPoolConfig config = new JedisPoolConfig();// 设置最大连接数config.setMaxTotal(200);// 设置最大空闲数config.setMaxIdle(8);// 设置最大等待时间config.setMaxWaitMillis(1000 * 100);// 在borrow一个jedis实例时,是否需要验证,若为true,则所有jedis实例均是可用的config.setTestOnBorrow(true);pool = new JedisPool(config, "127.0.0.1", 6379, 3000);}public void seckill() {// 返回锁的value值,供释放锁时候进行判断String identifier = lock.lockWithTimeout("resource", 5000, 1000);System.out.println(Thread.currentThread().getName() + "获得了锁");System.out.println(--n);lock.releaseLock("resource", identifier);}}

模拟线程进行秒杀服务:

public class ThreadA extends Thread {private Service service;public ThreadA(Service service) {this.service = service;}@Overridepublic void run() {service.seckill();}
}public class Test {public static void main(String[] args) {Service service = new Service();for (int i = 0; i < 50; i++) {ThreadA threadA = new ThreadA(service);threadA.start();}}
}

结果如下,结果为有序的:

若注释掉使用锁的部分:

public void seckill() {// 返回锁的value值,供释放锁时候进行判断//String indentifier = lock.lockWithTimeout("resource", 5000, 1000);System.out.println(Thread.currentThread().getName() + "获得了锁");System.out.println(--n);//lock.releaseLock("resource", indentifier);
}

从结果可以看出,有一些是异步进行的:

三、基于Zookeeper实现分布式锁

ZooKeeper是一个为分布式应用提供一致性服务的开源组件,它内部是一个分层的文件系统目录树结构,规定同一个目录下只能有一个唯一文件名。基于ZooKeeper实现分布式锁的步骤如下:

(1)创建一个目录mylock;
(2)线程A想获取锁就在mylock目录下创建临时顺序节点;
(3)获取mylock目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁;
(4)线程B获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点;
(5)线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。

这里推荐一个Apache的开源库Curator,它是一个ZooKeeper客户端,Curator提供的InterProcessMutex是分布式锁的实现,acquire方法用于获取锁,release方法用于释放锁。

实现源码如下:

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;/*** 分布式锁Zookeeper实现**/
@Slf4j
@Component
public class ZkLock implements DistributionLock {private String zkAddress = "zk_adress";private static final String root = "package root";private CuratorFramework zkClient;private final String LOCK_PREFIX = "/lock_";@Beanpublic DistributionLock initZkLock() {if (StringUtils.isBlank(root)) {throw new RuntimeException("zookeeper 'root' can't be null");}zkClient = CuratorFrameworkFactory.builder().connectString(zkAddress).retryPolicy(new RetryNTimes(2000, 20000)).namespace(root).build();zkClient.start();return this;}public boolean tryLock(String lockName) {lockName = LOCK_PREFIX+lockName;boolean locked = true;try {Stat stat = zkClient.checkExists().forPath(lockName);if (stat == null) {log.info("tryLock:{}", lockName);stat = zkClient.checkExists().forPath(lockName);if (stat == null) {zkClient.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(lockName, "1".getBytes());} else {log.warn("double-check stat.version:{}", stat.getAversion());locked = false;}} else {log.warn("check stat.version:{}", stat.getAversion());locked = false;}} catch (Exception e) {locked = false;}return locked;}public boolean tryLock(String key, long timeout) {return false;}public void release(String lockName) {lockName = LOCK_PREFIX+lockName;try {zkClient.delete().guaranteed().deletingChildrenIfNeeded().forPath(lockName);log.info("release:{}", lockName);} catch (Exception e) {log.error("删除", e);}}public void setZkAddress(String zkAddress) {this.zkAddress = zkAddress;}
}

优点:具备高可用、可重入、阻塞锁特性,可解决失效死锁问题。

缺点:因为需要频繁的创建和删除节点,性能上不如Redis方式。

四、对比

数据库分布式锁实现
缺点:

1、db操作性能较差,并且有锁表的风险。
2、非阻塞操作失败后,需要轮询,占用cpu资源。
3、长时间不commit或者长时间轮询,可能会占用较多连接资源。

Redis(缓存)分布式锁实现
缺点:

1、锁删除失败,过期时间不好控制。
2、非阻塞,操作失败后,需要轮询,占用cpu资源。

ZK分布式锁实现
缺点:

性能不如redis实现,主要原因是写操作(获取锁释放锁)都需要在leader上执行,然后同步到follower。

总之:ZooKeeper有较好的性能和可靠性。

从理解的难易程度角度(从低到高):数据库 > 缓存 > Zookeeper

从实现的复杂性角度(从低到高):Zookeeper >= 缓存 > 数据库

从性能角度(从高到低):缓存 > Zookeeper >= 数据库

从可靠性角度(从高到低):Zookeeper > 缓存 > 数据库

Java-分布式锁的实现方式相关推荐

  1. java mysql 分布式锁_Java分布式锁之数据库方式实现

    之前的文章<Java分布式锁实现>中列举了分布式锁的3种实现方式,分别是基于数据库实现,基于缓存实现和基于zookeeper实现.三种实现方式各有可取之处,本篇文章就详细讲解一下Java分 ...

  2. 【Java分布式锁都有哪几种实现方式】

    在分布式系统中,为了保证共享资源的互斥性,需要使用分布式锁.在Java中,实现分布式锁的方式有以下几种: 基于数据库实现分布式锁(唯一索引.悲观锁) 可以使用数据库中的唯一索引或者悲观锁来实现分布式锁 ...

  3. java分布式锁解决方案 redisson or ZooKeeper

    redis 分布式锁 Redisson 是 redis 官方推荐的Java分布式锁第三方框架. 高效分布式锁 当我们在设计分布式锁的时候,我们应该考虑分布式锁至少要满足的一些条件,同时考虑如何高效的设 ...

  4. 浅析“分布式锁”的实现方式丨C++后端开发丨底层原理

    线程锁.进程锁以及分布式锁相关视频讲解:详解线程锁.进程锁以及分布式锁 如何高效学习使用redis相关视频讲解:10年大厂程序员是如何高效学习使用redis Linux服务器开发高级架构学习视频:C/ ...

  5. Java版基于Redis分布式锁的实现方式

    一.什么是分布式锁? 要介绍分布式锁,首先要提到分布式锁相对应的线程锁和进程锁, 线程锁:组要是给方法.代码块加锁,当方法或者代码块使用锁时,在同一时刻只有一个线程可以执行该方法或者代码块,线程锁只在 ...

  6. java中分布式锁的实现方式

    分布式锁 有的时候,我们需要保证一个方法在同 一时间内只能被同一个线程执行.在单机模式下,可以通过sychronized.锁等方式来实现, 在分布式环境下,有以下的解决方案: 数据库锁 1.通过一个一 ...

  7. java分布式锁的三种实现方式

    分布式锁的核心思想,就是使用外部的一块共享的区域,来完成锁的实现. 一.使用mysql数据库实现(基本不用) 1.使用数据库悲观锁 可以使用select ... for update 来实现分布式锁. ...

  8. Java开发高级工程师面试,etcd:一款比Redis更骚的分布式锁的实现方式

    Watch 机制支持 Watch 某个固定的 key,也支持 Watch 一个范围(前缀机制). 当被 Watch 的 key 或范围发生变化,客户端将收到通知:在实现分布式锁时,如果抢锁失败, 可通 ...

  9. 搞懂Java分布式锁实现看这篇文章就对了

    2019独角兽企业重金招聘Python工程师标准>>> 前言: 随着微处理机技术的发展,人们只需花几百美元就能买到一个CPU芯片,这个芯片每秒钟执行的指令比80年代最大的大型机的处理 ...

最新文章

  1. MySQL WindowsCMD常用命令!
  2. 在SpringBoot的Web项目中使用于Thymeleaf(二)
  3. java 高并发第三阶段实战_JAVA多线程编程实战视频-第三阶段(共80节)
  4. Django1.5 自定义用户模型(总结)
  5. 3、MySQL查看或显示数据库(SHOW DATABASES语句)
  6. 发送附件时,防止文件名中的中文字符变成乱码
  7. LeetCode MySQL 618. 学生地理信息报告(row_number)
  8. python字典导入mongodb_Python语言生成内嵌式字典(dict)-案例从python提取内嵌json写入mongodb...
  9. 华为天才少年一人打造自动驾驶!
  10. Mail服务器的搭建(4)----Web方式的邮件系统
  11. 【D3 API 中文手册】
  12. 云筑网认证_云筑网供应商常见问题.doc
  13. 360桌面整理计算机图标,电脑桌面脏乱差 360桌面助手一键整理
  14. 360随身wifi2驱动 v5.3.0.1035 官方版
  15. c代码实现 ifft运算_二维FFT,IFFT,c语言实现 | 学步园
  16. Rasa课程、Rasa培训、Rasa面试、Rasa实战系列之 Countvectors and Spelling Errors
  17. PC usb蓝牙发射器
  18. 工业控制系统协议相关的安全问题
  19. 计算机鼠标与键盘基本知识,电脑鼠标和键盘的基础设置方法
  20. 用代码向网站提交数据

热门文章

  1. 图片处理系列:png图片背景色改为透明
  2. 六轴机器人光机_烂大街的六轴机器人
  3. opencv 双目摄像头拍照(分别左右镜头拍照)
  4. java long 百分比,Java 数字转百分比%
  5. 京东大客户开放平台VOP接口对接记录
  6. 第四章 OAuth2.0规范(史上最详细解释)——获得授权
  7. 输入框实时字数计算移动端bug解决
  8. 阴阳师最新的服务器,阴阳师6月30日正式服务器更新内容一览
  9. 二手车预测:task2:数据分布
  10. 社招和校招有什么不同?阿里美团等大厂JAVA社招面经分享!