Redis学习——Redisson 分布式锁集成及其简单使用

文章目录

  • 引言
  • 1. Redisson概述
    • 1.1 Redisson的基本概念
    • 1.2 Redisson的主要功能
    • 1.3 Redisson的优点
  • 2. 开发环境
  • 3. Redisson的安装与配置
    • 3.1 添加依赖
    • 3.2 配置Redisson
  • 4. 使用Redisson
    • 4.1 可重入锁
      • 4.1.1 可重入锁的概念
      • 4.1.2 可重入锁的实现原理
      • 4.1.3 简单使用
          • 锁的获取和释放
    • 4.2 公平锁
      • 4.2.1 公平锁的概念
      • 4.2.2 公平锁的实现原理
      • 4.2.3 简单使用
    • 4.3 读写锁
      • 4.3.1 读写锁的概念
      • 4.3.2 读写锁的实现原理
      • 4.3.3 简单使用
    • 4.4 联锁
      • 4.4.1 联锁的概念
      • 4.4.2 联锁的实现原理
      • 4.4.3 简单使用
  • 5. WatchDog机制
  • 6. 总结

在这里插入图片描述

引言

在分布式系统中,经常需要对共享资源进行并发访问控制,以确保数据的一致性和完整性。分布式锁是一种用于在分布式环境中控制对共享资源访问的机制,它可以保证在同一时刻只有一个客户端能够访问某些特定资源。

1. Redisson概述

1.1 Redisson的基本概念

Redisson是一个基于Redis的Java客户端,它不仅提供了对Redis的基础操作支持,还封装了许多高级功能,如分布式锁、分布式集合、分布式队列等。Redisson的设计目标是简化分布式系统的开发,提高开发效率和系统的可维护性。

下载 (6)

1.2 Redisson的主要功能

  1. 分布式锁:支持可重入锁、公平锁、读写锁、红锁等多种分布式锁机制,保证分布式环境下的资源访问控制。比如:在电商系统中,防止超卖现象;在订单系统中,防止同一订单被多次处理。
  2. 分布式集合:提供分布式Set、List、Map等集合类型,支持高并发环境下的数据操作。
  3. 分布式队列:支持分布式阻塞队列、延迟队列等,适用于任务调度和消息传递场景。
  4. 分布式对象:提供分布式AtomicLong、AtomicDouble、CountDownLatch、Semaphore等对象,简化分布式系统的开发。
  5. 分布式服务:支持分布式执行器、分布式调度器等服务,增强分布式系统的功能。

1.3 Redisson的优点

Redisson是一个基于Redis的Java客户端,提供了许多高级特性和分布式数据结构。相比其他Redis客户端,Redisson的优势在于:

  • 简洁易用:提供了丰富的API,简化了分布式编程的复杂性。
  • 高可用性:支持多种Redis部署模式,包括单节点、主从复制和集群模式。
  • 分布式对象:提供了分布式锁、分布式集合、分布式队列等高级数据结构,便于在分布式环境中使用。
  • 自动续期:Redisson的Watchdog机制可以自动续期分布式锁,避免锁超时问题。

2. 开发环境

  • JDK版本:JDK 17
  • Spring Boot版本:Spring Boot 3.2.2
  • Redis版本:5.0.14.1
  • 构建工具:Maven

3. Redisson的安装与配置

3.1 添加依赖

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.24.3</version>
</dependency>

3.2 配置Redisson

配置参考文档:2. Configuration · redisson/redisson Wiki (github.com)

添加配置类RedissonConfig

/**
 * Redisson配置类,用于配置Redisson客户端。
 */
@Configuration
public class RedissonConfig {

