分布式锁,分布式锁应该具备哪些条件,分布式锁的实现方式有:基于Zookeeper实现、Redis实现、数据库实现

文章目录

  • 分布式锁
    • 0-1分布式锁--包含CAP理论模型
      • 概述
        • 分布式锁:
        • 分布式锁应该具备哪些条件:
        • 分布式锁的业务场景:
      • 分布式锁的实现方式有:
        • 基于Zookeeper - 分布式锁
        • 实现思想
        • 优缺点
        • 基于Redis - 分布式锁
          • 实现思想
          • 实现思想的具体步骤:
          • 优缺点
          • Redis分布式锁实现-例子
          • 方案(一)
          • 改进 方案(一) :
          • 再改进 方案(一)
          • 方案(二)
          • 改进方案(二)
          • 再改进方案(二)
          • 再再次改进 方案(二)
          • 分段锁
        • 基于数据库 - 分布式锁
          • 实现思想
          • A. 悲观锁(排他锁)
          • B. 乐观锁

今天来和大家谈谈分布式锁的内容,在这个快速发展的经济时代,分布式锁也随之而发生。
分布式锁对应的也有 分布式事务,
链接如下:
https://blog.csdn.net/weixin_44797327/article/details/134611487?spm=1001.2014.3001.5502

分布式锁

0-1分布式锁–包含CAP理论模型

参考:文章-分布式锁

概述

随着互联网技术的不断发展,用户量的不断增加,越来越多的业务场景需要用到分布式系统

分布式系统有一个著名的理论CAP,指在一个分布式系统中,最多只能同时满足下面三项中的两项

一致性Consistency):在分布式系统中的所有数据备份,在同一时刻是否同样的值(等同于所有节点访问同一份最新的数据副本)
可用性Availability):保证每个请求不管成功或者失败都有响应
分区容错性Partition tolerance):系统中任意信息的丢失或失败不会影响系统的继续运作

所以在设计系统时,往往需要权衡,在CAP中作选择,要么AP,要么CP、要么AC

当然,这个理论也并不一定完美,不同系统对CAP的要求级别不一样,选择需要考虑方方面面。

在分布式系统中访问共享资源就需要一种互斥机制,来防止彼此之间的互相干扰,以保证一致性,这个时候就需要使用分布式锁

分布式锁:

当在分布式模型下,数据只有一份(或有限的),此时需要利用锁技术来控制某一时刻修改数据的进程数。这种锁即为分布式锁。

为了保证一个方法或属性高并发的情况下, 同一时间只能被同一个线程执行,在传统单体应用单机部署的情况下,可以使用并发处理相关的功能进行互斥控制。但是,随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的应用并不能提供分布式锁的能力。为了解决这个问题就需要一种跨机器互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题!

分布式锁应该具备哪些条件:

互斥性:在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行;
高可用的获取锁与释放锁
高性能的获取锁与释放锁
可重入性:具备可重入特性,具备锁失效机制,防止死锁,即就算一个客户端持有锁的期间崩溃而没有主动释放锁,也需要保证后续其他客户端能够加锁成功
非阻塞:具备非阻塞锁特性,即没有获取到直接返回获取锁失败

分布式锁的业务场景:

互联网秒杀(商品库存)
抢优惠券

分布式锁的实现方式有:

主要有几种实现方式:

基于数据库实现
基于Zookeeper实现
基于Redis实现

如图:在这里插入图片描述

分布式锁对比
从理解的难易程度角度(从低到高):数据库 > 缓存 > Zookeeper

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

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

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

基于Zookeeper - 分布式锁
实现思想

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

基于ZooKeeper实现分布式锁的步骤如下:

创建一个目录mylock

线程A想获取锁就在mylock目录下创建临时顺序节点;

获取mylock目录下所有的子节点,然后获取比自己小兄弟节点,如果不存在,则说明当前线程顺序号最小获得锁

线程B获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点;

线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是不是最小的节点,如果是则获得锁

整个过程如图:在这里插入图片描述

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

使用方式很简单:

