简洁易懂:源码+实战讲解Redisson并发锁及看门狗自动续期

news2024/11/27 15:32:52

1 缘起

有一次同事问Redisson存储的键是否为hash?
我当时,没有看Redisson的相关源码,只知道应用,
所以没有办法回答,于是开始看看Redisson实现的源码,
顺便写了一个单机Redisson测试,
发现Redisson有看门狗功能,但是,不是所有的方法都会触发,
只有lock()方法才会触发看门狗,其他的方法不会触发看门狗功能,
如lock(long, TimeUnit),tryLock(),
本文从结果触发,先理论分析,然后,代码实践验证。
先从源码讲解Redisson相关的知识,包括如何新建锁、释放锁、看门狗功能,
然后代码实战,验证这些功能,
感兴趣的同学可以手动调试。
帮助读者轻松应对知识交流与考核。

版本信息:
SpringBoot:2.1.7
Redisson:3.7.5

2 Redisson属性源码分析与验证

2.1 新建锁

位置:org.redisson.RedissonLock#tryLockInnerAsync
新建锁和可重入源码如下图所示,由源码可知,
Redisson使用Lua脚本新建锁。

  • 为什么?
    保证操作原子性。
    因为新建数据和添加过期时间是两步操作,不能保证原子性,
    因此使用Lua实现,保证新建锁时是原子操作。

Redisson实现的锁是可重入的。同一个线程获取锁,无需重新排队申请,只需增加锁的持有次数。

  • 怎么做?
    判断锁是的属性是否存在。
    通过hexists判断锁的属性是否存在(UUID:threadId),如果存在,值+1,同时,更新锁过期时间,
    使用Lua脚本,保证原子性。

在这里插入图片描述

Lua脚本中的参数KEYS[1],ARGV[1], ARGV[2],

  • 分别对应哪些参数呢?
    KEYS[1]:Collections.singletonList(getName()),即锁的键名;
    ARGV[1]:internalLockLeaseTime,即锁过期时间;
    ARGV[2]:getLockName(threadId),即锁(Hash)属性的键,UUDI:threadId。
    其中,getLockName源码如下图所示,由图可知,UUID拼接threadId作为锁属性的键。
    顺便补充一下,Hash结构,键名,即获取该Hash的键,属性键名,即Hash内部数据的键。

在这里插入图片描述

下面看一下新建锁的调试过程,如下图所示,
(单步调试,如果操作不及时或者有其他时延,会导致Redis数据过期,因此,选择单步调试)
由图可知,新建的锁,键名称为redisLockKey,
默认锁过期时间为30秒,UUID为edb0f8cb-ec11-4b29-a107-12be152c4a5e,
threadId为71,锁属性的键为:edb0f8cb-ec11-4b29-a107-12be152c4a5e:71,属性键的值为1。
在这里插入图片描述
新建锁之后,会在Redis生成对应的数据,
如下图所示,由图可知,Redisson新建的锁键为redisLockKey,
锁属性名称为:edb0f8cb-ec11-4b29-a107-12be152c4a5e:71,属性值为1。
过期时间TTL已经开始倒计时了,实时的值为17秒。
在这里插入图片描述

2.2 获取锁

Redisson获取锁有两种方式:lock获取和tryLock获取。
lock()获取锁,如果得不到,会一直等,同时,lock()会开启看门狗功能,在看门狗巡查期间,锁不会过期;
lock(long, java.util.concurrent.TimeUnit)获取锁,会一直等,当锁过期后,即可自动获取;
tryLock()获取锁,得不到会一直等,当锁过期后,可自动获取到;
tryLock(long, long, java.util.concurrent.TimeUnit)获取锁,有两个时间参数,可以同时指定等待锁的最大时间,以及锁过期时间,如果等待最大时间大于锁过期时间,则锁被提前释放,重新生成锁;

2.2.1 lock

位置:java.util.concurrent.locks.Lock#lock

无参的lock方法来自JUC接口,
Redisson自身实现lock,并加入看门狗功能。
无返回值。
在这里插入图片描述
Redisson的实现源码如下图所示,这里给出路径,感兴趣可自行查看。
位置:org.redisson.RedissonLock#lock()

在这里插入图片描述

有参的lock源码如下图所示,
这个lock来自Redisson的RLock,添加了锁过期时间,
可以自定义锁的过期时间,这个获取锁的方法不会触发看门狗。
无返回值。