    /**
     * 创建并配置RedissonClient Bean。
     * 
     * @return 配置好的RedissonClient实例
     */
    @Bean
    public RedissonClient redissonClient() {
        // 创建Redisson配置对象
        Config config = new Config();

        // 配置单节点模式
        config.useSingleServer()
                // 设置Redis服务器地址
                .setAddress("redis://127.0.0.1:6379")
                // 设置Redis服务器密码
                .setPassword("123321")
                // 设置连接池大小
                .setConnectionPoolSize(64)
                // 设置最小空闲连接数
                .setConnectionMinimumIdleSize(24)
                // 设置空闲连接超时时间(毫秒)
                .setIdleConnectionTimeout(10000)
                // 设置连接超时时间(毫秒)
                .setConnectTimeout(10000)
                // 设置命令等待超时时间(毫秒)
                .setTimeout(3000)
                // 设置命令重试次数
                .setRetryAttempts(3)
                // 设置命令重试间隔时间(毫秒)
                .setRetryInterval(1500);

        // 创建并返回RedissonClient实例
        return Redisson.create(config);
    }
}

4. 使用Redisson

官方wiki文档:8. Distributed locks and synchronizers · redisson/redisson Wiki (github.com)

中文版wiki文档(已经有5年没有更新了,不建议看):8. 分布式锁和同步器 · redisson/redisson Wiki (github.com)

4.1 可重入锁

4.1.1 可重入锁的概念

可重入锁(Reentrant Lock)是一种允许同一个线程多次获取同一把锁的锁机制。也就是说,当一个线程已经持有某个锁时,它可以再次获取该锁而不会被阻塞。这种锁机制能够避免死锁问题,并简化锁的使用。

可重入锁的主要特点是:

  • 同一线程可多次获取:同一个线程可以多次获取同一把锁,而不会被阻塞。
  • 计数器维护:可重入锁内部维护一个计数器,每次获取锁时计数器加1,每次释放锁时计数器减1,当计数器为0时,锁才真正被释放。

4.1.2 可重入锁的实现原理

可重入锁的实现通常依赖于一个计数器和一个持有锁的线程标识。当一个线程第一次获取锁时,计数器加1,并记录持有锁的线程标识。当同一个线程再次获取锁时,只需将计数器加1,而不会阻塞线程。当线程释放锁时,计数器减1,当计数器为0时,锁才真正被释放,并允许其他线程获取锁。

在Redisson中,可重入锁的实现基于Redis的原子操作和Lua脚本。Redisson通过维护一个计数器和持有锁的线程标识,实现了可重入锁的功能。

4.1.3 简单使用

import lombok.RequiredArgsConstructor;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

/**
 * 服务类示例
 */
@RequiredArgsConstructor
@Service
public class XXXXService {

    private final RedissonClient redissonClient; 

    /**
     * 使用 Redisson 可重入锁执行任务
     */
    public void performTaskWithLock() {
        // 获取可重入锁对象,指定锁的名称
        RLock lock = redissonClient.getLock("myLock"); 

        try {
            // 尝试获取锁,参数分别是:获取锁的最大等待时间,锁自动释放时间,时间单位,返回值为是否获取锁成功
            boolean isLock = lock.tryLock(1, 10, TimeUnit.SECONDS); 
            
            // 判断获取锁成功
            if (isLock) { 
                try {
                    System.out.println("执行业务"); 
                    // 在这里编写需要进行锁保护的业务逻辑
                } finally {
                    // 释放锁
                    lock.unlock(); 
                }
            } else {
                // 获取锁失败,可以进行相应的处理,例如记录日志或返回错误信息
                System.err.println("获取锁失败!"); 
            }
        } catch (InterruptedException e) {
            // 处理中断异常
            throw new RuntimeException(e); 
        }
    }
}

在上述代码中,我们使用redissonClient.getLock("myLock")获取一个分布式锁对象,然后使用lock.tryLock()方法尝试获取锁,并在任务完成后释放锁。

锁的获取和释放
  • 获取锁:使用RLock对象的tryLock()lock()方法来获取锁。tryLock()方法允许设置等待时间和锁的自动释放时间。
  • 释放锁:使用RLock对象的unlock()方法来释放锁。确保在finally块中释放锁,以避免死锁。

4.2 公平锁

4.2.1 公平锁的概念

公平锁(Fair Lock)是一种确保锁的获取顺序与请求顺序相同的锁机制。即先请求锁的线程优先获取锁,后请求的线程只能在前面的线程释放锁后才能获取锁。这种机制可以避免“饥饿”现象,确保每个线程都能公平地获取锁。

