阶段十-分布式锁

news2024/11/18 19:49:21

5.1 节 为什么要使用分布式锁

锁是多线程代码中的概念,只有当多任务访问同一个互斥共享资源时才需要。如下图:

在我们进行单机应用开发,涉及并发同步的时候,我们往往采用synchronized或者lock的方式来解决多线程间的代码同步问题,这时多线程的运行都是在同一个JVm之下。但当我们的应用是分布式集群工作的情况下,属于JVM下的工作环境,JVM之间已经无法通过多线程的锁来解决同步问题。需要更加高级的锁机制,来处理跨机器的进程的数据之间的同步问题——这就是分布式锁

5.2 节 分布式锁的几种方式

分布式锁的核心思路是记住外力解决多JVM进程操作共享数据时需要使用互斥锁问题。常见的方式有:

  1. mysql数据库

  2. zookeeper

  3. redis

5.3 节 搭建测试分布式锁的环境

【1】创建工程distributed-lock-study ,pom如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.6</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>top.psjj</groupId>
    <artifactId>distributed-lock-study</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>pom</packaging>
    <name>distributed-lock-study</name>
    <description>distributed-lock-study</description>
    <modules>
        <module>moduleB</module>
        <module>moduleA</module>
    </modules>
    <properties>
        <java.version>8</java.version>
        <mysql-connection.version>8.0.26</mysql-connection.version>
        <druid.version>1.2.1</druid.version>
        <mybatis-plus.version>3.5.2</mybatis-plus.version>
    </properties>
    <dependencyManagement>
        <dependencies>
            <!-- Dubbo 依赖 -->
            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo-spring-boot-starter</artifactId>
                <version>${dubbo.starter}</version>
            </dependency>
            <!-- zookeeper 注册中心 依赖 -->
            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo-registry-zookeeper</artifactId>
                <version>${dubbo.registry.zookeeper}</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql-connection.version}</version>
                <scope>runtime</scope>
            </dependency>
            <!--druid连接池-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>${druid.version}</version>
            </dependency>
            <!--mybatis-plus依赖-->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${mybatis-plus.version}</version>
            </dependency>
            <!--hutool-->
            <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-all</artifactId>
                <version>${hutool.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--mybatis-plus依赖-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!--druid连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
        </dependency>
        <!--redis依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--common-pool-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

【2】创建moduleA和moduleB两个模块

【3】在两个模块中编写application.yml

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/lock_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username: root
    password: root
  redis:
    host: 192.168.9.132
    port: 6379
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0
        max-wait: 100ms
server:
  port: 1111
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/lock_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
  redis:
    host: 192.168.184.200
    port: 6379
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0
        max-wait: 100ms
server:
  port: 2222

【4】编写启动类

package com.sh;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @Auther: 世豪讲java
 * @Date: 2024/1/14 - 01 - 14 - 20:55
 * @Decsription: com.sh
 * @version: 1.0
 */
@SpringBootApplication
@MapperScan("com.sh.ma.mapper")
public class ModuleAApplication {
    public static void main(String[] args) {
        SpringApplication.run(ModuleAApplication.class,args);
    }
}
package com.sh;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @Auther: 世豪讲java
 * @Date: 2024/1/14 - 01 - 14 - 20:55
 * @Decsription: com.sh
 * @version: 1.0
 */
@SpringBootApplication
@MapperScan("com.sh.ma.mapper")
public class ModuleBApplication {
    public static void main(String[] args) {
        SpringApplication.run(ModuleBApplication.class,args);
    }
}

【5】准备数据库local_db,并出入下张表

/*
 Navicat Premium Data Transfer

 Source Server         : conn
 Source Server Type    : MySQL
 Source Server Version : 80026
 Source Host           : 127.0.0.1:3306
 Source Schema         : lock_db

 Target Server Type    : MySQL
 Target Server Version : 80026
 File Encoding         : 65001

 Date: 12/01/2024 15:43:48
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_goods
-- ----------------------------
DROP TABLE IF EXISTS `t_goods`;
CREATE TABLE `t_goods`  (
  `id` int(0) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `goods` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL,
  `count` int(0) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_unicode_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of t_goods
-- ----------------------------
INSERT INTO `t_goods` VALUES (1, '手机', -1);
INSERT INTO `t_goods` VALUES (2, '笔记本', 100);

SET FOREIGN_KEY_CHECKS = 1;

【6】编写po 、mapper 、service 、controller,两个模块代码完全一样

package com.sh.ma.po;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * @Auther: 世豪讲java
 * @Date: 2024/1/14 - 01 - 14 - 21:01
 * @Decsription: com.sh.ma.po
 * @version: 1.0
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("t_goods")
public class Goods implements Serializable {
    @TableId(type = IdType.AUTO)
    private Integer id;
    private String goods;
    private Integer count;
}
package com.sh.ma.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.sh.ma.po.Goods;
import org.apache.ibatis.annotations.Update;

/**
 * @Auther: 世豪讲java
 * @Date: 2024/1/14 - 01 - 14 - 21:01
 * @Decsription: com.sh.ma.service
 * @version: 1.0
 */
public interface GoodsMapper extends BaseMapper<Goods> {
    @Update("update t_goods set count = count-1 where id=#{id}")
    void subCount(Integer id);
}
package com.sh.ma.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.sh.ma.po.Goods;

/**
 * @Auther: 世豪讲java
 * @Date: 2024/1/14 - 01 - 14 - 21:02
 * @Decsription: com.sh.ma.service
 * @version: 1.0
 */
public interface GoodsService extends IService<Goods> {
    void updateGoodsCount(Integer id);
}
package com.sh.ma.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sh.ma.mapper.GoodsMapper;
import com.sh.ma.po.Goods;
import com.sh.ma.service.GoodsService;
import org.springframework.stereotype.Service;

/**
 * @Auther: 世豪讲java
 * @Date: 2024/1/14 - 01 - 14 - 21:02
 * @Decsription: com.sh.ma.service.impl
 * @version: 1.0
 */
@Service
public class GoodsServiceImpl extends ServiceImpl<GoodsMapper, Goods> implements GoodsService {
    @Override
    public void updateGoodsCount(Integer id) {
        Goods goods = this.baseMapper.selectById(id);
        Integer count = goods.getCount();
        if(count>0){
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            this.baseMapper.subCount(id);
        }
    }
}
package com.sh.ma.controller;

import com.sh.ma.service.GoodsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Auther: 世豪讲java
 * @Date: 2024/1/14 - 01 - 14 - 21:03
 * @Decsription: com.sh.ma.controller
 * @version: 1.0
 */
@RestController
@RequestMapping("/goods")
public class GoodsController {
    @Autowired
    private GoodsService goodsService;

    @RequestMapping("/update")
    public String updateGoodsCount(Integer id){
        goodsService.updateGoodsCount(id);
        return "ok";
    }
}

【最后】测试

 

出现了超卖问题

5.4 节 基于Mysql实现的分布式锁

5.4.1 mysql实现分布式锁原理

基于分布式锁的实现,首先肯定是想单独分离出一台mysql数据库,所有服务要想操作文件(共享资源),那么必须先在mysql数据库中插入一个标志,插入标志的服务就持有了锁,并对文件进行操作,操作完成后,主动删除标志进行锁释放,其与服务会一直查询数据库,看是否标志有被占用,直到没有标志占用时自己才能写入标志获取锁。

  但是这样有这么一个问题,如果服务(jvm1)宕机或者卡顿了,会一直持有锁未释放,这样就造成了死锁,因此就需要有一个监视锁进程时刻监视锁的状态,如果超过一定时间未释放就要进行主动清理锁标记,然后供其与服务继续获取锁。

  如果监视锁字段进程和jvm1同时挂掉,依旧不能解决死锁问题,于是又增加一个监视锁字段进程,这样一个进程挂掉,还有另一个监视锁字段进程可以对锁进行管理。这样又诞生一个新的问题,两个监视进程必须进行同步,否则对于过期的情况管理存在不一致问题。

  因此存在以下问题,并且方案变得很复杂:

  1. 监视锁字段进程对于锁的监视时间周期过短,仍旧会造成多售(jvm1还没处理完其持有的锁就被主动销毁,造成多个服务同时持有锁进行操作)。

  2. 监视锁字段进程对于锁的监视时间周期过长,会造成整个服务卡顿过长,吞吐低下。

  3. 监视锁字段进程间的同步问题。

  4. 当一个jvm持有锁的时候,其余服务会一直访问数据库查看锁,会造成其余jvm的资源浪费。

5.4.2 基于update实现分布锁(特殊情况)

举一个电商系统,商品数量超卖问题:

如果库存递减使用下面的伪代码会产生超卖现象,超卖现象的本质都是多线程数据同步问题,因为时分布式系统,不能单纯的加锁处理,如果处理下面的逻辑需要使用分布式锁,关于分布式锁,因为我们的代码直接执行语句,有数据库行级锁,不会产生超卖问题

//mysql行锁解决分布锁问题
try {
    Thread.sleep(5000);
} catch (InterruptedException e) {
    throw new RuntimeException(e);
}
this.baseMapper.subCount2(goods);
@Update("update t_goods set count=count-1 where id=#{id} and count>0")
void subCount2(Goods goods);

              

5.4 节 基于Redis实现分布式锁

5.4.1 Redis实现分布式锁优点

(1)Redis有很高的性能; (2)Redis命令对此支持较好,实现起来比较方便

 

5.4.2 命令介绍

(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实现分布式锁的时候,主要就会使用到这三个命令。

5.4.3 Redis实现分布式锁原理

(1)获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。

(2)获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。

(3)释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。

5.4.4 Redisson分布式锁使用

1)引入依赖

<!-- redis 使用-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.14.0</version>
</dependency>

2)配置文件

