深入理解ZooKeeper分布式锁

news2024/11/18 13:52:22

第1章:引言

分布式系统,简单来说,就是由多台计算机通过网络相连,共同完成任务的系统。想象一下,咱们平时上网浏览网页、看视频,背后其实都是一大堆服务器在协同工作。这些服务器之间需要协调一致,保证数据的一致性和完整性,这就是分布式系统的挑战之一。

在这种环境下,锁就显得尤为重要了。为什么呢?因为在多个进程或者线程同时访问同一资源的时候,如果不加控制,就会造成数据混乱,比如同一时间两个线程都试图修改同一个数据,结果可能就乱套了。这就好比咱们去银行取钱,如果没有排队机制,大家都挤在一起,那取钱的过程就会变得混乱无比。

说到锁,大家可能首先想到的是传统的单机环境下的锁,比如Java里的synchronized关键字或者Lock接口。但是在分布式系统中,这些本地锁就不太管用了。因为在分布式环境下,多个进程可能在不同的机器上运行,它们无法直接通过本地锁来协调。

ZooKeeper是一个开源的分布式协调服务,它通过一种简洁的目录树结构来维护和监控存储在其上的数据,并且可以用来实现分布式锁。简单来说,ZooKeeper就像是一个分布式系统的“协调员”,帮助咱们管理和调度各种资源。

第2章:ZooKeeper概述

ZooKeeper,这个名字听起来就像是动物园的管理员,它在分布式系统中的角色也差不多。ZooKeeper是一个为分布式应用提供协调服务的软件,它的设计目标是将那些复杂的、易于出错的分布式协调工作封装起来,提供给我们一套简单易用的接口。

ZooKeeper的架构很有意思。它基于一个主从结构(Leader-Follower模式)。在这个架构中,一个Leader节点负责处理写请求,多个Follower节点则处理读请求,这样既保证了数据的一致性,又提高了系统的读性能。

咱们用ZooKeeper的时候,会跟一个叫做ZNode的东西打交道。ZNode是ZooKeeper中的数据节点,可以想象成文件系统中的文件或目录。ZooKeeper的数据模型其实就是一棵树,每个节点都可以存储数据,并且节点之间可以有父子关系。

第3章:分布式锁的基本概念

在分布式系统中,当多个进程需要共享某个资源时,如果没有适当的管理,就会出现混乱。这时候,分布式锁就派上用场了。分布式锁,顾名思义,是在分布式环境中用来控制资源访问的一种机制。它能保证在分布式系统中,同一时刻,只有一个进程能访问特定的资源。

那分布式锁和我们熟知的本地锁有什么不同呢?本地锁,像Java中的synchronizedReentrantLock,主要是用于单个进程内的多个线程之间的同步。但在分布式系统中,进程可能分布在不同的服务器上,这就需要一种机制能跨服务器工作,这就是分布式锁的用武之地。

实现分布式锁有多种方式,但原理大同小异。核心思想是在分布式系统的所有节点之间共享一个锁。这个锁可以是一个文件、一个数据库行,或者像ZooKeeper这样的系统中的一个节点。当一个进程想要访问共享资源时,它先尝试获取这个锁,成功获得锁的进程可以访问资源,其他进程则需要等待或者重试。

举个例子,假设咱们有一个购票系统,多个服务器同时在处理票务。为了避免同一张票被多次售出的情况,咱们可以使用分布式锁来保证在任何时刻,只有一个服务器能操作同一张票。

小黑现在给大家展示一个用Java实现的简单的分布式锁示例。请注意,这只是一个演示,真实环境下的分布式锁会复杂得多,并且需要考虑更多的异常情况和性能问题。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SimpleDistributedLock {
    private final Lock lock = new ReentrantLock();

    public void lock() {
        lock.lock();
        try {
            // 执行需要同步的代码
            // 例如,处理票务
        } finally {
            lock.unlock();
        }
    }
}

这个例子中,ReentrantLock是Java提供的可重入锁,但它只能用于单进程。在分布式环境中,咱们需要通过网络对这种锁进行扩展,比如使用ZooKeeper或Redis来实现锁的状态存储。