InterProcessMutex interProcessMutex = new InterProcessMutex(client,"/anyLock"); 
interProcessMutex.acquire(); 
interProcessMutex.release(); 

其他分布式锁核心源码如下:

private boolean internalLockLoop(long startMillis, Long millisToWait, String ourPath) throws Exception{ boolean  haveTheLock = false; boolean  doDelete = false; try { if ( revocable.get() != null ) { client.getData().usingWatcher(revocableWatcher).forPath(ourPath); } while ((client.getState() == CuratorFrameworkState.STARTED) && !haveTheLock ) { // 获取当前所有节点排序后的集合 List<String> children = getSortedChildren(); // 获取当前节点的名称 String sequenceNodeName = ourPath.substring(basePath.length() + 1); // +1 to include the slash // 判断当前节点是否是最小的节点 PredicateResults predicateResults = driver.getsTheLock(client, children, sequenceNodeName, maxLeases); if ( predicateResults.getsTheLock() ) { // 获取到锁 haveTheLock = true; } else { // 没获取到锁,对当前节点的上一个节点注册一个监听器 String  previousSequencePath = basePath + "/" + predicateResults.getPathToWatch(); synchronized(this){ Stat stat = client.checkExists().usingWatcher(watcher).forPath(previousSequencePath); if ( stat != null ){ if ( millisToWait != null ){ millisToWait -= (System.currentTimeMillis() - startMillis); startMillis = System.currentTimeMillis(); if ( millisToWait <= 0 ){ doDelete = true;    // timed out - delete our node break; } wait(millisToWait); }else{ wait(); } } } // else it may have been deleted (i.e. lock released). Try to acquire again } } } catch ( Exception e ) { doDelete = true; throw e; } finally{ if ( doDelete ){ deleteOurPath(ourPath); } } return haveTheLock; 
}

其实 Curator 实现分布式锁底层原理和上面分析的是差不多的。如图详细描述其原理:在这里插入图片描述

另外,可基于Zookeeper自身的特性和原生Zookeeper API自行实现分布式锁。

优缺点

优点:

可靠性非常高
性能较好
CAP模型属于CP,基于ZAB一致性算法实现

**缺点: **

性能并不如Redis(主要原因是在写操作,即获取锁释放锁都需要在Leader上执行,然后同步到follower)
实现复杂度高

基于Redis - 分布式锁

在这里插入图片描述

实现思想

主要是基于命令:SETNX key value

命令官方文档:https://redis.io/commands/setnx

用法可参考:Redis命令参考

如图:在这里插入图片描述

实现思想的具体步骤:

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

优缺点

优点:

  • 性能非常高
  • 可靠性较高
  • CAP模型属于AP

缺点:

  • 复杂度较高
  • 无一致性算法,可靠性并不如Zookeeper
  • 锁删除失败 过期时间不好控制
  • 非阻塞,获取失败后,需要轮询不断尝试获取锁,比较消耗性能,占用cpu资源
Redis分布式锁实现-例子

以减库存接口为例子,访问接口的时候自动减商品的库存

方案(一)
@Service
public class RedisLockDemo {@Autowiredprivate StringRedisTemplate redisTemplate;public String deduceStock() {ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();//获取redis中的库存int stock = Integer.valueOf(valueOperations.get("stock"));if (stock > 0) {int newStock = stock - 1;valueOperations.set("stock", newStock + "");System.out.println("扣减库存成功, 剩余库存:" + newStock);} else {System.out.println("库存已经为0,不能继续扣减");}return "success";}
}

表示:

  • 先从Redis中读取stock的值,表示商品的库存
  • 判断商品库存是否大于0,如果大于0,则库存减1,然后再保存到Redis里面去,否则就报错
改进 方案(一) :

这种简单的从Redis读取、判断值再减1保存到Redis的操作,很容易在并发场景下出问题:

  • 商品超卖

比如:

假设商品的库存有50个,有3个用户同时访问该接口,先是同时读取Redis中商品的库存值,即都是读取到了50,即同时执行到了这一行:

int stock = Integer.valueOf(valueOperations.get("stock"));

然后减1,即到了这一行:

int newStock = stock - 1;

此时3个用户的realStock都是49,然后3个用户都去设置stock为49,那么就会产生库存明明被3个用户抢了,理论上是应该减去3的,结果库存数只减去了1导致商品超卖。

这种问题的产生原因是因为读取库存减库存保存到Redis这几步并不是原子操作

那么可以使用加并发锁synchronized来解决:

@Service
public class RedisLockDemo {@Autowiredprivate StringRedisTemplate redisTemplate;public String deduceStock() {ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();synchronized (this) {//获取redis中的库存int stock = Integer.valueOf(valueOperations.get("stock"));if (stock > 0) {int newStock = stock - 1;valueOperations.set("stock", newStock + "");System.out.println("扣减库存成功, 剩余库存:" + newStock);} else {System.out.println("库存已经为0,不能继续扣减");}}return "success";}
}

注意:在Java中关键字synchronized可以保证在同一时刻,只有一个线程可以执行某个方法或某个代码块。

再改进 方案(一)

以上的代码在单体模式下并没太大问题,但是在分布式或集群架构环境下存在问题,比如架构如下:

在这里插入图片描述

分布式或集群架构下,synchronized只能保证当前的主机同一时刻只能有一个线程执行减库存操作,但如图同时有多个请求过来访问的时候,不同主机在同一时刻依然是可以访问减库存接口的,这就导致问题1(商品超卖)在集群架构下依然存在。

解决方法

使用如下方案(二)分布式锁进行解决; 注意:方案(一)并不是分布式锁

方案(二)

分布式锁的简单实现图如下:

在这里插入图片描述

简单的实现代码如下:

@Service
public class RedisLockDemo {@Autowiredprivate StringRedisTemplate redisTemplate;public String deduceStock() {ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();String lockKey = "product_001";//加锁: setnxBoolean isSuccess = valueOperations.setIfAbsent(lockKey, "1");if(null == isSuccess || isSuccess) {System.out.println("服务器繁忙, 请稍后重试");return "error";}//------ 执行业务逻辑 ----start------int stock = Integer.valueOf(valueOperations.get("stock"));if (stock > 0) {int newStock = stock - 1;//执行业务操作减库存valueOperations.set("stock", newStock + "");System.out.println("扣减库存成功, 剩余库存:" + newStock);} else {System.out.println("库存已经为0,不能继续扣减");}//------ 执行业务逻辑 ----end------//释放锁redisTemplate.delete(lockKey);return "success";}
}

其实就是对每一个商品加一把锁,代码里面是product_001

  • 使用setnx对商品进行加锁
  • 如成功说明加锁成功,如失败说明有其他请求抢占了该商品的锁,则当前请求失败退出
  • 加锁成功之后进行扣减库存操作
  • 删除商品锁

以上的代码方式是有可能会造成死锁的,比如说加锁成功之后,扣减库存的逻辑可能抛异常了,即并不会执行到释放锁的逻辑,那么该商品锁是一直没有释放,会成为死锁的,其他请求完全无法扣减该商品的

使用try...catch...finally的方式可以解决抛异常的问题,如下:

@Service
public class RedisLockDemo {@Autowiredprivate StringRedisTemplate redisTemplate;public String deduceStock() {ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();String lockKey = "product_001";try {//加锁: setnxBoolean isSuccess = valueOperations.setIfAbsent(lockKey, "1");if(null == isSuccess || isSuccess) {System.out.println("服务器繁忙, 请稍后重试");return "error";}//------ 执行业务逻辑 ----start------int stock = Integer.valueOf(valueOperations.get("stock"));if (stock > 0) {int newStock = stock - 1;//执行业务操作减库存valueOperations.set("stock", newStock + "");System.out.println("扣减库存成功, 剩余库存:" + newStock);} else {System.out.println("库存已经为0,不能继续扣减");}//------ 执行业务逻辑 ----end------} finally {//释放锁redisTemplate.delete(lockKey);}return "success";}
}
改进方案(二)

那么上面的方式是不是能够解决死锁的问题呢?

其实不然,除了抛异常之外,比如程序崩溃、服务器宕机、服务器重启请求超时被终止、发布、人为kill等都有可能导致释放锁的逻辑没有执行,比如对商品加分布式锁成功之后,在扣减库存的时候服务器正在执行重启,会导致没有执行释放锁。

可以通过对锁设置超时时间来防止死锁的发生,使用Redisexpire命令可以对key进行设置超时时间,如图:
在这里插入图片描述

代码实现如下:

@Service
public class RedisLockDemo {@Autowiredprivate StringRedisTemplate redisTemplate;public String deduceStock() {ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();String lockKey = "product_001";try {//加锁: setnxBoolean isSuccess = valueOperations.setIfAbsent(lockKey, "1");//expire增加超时时间redisTemplate.expire(lockKey, 10, TimeUnit.SECONDS);if(null == isSuccess || isSuccess) {System.out.println("服务器繁忙, 请稍后重试");return "error";}//------ 执行业务逻辑 ----start------int stock = Integer.valueOf(valueOperations.get("stock"));if (stock > 0) {int newStock = stock - 1;//执行业务操作减库存valueOperations.set("stock", newStock + "");System.out.println("扣减库存成功, 剩余库存:" + newStock);} else {System.out.println("库存已经为0,不能继续扣减");}//------ 执行业务逻辑 ----end------} finally {//释放锁redisTemplate.delete(lockKey);}return "success";}
}

加锁成功之后,把锁的超时时间设置为10秒,即10秒之后自动会释放锁避免死锁的发生

再改进方案(二)

但是上面的方式同样会产生死锁问题加锁和对锁设置超时时间并不是原子操作,在加锁成功之后,即将执行设置超时时间的时候系统发生崩溃,同样还是会导致死锁。

改进图案如下:

在这里插入图片描述

对此,有两种做法:

lua脚本
set原生命令(Redis 2.6.12版本及以上)

一般是推荐使用set命令,Redis官方在2.6.12版本对set命令增加了NX、EX、PX等参数,即可以将上面的加锁和设置时间放到一条命令上执行,通过set命令即可:

命令官方文档:https://redis.io/commands/set

用法可参考:Redis命令参考

如图:
在这里插入图片描述

SET key value NX 等同于 SETNX key value命令,并且可以使用EX参数来设置过期时间

注意:其实目前在Redis 2.6.12版本之后,所说的setnx命令,并非单单指Redis的SETNX key value命令,一般是代指Redis中对set命令加上nx参数进行使用,一般不会直接使用SETNX key value命令了

注意:Redis2.6.12之前的版本,只能通过lua脚本来保证原子性了。

如图:
在这里插入图片描述

@Service
public class RedisLockDemo {@Autowiredprivate StringRedisTemplate redisTemplate;public String deduceStock() {ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();String lockKey = "product_001";try {//加锁: setnx 和 expire增加超时时间Boolean isSuccess = valueOperations.setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);if(null == isSuccess || isSuccess) {System.out.println("服务器繁忙, 请稍后重试");return "error";}//------ 执行业务逻辑 ----start------int stock = Integer.valueOf(valueOperations.get("stock"));if (stock > 0) {int newStock = stock - 1;//执行业务操作减库存valueOperations.set("stock", newStock + "");System.out.println("扣减库存成功, 剩余库存:" + newStock);} else {System.out.println("库存已经为0,不能继续扣减");}//------ 执行业务逻辑 ----end------} finally {//释放锁redisTemplate.delete(lockKey);}return "success";}
}
再再次改进 方案(二)

以上的方式其实还是存在着问题,在高并发场景下会存在问题超时时间设置不合理导致的问题

大概的流程图可参考:

在这里插入图片描述

流程:

  • 进程A加锁之后,扣减库存的时间超过设置的超时时间,这里设置的锁是10秒
  • 在第10秒的时候由于时间到期了所以进程A设置的锁被Redis释放了(T5)
  • 刚好进程B请求进来了,加锁成功(T6)
  • 进程A操作完成(扣减库存)之后,把进程B设置的锁给释放了
  • 刚好进程C请求进来了,加锁成功
    进程B操作完成之后,也把进程C设置的锁给释放了
  • 以此类推…

解决方法也很简单:

  • 加锁的时候,把值设置为唯一值,比如说UUID这种随机数
  • 释放锁的时候,获取锁的值判断value是不是当前进程设置的唯一值,如果是再去删除

如图:
在这里插入图片描述

实现的代码如下:

@Service
public class RedisLockDemo {@Autowiredprivate StringRedisTemplate redisTemplate;public String deduceStock() {ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();String lockKey = "product_001";String clientId = UUID.randomUUID().toString();try {//加锁: setnx 和 expire增加超时时间Boolean isSuccess = valueOperations.setIfAbsent(lockKey, clientId, 10, TimeUnit.SECONDS);if(null == isSuccess || isSuccess) {System.out.println("服务器繁忙, 请稍后重试");return "error";}//------ 执行业务逻辑 ----start------int stock = Integer.valueOf(valueOperations.get("stock"));if (stock > 0) {int newStock = stock - 1;//执行业务操作减库存valueOperations.set("stock", newStock + "");System.out.println("扣减库存成功, 剩余库存:" + newStock);} else {System.out.println("库存已经为0,不能继续扣减");}//------ 执行业务逻辑 ----end------} finally {if (clientId.equals(valueOperations.get(lockKey))) {//释放锁redisTemplate.delete(lockKey);}}return "success";}
}
分段锁

怎么在高并发的场景去实现一个高性能的分布式锁呢?

电商网站在大促的时候并发量很大:

(1)若抢购不是同一个商品,则可以增加Redis集群的cluster来实现,因为不是同一个商品,所以通过计算 key 的hash会落到不同的 cluster上;

(2)若抢购的是同一个商品,则计算key的hash值会落同一个cluster上,所以加机器也是没有用的。

针对第二个问题,可以使用库存分段锁的方式去实现。

分段锁

假如产品1有200个库存,可以将这200个库存分为10个段存储(每段20个),每段存储到一个cluster上;将key使用hash计算,使这些key最后落在不同的cluster上。

每个下单请求锁了一个库存分段,然后在业务逻辑里面,就对数据库或者是Redis中的那个分段库存进行操作即可,包括查库存 -> 判断库存是否充足 -> 扣减库存。

具体可以参照 ConcurrentHashMap 的源码去实现,它使用的就是分段锁。

高性能分布式锁具体可参考链接:每秒上千订单场景下的分布式锁高并发优化实践!【石杉的架构笔记】

原理如图:
在这里插入图片描述

基于数据库 - 分布式锁
实现思想

主要有两种方式:

悲观锁
乐观锁

A. 悲观锁(排他锁)

利用select * form table where xx=yy for update 排他锁

注意:这里需要注意的是 where xx=yy,xx字段必须要走索引,否则会锁表。有些情况下,比如表不大,mysql优化器会不走这个索引,导致锁表问题。

**核心思想:**以「悲观的心态」操作资源,无法获得锁成功,就一直阻塞着等待。

注意:该方式有很多缺陷,一般不建议使用。

实现:

创建一张资源锁表:

CREATE TABLE `resource_lock` (`id` int(4) NOT NULL AUTO_INCREMENT COMMENT '主键',`resource_name` varchar(64) NOT NULL DEFAULT '' COMMENT '锁定的资源名',`owner` varchar(64) NOT NULL DEFAULT '' COMMENT '锁拥有者',`desc` varchar(1024) NOT NULL DEFAULT '备注信息',`update_time` timestamp NOT NULL DEFAULT '' COMMENT '保存数据时间,自动生成',PRIMARY KEY (`id`),UNIQUE KEY `uidx_resource_name` (`resource_name `) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='锁定中的资源';

注意:resource_name 锁资源名称必须有唯一索引

使用事务查询更新:

@Transaction
public void lock(String name) {ResourceLock rlock = exeSql("select * from resource_lock where resource_name = name for update");if (rlock == null) {exeSql("insert into resource_lock(reosurce_name,owner,count) values (name, 'ip',0)");}
}

使用 for update 锁定的资源 :

​ 如果执行成功,会立即返回,执行插入数据库,后续再执行一些其他业务逻辑,直到事务提交,执行结束;

​ 如果执行失败,就会一直阻塞着。

可以在数据库客户端工具上测试出来这个效果,当在一个终端执行了 for update,不提交事务。在另外的终端上执行相同条件for update,会一直卡着

虽然也能实现分布式锁的效果,但是会存在性能瓶颈

优点:

简单易用,好理解,保障数据强一致性。

缺点:

1)在 RR 事务级别,select 的 for update 操作是基于间隙锁(gap lock) 实现的,是一种悲观锁的实现方式,所以存在阻塞问题。

2)高并发情况下,大量请求进来,会导致大部分请求进行排队,影响数据库稳定性,也会耗费服务的CPU等资源。

当获得锁的客户端等待时间过长时,会提示:

[40001][1205] Lock wait timeout exceeded; try restarting transaction

高并发情况下,也会造成占用过多的应用线程,导致业务无法正常响应。

3)如果优先获得锁的线程因为某些原因,一直没有释放掉锁,可能会导致死锁的发生。

4)锁的长时间不释放,会一直占用数据库连接,可能会将数据库连接池撑爆,影响其他服务。

5)MySql数据库会做查询优化,即便使用了索引,优化时发现全表扫效率更高,则可能会将行锁升级为表锁,此时可能就更悲剧了。

6)不支持可重入特性,并且超时等待时间是全局的,不能随便改动。

B. 乐观锁

所谓乐观锁与悲观锁最大区别在于基于 CAS思想 ,表中添加一个时间戳或者是版本号的字段来实现,update xx set version=new_version where xx=yy and version=Old_version,通过增加递增的版本号字段实现乐观锁

不具有互斥性,不会产生锁等待消耗资源,操作过程中认为不存在并发冲突,只有update version失败后才能觉察到。

抢购、秒杀就是用了这种实现以防止超卖的现象。

实现:

创建一张资源锁表:

CREATE TABLE `resource` (`id` int(4) NOT NULL AUTO_INCREMENT COMMENT '主键',`resource_name` varchar(64) NOT NULL DEFAULT '' COMMENT '资源名',`share` varchar(64) NOT NULL DEFAULT '' COMMENT '状态',`version` int(4) NOT NULL DEFAULT '' COMMENT '版本号',`desc` varchar(1024) NOT NULL DEFAULT '备注信息',`update_time` timestamp NOT NULL DEFAULT '' COMMENT '保存数据时间,自动生成',PRIMARY KEY (`id`),UNIQUE KEY `uidx_resource_name` (`resource_name `) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='资源';

为表添加一个字段,版本号或者时间戳都可以。通过版本号或者时间戳,来保证多线程同时间操作共享资源的有序性和正确性。

伪代码实现:

Resrouce resource = exeSql("select * from resource where resource_name = xxx");
boolean succ = exeSql("update resource set version= 'newVersion' ... where resource_name = xxx and version = 'oldVersion'");if (!succ) {// 发起重试
}

实际代码中可以写个while循环不断重试,版本号不一致,更新失败,重新获取新的版本号,直到更新成功。

优缺点
优点

实现简单,复杂度低
保障数据一致性

缺点:

性能低,并且有锁表的风险
可靠性差
非阻塞操作失败后,需要轮询,占用CPU资源
长时间不commit或者是长时间轮询,可能会占用较多的连接资源

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/207201.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux驱动开发——网络设备驱动(实战篇)

目录 四、 网络设备驱动实例 五、DM9000 网络设备驱动代码分析 六、NAPI 七、习题 书接上回&#xff1a; Linux驱动开发——网络设备驱动&#xff08;理论篇&#xff09;-CSDN博客 &#xff08;没看过上面博客的同学&#xff0c;skb是linux对于网络套接字缓冲区的一个虚拟…

【微服务 SpringCloudAlibaba】实用篇 · Nacos配置中心

微服务&#xff08;6&#xff09; 文章目录 微服务&#xff08;6&#xff09;1. 统一配置管理1.1 在nacos中添加配置文件1.2 从微服务拉取配置 2. 配置热更新2.1 方式一2.2 方式二 3. 配置共享1&#xff09;添加一个环境共享配置2&#xff09;在user-service中读取共享配置3&am…

vue2 el-table 封装

vue2 el-table 封装 在 custom 文件夹下面创建 tableList.vue直接上代码&#xff08;代码比较多&#xff0c;复制可直接用&#xff09; <template><div class"mp-list"><el-tableref"multipleTable"class"mp-custom-table":dat…

Ubuntu18.4中安装wkhtmltopdf + Odoo16配置【二】

deepin Linux 安装wkhtmltopdf 1、先从官网的链接里下载linux对应的包 wkhtmltopdf/wkhtmltopdf 下载需要的版本&#xff0c;推荐版本&#xff0c;新测有效&#xff1a; wkhtmltox-0.12.4_linux-generic-amd64.tar.xz 2、解压下载的文件 解压后会有一个wkhtmltox文件夹 3…

【opencv】计算机视觉基础知识

目录 前言 1、什么是计算机视觉 2、图片处理基础操作 2.1 图片处理&#xff1a;读入图像 2.2 图片处理&#xff1a;显示图像 2.3 图片处理&#xff1a;图像保存 3、图像处理入门基础 3.1 图像成像原理介绍 3.2 图像分类 3.2.1 二值图像 3.2.2灰度图像 3.2.3彩色图像…

党建引领·和谐共建——赤岗街首届微型社区养老服务公益博览会开幕

服务咨询平台&#xff0c;让社区长者更便捷地了解到养老相关政策、信息。 本次活动由赤岗街公共卫生委员会、赤岗街道办事处、中国老龄事业发展基金会老年维权基金管理委员会主办&#xff0c;珠影社区居委会、广州市穗星社会工作服务中心、广州市盈泽信息科技有限公司承办&…

4/5G互操作 EPSFB讲解

今天我们来讲一下4/5G之间之间互操作&#xff0c;以及5G的EPSFB是基于什么实现的~ 目录 4/5G互操作 重选 切换 基于覆盖的切换 基于业务的切换 两个面试问题 想要加快4G切换5G的速度&#xff0c;调哪个参数怎么调高效&#xff1f; 想要减慢5G切换4G的速度调哪个参数怎…

项目计划阶段项目管理文档(风险管理、软件估计、立项、计划)

项目计划过程支撑文件&#xff1a; 1、风险和机会管理表 2、软件估计书模板 3、立项通知 4、项目计划书 软件开发全文档获取&#xff1a;点我获取 1、风险和机会管理表 2、软件估计书模板 3、立项通知 4、项目计划模板

Spring简单的存储和读取

前言 前面讲了spring的创建&#xff0c;现在说说关于Bean和五大类注解 一、Bean是什么&#xff1f; 在 Java 语⾔中对象也叫做 Bean&#xff0c;所以后⾯咱们再遇到对象就以 Bean 著称。这篇文章还是以spring创建为主。 二、存储对象 2.1 俩种存储方式 需要在 spring-conf…

A++ 敏捷开发-1 如何改善

1 如何改善 敏捷开发过程改进案例 5月 A公司一直专门为某电信公司提供针对客服、线上播放等服务服务。 张工是公司的中层管理者&#xff0c;管理好几个开发团队&#xff0c;有5位项目经理向他汇报。 他听说老同学的团队都开始用敏捷开发&#xff0c;很感兴趣&#xff0c;便参…

kafka中的常见问题处理

文章目录 1. 如何防⽌消息丢失2. 如何防⽌重复消费3. 如何做到消息的顺序消费4. 如何解决消息积压问题4.1 消息积压问题的出现4.2 消息积压的解决⽅案 5. 实现延时队列的效果5.1 应用场景5.2 具体方案 1. 如何防⽌消息丢失 ⽣产者&#xff1a;1&#xff09;使⽤同步发送 2&…

C语言——有一个3*4的矩阵,要求求出其中值最大的那个元素的值,以及其所在的行号和列号

#define _CRT_SECURE_NO_WARNINGS 1#include<stdio.h> int main() {int i,j,row0,colum0,a[3][4]{{1,2,3,4},{9,8,7,6},{-10,10,-5,2}};int maxa[0][0];for ( i 0; i < 3; i)//行&#xff08;row&#xff09;{for ( j 0; j < 4; j)//列&#xff08;colum&#xf…

Microsoft Remote Desktop高效、安全、稳定的远程办公解决方案

在今天的数字化时代&#xff0c;Remote Desktop远程办公已成为许多人的日常生活。无论你是因为工作需要&#xff0c;还是因为在家中需要访问公司服务器&#xff0c;微软远程连接软件都是一个理想的选择。 微软远程连接软件Remote Desktop是一款高效、安全、稳定的远程办公解决…

sublime Text使用

1、增加install 命令面板 工具(tool)->控制面板(command palette) -> 输入install ->安装第一个install package controller&#xff0c;以下安装过了&#xff0c;所以没展示 2、安装json格式化工具 点击install package&#xff0c;等几秒会进入控制面板&#xff0…

R语言30分钟上手

文章目录 1. 环境&安装1.1. rstudio保存工作空间 2. 创建数据集2.1. 数据集概念2.2. 向量、矩阵2.3. 数据框2.3.1. 创建数据框2.3.2. 创建新变量2.3.3. 变量的重编码2.3.4. 列重命名2.3.5. 缺失值2.3.6. 日期值2.3.7. 数据框排序2.3.8. 数据框合并(合并沪深300和中证500收盘…

B 树和 B+树 的区别

文章目录 B 树和 B树 的区别 B 树和 B树 的区别 了解二叉树、AVL 树、B 树的概念 B 树和 B树的应用场景 B 树是一种多路平衡查找树&#xff0c;为了更形象的理解。 二叉树&#xff0c;每个节点支持两个分支的树结构&#xff0c;相比于单向链表&#xff0c;多了一个分支。 …

WSL2 docker GUI 界面

在 WSL2 docker 中运行GUI界面。 具体流程和远程显示Ubuntu界面类似&#xff0c;链接, 更简单一点&#xff0c; 少了 ssh 的部分。 安装好wsl2 和 docker wsl2 运行GUI程序&#xff0c;windows 会默认弹出窗口。 可以安装 gedit 测试一下 windows 下载并运行 Xlaunch. 运行 d…

(二) Windows 下 Sublime Text 3 安装离线插件 Anaconda

1 下载 Sublime Text 3 免安装版 Download - Sublime Text 2 下载 Package Control&#xff0c;放到 Sublime Text Build 3211\Data\Installed Packages 目录下。 Installation - Package Control 3 页面搜索 anaconda anaconda - Search - Package Control Anaconda - Pac…

python opencv -模板匹配

python opencv -模板匹配 模板匹配就是&#xff0c;我们现有一个模板和一个图片&#xff0c;然后&#xff0c;在这个图片中寻找和模板近似的部分。 在opencv 中主要通过cv2.matchTemplate这个函数去实现。 下面我们先看一下&#xff0c;模板图片和需要匹配的图片&#xff1a…

HTML-标签之文字排版、图片、链接、音视频

1、标签语法 HTML超文本标记语言——HyperText Markup Language 超文本是链接标记也叫标签&#xff0c;带尖括号的文本 2、HTML基本骨架 HTML基本骨架是网页模板 html&#xff1a;整个网页head&#xff1a;网页头部&#xff0c;存放给浏览器看的代码&#xff0c;例如CSSbody…