spring:
  redis:
    host: 192.168.9.132
    port: 6379

关键代码

//创建锁
RLock lock = redissonClient.getLock("goods-" + id);
//加锁
try {
    lock.lock();
    Goods goods = this.baseMapper.selectById(id);
    Integer count = goods.getCount();
    if(count>0){
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        this.baseMapper.subCount(id);
    }
} catch (RuntimeException e) {
    throw new RuntimeException("超卖了");
} finally {
    //释放锁
    lock.unlock();
}

5.4.5 总结

可以使用缓存来代替数据库来实现分布式锁,这个可以提供更好的性能,同时,很多缓存服务都是集群部署的,可以避免单点问题。并且很多缓存服务都提供了可以用来实现分布式锁的方法,比如Tair的put方法,redis的setnx方法等。并且,这些缓存服务也都提供了对数据的过期自动删除的支持,可以直接设置超时时间来控制锁的释放。

使用缓存实现分布式锁的优点

  • 性能好,实现起来较为方便。

使用缓存实现分布式锁的缺点

  • 通过超时时间来控制锁的失效时间并不是十分的靠谱。

后面项目中采用的Redisson,就是采用redis实现分布式锁

Redisson是Redis官方推荐的Java版的Redis客户端。它提供的功能非常多,也非常强大,此处我们只用它的分布式锁功能。