第4章:ZooKeeper分布式锁的实现原理

ZooKeeper的数据模型是一棵树,树上的每个节点称为ZNode。ZooKeeper利用这些ZNode来实现分布式锁。具体怎么做的呢?就让小黑给大家慢慢道来。

在ZooKeeper中,实现分布式锁的一个关键点是利用ZNode的特性。ZooKeeper提供了一种特殊类型的节点,叫做临时顺序节点(Ephemeral Sequential)。这种节点有两个关键特性:一是节点在创建者断开连接后会自动被删除;二是每个节点都有一个唯一的递增序号。

那怎么用这个特性来实现锁呢?咱们举个例子。假设有个共享资源,小黑想要对其加锁。小黑会在ZooKeeper的一个指定路径下创建一个临时顺序节点。这个节点的创建,就相当于是尝试获取锁。因为是顺序节点,所以每个尝试获取锁的进程都会有一个唯一且递增的序号。

获取锁的过程就是比较序号的过程。每个进程会检查自己创建的节点是否是当前路径下序号最小的节点。如果是,那么恭喜,获取锁成功,可以访问共享资源了。如果不是,就等待序号比自己小的节点释放锁。

锁的释放很简单。一旦任务完成,进程会删除自己创建的节点。一旦这个节点被删除,ZooKeeper会通知序号紧随其后的节点。

下面,小黑展示一下用Java实现ZooKeeper分布式锁的简化代码。请记住,这只是个示例,真实环境中需要考虑更多的异常处理和边界情况。

import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;

import java.util.Collections;
import java.util.List;

public class ZooKeeperDistributedLock {
    private ZooKeeper zooKeeper;
    private String lockBasePath;
    private String lockNodePath;
    private String ourLockPath;

    public ZooKeeperDistributedLock(ZooKeeper zooKeeper, String lockBasePath, String lockNodePath) {
        this.zooKeeper = zooKeeper;
        this.lockBasePath = lockBasePath;
        this.lockNodePath = lockNodePath;
    }

    public boolean lock() throws Exception {
        // 创建临时顺序节点
        ourLockPath = zooKeeper.create(lockBasePath + "/" + lockNodePath, new byte[0], 
                                       ZooDefs.Ids.OPEN_ACL_UNSAFE, 
                                       CreateMode.EPHEMERAL_SEQUENTIAL);

        while (true) {
            List<String> locks = zooKeeper.getChildren(lockBasePath, false);
            Collections.sort(locks);
            String smallestLock = locks.get(0);

            if (ourLockPath.endsWith(smallestLock)) {
                // 如果我们的锁是最小的,那么获得锁
                return true;
            }

            // 如果不是最小的,等待前一个锁的释放
            // 这里简化处理,实际应用中需要监听节点变化
            Thread.sleep(1000);
        }
    }

    public void unlock() throws Exception {
        // 完成任务后,删除节点,释放锁
        zooKeeper.delete(ourLockPath, -1);
    }
}

在这个代码中,lock()方法尝试获取锁,unlock()方法释放锁。咱们在尝试获取锁时,创建了一个临时顺序节点。然后检查这个节点是否是所有子节点中序号最小的。如果是,就获取了锁;如果不是,就等待。

第5章:ZooKeeper分布式锁的代码实现

咱们得有个ZooKeeper客户端的连接。这个连接是实现分布式锁的基础。下面是创建ZooKeeper客户端连接的代码:

import org.apache.zookeeper.ZooKeeper;

public class ZooKeeperConnector {
    private ZooKeeper zooKeeper;

    public ZooKeeper connect(String host) throws Exception {
        zooKeeper = new ZooKeeper(host, 3000, watchedEvent -> {
            if (watchedEvent.getState() == Watcher.Event.KeeperState.SyncConnected) {
                System.out.println("连接创建成功!");
            }
        });
        return zooKeeper;
    }

    public void close() throws Exception {
        zooKeeper.close();
    }
}

在这段代码中,ZooKeeperConnector类负责创建和关闭与ZooKeeper集群的连接。connect方法接受一个ZooKeeper服务地址,然后创建一个连接。这里用了一个简单的Watcher来确认连接是否成功建立。