位置:org.redisson.api.RLock#lock
在这里插入图片描述
Redisson实现源码如下,这里给出路径,感兴趣可自行查看。
位置:org.redisson.RedissonLock#lock(long, java.util.concurrent.TimeUnit)
在这里插入图片描述

2.2.2 tryLock

位置:java.util.concurrent.locks.Lock#tryLock()
获取锁,无法获取时等待,当锁自动过期后,可自动获取。
有返回值,可通过返回值判断是否获得锁。
true:成功获取锁;
false:未获取锁。
在这里插入图片描述
Redisson实现tryLock源码如下图所示。
位置:org.redisson.RedissonLock#tryLock()
在这里插入图片描述

有参数的tryLock源码下图所示,有源码可知,
可指定最大等待锁时间、锁过期时间,
如果等待锁的最大时间小于锁过期时间,返回false,标识未得到锁。
如果等待锁的最大时间大于锁过期时间,等锁释放后,可自动获取锁,返回true,标识得到锁。
有返回值,可通过返回值判断是否获得锁。
true:成功获取锁;
false:未获取锁。
位置:org.redisson.api.RLock#tryLock

在这里插入图片描述

获取锁的实现源码如下图所示,这里给出路径,感兴趣可自行查看。
位置:org.redisson.RedissonLock#tryLock(long, long, java.util.concurrent.TimeUnit)
在这里插入图片描述

2.3 释放锁

位置:org.redisson.RedissonLock#unlockInnerAsync
释放锁源码如下图所示,由图可知,释放锁时会将锁属性的值-1,(加-1),
当释放的锁存在时,会将锁属性的值减1,减后的值大于零,重置锁的过期时间,保证锁的过期时间周期,
如果减1之后,锁属性值为0,即没有线程再持有锁,则删除该锁(del)。
在这里插入图片描述

2.4 锁续期

看门狗
位置:org.redisson.config.Config#lockWatchdogTimeout
看门狗的默认超时时间为30秒,即锁过期时间的续期为30秒,
看门狗的巡查周期为10秒,即每10秒更新一次锁的过期时间,设置为30秒。
两个是独立的时间系统。
30秒:锁的过期时间;
10秒:看门狗巡查周期。
这个10秒是如何得到的:30/3=10。

在这里插入图片描述

位置:org.redisson.RedissonLock#scheduleExpirationRenewal
看门狗进行锁续期的源码如下图所示,由源码可知,
当锁存在时,会延迟10秒执行更新锁过期时间,过期时间为30秒,
看门狗的巡查周期就是这个延迟时间:10秒。
在这里插入图片描述
当然,好奇的同学会问,锁的默认过期时间怎么就是30秒,
这个30秒是从哪里来的呢?
过期时间(internalLockLeaseTime)默认值取自看门狗的超时时间:30秒,上文有看门狗的默认值源码。
在这里插入图片描述

3 实战

版本信息:
SpringBoot:2.1.7
Redisson:3.7.5

3.1 依赖

<!--Redisson-->
<dependency>
	<groupId>org.redisson</groupId>
	<artifactId>redisson</artifactId>
	<version>3.7.5</version>
</dependency>

3.2 配置

3.2.1 Redis配置

application-dev.yml

spring:
  profiles: dev
  redis:
    host: localhost
    port: 6379
    # 连接超时时间:毫秒
    timeout: 60000
    database: 0
    lettuce:
      pool:
        # 连接池最大连接数量
        max-active: 8
        # 连接池最大阻塞等待时间:-1无限制
        max-wait: 60000
        # 连接池最大空闲连接数量
        max-idle: 8
        # 连接池最小空闲连接数量
        min-idle: 0

3.2.2 绑定Redis配置

package com.hardsoft.monkeyrun.common.lock;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

/**
 * Redis连接参数.
 *
 * @author xindaqi
 * @since 2021/4/2 11:41
 */
@Configuration
@ConfigurationProperties(prefix="spring.redis")
public class RedisConnectionParams {

    /**
     * 主机名
     */
    private String host;

    /**
     * 主机端口
     */
    private String port;

    /**
     * Redis连接超时时间:毫秒
     */
    private int timeout;

    /**
     * Redis数据库编号
     */
    private int database;