5.5 节 基于Zookeeper实现的分布式锁

基于以上两种实现方式,有了基于zookeeper实现分布式锁的方案。由于zookeeper有以下特点:

  1. 维护了一个有层次的数据节点,类似文件系统。

  2. 有以下数据节点:临时节点、持久节点、临时有序节点(分布式锁实现基于的数据节点)、持久有序节点。

  3. zookeeper可以和client客户端通过心跳的机制保持长连接,如果客户端链接zookeeper创建了一个临时节点,那么这个客户端与zookeeper断开连接后会自动删除。

  4. zookeeper的节点上可以注册上用户事件(自定义),如果节点数据删除等事件都可以触发自定义事件。

  5. zookeeper保持了统一视图,各服务对于状态信息获取满足一致性。

Zookeeper的每一个节点,都是一个天然的顺序发号器。

  在每一个节点下面创建子节点时,只要选择的创建类型是有序(EPHEMERAL_SEQUENTIAL 临时有序或者PERSISTENT_SEQUENTIAL 永久有序)类型,那么,新的子节点后面,会加上一个次序编号。这个次序编号,是上一个生成的次序编号加一

  比如,创建一个用于发号的节点“/test/lock”,然后以他为父亲节点,可以在这个父节点下面创建相同前缀的子节点,假定相同的前缀为“/test/lock/seq-”,在创建子节点时,同时指明是有序类型。如果是第一个创建的子节点,那么生成的子节点为/test/lock/seq-0000000000,下一个节点则为/test/lock/seq-0000000001,依次类推,等等。

5.5.1 Zookeeper实现分布式锁原理

 

  1. 创建一个目录mylock;

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

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

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

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