连接创建好后,接下来就是实现锁的逻辑了。咱们需要实现两个主要的方法:lockunlock。这两个方法分别用于获取锁和释放锁。下面是实现这两个方法的代码:

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;

public class DistributedLock {
    private final ZooKeeper zooKeeper;
    private final String lockRootPath = "/distributed_lock";
    private String lockNodePath;
    private String currentLockPath;

    public DistributedLock(ZooKeeper zooKeeper) {
        this.zooKeeper = zooKeeper;
    }

    public void lock() throws Exception {
        // 确保锁的根路径存在
        Stat stat = zooKeeper.exists(lockRootPath, false);
        if (stat == null) {
            zooKeeper.create(lockRootPath, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        }

        // 创建临时顺序节点
        currentLockPath = zooKeeper.create(lockRootPath + "/lock_", new byte[0], 
                                           ZooDefs.Ids.OPEN_ACL_UNSAFE, 
                                           CreateMode.EPHEMERAL_SEQUENTIAL);

        // 尝试获取锁
        tryLock();
    }

    private void tryLock() throws Exception {
        List<String> lockNodes = zooKeeper.getChildren(lockRootPath, false);
        Collections.sort(lockNodes);

        int index = lockNodes.indexOf(currentLockPath.substring(lockRootPath.length() + 1));
        if (index == 0) {
            // 如果是最小的节点,则表示获取锁成功
            System.out.println("锁获取成功:" + currentLockPath);
            return;
        }

        // 否则,监视前一个节点
        String prevNode = lockNodes.get(index - 1);
        CountDownLatch latch = new CountDownLatch(1);
        Stat prevStat = zooKeeper.exists(lockRootPath + "/" + prevNode, event -> {
            if (event.getType() == Watcher.Event.EventType.NodeDeleted) {
                latch.countDown();
            }
        });

        if (prevStat != null) {
            // 等待前一个节点释放
            latch.await();
            tryLock();
        } else {
            tryLock();
        }
    }

    public void unlock() throws Exception {
        // 删除节点,释放锁
        zooKeeper.delete(currentLockPath, -1);
        System.out.println("锁释放成功:" + currentLockPath);
    }
}

在这个实现中,lock方法首先确保锁的根路径存在。如果不存在,就创建一个。然后创建一个临时顺序节点。通过检查这个节点是否是最小的节点来尝试获取锁。

第6章:ZooKeeper分布式锁的高级应用

公平锁的实现

所谓公平锁,就是指等待获取锁的进程按照请求锁的顺序来获取锁。在ZooKeeper中,由于使用了临时顺序节点,实际上已经隐含了公平锁的特性。每个进程创建节点时都会被赋予一个唯一的序号,这个序号决定了它们获取锁的顺序。

读写锁的实现

读写锁是另一个常见的需求,它允许多个读操作同时进行,但写操作会独占锁。这在很多场景下都非常有用,比如允许多个用户同时读取数据,但只允许一个用户进行修改。

在ZooKeeper中实现读写锁需要更细致的控制。咱们可以创建两种类型的节点:读锁节点和写锁节点。读锁节点之间不互斥,但写锁节点会与所有其他节点互斥。下面是一个简化的读写锁实现:

public class ReadWriteLock {
    // 省略了连接ZooKeeper和基本设置的代码

    public void acquireReadLock() throws Exception {
        // 创建读锁节点
        String readLockPath = zooKeeper.create(lockRootPath + "/read_", new byte[0], 
                                               ZooDefs.Ids.OPEN_ACL_UNSAFE, 
                                               CreateMode.EPHEMERAL_SEQUENTIAL);
        // 检查是否可以获取读锁
        checkReadLock(readLockPath);
    }

    public void acquireWriteLock() throws Exception {
        // 创建写锁节点
        String writeLockPath = zooKeeper.create(lockRootPath + "/write_", new byte[0], 
                                                ZooDefs.Ids.OPEN_ACL_UNSAFE, 
                                                CreateMode.EPHEMERAL_SEQUENTIAL);
        // 检查是否可以获取写锁
        checkWriteLock(writeLockPath);
    }