    public void setHost(String host) {
        this.host = host;
    }

    public String getHost() {
        return host;
    }

    public void setPort(String port) {
        this.port = port;
    }

    public String getPort() {
        return port;
    }

    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }

    public int getTimeout() {
        return timeout;
    }

    public void setDatabase(int database) {
        this.database = database;
    }

    public int getDatabase() {
        return database;
    }

    @Override
    public String toString() {
        return "RedisConnectionParams{" +
                "host='" + host + '\'' +
                ", port='" + port + '\'' +
                ", timeout=" + timeout +
                ", database=" + database +
                '}';
    }
}

3.2.3 Redisson配置

package com.hardsoft.monkeyrun.common.config;

import com.hardsoft.monkeyrun.common.lock.RedisConnectionParams;
import com.hardsoft.monkeyrun.common.lock.RedissonLocker;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.config.SingleServerConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;

import static com.hardsoft.monkeyrun.common.constrant.StringConstant.COLON;
import static com.hardsoft.monkeyrun.common.constrant.StringConstant.REDISSON_SCHEMA;

/**
 * Redisson配置.
 *
 * @author xindaqi
 * @since 2021/4/2 11:34
 */
@Configuration
public class RedissonConfig {

    @Resource
    RedisConnectionParams redisConnectionParams;

    /**
     * 单机模式RedissonClient
     *
     * @return
     */
    @Bean(destroyMethod = "shutdown")
    public RedissonClient redissonClient() {
        Config config = new Config();
        SingleServerConfig singleServerConfig = config.useSingleServer();
        StringBuilder redisAddress = new StringBuilder();
        redisAddress.append(REDISSON_SCHEMA).append(redisConnectionParams.getHost()).append(COLON).append(redisConnectionParams.getPort());
        singleServerConfig.setAddress(redisAddress.toString());
        return Redisson.create(config);
    }

    @Bean
    public RedissonLocker redissonLocker(RedissonClient redissonClient) {
        return new RedissonLocker(redissonClient);
    }

}

3.2.4 Redisson获取锁封装

package com.hardsoft.monkeyrun.common.lock;

import com.hardsoft.monkeyrun.common.constrant.BooleanConstant;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * Redisson锁
 *
 * @author xindaqi
 * @since 2021/3/30 19:01
 */
@Component
public class RedissonLocker {

    RedissonClient redissonClient;

    public RedissonLocker(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    /**
     * 获取锁
     * 如果锁不可用,则当前线程处于休眠状态,直到获得锁为止
     *
     * @param lockKey 锁键
     */
    public void lock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock();
    }

    /**
     * 获取锁
     * 如果锁不可用,则当前线程处于休眠状态,直到获得锁为止,
     * 如果获取到锁后,执行结束后解锁或达到超时时间后自动释放锁
     *
     * @param lockKey 锁键
     * @param timeout 超时时间
     */
    public void lock(String lockKey, int timeout) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock(timeout, TimeUnit.SECONDS);
    }

    /**
     * 获得锁
     * 如果锁不可用,则当前线程处于休眠状态,直到获得锁后,
     * 执行结束后解锁或达到超时时间后会自动释放锁
     *
     * @param lockKey 锁键
     * @param unit 时间单位
     * @param timeout 超时时间
     */
    public void lock(String lockKey, TimeUnit unit, int timeout) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock(timeout, unit);
    }

    /**
     * 释放锁
     *
     * @param lockKey 锁键
     */
    public void unlock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.unlock();
    }

    /**
     * 尝试获取锁
     * 获取到立即返回True
     * 未获取到返回False
     *
     * @param lockKey 锁键
     * @return 获取锁标志位,True:成功获取锁,False:未获取锁
     */
    public boolean tryLock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        return lock.tryLock();
    }

    /**
     * 尝试获取锁
     * 在等待时间内获取到锁则返回True,否则返回False
     * 获取到锁,两种执行逻辑:
     * 1.执行之后,释放锁
     * 2.达到释放时间leaseTime后,释放锁
     *
     * @param lockKey 锁键
     * @param waitTime 等待时间
     * @param leaseTime 释放时间
     * @param unit 时间单位
     * @return 获取锁标志位,True:成功获取锁,False:未获取锁
     * @throws InterruptedException
     */
    public boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
        RLock lock = redissonClient.getLock(lockKey);
        return lock.tryLock(waitTime, leaseTime, unit);
    }

    /**
     * 判断锁是否被任意一个线程持有
     *
     * @param lockKey 锁键
     * @return 锁持有标志位,True:锁被线程持有,False:锁没有被线程持有
     */
    public boolean isLocked(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        return lock.isLocked();
    }

    public void setRedissonClient(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }
}