来看下Zookeeper能不能解决前面提到的问题。

  • 锁无法释放?使用Zookeeper可以有效的解决锁无法释放的问题,因为在创建锁的时候,客户端会在ZK中创建一个临时节点,一旦客户端获取到锁之后突然挂掉(Session连接断开),那么这个临时节点就会自动删除掉。其他客户端就可以再次获得锁。

  • 非阻塞锁?使用Zookeeper可以实现阻塞的锁,客户端可以通过在ZK中创建顺序节点,并且在节点上绑定监听器,一旦节点有变化,Zookeeper会通知客户端,客户端可以检查自己创建的节点是不是当前所有节点中序号最小的,如果是,那么自己就获取到锁,便可以执行业务逻辑了。

  • 不可重入?使用Zookeeper也可以有效的解决不可重入的问题,客户端在创建节点的时候,把当前客户端的主机信息和线程信息直接写入到节点中,下次想要获取锁的时候和当前最小的节点中的数据比对一下就可以了。如果和自己的信息一样,那么自己直接获取到锁,如果不一样就再创建一个临时的顺序节点,参与排队。

  • 单点问题?使用Zookeeper可以有效的解决单点问题,ZK是集群部署的,只要集群中有半数以上的机器存活,就可以对外提供服务。 

 

5.5.2 基于Curator 客户端实现分布式锁

Curator Framework提供了简化使用zookeeper更高级的API接口。它包涵很多优秀的特性,主要包括以下三点

  1. 自动连接管理:自动处理zookeeper的连接和重试存在一些潜在的问题;可以watch NodeDataChanged event和获取updateServerList;Watches可以自动被Cruator recipes删除;

  2. 更干净的API:简化raw zookeeper方法,事件等;提供现代流式API接口

  3. Recipe实现:leader选举,分布式锁,path缓存,和watcher,分布式队列等。

单独使用依赖

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-framework</artifactId>
    <version>4.0.1</version>
</dependency>
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>4.0.1</version>
</dependency>

代码:

package com.sh.ma.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sh.ma.mapper.GoodsMapper;
import com.sh.ma.po.Goods;
import com.sh.ma.service.GoodsService;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

/**
 * @Auther: 世豪讲java
 * @Date: 2024/1/14 - 01 - 14 - 21:02
 * @Decsription: com.sh.ma.service.impl
 * @version: 1.0
 */
@Service
public class GoodsServiceImpl extends ServiceImpl<GoodsMapper, Goods> implements GoodsService {
    @Autowired
    private RedissonClient redissonClient;