    // 实现checkReadLock和checkWriteLock方法
    // 这里需要根据读写锁的逻辑来实现具体的检查逻辑
}

在这个代码中,acquireReadLockacquireWriteLock分别用于获取读锁和写锁。这两个方法都会创建相应类型的临时顺序节点,然后根据读写锁的规则来检查是否能够获取锁。

性能优化

在实现分布式锁时,性能也是一个非常重要的考虑点。例如,避免羊群效应(herd effect),即大量进程同时响应某个事件的情况。为了减少这种情况,可以优化锁的获取逻辑,比如使用ZooKeeper的Watcher机制来有效地通知等待的进程,而不是让所有进程都去轮询检查锁的状态。

第7章:ZooKeeper分布式锁的局限性和替代方案

虽然ZooKeeper分布式锁在很多场景下都非常有用,但小黑得实话实说,它并不是银弹,也有它的局限性。理解这些局限性,可以帮助咱们更好地选择和设计分布式锁方案。

ZooKeeper分布式锁的局限性
  1. 性能问题:ZooKeeper的节点创建和删除操作涉及到网络通信和磁盘I/O,这可能会成为性能瓶颈。特别是在锁的竞争非常激烈的情况下,性能问题会更加明显。

  2. 集群依赖:ZooKeeper自身是一个集群系统,它的可用性和稳定性直接影响到分布式锁的可靠性。如果ZooKeeper集群出现问题,那么基于它的分布式锁也会受到影响。

  3. 复杂性:ZooKeeper的使用和维护比较复杂,需要有一定的学习曲线。对于一些小团队来说,可能没有足够的资源去维护一个ZooKeeper集群。

替代方案

鉴于ZooKeeper分布式锁的这些局限性,咱们可以考虑一些其他的替代方案:

  1. 基于数据库的锁:使用数据库的行锁或表锁来实现分布式锁。这种方法简单直接,但可能会受限于数据库的性能和可扩展性。

  2. Redis分布式锁:Redis是一种高性能的键值存储系统,它也可以用来实现分布式锁。Redis分布式锁的实现通常基于SET命令的NX(Not eXists)和EX(Expire)选项,性能较好,但需要处理好锁的续租问题。

  3. Etcd分布式锁:Etcd是一个高可用的键值存储系统,专为分布式系统的配置管理和服务发现而设计。Etcd的分布式锁基于租约机制,提供了比ZooKeeper更为简洁的API。

咱们来看一个使用Redis实现分布式锁的简单例子:

import redis.clients.jedis.Jedis;

public class RedisDistributedLock {
    private Jedis jedis;
    private String lockKey;
    private String lockValue;

    public RedisDistributedLock(Jedis jedis, String lockKey, String lockValue) {
        this.jedis = jedis;
        this.lockKey = lockKey;
        this.lockValue = lockValue;
    }