3.3 接口测试

package com.hardsoft.monkeyrun.api.facade.lock;

import com.hardsoft.monkeyrun.common.constrant.BooleanConstant;
import com.hardsoft.monkeyrun.common.constrant.DigitalConstant;
import com.hardsoft.monkeyrun.common.enums.BizExceptionCodeEnums;
import com.hardsoft.monkeyrun.common.exception.BizException;
import com.hardsoft.monkeyrun.common.lock.RedissonLocker;
import com.hardsoft.monkeyrun.common.response.Response;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.TimeUnit;

import static com.hardsoft.monkeyrun.common.constrant.StringConstant.REDIS_LOCK_KEY;

/**
 * 锁测试
 *
 * @author xindaqi
 * @since 2021-04-03 11:28:18
 */
@RestController
@RequestMapping("/v1/lock")
public class LockTest {

    private static final Logger logger = LoggerFactory.getLogger(LockTest.class);

    private final String STOCK_WITH_REDISSON = "stock_with_redisson";

    private final String STOCK_WITHOUT_REDISSON = "stock_without_redisson";

    @Resource
    RedissonLocker redissonLocker;

    /**
     * 测试Redisson锁tryLock,不会激活看门狗,锁过期后,直接被清除
     * 
     * @return 响应
     */
    @GetMapping("/redisson/try-lock/test")
    public Response<String> testRedissonTryLock() {
        try {
            boolean lock1 = redissonLocker.tryLock(REDIS_LOCK_KEY, 100, 10, TimeUnit.SECONDS);
            if (lock1) {
                logger.info(">>>>>>>>得到锁");
                Thread.sleep(70000);
                return Response.success();
            } else {
                logger.info(">>>>>>>>未得到锁");
                return Response.fail();
            }
        } catch(Exception ex) {
            logger.error(">>>>>>>>Redisson异常:", ex);
            throw new BizException(BizExceptionCodeEnums.FAIL);
        } finally {
            logger.info(">>>>>>>释放Redisson锁");
            redissonLocker.unlock(REDIS_LOCK_KEY);
        }
    }

    /**
     * 测试Redisson看门狗功能:使用lock()获取锁,激活看门狗功能,每10秒自动续期30秒过期时间。
     * 
     * @return 响应
     */
    @GetMapping("/redisson/lock/test")
    public Response<String> testRedissonLock() {
        try {
            redissonLocker.lock(REDIS_LOCK_KEY);
            Thread.sleep(70000);
            return Response.success();
        } catch(Exception ex) {
            logger.error(">>>>>>>>Redisson异常:", ex);
            throw new BizException(BizExceptionCodeEnums.FAIL);
        } finally {
            logger.info(">>>>>>>释放Redisson锁");
            redissonLocker.unlock(REDIS_LOCK_KEY);
        }
    }
}

Redis结果:

在这里插入图片描述

4 结论

(1)Redisson锁存储使用Hash,Hash键为锁的键名,属性的键为UUID拼接ThreadId,格式:UUID:ThreadID,如xxx-xxx:30,属性的值为同一个线程获取锁的次数,Redisson的锁是可重入的,同一个线程获取锁,锁的数量+1,释放锁,数量减1;
(2)Redisson获取锁有两种方式,lock获取和tryLock获取,其中:
(2.1)lock获取锁时,如果第一次尝试获取获取失败,会一直等待,直到获取锁。Redisson的lock获取锁时,会为锁配置过期时间,当锁超过过期时间后,会自动释放,避免死锁,可以重新获得锁;
(2.2)lock()原始方法获取(不自定义超时(过期)时间)会激活看门狗。即通过lock()方法成功获取锁后,逻辑执行的时间超过看门狗超时时间:10秒,会自动为锁续期(增加)10秒的过期时间,使锁的过期时间保持在30秒(默认的锁过期时间);
(2.3)lock(long, TimeUnit)带参方法可以指定锁过期时间,但是,不会触发看门狗自动续期;锁过期后,自动释放锁;
(2.4)tryLock方法返回标识位,获取成功,返回true,获取失败,返回false;tryLock尝试获取锁,无法获取锁时会等待,同时,tryLock有最大的等待时间,如果获取锁的等待时间超过最大等待时间,会放弃获取锁,并返回false;tryLock不会触发看门狗,无法自动为锁续期;
(2.5)tryLock可以同时指定等待锁的最大时间,以及锁过期时间,如果等待最大时间大于锁过期时间,则锁被提前释放,重新生成锁;
(3)Redisson看门狗默认巡查周期为10秒,锁续期时间(过期时间)30秒;
(4)Redisson看门口生效的方法为:lock(),其他方法均不会触发看门狗。

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

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