4.2.2 公平锁的实现原理

公平锁的实现通常依赖于一个队列来记录请求锁的顺序。每次有线程请求锁时,会将其添加到队列中,当锁被释放时,从队列中按照请求顺序依次唤醒等待的线程。

在Redisson中,公平锁的实现基于Redis的有序集合(Sorted Set)和Lua脚本。每次请求锁时,线程会被添加到一个有序集合中,并按照时间戳排序。当锁被释放时,按照有序集合中的顺序依次唤醒等待的线程。

4.2.3 简单使用

public void performTaskWithFairLock() {
    // 1. 获取公平锁对象
    RLock fairLock = redissonClient.getFairLock("myFairLock");

    try {
        // 2. 尝试获取锁
        boolean isLock = fairLock.tryLock(1, 10, TimeUnit.SECONDS); 

        // 3. 判断是否获取到锁
        if (isLock) { 
            try {
                System.out.println("获得公平锁,正在执行任务...");
                // 执行任务
            } finally {
                // 4. 释放锁
                fairLock.unlock(); 
                System.out.println("释放公平锁。");
            }
        } else {
            System.out.println("无法获取公平锁。");
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

4.3 读写锁

4.3.1 读写锁的概念

读写锁(Read-Write Lock)是一种允许多个读操作同时进行,但写操作必须独占的锁机制。读写锁分为两种锁:读锁和写锁。

  • 读锁:允许多个线程同时获取读锁,只要没有线程持有写锁。读锁之间是共享的。
  • 写锁:只允许一个线程获取写锁,并且在写锁持有期间,其他线程不能获取读锁或写锁。写锁是独占的。

读写锁的主要目的是提高并发性和性能。在读多写少的场景下,读写锁可以显著提高系统的并发处理能力。

4.3.2 读写锁的实现原理

读写锁的实现通常依赖于两个锁:一个读锁和一个写锁。读锁允许多个线程同时获取,而写锁只允许一个线程获取。在获取写锁时,需要确保没有线程持有读锁或写锁。

在Redisson中,读写锁的实现基于Redis的原子操作和Lua脚本。Redisson通过两个键来分别控制读锁和写锁,并使用Lua脚本确保锁操作的原子性。

4.3.3 简单使用

public void performTaskWithReadWriteLock() {
    // 1. 获取读写锁对象
    RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("myReadWriteLock");

    // 2. 从读写锁对象中分别获取读锁和写锁
    RLock readLock = readWriteLock.readLock();
    RLock writeLock = readWriteLock.writeLock();

    try {
        // 3. 尝试获取读锁
        if (readLock.tryLock(10, 60, TimeUnit.SECONDS)) { 
            try {
                System.out.println("获取读锁,正在执行读任务...");
                // 执行读任务
            } finally {
                // 4. 释放读锁
                readLock.unlock(); 
                System.out.println("释放读锁。");
            }
        }

        // 5. 尝试获取写锁
        if (writeLock.tryLock(10, 60, TimeUnit.SECONDS)) { 
            try {
                System.out.println("获取写锁,正在执行写任务...");
                // 执行写任务
            } finally {
                // 6. 释放写锁
                writeLock.unlock();
                System.out.println("释放写锁。");
            }
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

4.4 联锁

4.4.1 联锁的概念

联锁(MultiLock)是一种允许将多个锁关联在一起,实现“全部获取”或“全部释放”的锁机制。

  • 全部获取: 只有当所有参与联锁的锁都被成功获取后,才算成功获取联锁。
  • 全部释放: 释放联锁时,会自动释放所有参与联锁的锁。

联锁适用于需要同时获取多个资源的场景,例如分布式事务中需要锁定多个数据表。

4.4.2 联锁的实现原理

Redisson 的联锁基于 RedissonMultiLock 对象实现。RedissonMultiLock 对象可以将多个 RLock 对象关联在一起,并提供 tryLock()unlock() 方法来统一管理这些锁。

在调用 tryLock() 方法时,RedissonMultiLock 会尝试依次获取所有参与联锁的锁。如果所有锁都获取成功,则返回 true,否则释放已经获取到的锁,并返回 false

在调用 unlock() 方法时,RedissonMultiLock 会自动释放所有参与联锁的锁,无论这些锁是否被当前线程持有。

4.4.3 简单使用

public void performTaskWithMultiLock() {
    // 获取多个锁对象
    RLock lock1 = redissonClient.getLock("lock1");
    RLock lock2 = redissonClient.getLock("lock2");
    RLock lock3 = redissonClient.getLock("lock3");

    // 创建联锁对象
    RLock multiLock = redissonClient.getMultiLock(lock1, lock2, lock3);

    try {
        // 尝试获取联锁,等待 10 秒
        if (multiLock.tryLock(10, TimeUnit.SECONDS)) {
            try {
                System.out.println("获取联锁成功,正在执行任务...");
                // 执行需要所有锁的任务
            } finally {
                // 释放联锁
                multiLock.unlock();
                System.out.println("释放联锁。");
            }
        } else {
            System.out.println("获取联锁失败。");
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

代码分析:

  1. 获取多个锁对象: 首先,获取需要参与联锁的多个 RLock 对象。
  2. 创建联锁对象: 使用 redissonClient.getMultiLock(lock1, lock2, lock3) 创建一个 RLock 对象,并将之前获取的多个锁对象作为参数传入。
  3. 尝试获取联锁: 调用 multiLock.tryLock(10, TimeUnit.SECONDS) 尝试获取联锁,最多等待 10 秒。
  4. 执行任务: 如果成功获取联锁,则执行需要所有锁保护的任务。
  5. 释放联锁: 最后,在 finally 块中调用 multiLock.unlock() 释放联锁,这会自动释放所有参与联锁的锁。

5. WatchDog机制

想象一下,我们正在进行一场激烈的拔河比赛。我们队好不容易抓住了绳子,眼看就要赢了,结果突然有人手滑,绳子就被对方抢走了!

在分布式系统中,获取锁就好像抓住这根拔河绳。Redisson 分布式锁的租约时间就好像我们抓住绳子的时间。如果在租约时间内,我们没有完成任务,锁就自动释放了,其他线程就有机会获取锁,这就像拔河比赛中我们手滑绳子被抢走一样,可能会导致数据不一致的问题。

为了避免这种情况发生,Redisson 提供了 Watch Dog 机制,就像我们队伍里安排了一个“观察员”。这位观察员会每隔一段时间关注我们是否还抓着绳子,如果发现我们快坚持不住了,就会及时提醒我们,让我们重新握紧绳子,并延长我们抓住绳子的时间。

具体来说,Redisson 的Watch Dog 机制会在 Redisson 实例被关闭前,不断的延长锁的有效期,也就是说,如果一个拿到锁的线程一直没有完成逻辑,那么看门狗会帮助线程不断的延长锁超时时间,锁不会因为超时而被释放。

**开启方式:**在获取锁的时候,不能指定leaseTime或者只能将leaseTime设置为-1,这样才能开启看门狗机制。

public void test() throws Exception {
    RLock lock = redissonClient.getLock("myLock");

    // 方式一: 不停重试,直到获取锁成功,具有 Watch Dog 自动延期机制,默认续约时间为 30 秒
    lock.lock(); 

    // 方式二: 尝试获取锁 10 秒,获取成功返回 true,否则返回 false,具有 Watch Dog 自动延期机制,默认续约时间为 30 秒
    boolean res1 = lock.tryLock(10, TimeUnit.SECONDS); 

    // 方式三:  尝试获取锁 10 秒,如果获取成功,则持有锁,否则抛出异常,leaseTime 为 10 秒,不会自动续约
    try {
        lock.lock(10, TimeUnit.SECONDS); 
    } catch (InterruptedException e) {
        // 处理异常
    }

    // 方式四: 尝试获取锁 100 秒,如果获取成功,则持有锁 10 秒,leaseTime 为 10 秒,不会自动续约
    boolean res2 = lock.tryLock(100, 10, TimeUnit.SECONDS); 

    Thread.sleep(40000L);
    lock.unlock();
}

6. 总结

在本文中,我们简要介绍了Redisson及其优势,介绍了如何在Spring Boot项目中集成Redisson。通过代码示例展示了基本的分布式锁用法,以及高级用法如公平锁、可重入锁、读写锁和联锁。除此之外我们还简要介绍了Redisson 的Watch Dog 机制,希望本文对大家有所帮助😊。

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

无线麦克风哪个品牌音质最好,一篇看懂无线领夹麦克风怎么挑选

在数字化时代背景下&#xff0c;直播和个人视频日志&#xff08;Vlog&#xff09;已成为新的文化现象&#xff0c;这些趋势不仅重塑了内容创作&#xff0c;也促进了音频设备市场的繁荣。无线领夹麦克风&#xff0c;以其设计上的轻便和录音上的高效率&#xff0c;成为视频创作者…

手把手带你薅一台云服务器

前两篇&#xff0c;带着大家在自己本地搞了一台 Linux 虚拟机&#xff1a; 【保姆级教程】Windows上安装Linux子系统&#xff0c;搞台虚拟机玩玩【保姆级教程】Windows 远程登陆 Linux 服务器的两种方式&#xff1a;SSH VS Code&#xff0c;开发必备 问题来了&#xff1a;本…

nacos漏洞小结

Alibaba Nacos是阿里巴巴推出来的一个新开源项目&#xff0c;是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。致力于帮助发现、配置和管理微服务。Nacos提供了一组简单易用的特性集&#xff0c;可以快速实现动态服务发现、服务配置、服务元数据及流量管理…

51单片机第18步_将TIM0用作13位定时器

本章重点学习将TIM0用作13位定时器。 1、定时器0工作在模式0框图 2、定时器0工作在模式0举例 1、Keil C51中有一些关键字&#xff0c;需要牢记&#xff1a; interrupt 0&#xff1a;指定当前函数为外部中断0&#xff1b; interrupt 1&#xff1a;指定当前函数为定时器0中断…

onInterceptTouchEvent() 与 onTouch() 事件分析

前言 本文主要分析 onTouch() 与 onTouchEvent() 事件的差异 正文 先看布局文件&#xff1a; <?xml version"1.0" encoding"utf-8"?> <com.longzhiye.intercepttouch.MyFrameLayout xmlns:android"http://schemas.android.com/apk/res…

PointNet++论文导读

PointNet论文导读 主要改进网络结构&#xff1a;非均匀采样下的特征学习的鲁棒性利用点特征传播处理数据集分割 论文链接:https://arxiv.org/abs/1612.00593 主要改进 PointNet的基本思想是学习每个点的空间编码&#xff0c;然后将所有单个点的特征聚合成一个全局点云标签&am…

模块化编程(二)

模块的导入 经常有这样一句话&#xff1a;“不要重复造轮子”&#xff0c;知道别人已经造好了轮子&#xff0c;并且轮子也好用&#xff0c;那就直接拿别人的轮子来用&#xff0c;此处的“模块导入”就是“拿别人的轮子过来”。前文提到模块化编程的好处之一就是“代码复用性高…

【6.26更新】Win10 22H2 19045.4598镜像:免费下载!

当前微软已经发布了六月最新的KB5039299更新补丁&#xff0c;用户完成升级后&#xff0c;系统版本号将更新至19045.4598。此次更新解决了任务栏上应用跳转列表失败、可能导致系统无法从休眠状态恢复等多个问题&#xff0c;推荐大家升级。如果您不知道去哪里才能下载到该版本&am…

mac|tableau public 仪表盘使用

对华东地区的利润进行仪表盘可视化 选择下面的功能表的新建仪表盘,把上面的表1表2放入其中 通过下图操作将两个表联合起来&#xff0c;即上图使用筛选器时下面的表随之改变 将上图设置为筛选器&#xff0c;可以通过点击地区查看数据

防护用品穿戴自动监测摄像机

随着技术的不断发展&#xff0c;防护用品穿戴自动监测摄像机作为现代安全监控领域的创新应用&#xff0c;正逐渐成为各类工作场所和特定环境中的重要设备。这种摄像机不仅能够实时记录和监控员工的工作状态和安全情况&#xff0c;还能提供数据支持和预警功能&#xff0c;显著提…

第四十篇——系统论:如何让整体效用大于部分之和?

目录 一、背景介绍二、思路&方案三、过程1.思维导图2.文章中经典的句子理解3.学习之后对于投资市场的理解4.通过这篇文章结合我知道的东西我能想到什么&#xff1f; 四、总结五、升华 一、背景介绍 系统论&#xff0c;又从一个大的生态的角度去考虑&#xff0c;我们应该如…

贪心问题(POJ1700/1017/1065)(C++)

一、贪心问题 贪心算法 贪心算法&#xff08;greedy algorithm&#xff09;&#xff0c;是用计算机来模拟一个「贪心」的人做出决策的过程。这个人十分贪婪&#xff0c;每一步行动总是按某种指标选取最优的操作。而且他目光短浅&#xff0c;总是只看眼前&#xff0c;并不考虑…

CSS 背景添加白色小圆点样式

css也是开发过程中不可忽视的技巧 此专栏用来纪录不常见优化页面样式的css代码 效果图: 未添加之前: 代码: background: radial-gradient(circle at 1px 1px, #3d3c3c 2px, transparent 0);background-size: 20px 25px;

在AvaotaA1全志T527开发板上烧录AvaotaOS固件

烧写到 TF 卡上 材料准备 首先需要准备的材料有: SD-Card Formatter&#xff1a;balenaEtcherAvaota Pi - A1开发板 x1TF-Card ( 不小于4Gb ) x112V-DC电源适配器 x1TF读卡器 x1USBTTL Splitter x1&#xff08;可选&#xff09;HDMI 采集卡 x1 &#xff08;可选&#xff09;…

体验升级:扫描全能王智能高清滤镜2.0全面测评

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

MSPM0G3507——串口

配置&#xff1a; 主函数: #include "ti_msp_dl_config.h"volatile uint8_t gEchoData 0; int main(void) {SYSCFG_DL_init();NVIC_ClearPendingIRQ(UART_0_INST_INT_IRQN); //先清除中断&#xff0c;防止直接进中断NVIC_EnableIRQ(…

手机数据恢复篇:如何在恢复出厂设置后的 iPhone 恢复短信

您可能会认为&#xff0c;在恢复出厂设置iPhone后恢复短信时&#xff0c;一切都会丢失&#xff0c;但是仍然有一些方法可以检索您的重要对话。截至 2024 年&#xff0c;数据恢复技术的进步使得从备份甚至直接从设备内存中抢救消息变得更加容易。无论是通过 iCloud、iTunes 还是…

【Linux】虚拟机安装openEuler 24.03 X86_64 教程

目录 一、概述 1.1 openEuler 覆盖全场景的创新平台 1.2 系统框架 1.3 平台框架 二、安装详细步骤 一、概述 1.1 openEuler 覆盖全场景的创新平台 openEuler 已支持 x86、Arm、SW64、RISC-V、LoongArch 多处理器架构&#xff0c;逐步扩展 PowerPC 等更多芯片架构支持&…

密码学:对称加密算法、非对称加密算法、哈希算法

「作者简介」&#xff1a;冬奥会网络安全中国代表队&#xff0c;CSDN Top100&#xff0c;就职奇安信多年&#xff0c;以实战工作为基础著作 《网络安全自学教程》&#xff0c;适合基础薄弱的同学系统化的学习网络安全&#xff0c;用最短的时间掌握最核心的技术。 这一章节我们需…

为PPT加密:如何设置和管理“打开密码”?

在保护演示文稿的内容时&#xff0c;给PPT文件设置“打开密码”是一个简单而有效的方法。今天一起来看看如何设置和管理PPT文件的“打开密码”吧&#xff01; 一、设置PPT“打开密码” 首先&#xff0c;打开需要加密的PPT文件&#xff0c;点击左上角的“文件”选项卡&#x…