    @Override
    public void updateGoodsCount(Integer id) {
//        Goods goods = this.baseMapper.selectById(id);
//        Integer count = goods.getCount();
//        if(count>0){
//            try {
//                Thread.sleep(5000);
//            } catch (InterruptedException e) {
//                throw new RuntimeException(e);
//            }
        //this.baseMapper.subCount(id);
        //this.baseMapper.subCount2(goods);
        //创建锁
//        RLock lock = redissonClient.getLock("goods-" + id);
//        try {
//            //加锁
//            lock.lock();
//            Goods goods = this.baseMapper.selectById(id);
//            Integer count = goods.getCount();
//            if (count > 0) {
//                try {
//                    Thread.sleep(5000);
//                } catch (InterruptedException e) {
//                    throw new RuntimeException(e);
//                }
//                this.baseMapper.subCount(id);
//            }
//        } catch (RuntimeException e) {
//            throw new RuntimeException(e);
//        } finally {
//            //释放锁
//            lock.unlock();
//        }

        //zookeeper 分布式锁解决超卖问题
        //1.创建zookeeper连接
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);
        CuratorFramework client= CuratorFrameworkFactory.newClient("192.168.9.132:2181", retryPolicy);
        client.start();
        //创建分布式锁
        InterProcessMutex interProcessMutex = new InterProcessMutex(client,"/ordersettinglock");
        try {
            interProcessMutex.acquire();
            Goods goods = this.baseMapper.selectById(id);
            Integer count = goods.getCount();
            if(count>0){
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                this.baseMapper.subCount(id);
            }
        } catch (Exception e) {
            throw new RuntimeException("超卖了");
        }finally {
            //释放锁
            try {
                interProcessMutex.release();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
}

5.5.3 总结

使用Zookeeper实现分布式锁的优点

  • 有效的解决单点问题,不可重入问题,非阻塞问题以及锁无法释放的问题。实现起来较为简单。

使用Zookeeper实现分布式锁的缺点

  • 性能上不如使用缓存实现分布式锁。 需要对ZK的原理有所了解,比较复杂

5.6 节 分布式锁总结

上面几种方式,哪种方式都无法做到完美。就像CAP一样,在复杂性、可靠性、性能等方面无法同时满足,所以,根据不同的应用场景选择最适合自己的才是王道。

从理解的难易程度角度(从低到高)

  • 数据库 > 缓存 > Zookeeper

从实现的复杂性角度(从低到高)

  • Zookeeper >= 缓存 > 数据库

从性能角度(从高到低)

  • 缓存 > Zookeeper >= 数据库

从可靠性角度(从高到低)

  • Zookeeper > 缓存 > 数据库    

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

分布式任务调度平台XXL-JOB使用(二)

说明&#xff1a;之前总结过在CentOS系统里使用XXL-JOB。但在代码开发阶段&#xff0c;我们有时需要在本地环境测试定时任务代码&#xff0c;本文介绍如何在Windows系统使用XXL-JOB。 下载 &#xff08;1&#xff09;下载代码&#xff0c;解压打开 首先&#xff0c;去Github…

权责发生制和收付实现制

目录 一. 权责发生制(应记制)二. 收付实现制 \quad 一. 权责发生制(应记制) 应计制就是应该记入的意思 各项收入和费用的确认应当以“实际发生”&#xff08;归属期&#xff09;而不是以款项的实际收付作为记账的基础。 正是有会计期间假设&#xff0c;才有权责发生制和收付实…

逸学Docker【java工程师基础】3.1安装Jenkins

1.下载镜像 docker pull jenkins/jenkins:lts 2.运行容器 docker run -d -u root -p 8080:8080 -p 50000:50000 -v /var/jenkins_home:/var/jenkins_home -v /etc/localtime:/etc/localtime --name jenkins jenkins/jenkins:lts 3.要启动名为 jenkins 的 Docker 容器 docker st…

【软件测试】学习笔记-性能测试的基本方法与应用领域

这篇文章探讨并发用户数、响应时间和系统吞吐量这三个指标之间的关系和约束&#xff0c;性能测试七种常用方法&#xff0c;以及四大应用领域。 由于性能测试是一个很宽泛的话题&#xff0c;所以不同的人对性能测试的看法也不完全一样&#xff0c;同样一种方法可能也会有不同的…

基于STM32的CMT液晶屏控制器驱动程序设计与优化

本文以STM32微控制器为基础&#xff0c;设计并优化了一个用于控制CMT液晶屏的驱动程序。在设计过程中&#xff0c;我们首先介绍了液晶屏的基本工作原理&#xff0c;包括CMT液晶屏的结构和信号传输机制。然后&#xff0c;我们详细讨论了STM32微控制器的GPIO、SPI和DMA模块的特性…

Invalid bound statement (not found)(xml文件创建问题)

这边大致讲一下我的经历&#xff0c;不想看的直接点目录去解决方法 今天照着老师视频学习&#xff0c;中间老师在使用动态SQL时&#xff0c;直接复制了一份&#xff0c;我想这么简单的一个&#xff0c;我直接从网上找内容创建一个好了&#xff0c;但是&#xff0c;但是没想到过…

书生·浦语大模型实战营-学习笔记3

目录 (3)基于 InternLM 和 LangChain 搭建你的知识库1. 大模型开发范式&#xff08;RAG、Fine-tune&#xff09;RAG微调 &#xff08;传统自然语言处理的方法&#xff09; 2. LangChain简介&#xff08;RAG开发框架&#xff09;3. 构建向量数据库4. 搭建知识库助手5. Web Demo部…

word无法插入方程式(方程式反灰)

word无法插入方程式&#xff08;方程式反灰&#xff09; 来自实测>插入方程式&#xff0c;反灰用不了>随便存在哪里&#xff0c;右键看属性&#xff1a;>发现真的是doc&#xff0c;得改成docx才可以&#xff1a;>打开原始档案&#xff0c;另存为word文件即可&#…

机器学习 | 多层感知机MLP

机器学习 | 多层感知机MLP 1. 实验目的 自行构造一个多层感知机&#xff0c;完成对某种类型的样本数据的分类&#xff08;如图像、文本等&#xff09;&#xff0c;也可以对人工自行构造的二维平面超过3类数据点&#xff08;或者其它标准数据集&#xff09;进行分类。 2. 实验…

Java安装(可多版本共存)及IIntelliJ IDEA环境搭建汉化(保姆级教程!)

编程如画&#xff0c;我是panda&#xff01; 这次给大家出一期JAVA安装以及IIntelliJ IDEA的安装教程 IIntelliJ IDEA分为社区版和专业版&#xff0c;两版的教程都有&#xff0c;小伙伴们根据需要自行选择使用 并且我会讲解一台计算机中多个版本JAVA JDK配置安装 前言 我最早接…

书生·浦语大模型实战营笔记(四)

Finetune模型微调 直接使用现成的大语言模型&#xff0c;在某些场景下效果不好&#xff0c;需要根据具体场景进行微调 增量预训练&#xff1a;投喂垂类领域知识 陈述形式&#xff0c;无问答&#xff0c;即只有assistant 指令跟随&#xff1a;system-user-assistant XTuner …

【力扣每日一题】力扣2696删除子串后的字符串最小长度

题目来源 力扣2696删除子串后的字符串最小长度 题目概述 给你一个仅由 大写 英文字符组成的字符串 s 。 你可以对此字符串执行一些操作&#xff0c;在每一步操作中&#xff0c;你可以从 s 中删除 任一个 "AB" 或 "CD" 子字符串。 通过执行操作&#xff…

保卫战小游戏

欢迎来到程序小院 保卫战 玩法&#xff1a;当鬼子进入射击范围内点击鼠标左键射击&#xff0c;不要让鬼子越过炮台哦&#xff0c;快去杀鬼子去吧^^。开始游戏https://www.ormcc.com/play/gameStart/249 html <div style"position: relative;" id"gameDiv&q…

关于浏览器下载的时候出现失败,网络错误

我试过所有浏览器&#xff0c;谷歌&#xff0c;firefox,qq浏览器&#xff0c;还是edge都不好使&#xff0c; 1.看网上说是http debugger的问题&#xff0c;但是我没有找到这个服务项 2.也有说可以通过修改或设置下载路径解决 -------- 我通过下载一个叫xdm的软件&#xff…

网络协议与攻击模拟_05TCP协议

一、传输层知识回顾 &#xff11;、传输层的功能 定义应用层协议数据报文的端口号&#xff0c;流量控制对原始数据进行分段处理 &#xff12;、传输层提供的服务 传输连接服务数据传输服务、流量控制、差错控制、序列控制 &#xff13;、传输层的协议 面向连接的传输协议…

VQGAN:从图像重建到图像生成

本文的目标是作为全新图像生成系统的VQGAN。我已经开始讨论VQGAN的一部分——自编码器&#xff08;VQVAE&#xff1a;矢量量化变分自动编码器&#xff09;。VQVAE的概念是对编码器、解码器和码书的同时训练&#xff0c;该码书适用于所有可能的图像。码书是一组256个嵌入向量。具…

毕业设计:基于python微博舆情分析系统+可视化+Django框架 K-means聚类算法(源码)✅

毕业设计&#xff1a;2023-2024年计算机专业毕业设计选题汇总&#xff08;建议收藏&#xff09; 毕业设计&#xff1a;2023-2024年最新最全计算机专业毕设选题推荐汇总 &#x1f345;感兴趣的可以先收藏起来&#xff0c;点赞、关注不迷路&#xff0c;大家在毕设选题&#xff…

Material Design 进阶(十一)——Chip,ChipGroup,ChipDrawable使用

流式布局标签发展历程 第一阶段&#xff1a;实现这种界面的时候&#xff0c;基本都是自定义一个控件&#xff0c;然后在Java代码中动态的 添加一个个的TextView&#xff0c;还需要计算布局宽度/高度&#xff0c;进行换行等等处理&#xff0c;比较复杂;第二阶段&#xff1a;使用…

CSC8021_computer network_The Transport Layer

Role of the transport layer • The transport layer is responsible for providing a reliable end-to-end connection between two application processes in a network • Abstracting away the physical subnet • Does not involve intermediate nodes • Takes a netwo…

Centos源码编译安装Redis

Redis是常用的内容使用工具&#xff0c;每次安装服务器都需要安装Redis 为了减少重复工作&#xff0c;写了一个脚本自动安装Redis&#xff0c;如下 #!/bin/sh #下载源码 curl -O http://download.redis.io/redis-stable.tar.gz # 解压缩 tar zxf redis-stable.tar.gz cd redi…