相关文章

leaflet 加载CSV数据,显示图形(代码示例046)

第046个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+leaflet中加载CSV文件,将图形显示在地图上。 直接复制下面的 vue+openlayers源代码,操作2分钟即可运行实现效果; 注意如果OpenStreetMap无法加载,请加载其他来练习 文章目录 示例效果配置方式示例源代码(共74…

海思3559:BT656调试笔记

前言 海思3559a的sdk例子是没有提供BT1120和BT656视频接入的&#xff0c;但实际上硬件是可以支持接入的。不过前提是只支持逐行方式输入&#xff0c;不支持隔行视频&#xff0c;如果想输入PAL制式的隔行视频&#xff0c;请先用芯片转成逐行再接入。不知道是官方手册有意无意的忽…

弄懂自定义 Hooks 不难,改变开发认知有点不习惯

前言 我之前总结逻辑重用的时候&#xff0c;就一直在思考一个问题。 对于逻辑复用&#xff0c;render props 和 高阶组件都可以实现&#xff0c;同样官方说 Hooks 也可以实现&#xff0c;且还是在不增加额外的组件的情况下。 但是我在项目代码中&#xff0c;没有找到自定义 …

python | 第二章考试题和练习题

一、考试题 1、turtle八边形绘制 问题描述&#xff1a; 使用turtle库&#xff0c;绘制一个八边形。 参考代码&#xff1a; import turtle as t t.pensize(2) for i in range(8):t.fd(100)t.left(45) 2、turtle八角图形绘制 问题描述&#xff1a; 使用turtle库&#xff0c;…

SaleSmartly(ss客服)带你了解:缩短B2B销售周期的秘诀

缩短B2B销售周期的秘诀&#xff1a;即时聊天 关键词&#xff1a;B2B 销售&#xff1b;即时沟通&#xff1b;SaleSmartly&#xff08;ss客服&#xff09; 在B2B销售中&#xff0c;时间就是一切。在某些情况下&#xff0c;买家正在积极寻找即时解决方案&#xff0c;潜在客户以多种…

【2023unity游戏制作-mango的冒险】-开始画面API制作

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 秩沅 原创 收录于专栏&#xff1a;游戏制作 ⭐mango的冒险-开始画面制作⭐ 文章目录⭐mango的冒险-开始画面制作⭐&#x1f468;‍&…

EasyCVR视频云存储的架构解析与Sharelist云存挂载方法介绍

一、什么是视频云存储&#xff1f; 视频云存储主要用于为上层应用提供视频文件、结构化信息、事件信息的相关服务。云存储节点分为数据文件存储节点和结构化数据存储节点。数据文件存储节点主要用于视频、图片的存储。结构化数据存储节点用于存储结构化数据并提供相关服务。 …

【学习记录】PCA主成分分析 SVD奇异值分解

在看MSC-VO代码的过程中&#xff0c;大量出现了奇异值分解的内容&#xff0c;本身对这部分了解不多&#xff0c;这里补一下课&#xff0c;参考b站up主小旭学长的视频&#xff0c;链接为&#xff1a;PCA主成分分析和SVD主成分分析 PCA主成分分析 PCA根本目的在于让数据在损失尽…

机器学习笔记之生成模型综述(三)生成模型的表示、推断、学习任务

机器学习笔记之生成模型综述——表示、推断、学习任务引言生成模型的表示任务从形状的角度观察生成模型的表示任务从概率分布的角度观察生成模型的表示任务生成模型的推断任务生成模型的学习任务引言 上一节介绍了从监督学习、无监督学习任务的角度介绍了经典模型。本节将从表…

概率论面试题1:玫瑰花

概率论面试题 1. 一个活动&#xff0c;n个女生手里拿着长短不一的玫瑰花&#xff0c;无序的排成一排&#xff0c;一个男生从头走到尾&#xff0c;试图拿更长的玫瑰花&#xff0c;一旦拿了一朵就不能再拿其他的&#xff0c;错过了就不能回头&#xff0c;问最好的策略&#xff1…

3年自动化测试这水平?我还不如去招应届生

公司前段缺人&#xff0c;也面了不少测试&#xff0c;结果竟然没有一个合适的。一开始瞄准的就是中级的水准&#xff0c;也没指望来大牛&#xff0c;提供的薪资在10-20k&#xff0c;面试的人很多&#xff0c;但平均水平很让人失望。看简历很多都是3年工作经验&#xff0c;但面试…

什么是响应性?

响应性&#xff1a; 这个术语在今天的各种编程讨论中经常出现&#xff0c;但人们说它的时候究竟是想表达什么意思呢&#xff1f;本质上&#xff0c;响应性是一种可以使我们声明式地处理变化的编程范式。一个经常被拿来当作典型例子的用例即是 Excel 表格&#xff1a; 这里单元…

angular相关知识点总结

创建 angualr 组件和传值 angular组件其实就是个xxx.component.ts,本质还是ts文件一个html文件 1.创建组件&#xff1a;在Angular中&#xff0c;可以使用命令行工具ng generate component创建一个新组件。例如&#xff1a; ng generate component my-component这将创建一个名…

Ubuntu 系统下Docker安装与使用

Ubuntu 系统下Docker安装与使用Docker安装与使用Docker安装安装环境准备工作系统要求卸载旧版本Ubuntu 14.04 可选内核模块Ubuntu 16.04 使用 APT 安装安装 Docker CE使用脚本自动安装启动 Docker CE建立 docker 用户组测试 Docker 是否安装正确镜像加速Docker使用拉取镜像创建…

SPSS数据分析软件的安装与介绍(附网盘链接)

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

SAP COPA 获利能力分析深度解析

一、获利分析配置及相关值概述 二、配置&#xff1a;组织结构 2.1 定义经营范围-KEP8 2.2 维护经营关注点-KEA0 2.3 获利能力分析类型解析 2.4 控制范围分配给经营范围-KEKK 三、配置&#xff1a;数据结构-KEA0 3.1 特征字段 3.1.1 特征字段类别 3.1.2 维护特征字段-K…

分类预测 | Matlab实现SSA-RF和RF麻雀算法优化随机森林和随机森林多特征分类预测

分类预测 |Matlab实现SSA-RF和RF麻雀算法优化随机森林和随机森林多特征分类预测 目录分类预测 |Matlab实现SSA-RF和RF麻雀算法优化随机森林和随机森林多特征分类预测分类效果基本介绍模型描述程序设计参考资料分类效果 基本介绍 Matlab实现SSA-RF和RF麻雀算法优化随机森林和随机…

C++学习笔记(四)

组合、继承。委托&#xff08;类与类之间的关系&#xff09; 复合 queue类里有一个deque&#xff0c;那么他们的关系叫做复合。右上角的图表明复合的概念。上图的特例表明&#xff0c;queue中的功能都是通过调用c进行实现&#xff08;adapter&#xff09;。 复合关系下的构造和…

CS5260测试版|CS5260demoboard|typec转VGA参考PCB原理图

CS5260测试版|CS5260demoboard|typec转VGA参考PCB原理图 CS5260是一款高度集成的TYPEC转VGA转换方案芯片。 CS5260输出端接口:外接高清VGA设备如:显示器投影机电视带高清的设备&#xff0c;广泛应用于 笔记本Macbook Air 12寸USB3.1输出端对外接高清VGA设备如:显示器投影机电视…

11.hadoop系列之MapReduce框架原理之InputFormat数据输入

我们先简要了解下InputFormat输入数据 1.数据块与数据切片 数据块&#xff1a; Block在HDFS物理上数据分块&#xff0c;默认128M。数据块是HDFS存储数据单位 数据切片&#xff1a; 数据切片只是在逻辑上对输入进行分片&#xff0c;并不会物理上切片存储。数据切片是MapReduce…