记录Java秋招面经(网上找的)
1.Mysql的存储机制,怎么落到库里面的?
当数据插入 MySQL 时,首先数据修改会在内存中的 Buffer Pool 中完成,同时记录写入 Redo Log 以保证事务的持久性。事务提交时,日志会被刷入磁盘,确保数据可以恢复。修改后的数据页暂时不会立即写入磁盘,而是由后台线程异步将内存中的脏页(已修改但未写回的数据)写入到实际的表空间文件中。这种机制提高了写入性能,同时通过 Redo Log 和数据文件的同步确保数据的一致性和持久性。
1. 存储引擎
MySQL 支持多种存储引擎,不同的存储引擎有不同的存储机制。常见的存储引擎有:
InnoDB: 默认的存储引擎,支持事务,采用行级锁定,具有崩溃恢复功能。
MyISAM: 较老的存储引擎,不支持事务和外键,采用表级锁定,查询速度快,适合只读操作较多的应用。
2. InnoDB 存储引擎
InnoDB 是 MySQL 的默认存储引擎,它的存储机制比较复杂,涉及以下几个方面:
a. 表空间和数据文件
InnoDB 采用表空间(Tablespace)来管理数据库的数据。数据被存储在数据文件中,常见的有:
系统表空间: 这是一个共享的表空间,包含了所有 InnoDB 表和索引的数据,存储在 MySQL 数据目录中的 ibdata1 文件中。
独立表空间: InnoDB 可以为每个表创建独立的表空间(如果启用了 innodb_file_per_table),每个表的数据存储在单独的 .ibd 文件中。每个表的表空间包含表数据、索引等内容。
b. 数据页和索引页
InnoDB 将数据存储在页(Page)中,默认页大小为 16KB。表中的每一行都存储在这些页中,页是存储的基本单位。
数据页: 存储表的行数据,每一页包含多个行。
索引页: 存储表的索引信息。
c. 聚簇索引(Clustered Index)
InnoDB 使用聚簇索引来存储数据表的行。聚簇索引按主键顺序存储行数据,这意味着行数据实际上存储在 B+ 树结构中,树的叶子节点包含实际的数据行。每个表都必须有一个主键,如果没有显式定义主键,InnoDB 会选择一个唯一的非空索引或自动生成一个隐藏的列作为主键。
d. Redo Log 和 Undo Log
Redo Log(重做日志): InnoDB 为了保证事务的持久性,在事务提交前,先将修改写入重做日志(ib_logfile 文件),即使发生崩溃,也可以通过重做日志恢复数据。
Undo Log(回滚日志): 为了支持事务的原子性和一致性,InnoDB 在事务执行过程中会记录 Undo Log,允许事务在出错时回滚到之前的状态。
e. 数据落盘过程
InnoDB 使用缓冲池(Buffer Pool)来缓存数据页和索引页,数据的修改首先发生在缓冲池中,随后通过以下步骤将数据持久化到磁盘:
修改页: 当事务对表的数据进行修改时,这些修改会先在缓冲池中的页上进行。
写入重做日志: 事务提交时,InnoDB 会将修改先写入重做日志,保证数据可以在崩溃时恢复。
刷新脏页: InnoDB 会定期或在内存不足时将修改后的脏页从缓冲池刷新到磁盘上的数据文件中。
3. MyISAM 存储引擎
MyISAM 相对较为简单,它的数据存储方式如下:
数据文件: 每个表有两个文件,一个是 .MYD 文件,用于存储表的数据,另一个是 .MYI 文件,用于存储索引。
存储形式: MyISAM 的表数据按照表结构定义存储,表的数据和索引是分开存储的,索引是非聚簇索引。
4. 文件系统和磁盘
MySQL 最终将数据以文件的形式存储在操作系统的文件系统中。MySQL 支持多种文件系统(如 EXT4、NTFS、XFS 等),不同文件系统对文件的组织和管理方式不同,可能会影响 MySQL 数据库的性能。
5. 数据库缓冲机制
MySQL 通过缓存机制来提高性能:
查询缓存: MySQL 会缓存查询结果,当有相同的查询请求时,可以直接从缓存中返回结果(InnoDB 不再使用查询缓存,改用 Buffer Pool 来管理缓存)。
Buffer Pool: InnoDB 的缓冲池用于缓存表和索引数据,减少对磁盘的直接访问,提高性能。
6. 数据存储的事务性和一致性
InnoDB 提供 ACID 特性,通过锁机制、事务日志、缓冲池等多种手段来保证数据的一致性和持久性:
原子性:每个事务是一个不可分割的工作单元,要么完全执行,要么完全回滚。
一致性:事务前后,数据库状态必须保持一致。
隔离性:多个事务不会互相干扰,InnoDB 提供了多种隔离级别。
持久性:事务提交后,数据会持久保存到磁盘,即使系统崩溃也可以恢复。
通过这些机制,MySQL 能够高效地将数据写入数据库,并保证数据的安全性和一致性。
当我们将数据插入 MySQL 数据库时,MySQL 会通过一系列的步骤和机制将数据最终存储在磁盘上的数据库文件中。这整个过程涉及到内存、日志文件、缓存等。以 InnoDB 存储引擎为例,具体步骤如下:
1. 客户端发送 SQL 请求
当客户端发送 INSERT, UPDATE 或 DELETE 等写操作的 SQL 请求时,MySQL 的查询解析器会首先对请求进行解析,生成查询计划。
2. 引擎层处理
MySQL 的存储引擎负责实际的数据存储工作,不同的存储引擎有不同的实现。以 InnoDB 为例,InnoDB 存储引擎会开始执行写操作的流程。
3. 检查 Buffer Pool
InnoDB 有一个内存中的缓存区,称为 Buffer Pool,用来缓存数据页(pages)。当我们要修改数据时,首先会检查该数据是否已经在 Buffer Pool 中:
如果数据在 Buffer Pool 中,直接在内存中修改数据;
如果数据不在 Buffer Pool 中,InnoDB 会从磁盘上读取相应的数据页到 Buffer Pool 中,然后再进行修改。
4. 修改数据页(脏页)
数据修改首先在内存中的数据页上进行,这时的页被称为 脏页(dirty page),因为它已经被修改,但还没有写回磁盘。此时数据只是暂存在内存中,还没有永久存储到磁盘上。
5. 写入 Redo Log(重做日志)
为了确保数据的持久性和防止数据丢失,InnoDB 会在修改内存中的数据页的同时,将这次操作的记录写入 Redo Log。Redo Log 是一个顺序写入的日志文件,记录了所有数据的修改操作。
这个操作是很快的,因为日志是顺序写入磁盘的,不像随机写数据文件那样慢。
即使数据库崩溃,由于 Redo Log 的存在,InnoDB 能够在重启后通过 Redo Log 恢复数据。
6. 事务提交
在事务执行 COMMIT 时,InnoDB 会将事务相关的 Redo Log 刷入磁盘,保证事务的持久性。这意味着只要 Redo Log 写入成功,即使数据还没有写到表空间的数据文件中,事务也被认为已经提交成功。
7. 数据异步刷新到磁盘
修改的数据页不会立即写回磁盘,而是由 InnoDB 后台的线程在合适的时候(例如 Buffer Pool 缓存不足时、数据库空闲时、定时任务等)将这些脏页异步写回磁盘中的数据文件。这称为 刷脏页 操作。
刷脏页并不是每次修改都立即进行,而是为了提高性能,在合适的时候批量进行。
8. 写入 Undo Log(回滚日志)
为了支持事务的回滚和一致性读,InnoDB 会在修改数据时生成 Undo Log,记录数据修改前的状态。Undo Log 保存在磁盘上,它允许事务回滚到执行前的状态,并且在读取数据时,能够提供一致性的快照。
9. 数据文件存储
最终,修改后的数据会被写入到表空间的实际数据文件中。对于 InnoDB 来说,这些数据通常存储在 .ibd 文件中(如果使用独立表空间),或者存储在 ibdata 文件中(如果使用共享表空间)。
10. 刷写到数据文件
InnoDB 的后台线程定期会将脏页刷新到表空间文件中,从而确保内存中的数据与磁盘上的数据同步。这一步是为了提高写性能而设计的,数据写入操作首先是在内存中完成,最后异步刷写到磁盘。
11. 日志文件和数据文件合并
Redo Log 和 数据文件 保持了不同步的状态,MySQL 通过后台线程定期将脏页和 Redo Log 的内容同步到数据文件中,保证最终一致性。
当发生宕机等故障时,MySQL 可以通过重做日志恢复最新的数据修改。
2.Mysql执行全流程
当 MySQL 接收到客户端的 SQL 请求后,首先进行连接管理和权限验证,确保用户有执行权限。然后,查询会经过解析器进行词法和语法分析,接着由优化器选择最优的执行计划,比如使用合适的索引或连接方式。执行器根据生成的计划与存储引擎(如 InnoDB)交互,读取或修改数据。对于写操作,事务的修改会记录到日志中确保持久性。最终,查询结果通过 TCP/IP 返回给客户端。
MySQL 执行全流程:
1.客户端请求: 客户端发送 SQL 查询到 MySQL 服务器,MySQL 通过 TCP/IP 协议接收请求。
2.连接管理与权限验证: MySQL 先检查用户权限,确保有执行该查询的权限,如果通过,则分配线程处理该请求。
3.查询解析: MySQL 解析器对 SQL 语句进行词法和语法分析,生成解析树,检查语法是否正确。
4.查询优化: 优化器分析解析树,生成最优的执行计划,选择合适的索引和连接顺序来提高性能。
5.执行计划: 执行器根据优化后的执行计划开始操作数据库,比如从表中读取数据或更新数据。
6.存储引擎交互: MySQL 通过存储引擎(如 InnoDB)来处理数据的实际存储和读取操作,存储引擎负责行级锁、事务处理等。
7.返回结果: 查询执行器获取数据后,将结果集返回给客户端。
8.事务处理(针对写操作): 对于写操作,InnoDB 会记录日志,确保事务的原子性和持久性。
3.索引机制
MySQL 的索引机制主要通过 B+树索引、哈希索引和全文索引来加速查询。B+树索引是最常用的,适合范围查询和排序,哈希索引用于精确查询,而全文索引用于处理大量文本搜索。索引可以显著提高查询速度,尤其是在大表中,但会增加插入、更新时的维护开销和存储空间消耗。因此,在设计索引时需要权衡查询性能和资源成本。
MySQL 的索引机制用于加速数据查询,类似于书的目录,可以帮助快速定位数据,而不必逐行扫描整个表。常见的 MySQL 索引类型包括 B+树索引、哈希索引 和 全文索引。以下是各索引的机制及特点:
1. B+树索引(默认类型,适用于 InnoDB 和 MyISAM)
结构:B+树是一种平衡的多叉树结构,叶子节点存储指向实际数据的指针。所有数据节点都在树的叶子节点上,且同一层的叶子节点通过指针相连。
特点:B+树的高度一般很低,通常为 2-4 层,查找、插入、删除等操作的时间复杂度是 O(log N),非常适合范围查询和排序查询。
聚簇索引:在 InnoDB 中,主键索引是聚簇索引,数据按主键顺序存储,叶子节点直接存储数据。
非聚簇索引:非主键索引为非聚簇索引,叶子节点存储主键的引用,查询时需要回表。
2. 哈希索引(适用于 Memory 引擎)
结构:哈希索引通过将键值经过哈希函数处理后映射到哈希表中,实现 O(1) 时间复杂度的快速定位。
特点:哈希索引非常适合精确查询,但不适合范围查询,因为哈希函数会打乱键值的顺序,无法进行顺序扫描。
3. 全文索引(适用于 MyISAM 和 InnoDB)
结构:全文索引主要用于搜索大文本字段,它通过建立倒排索引来快速查找包含某个关键词的记录。
特点:适合处理复杂的文本搜索,例如匹配某个关键词或短语,但不适合精确匹配的场景。
索引的优点:
提高查询速度:通过索引,MySQL 可以跳过大量不相关的数据行,直接定位到所需的数据,尤其是在大表中效果显著。
排序优化:索引可以帮助优化 ORDER BY 和 GROUP BY 查询,因为索引列的数据是有序的。
范围查询加速:B+树索引特别适合范围查询,如 BETWEEN 或 >= 操作。
索引的缺点:
插入/删除的开销:索引需要维护,当有插入、更新或删除时,MySQL 需要重新平衡索引树或更新哈希表,增加了额外的开销。
占用存储空间:索引会占用额外的存储空间,尤其是对于大表来说,索引文件可能会很大。
总结:
MySQL 的索引机制通过 B+树索引、哈希索引和全文索引等类型,有效地加速了查询操作,尤其适合大数据量的表。然而,索引的使用也带来了维护开销和存储空间的消耗,因此需要根据具体的查询需求合理选择和设计索引。
4.AQS
AQS(AbstractQueuedSynchronizer)是 Java 并发包中的一个核心组件,用于实现各种同步器如 ReentrantLock 和 Semaphore。它通过维护一个 FIFO 等待队列和一个状态变量来管理线程对共享资源的访问。AQS 支持独占模式(一个线程独占资源)和共享模式(多个线程可以同时访问资源),通过 CAS 操作和 CLH 队列高效地处理线程的同步和唤醒。它为各种同步工具提供了基础,确保了线程管理的高效性和可靠性。
AQS(AbstractQueuedSynchronizer)是 Java 并发包(java.util.concurrent)中的一个基础框架,主要用于构建锁和同步器(如 ReentrantLock, CountDownLatch, Semaphore 等)。它通过维护一个先进先出(FIFO)的等待队列,来管理多个线程的同步访问。
AQS 工作原理:
状态管理:AQS 通过一个 int 类型的状态变量 state 来表示共享资源的状态。线程通过 CAS(Compare-And-Swap)操作修改这个状态,用于判断资源是否可以获取。
state = 0 表示资源可用。
state > 0 表示资源已被占用。
独占模式与共享模式:
独占模式:只有一个线程可以获得资源,如 ReentrantLock。在独占模式下,如果某个线程获取资源失败,则会进入等待队列。
共享模式:允许多个线程同时获取资源,如 CountDownLatch 和 Semaphore。多个线程可以并发访问资源。
FIFO 等待队列: 当一个线程尝试获取资源失败时,它会被加入到 AQS 的等待队列中,队列遵循 FIFO 规则。队列中的每个节点代表一个线程,AQS 会管理队列中线程的排队和唤醒。
CLH(Craig, Landin, and Hagersten)队列: AQS 的等待队列基于 CLH 队列,是一种双向链表结构。线程被封装为队列节点,当资源可用时,AQS 会按照队列顺序唤醒等待的线程。
锁的获取与释放:
获取锁:线程通过 acquire() 尝试获取锁,若资源可用则直接成功,否则进入等待队列。
释放锁:锁释放后,AQS 会通过 release() 唤醒等待队列中的下一个线程。
AQS 核心方法:
acquire(int arg):独占模式下获取锁的方法,若获取失败则进入等待队列。
release(int arg):独占模式下释放锁的方法,唤醒队列中的下一个线程。
acquireShared(int arg):共享模式下获取锁,多个线程可以并发获取资源。
releaseShared(int arg):共享模式下释放锁,通知所有等待线程资源已可用。
AQS 优势:
通用性强:AQS 提供了基本的队列和同步状态管理,允许轻松实现多种同步工具。
高效的线程管理:通过 FIFO 队列有效管理线程的同步操作,减少竞争和开销。
总结:
AQS 是 Java 并发框架中的基础组件,通过 CAS、CLH 队列等机制管理线程对资源的访问,支持独占和共享两种模式。AQS 为许多高级同步工具(如锁、信号量)提供了核心支撑。
5.volatile和synconized区别以及实现
volatile 和 synchronized 是 Java 中用于处理并发问题的两种机制。volatile 确保变量的内存可见性和禁止指令重排序,适用于简单的状态变量,但不保证原子性。synchronized 提供了互斥锁、内存可见性和原子性,适用于复杂的同步需求。volatile 通过直接的内存访问实现,而 synchronized 涉及到锁的获取和释放,具有更高的开销。
1. volatile
作用:volatile 关键字用于声明一个变量,以确保该变量的值在所有线程中都是一致的。它主要用来保证内存可见性,即当一个线程修改了被 volatile 修饰的变量时,其他线程能够立即看到这个修改。
实现:
内存可见性:volatile 变量的读写操作直接与主内存交互,而不是从线程的缓存中读取或写入数据。这保证了所有线程看到的都是最新的值。
禁止指令重排序:编译器和处理器不会对 volatile 变量的读写操作进行重排序,这避免了某些操作顺序不一致的问题。
适用场景:适用于简单的标志位、状态变量等场景,确保所有线程看到一致的值。它不能保证原子性,因此不适合用于复合操作(如自增)。
2. synchronized
作用:synchronized 关键字用于在方法或代码块上加锁,以确保同一时刻只有一个线程能够执行被锁定的代码段。它不仅保证了内存的可见性,还提供了原子性和互斥性。
实现:
互斥性:synchronized 确保在同一时刻只有一个线程可以执行被 synchronized 修饰的代码块或方法,防止多个线程同时访问共享资源。
内存可见性:进入 synchronized 代码块时,线程会从主内存中重新加载数据;退出 synchronized 代码块时,线程会将数据刷新到主内存中,确保对共享数据的修改对其他线程立即可见。
锁对象:每个 synchronized 代码块或方法都有一个锁对象,锁对象是与对象或类实例相关联的。锁的获取和释放操作需要维护锁的状态,涉及到线程的上下文切换和调度。
适用场景:适用于需要保证线程安全的复杂操作和临界区,例如对共享资源的访问和修改。
总结
volatile 保证变量的可见性和禁止指令重排序,但不保证原子性,适合于简单的状态标志。它的实现依赖于直接的内存访问和特定的内存屏障操作。
synchronized 提供了互斥锁、原子性和可见性,适用于复杂的同步场景。它的实现涉及到锁的获取和释放、上下文切换等开销。
6.kafka实现原理
Kafka 是一个高吞吐量的分布式流平台,它通过将数据分为多个分区,并在不同的代理节点上存储副本来实现高可靠性和可扩展性。数据顺序写入到分区的日志文件中,保证了数据的一致性和持久性。生产者将数据写入主题,消费者从主题中拉取数据,支持高效的数据传输和处理。通过分区、副本机制和消费者组,Kafka 能够实现负载均衡、高可用性以及大规模的数据处理。
Kafka 是一个分布式流平台,用于处理高吞吐量的实时数据流。它的实现原理涉及多个关键组件和技术,包括生产者、消费者、代理、主题、分区和日志。以下是 Kafka 的主要实现原理:
1. 核心组件
生产者 (Producer):负责将数据发送到 Kafka 集群中的一个或多个主题。生产者将数据写入到主题的分区中,并可以选择使用不同的分区策略来优化数据分布和负载均衡。
消费者 (Consumer):从 Kafka 中读取数据。消费者从一个或多个主题的分区中消费数据,并且可以通过消费者组来实现负载均衡和高可用性。
代理 (Broker):Kafka 集群中的每个节点称为代理。代理负责接收来自生产者的数据,将其写入磁盘,并向消费者提供数据。每个代理可以处理多个主题和分区。
主题 (Topic):数据的逻辑分类。每个主题可以有多个分区,生产者将数据写入到主题中的分区,消费者从主题的分区中读取数据。
分区 (Partition):主题的子集,用于提高并发处理能力和扩展性。每个分区是一个有序的、不可变的消息序列,数据按追加的顺序写入。
日志 (Log):每个分区的数据存储在日志文件中,日志文件是一个有序的、不可变的消息序列。数据追加到日志的末尾,消费者可以从日志中按顺序读取数据。
2. 数据存储与管理
消息持久化:Kafka 将消息写入磁盘并进行持久化,以确保数据的可靠性。每个分区的日志是一个有序的文件集合,数据按顺序追加到日志末尾。
副本机制:为了提高数据的可靠性和可用性,Kafka 使用副本机制。每个分区可以有多个副本,副本在不同的代理上进行存储。一个副本是主副本(Leader),其他副本是从副本(Follower)。生产者将数据写入主副本,从副本从主副本同步数据。
数据删除:Kafka 使用配置的保留策略(基于时间或大小)来控制数据的保留和删除。过期的数据会被删除,以释放存储空间。
3. 消息传递机制
顺序写入:Kafka 将数据顺序写入到分区的日志文件中,确保数据的顺序性。这种顺序写入可以提高写入性能并简化数据恢复。
消费者偏移量:消费者读取数据时会记录偏移量(offset),以跟踪读取的位置。偏移量是分区内消息的唯一标识。消费者可以根据自己的需求选择何时提交偏移量,从而实现数据的准确消费和重复消费的控制。
拉取式模型:消费者通过拉取的方式从 Kafka 中读取数据。消费者定期向 Kafka 请求数据,Kafka 将数据发送给消费者。这种模型使得消费者可以控制数据的处理速度和流量。
4. 负载均衡与扩展
分区与副本:通过将主题划分为多个分区,并将副本分布在不同的代理上,Kafka 可以实现负载均衡和扩展。生产者和消费者可以并行处理多个分区的数据,从而提高吞吐量和处理能力。
消费者组:消费者组可以实现负载均衡,一个消费者组内的消费者共同消费主题中的所有分区,每个分区只能由一个消费者处理,从而实现数据的并行处理和容错。
总结
Kafka 是一个高吞吐量、分布式的流平台,通过将数据分区和副本分布在不同的代理上,实现了高可靠性和扩展性。数据在分区的日志文件中顺序写入,并通过副本机制确保数据的可靠性。消费者通过拉取的方式读取数据,消费者组实现了负载均衡和高可用性。