    public boolean tryLock(long timeout) {
        long endTime = System.currentTimeMillis() + timeout;
        while (System.currentTimeMillis() < endTime) {
            if (jedis.setnx(lockKey, lockValue) == 1) {
                jedis.expire(lockKey, 30); // 设置锁的过期时间
                return true;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        return false;
    }

    public void unlock() {
        if (lockValue.equals(jedis.get(lockKey))) {
            jedis.del(lockKey);
        }
    }
}

在这个例子中,tryLock方法尝试设置一个键值对,如果设置成功(即之前没有这个锁),则获取锁成功;unlock方法则检查并删除这个键值对来释放锁。这只是一个基础版本,实际使用时还需要加入更多的错误处理和优化。

第8章:总结

现代应用越来越多地采用分布式架构。无论是大型的互联网服务还是微服务架构,分布式系统已经成为了主流。在这种环境下,对资源的并发访问和协调变得非常重要。ZooKeeper分布式锁正是为解决这种并发问题而生。

ZooKeeper分布式锁不仅是一个技术问题,它还体现了对分布式系统理解的深度和广度。

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

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

相关文章

拥有大规模犯罪联盟链的网络攻击中心

VexTrio 是一个网络犯罪集团&#xff0c;其历史至少可以追溯到 2017 年&#xff0c;该集团涉嫌利用复杂的字典域生成算法 (DDGA) 进行邪恶活动。 他们的恶意活动包括诈骗、风险软件、间谍软件、广告软件、隐匿垃圾程序 (PUP) 和露骨内容&#xff0c;其中 2022 年发生的一次引…

【广度优先搜索】【拓扑排序】【C++算法】913. 猫和老鼠

作者推荐 【动态规划】【map】【C算法】1289. 下降路径最小和 II 本文涉及知识点 广度优先搜索 拓扑排序 逆推 LeetCode913. 猫和老鼠 两位玩家分别扮演猫和老鼠&#xff0c;在一张 无向 图上进行游戏&#xff0c;两人轮流行动。 图的形式是&#xff1a;graph[a] 是一个列…

067:Vue2 + vite 开发环境的搭建(含源文件包,运行即可)

第067个 查看专栏目录: VUE 专栏目标 在vue和element UI联合技术栈的操控下&#xff0c;本专栏提供行之有效的源代码示例和信息点介绍&#xff0c;做到灵活运用。 提供vue2的一些基本操作&#xff1a;安装、引用&#xff0c;模板使用&#xff0c;computed&#xff0c;watch&am…

【机组】单元模块实验的综合调试与驻机键盘和液晶显示器的使用方式

​&#x1f308;个人主页&#xff1a;Sarapines Programmer&#x1f525; 系列专栏&#xff1a;《机组 | 模块单元实验》⏰诗赋清音&#xff1a;云生高巅梦远游&#xff0c; 星光点缀碧海愁。 山川深邃情难晤&#xff0c; 剑气凌云志自修。 目录 1. 综合实验的调试 1.1 实验…

YOLOv8改进 | Conv篇 | 结合Dual思想利用HetConv创新一种全新轻量化结构CSPHet(参数量下降70W)

一、本文介绍 本文给大家带来的改进机制是我结合Dual的思想利用HetConv提出一种全新的结构CSPHet,我们将其用于替换我们的C2f结构,可以将参数降低越75W,GFLOPs降低至6.6GFLOPs,同时本文结构为我独家创新,全网无第二份,非常适合用于发表论文,该结构非常灵活,利用Dual卷…

调用阿里通义千问大语言模型API-小白新手教程-python

阿里大语言模型通义千问API使用新手教程 最近需要用到大模型&#xff0c;了解到目前国产大模型中&#xff0c;阿里的通义千问有比较详细的SDK文档可进行二次开发,目前通义千问的API文档其实是可以进行精简然后学习的,也就是说&#xff0c;是可以通过简单的API调用在自己网页或…

【GitHub项目推荐--推荐一个开源的任务管理工具(仿X书/X钉)】【转载】

推荐一个开源的任务管理工具&#xff0c;该工具会提供各类文档协作功能、在线思维导图、在线流程图、项目管理、任务分发、即时 IM&#xff0c;文件管理等等。该开源项目使用到 Vue、Element-UI、ECharts 等技术栈。 开源地址&#xff1a;www.github.com/kuaifan/dootask 预览地…

ES的一些名称和概念总结

概念 先看看ElasticSearch的整体架构&#xff1a; 一个 ES Index 在集群模式下&#xff0c;有多个 Node &#xff08;节点&#xff09;组成。每个节点就是 ES 的Instance (实例)。每个节点上会有多个 shard &#xff08;分片&#xff09;&#xff0c; P1 P2 是主分片, R1 R2…

Flink实现数据写入MySQL

先准备一个文件里面数据有&#xff1a; a, 1547718199, 1000000 b, 1547718200, 1000000 c, 1547718201, 1000000 d, 1547718202, 1000000 e, 1547718203, 1000000 f, 1547718204, 1000000 g, 1547718205, 1000000 h, 1547718210, 1000000 i, 1547718210, 1000000 j, 154771821…

数学建模-------误差来源以及误差分析

绝对误差&#xff1a;精确值-近似值&#xff1b; 举个例子&#xff1a;从A到B&#xff0c;应该有73千米&#xff0c;但是我们近似成了70千米&#xff1b;从C到D&#xff0c;应该是1373千米&#xff0c;我们近似成了1370千米&#xff0c;如果使用绝对误差&#xff0c;结果都是3…

Docker容器部署OpenCV,打造高效可移植的计算机视觉开发环境

推荐 海鲸AI-ChatGPT4.0国内站点&#xff1a;https://www.atalk-ai.com 前言 在计算机视觉领域&#xff0c;快速部署和测试算法是研究和开发的关键。OpenCV作为一个强大的开源计算机视觉库&#xff0c;广泛应用于各种图像处理和视频分析任务。然而&#xff0c;配置OpenCV环境可…

compose部署tomcat

1.部署tomcat 1.1.下载相关镜像tomcat8.5.20 $ docker pull tomcat:8.5.20 1.2 在/data目录下创建tomcat/webapps目录 mkdir -p /data/tomcat/webapps 注意&#xff1a;这里是准备将宿主机的/data/tomcat/webapps映射到容器的 /usr/…

HDFS的standby节点启动过慢原因分析以及应对策略

HDFS的standby节点启动过慢原因分析以及应对策略 1. NN启动大致流程2. Editlog日志清理策略2.1 为什么需要合并editlog&#xff1f;2.2 什么时候删除editlog&#xff1f; 3. NN启动的日志加载策略4. Standby启动慢应对策略5. 疑问和思考5.1 如何人工阅读editlog文件的内容&…

IDEA jdk版本切换问题

打开 IntelliJ IDEA 的 Project Structure&#xff08;快捷键通常是 Ctrl Alt Shift S&#xff09;。 转到 Project Settings > Modules。 选择相应的模块&#xff0c;然后在 Sources 标签页下&#xff0c;查看 Language level 是否设置为 自己需要的jdk版本语言。 接…

YOLOv8训练自己的数据集,通过LabelImg

记录下labelImg标注数据到YOLOv8训练的过程,其中容易遇到labelImg的坑 数据集处理 首先在mydata下创建4个文件夹 images文件夹下存放着所有的图片&#xff0c;包括训练集和测试集等。后续会根据代码进行划分。 json文件夹里存放的是labelImg标注的所有数据。需要注意的是&…

qtcreator使用qwt库

先配置好.pro文件&#xff0c;再去ui界面拖拽控件 ui界面会更改配置&#xff0c;故顺序错一个&#xff0c;就凉了&#xff0c;重来吧 准备&#xff1a;库&#xff0c;库头文件 库文件&#xff1a;路径如下 头文件&#xff1a;路径如下 鼠标->右键 &#xff08;有些不用勾…

读元宇宙改变一切笔记13_治理与管理

1. 元宇宙的经济价值 1.1. 元宇宙的价值最终将“超过”物理世界 1.2. 人们之所以对低延迟网络进行投资&#xff0c;是因为有一些体验需要元宇宙&#xff1a;同步实时渲染的虚拟世界、AR和云游戏流 1.3. 在大多数情况下&#xff0c;数字经济并不是什么新鲜事 1.3.1. 数字经济…

【算法】北极通讯网络(Kruskal)

题目 北极的某区域共有 n 座村庄&#xff0c;每座村庄的坐标用一对整数 (x,y) 表示。 为了加强联系&#xff0c;决定在村庄之间建立通讯网络&#xff0c;使每两座村庄之间都可以直接或间接通讯。 通讯工具可以是无线电收发机&#xff0c;也可以是卫星设备。 无线电收发机有…

【shell-10】shell实现的各种kafka脚本

kafka-shell工具 背景日志 log一.启动kafka->(start-kafka)二.停止kafka->(stop-kafka)三.创建topic->(create-topic)四.删除topic->(delete-topic)五.获取topic列表->(list-topic)六. 将文件数据 录入到kafka->(file-to-kafka)七.将kafka数据 下载到文件-&g…

Oracle RAC集群日志

文章目录 一、DB日志1、日志所在位置介绍2、知识介绍 二、ASM日志1、日志所在位置介绍2、知识介绍 三、CRS日志1、日志所在位置介绍2、知识介绍 四、RAC相关日志详细总结 一、DB日志 DB日志也就是数据库日志&#xff0c;全称Oracle Database Logs 1、日志所在位置介绍 日志位…