Java 后端面试指南
面试指南
TMD,一个后端为什么要了解那么多的知识,真是服了。啥啥都得了解
MySQL
MySQL索引可能在以下几种情况下失效:
- 不遵循最左匹配原则:在联合索引中,如果没有使用索引的最左前缀,即查询条件中没有包含联合索引的第一列,那么索引将会失效。
- 使用了OR操作符:即使在查询条件中使用了联合索引的全部列,如果这些列之间是使用OR操作符连接的,索引也可能会失效。
- 数据类型转换:如果在查询条件中对字段进行了隐式的类型转换,比如将字符类型的字段与数字进行比较,这可能导致索引失效。
- 使用了函数或表达式:在查询条件中对字段使用了函数或表达式,如
WHERE YEAR(date_column) = 2023
,这样会使得索引失效。 - 选择性低的索引:如果索引的选择性很低,即索引列的值重复率很高,数据库优化器可能会选择全表扫描而不是使用索引。
- 索引列上有函数或计算:当索引列上有函数或计算时,MySQL无法使用索引来查找行。
- 全表扫描更快的情况:当MySQL优化器估计出全表扫描比使用索引更快时,系统会选择不使用索引。这通常发生在表中数据量较少,或者查询返回大部分甚至所有行的情况下。
总的来说,索引失效可能会导致查询性能下降,因此在设计查询语句和索引时需要特别注意以上情况,以确保索引能够发挥其应有的作用。
索引不适合哪些场景
- 数据量少的不适合加索引
- 更新比较频繁的也不适合加索引
- 区分度低的字段不适合加索引(如性别)
日常工作中你是怎么优化 SQL 的?
在日常工作中使用SQL时,优化是一个持续的过程,旨在确保数据库查询的效率和性能。以下是一些常见的优化策略:
-
使用EXPLAIN分析查询:
- 使用
EXPLAIN
命令来分析SQL语句的执行计划,从而理解MySQL如何执行查询,哪些地方可能成为瓶颈。
- 使用
-
正确地创建和使用索引:
- 确保为经常用于搜索和排序的列创建索引。
- 避免创建不必要的索引,以减少插入、更新和删除操作的成本。
- 使用复合索引来优化多列的查询条件。
- 定期审查现有索引的有效性,删除不再使用的索引。
-
编写高效的SQL语句:
- 避免在WHERE子句中对字段进行函数转换或计算。
- 减少使用OR操作符,尤其是在索引列上,因为它可能导致全表扫描。
- 使用连接(JOIN)而不是子查询,以便更有效地利用索引。
- 仅选择需要的列,而不是使用
SELECT *
。
-
优化数据模型:
- 规范化表结构以避免数据冗余。
- 在必要时使用反规范化来减少连接操作,提高查询性能。
-
使用分区和分表:
- 对于大型表,考虑使用分区来提高查询性能。
- 在数据量非常大的情况下,可以考虑分表来分散负载。
-
调整数据库配置:
- 根据服务器的硬件资源和应用需求调整MySQL的配置参数,如缓冲池大小、连接数等。
-
监控和诊断:
- 使用慢查询日志来识别低效的查询。
- 使用性能监控工具来跟踪数据库的性能指标。
-
批量操作和事务控制:
- 使用批量操作来减少数据库的I/O次数。
- 合理使用事务,确保数据的一致性,同时避免长事务导致的锁竞争。
-
避免使用锁定的查询:
- 尽量使用读已提交隔离级别,避免不必要的行级锁。
-
定期维护:
- 定期运行
OPTIMIZE TABLE
来整理表空间,特别是对于经常修改的表。 - 定期检查并修复表的错误。
- 定期运行
-
学习和使用新的数据库特性:
- 保持对MySQL新特性的了解,如窗口函数、CTE(公共表表达式)等,这些可以帮助编写更高效的查询。
通过上述方法,可以显著提高SQL查询的性能。然而,每个数据库和应用场景都是独特的,因此可能需要根据具体情况调整优化策略。
OPTIMIZE TABLE 是什么?
OPTIMIZE TABLE
是MySQL中用于改善表性能的命令。它的主要作用是整理表的空间使用,减少碎片,提高数据访问效率。在表中进行大量插入、删除或更新操作后,可能会产生空间碎片,导致表的性能下降。使用OPTIMIZE TABLE
命令可以重新组织表的数据,释放未使用的空间,让数据更紧凑地存储。
以下是使用OPTIMIZE TABLE
的基本语法:
OPTIMIZE TABLE table_name;
其中,table_name
是要优化的表的名称。
OPTIMIZE TABLE
命令执行的操作包括:
- 整理表空间:通过整理表的磁盘空间,减少碎片,使得数据行紧密排列,从而提高I/O效率。
- 更新统计信息:更新表的统计信息,帮助优化器选择更有效的执行计划。
- 重建索引:如果表的索引已经损坏或者因为碎片而效率低下,
OPTIMIZE TABLE
可以重建索引,提高索引的效率。 - 减少文件碎片:对于使用InnoDB存储引擎的表,
OPTIMIZE TABLE
可以减少文件碎片,提高空间利用率。
需要注意的是,OPTIMIZE TABLE
命令需要对表具有一定的锁定时间,在此期间表无法进行写入操作。因此,建议在系统负载较低的时段执行此命令,以减少对应用的影响。
此外,对于使用InnoDB存储引擎的表,OPTIMIZE TABLE
命令的效果可能不如预期,因为InnoDB会自动进行页的合并和分裂来管理碎片。在这种情况下,可以考虑使用ALTER TABLE
命令来更改表的压缩模式,以提高空间利用率和性能。
InnoDB 与 MyISAM 的区别
下面是InnoDB和MyISAM两种存储引擎在关键特性上的对比表格:
特性 | InnoDB | MyISAM |
---|---|---|
事务支持 | 支持完整的ACID事务 | 不支持事务 |
行级锁 | 支持行级锁,提高并发性能 | 仅支持表级锁 |
外键约束 | 支持外键约束 | 不支持外键 |
数据文件和索引 | 使用聚集索引,数据文件存放在主键索引的叶子节点上 | 使用非聚集索引,数据文件和索引分开存储 |
全文索引 | 支持全文索引,但需要额外配置和插件 | 支持全文索引,且无需额外配置 |
数据恢复能力 | 具有崩溃后的数据恢复能力 | 遇到系统崩溃时,数据恢复能力较弱 |
缓存 | 支持数据和索引的缓存 | 只缓存索引 |
表空间 | 所有表共享一个表空间,方便管理 | 每个表单独存储为.frm、.MYD、.MYI文件 |
适用场景 | 适用于需要高并发、事务完整性保障的应用 | 适用于读取密集型以及不要求事务的应用 |
注意:这个表格中的信息可能随着MySQL版本的更新而有所变化。例如,InnoDB在MySQL 5.6版本以后开始支持全文索引,但通常认为MyISAM在全文索引方面更为成熟。此外,MyISAM由于其设计简单,在某些读密集的场景下可能有较好的性能表现,但随着现代硬件的发展和多核处理器的普及,InnoDB的性能优势愈发明显。
数据库索引的原理,为什么要用 B+树?
数据库索引的原理是利用数据结构对数据进行排序,以便快速查找。在数据库中,B+树是最常用的索引结构,因为它具有以下优点:
- 减少磁盘I/O操作:B+树的设计能够有效地减少访问节点的次数,因为每个节点可以存储多个元素,这意味着在查找过程中需要的磁盘I/O操作更少。每次磁盘访问都是昂贵的,因此减少I/O操作次数对于性能至关重要。
- 增加存储效率:B+树的非叶子节点不存储数据,只存储索引,而所有数据都保存在叶子节点中。这样的设计使得每个节点可以存储更多的索引,从而使整个树的高度降低,进一步减少了I/O操作的次数。
- 提高查询稳定性:由于所有数据都存在于叶子节点,并且叶子节点之间通过链表连接,这使得范围查询更加高效。在B+树中进行范围查询时,只需遍历叶子节点的链表即可,而在二叉树中可能需要进行二次遍历。
- 方便数据插入和删除:B+树的结构允许在不影响其他部分的情况下插入和删除数据,这有助于保持树的平衡,从而维持高效的查询性能。
为什么不用一般二叉树?
不使用一般二叉树作为数据库索引的主要原因在于磁盘I/O操作和存储效率。让我们具体来看一下这些方面:
-
磁盘I/O操作:一般二叉树的节点只包含两个子节点的引用(在二分查找的情况下)以及数据,这意味着每次查找都可能涉及到对磁盘的多次访问。因为数据库系统通常运行在磁盘上,而不是内存中,所以减少对磁盘的访问次数是提高性能的关键。
-
存储效率:一般二叉树的节点存储了数据以及指向子节点的指针,这导致存储密度较低。相比之下,B+树的非叶子节点仅存储键值,没有实际的数据,这使得每个节点可以拥有更多的键,降低了树的高度,提高了存储效率。
-
分支因子:由于二叉树的结构限制,每个节点只有两个分支。在B+树中,每个节点可以有更多的分支,这增加了分支因子,并减少了树的高度。
-
查询性能稳定性:在二叉树中,范围查询可能会导致性能不稳定,因为需要遍历多个不连续的节点。而在B+树中,由于所有叶子节点通过指针连接成一个有序链表,范围查询的性能更为稳定。
-
维护成本:一般二叉树在插入和删除操作后可能需要重新平衡,这个过程可能相对复杂。B+树通过其设计来简化节点的分裂和合并过程,使得维护成本更低。
因此,虽然一般二叉树在理论的查找效率上可能与B+树相当,但在实际的数据库系统中,B+树提供了更好的性能,特别是在处理大量数据时,能够提供更高的数据访问效率和更低的存储成本。
为什么不是平衡二叉树?
不使用平衡二叉树作为数据库索引的原因主要在于磁盘I/O操作的优化和范围查询的效率。B+树相对于平衡二叉树有以下优势:
- 减少磁盘I/O操作:B+树的高度较低,通常在2-4层之间,这意味着在查找记录时最多只需要2-4次磁盘I/O操作。而平衡二叉树的高度通常会更高,因为它每个节点只存储一个数据项,这会导致更多的磁盘访问,从而降低了性能。
- 提高范围查询效率:B+树的叶子节点通过指针连接成一个有序链表,这使得进行范围查询时非常高效。例如,在查找大于等于某个值的所有数据时,一旦找到该值,就可以通过叶子节点的指针连续获取所有相关数据,而不需要像在平衡二叉树中那样回溯到父节点。
- 增加存储效率:B+树的非叶子节点不存储实际数据,只存储键值,这样可以在每个节点中存储更多的键,从而降低树的整体高度。平衡二叉树的每个节点存储了数据,这限制了每个节点的键数,导致树的高度增加。
- 方便数据插入和删除:B+树的结构允许在不影响其他部分的情况下插入和删除数据,这有助于保持树的平衡,而平衡二叉树在插入和删除数据时需要更多的旋转操作来维持平衡,这会增加维护成本。
综上所述,虽然平衡二叉树提供了快速的查找性能,但是B+树在数据库索引中更为常用,因为其结构更加适合磁盘I/O的特性,并且在处理范围查询时更加高效。
为什么不是 B 树?
数据库索引选择使用B+树而不是B树,主要是因为B+树在磁盘I/O操作和存储效率方面具有更明显的优势。具体分析如下:
- 磁盘I/O优化:B+树通过增加分支因子,减少树的高度,从而减少了查找数据时所需的磁盘I/O次数。由于磁盘读取是相对较慢的操作,这种优化对于性能至关重要。
- 提高查询效率:B+树的所有叶子节点都在同一层,并且通过指针相连,这为范围查询提供了便利。在B树中,范围查询可能需要多次遍历不同层的节点,效率较低。
- 减少内存开销:B+树的非叶子节点不存储实际数据,只存储键值,这样可以减少内存的使用,提高缓存的效率。
- 方便维护:B+树的叶子节点包含所有键值,而非叶子节点仅作为索引,这使得节点的分裂和合并操作更加简单,便于维护。
总的来说,虽然B树和B+树都是平衡多路查找树,但是B+树在数据库索引中的应用场景下,因其结构特点,提供了更好的性能优势。这也是为什么关系型数据库普遍采用B+树作为索引结构的原因。
总结
综上所述,B+树通过其特有的结构优势,能够提供更高效的数据访问路径,尤其是在处理大量数据时,这些优势使得B+树成为数据库索引的首选结构。
事务的隔离级别有哪些?
- 读未提交(Read Uncommitted)
- 读已提交(Read Committed)
- 可重复读(Repeatable Read)
- 串行化(Serializable)
MySQL 的默认隔离级别是什么?
Mysql 默认的事务隔离级别是可重复读(Repeatable Read)
MySQL中的Explain命令
MySQL中的Explain命令用于查看查询的执行计划。
Explain命令在MySQL中扮演着重要的角色,它能够帮助开发者理解SQL语句的执行路径和成本,从而对查询进行优化。使用Explain非常简单,只需在SQL查询语句前加上EXPLAIN关键字即可。Explain的结果会以表格形式返回,展示查询执行的细节信息。从MySQL 5.6版本开始,Explain也支持非SELECT语句的解释。
Explain输出结果中包含多个字段,每个字段代表不同的信息:
- id:标识每个SELECT子句的唯一ID。
- select_type:表示查询的类型,例如简单查询、主查询、子查询等。
- table:指出查询将访问哪张表。
- type:显示了如何查找数据,比如全表扫描、索引扫描或者范围扫描等。
- possible_keys:可能应用在这张表上的索引。
- key:实际使用的索引。如果为NULL,则没有使用索引。
- key_len:使用的索引的长度。
- ref:哪个字段或常数与key一起被使用。
- rows:预计需要读取的行数,这个数值越小,表明查询效率越高。
- filtered:过滤后剩余的行数百分比,这个值越大越好。
- extra:额外的信息,比如是否使用了临时表、是否进行了排序等。
总之,通过分析这些字段的内容,我们可以了解查询的性能瓶颈所在,并据此采取相应的优化措施,如添加或调整索引、改写查询逻辑等。
如果某个表有近千万数据,CRUD 比较慢,如何优化?
如果某个表有近千万数据,CRUD操作比较慢,可以考虑以下几种优化方法:
-
索引优化:为经常用于查询条件的字段创建索引,可以加快查询速度。但是要注意不要创建过多的索引,因为索引也会占用存储空间和影响写入性能。
-
分库分表:将数据分散到多个数据库或表中,可以提高并发处理能力和扩展性。可以根据业务需求选择合适的分库分表策略,如按照时间、地域、用户等进行分片。
-
读写分离:将读操作和写操作分别分配给不同的数据库服务器,可以提高系统的并发处理能力。可以通过主从复制或者使用专门的读写分离中间件实现。
-
缓存优化:将热点数据缓存在内存中,可以减少对数据库的访问次数,提高系统性能。可以使用分布式缓存框架如Redis来实现。
-
SQL优化:优化SQL语句,避免使用子查询、临时表等可能导致性能下降的操作。可以使用Explain命令分析SQL执行计划,找出性能瓶颈并进行优化。
-
硬件升级:增加服务器的内存、CPU等硬件资源,可以提高系统的处理能力。
-
数据库参数调优:根据服务器的硬件配置和业务需求,调整数据库的参数设置,如缓冲区大小、连接数等,以提高数据库的性能。
-
数据压缩:对存储的数据进行压缩,可以减少存储空间的占用,提高I/O效率。
-
分区表:将大表分成多个小表,可以提高查询性能。可以根据业务需求选择合适的分区键,如按照时间、地域等进行分区。
-
垂直拆分:将一个大表拆分成多个小表,每个小表只包含部分字段,可以减少查询时需要扫描的数据量,提高查询速度。
总之,针对具体的业务场景和系统状况,可以采用多种方法进行优化,以达到提高CRUD性能的目的。
Mysql 主从复制原理
MySQL主从复制是一种数据同步机制,允许数据从一个MySQL数据库服务器(主节点)复制到一个或多个其他服务器(从节点)。这种机制常用于实现数据的热备份、负载均衡和扩展。
以下是MySQL主从复制的基本原理:
- binlog(二进制日志): 在主服务器上,每当有数据变更发生时,如INSERT、UPDATE或DELETE操作,这些变更会被记录在binlog中。
- 读取binlog: 从服务器连接到主服务器,并请求主服务器发送新的binlog事件。这个过程可以通过IO线程来完成。
- relay log(中继日志): 从服务器接收到来自主服务器的binlog事件后,会将这些事件写入到自己的relay log中。
- 应用binlog: 从服务器的另一个线程,称为SQL线程,会读取relay log中的事件,并将它们依次应用到从服务器的数据库中,从而保持与主服务器的数据一致性。
此外,在实际应用中,主从复制可能会存在一定的延时,即从服务器的数据更新可能落后于主服务器。这种延时通常被称为同步延时。为了减少这种延时,可以采用半同步复制的方式,即在主服务器上等待至少一个从服务器确认接收到binlog事件后才认为该事件提交成功。
主从复制分了五个步骤进行:
步骤一:主库的更新事件(update、insert、delete)被写到 binlog
步骤二:从库发起连接,连接到主库。
步骤三:此时主库创建一个 binlog dump thread,把 binlog 的内容发送到从库。
步骤四:从库启动之后,创建一个 I/O 线程,读取主库传过来的 binlog 内容并写入到 relay log
步骤五:还会创建一个 SQL 线程,从 relay log 里面读取内容,从Exec_Master_Log_Pos 位置开始执行读取到的更新事件,将更新内容写入到slave 的 db
Hash索引和B+树索引的区别是什么?
Hash索引和B+树索引是数据库中常用的两种索引类型,它们在查询效率和数据组织等方面存在一些区别。具体分析如下:
- 查询效率:对于等值查询,Hash索引可以提供更快的查找速度,因为它通过一次散列运算就能直接定位到数据的存储位置。而B+树索引需要从根节点开始,通过逐层遍历找到叶子节点,这个过程涉及到多次磁盘I/O操作。
- 数据组织:B+树索引的所有数据都存储在叶子节点,并且叶子节点之间通过指针相连,这使得范围查询非常高效。而Hash索引由于其数据组织方式,不适合进行范围查询。
- 冲突处理:当多个不同的键值散列到同一个索引位置时,会发生冲突。Hash索引通常使用链表来解决冲突,这可能导致在最坏情况下,查询效率降低。B+树索引则没有这种冲突问题。
- 插入和删除:由于B+树的结构特性,插入和删除操作可以保持较高的效率,尤其是在保持页面填充率的情况下。而Hash索引在处理大量插入和删除导致冲突增多时,性能可能会受到影响。
总的来说,Hash索引在等值查询方面具有明显的优势,尤其是在查询效率上。然而,B+树索引在范围查询和有序性方面表现更好,且更适合处理大量数据的插入和删除。在实际应用中,选择哪种索引取决于具体的应用场景和需求。
count(1)、count(*) 与 count(列名) 的区别?
在SQL中,COUNT(1)
、COUNT(*)
和COUNT(列名)
都是用来统计记录数量的函数,但它们之间存在一些细微的区别:
COUNT(1)
:这个函数会统计表中的所有记录数,包括那些所有列都为NULL的记录。换句话说,只要数据库中有这条记录,无论记录的内容如何,COUNT(1)
都会将其计入总数。COUNT(*)
:这个函数与COUNT(1)
的行为相同,也会统计表中的所有记录数,包括那些所有列都为NULL的记录。在大多数数据库系统中,COUNT(*)
和COUNT(1)
的效率是相同的。COUNT(列名)
:这个函数会统计指定列中非NULL值的数量。如果某条记录的指定列值为NULL,则这条记录不会被计入总数。这在统计某一特定列的有效数据时非常有用。
总的来说,COUNT(1)
和COUNT(*)
在功能上是等价的,它们都会计算表中的所有记录,而COUNT(列名)
则会忽略掉指定列中值为NULL的记录。在实际使用时,选择哪种方式取决于你想要统计的内容。如果你想统计所有记录,可以使用COUNT(1)
或COUNT(*)
;如果你想统计特定列的非NULL值的数量,那么应该使用COUNT(列名)
。
mysql 中 int(20)和 char(20)以及 varchar(20)的区别?
在MySQL中,INT(20)
、CHAR(20)
和VARCHAR(20)
是三种不同的数据类型,它们用于存储不同类型的数据,并且在存储方式和空间占用上也有所不同。
-
INT(20):
- 数据类型:整数(Integer)。
- 存储范围:通常为-2147483648到2147483647(取决于具体的数据库系统)。
- 显示长度:括号中的数字表示显示宽度,即在结果集中显示的字符数,但这并不影响实际存储的空间大小或值的范围。
- 空间占用:通常占用4个字节的存储空间。
-
CHAR(20):
- 数据类型:定长字符串(Character)。
- 存储范围:最多可以存储20个字符。
- 显示长度:括号中的数字表示字段可以存储的最大字符数。
- 空间占用:无论实际存储的数据长度如何,都会占用20个字符的存储空间。如果实际数据长度不足20个字符,剩余的空间会用空格填充。
-
VARCHAR(20):
- 数据类型:变长字符串(Variable Character)。
- 存储范围:最多可以存储20个字符。
- 显示长度:括号中的数字表示字段可以存储的最大字符数。
- 空间占用:根据实际存储的数据长度动态分配存储空间,最大不超过20个字符。不会像CHAR那样浪费空间。
需要注意的是,这些数据类型的具体实现可能会因不同的数据库管理系统而有所差异。在实际使用时,应根据数据的性质和应用场景选择合适的数据类型。
消息队列
什么是消息队列
你可以把消息队列理解为一个使用队列来通信的组件。它的本质,就是个转发
器,包含发消息、存消息、消费消息的过程。最简单的消息队列模型如下:
我们通常说的消息队列,简称 MQ(Message Queue),它其实就指消息
中间件,当前业界比较流行的开源消息中间件包括:
RabbitMQ、RocketMQ、Kafka。
消息队列有哪些使用场景?/为什么使用消息队列?
- 应用解耦
- 流量削峰
- 异步处理
- 消息通讯
- 远程调用
消息队列如何解决消息丢失问题?
一个消息从生产者产生,到被消费者消费,主要经过这 3 个过程:
因此如何保证 MQ 不丢失消息,可以从这三个阶段阐述:
- 生产者保证不丢消息
- 存储端不丢消息
- 消费者不丢消息
生产者保证不丢消息
生产端如何保证不丢消息呢?确保生产的消息能到达存储端。
如果是 RocketMQ 消息中间件,Producer 生产者提供了三种发送消息的方式,分别是:
- 同步发送
- 异步发送
- 单向发送
生产者要想发消息时保证消息不丢失,可以:
- 采用同步方式发送,send 消息方法返回成功状态,就表示消息正常到达了存储端Broker。
- 如果 send 消息异常或者返回非成功状态,可以重试。
- 可以使用事务消息,RocketMQ 的事务消息机制就是为了保证零丢失来设计的
存储端不丢消息
如何保证存储端的消息不丢失呢? 确保消息持久化到磁盘。大家很容易想到就
是刷盘机制。
刷盘机制分同步刷盘和异步刷盘:
- 生产者消息发过来时,只有持久化到磁盘,RocketMQ 的存储端 Broker 才返回一
个成功的 ACK 响应,这就是同步刷盘。它保证消息不丢失,但是影响了性能。 - 异步刷盘的话,只要消息写入 PageCache 缓存,就返回一个成功的 ACK 响应。
这样提高了 MQ 的性能,但是如果这时候机器断电了,就会丢失消息。
Broker 一般是集群部署的,有 master 主节点和 slave 从节点。消息到Broker 存储端,只有主节点和从节点都写入成功,才反馈成功的 ack 给生产者。这就是同步复制,它保证了消息不丢失,但是降低了系统的吞吐量。与之对应的就是异步复制,只要消息写入主节点成功,就返回成功的 ack,它速度快,但是会有性能问题。
消费阶段不丢消息
消费者执行完业务逻辑,再反馈会 Broker 说消费成功,这样才可以保证消费
阶段不丢消息。
消息队列有可能发生重复消费,如何避免,如何做到幂等?
消息队列是可能发生重复消费的。
- 生产端为了保证消息的可靠性,它可能往 MQ 服务器重复发送消息,直到拿到成功
的 ACK。 - 再然后就是消费端,消费端消费消息一般是这个流程:拉取消息、业务逻辑处理、
提交消费位移。假设业务逻辑处理完,事务提交了,但是需要更新消费位移时,消
费者挂了,这时候另一个消费者就会拉到重复消息了。
如何幂等处理重复消息呢?
幂等处理重复消息,简单来说,就是搞个本地表,带唯一业务标记的,利用主
键或者唯一性索引,每次处理业务,先校验一下就好啦。又或者用 redis 缓存
下业务标记,每次看下是否处理过了。
如何处理消息队列的消息积压问题?
消息积压是因为生产者的生产速度,大于消费者的消费速度。遇到消息积压问
题时,我们需要先排查,是不是有 bug 产生了。
如果不是 bug,我们可以优化一下消费的逻辑,比如之前是一条一条消息消费
处理的话,我们可以确认是不是可以优为批量处理消息。
如果还是慢,我们可
以考虑水平扩容,增加 Topic 的队列数,和消费组机器的数量,提升整体消费
能力
如果是 bug 导致几百万消息持续积压几小时。有如何处理呢? 需要解决
bug,临时紧急扩容,大概思路如下:
- 先修复 consumer 消费者的问题,以确保其恢复消费速度,然后将现有consumer 都停掉。
- 新建一个 topic,partition 是原来的 10 倍,临时建立好原先 10 倍的 queue数量。
- 然后写一个临时的分发数据的 consumer 程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的 10 倍数量的 queue。
- 接着临时征用 10 倍的机器来部署 consumer,每一批 consumer 消费一个临时 queue 的数据。这种做法相当于是临时将 queue 资源和 consumer 资源扩大 10 倍,以正常的 10 倍速度来消费数据。
- 等快速消费完积压数据之后,得恢复原先部署的架构,重新用原先的consumer 机器来消费消息。
消息队列技术选型,Kafka 还是 RocketMQ,还是RabbitMQ?
先可以对比下它们优缺点:
- RabbitMQ 是开源的,比较稳定的支持,活跃度也高,但是不是 Java 语言开发的。
- 很多公司用 RocketMQ,是阿里出品的。
- 如果是大数据领域的实时计算、日志采集等场景,用 Kafka 是业内标准的。
如何保证数据一致性,事务消息如何实现?
一条普通的 MQ 消息,从产生到被消费,大概流程如下:
- 生产者产生消息,发送带 MQ 服务器
- MQ 收到消息后,将消息持久化到存储系统。
- MQ 服务器返回 ACK到生产者。
- MQ 服务器把消息 push 给消费者
- 消费者消费完消息,响应 ACK
- MQ 服务器收到 ACK,认为消息消费成功,即在存储中删除消息。
我们举个下订单的例子吧。订单系统创建完订单后,再发送消息给下游系统。如果订单创建成功,然后消息没有成功发送出去,下游系统就无法感知这个事情,出导致数据不一致。
如何保证数据一致性呢?可以使用事务消息。一起来看下事务消息是如何实现的吧。
- 生产者产生消息,发送一条半事务消息到 MQ 服务器
- MQ 收到消息后,将消息持久化到存储系统,这条消息的状态是待发送状态。
- MQ 服务器返回 ACK 确认到生产者,此时 MQ 不会触发消息推送事件
- 生产者执行本地事务
- 如果本地事务执行成功,即 commit 执行结果到 MQ 服务器;如果执行失败,发
送 rollback。 - 如果是正常的 commit,MQ 服务器更新消息状态为可发送;如果是 rollback,即
删除消息。 - 如果消息状态更新为可发送,则 MQ 服务器会 push 消息给消费者。消费者消费完
就回 ACK。 - 如果 MQ 服务器长时间没有收到生产者的 commit 或者 rollback,它会反查生产
者,然后根据查询到的结果执行最终状态。
网络
对称加密与非对称加密有什么区别?
对称加密:指加密和解密使用同一密钥,优点是运算速度较快,缺点是如何安
全将密钥传输给另一方。常见的对称加密算法有:DES、AES 等。
非对称加密:指的是加密和解密使用不同的密钥(即公钥和私钥)。公钥与私
钥是成对存在的,如果用公钥对数据进行加密,只有对应的私钥才能解密。常
见的非对称加密算法有 RSA。
DNS 的解析过程?
DNS,英文全称是 domain name system,域名解析系统,是 Internet上作为域名和 IP 相互映射的一个分布式数据库。它的作用很明确,就是可以根据域名查出对应的 IP 地址。在浏览器缓存、本地 DNS 服务器、根域名服务器都是怎么查找的,大家回答的时候都可以说下哈。
DNS 的解析过程如下图:
forward 和 redirect 的区别?
- 直接转发方式(Forward) ,客户端和浏览器只发出一次请求,
Servlet、HTML、JSP 或其它信息资源,由第二个信息资源响应该请求,在请
求对象 request 中,保存的对象对于每个信息资源是共享的。 - 间接转发方式(Redirect) 实际是两次 HTTP 请求,服务器端在响应第一次
请求的时候,让浏览器再向另外一个 URL 发出请求,从而达到转发的目的。
Redirect 的工作原理:
forward 的工作原理
聊聊 SQL 注入?
SQL 注入是一种代码注入技术,一般被应用于攻击 web 应用程序。它通过在web 应用接口传入一些特殊参数字符,来欺骗应用服务器,执行恶意的 SQL命令,以达到非法获取系统信息的目的。它目前是黑客对数据库进行攻击的最常用手段之一
请详细介绍一下 TCP 的三次握手机制?
- 第一次握手(SYN=1, seq=x),发送完毕后,客户端就进入 SYN_SEND 状态
- 第二次握手(SYN=1, ACK=1, seq=y, ACKnum=x+1), 发送完毕后,服务器
端就进入 SYN_RCV 状态。 - 第三次握手(ACK=1,ACKnum=y+1),发送完毕后,客户端进入
ESTABLISHED 状态,当服务器端接收到这个包时,也进入 ESTABLISHED 状态。
TCP 握手为什么是三次,为什么不能是两次?不能是四次
思路: TCP 握手为什么不能是两次,为什么不能是四次呢?为了方便理解,我
们以男孩子和女孩子谈恋爱为例子:两个人能走到一起,最重要的事情就是相
爱,就是我爱你,并且我知道,你也爱我,接下来我们以此来模拟三次握手的
过程:
为什么握手不能是两次呢?
如果只有两次握手,女孩子可能就不知道,她的那句我也爱你,男孩子是否收
到,恋爱关系就不能愉快展开。
为什么握手不能是四次呢?
因为握手不能是四次呢?因为三次已经够了,三次已经能让双方都知道:你爱
我,我也爱你。而四次就多余了。
TCP 四次挥手过程?
TCP 四次挥手过程
- 第一次挥手(FIN=1,seq=u),发送完毕后,客户端进入 FIN_WAIT_1 状态。
- 第二次挥手(ACK=1,ack=u+1,seq =v),发送完毕后,服务器端进入CLOSE_WAIT 状态,客户端接收到这个确认包之后,进入 FIN_WAIT_2 状态。
- 第三次挥手(FIN=1,ACK1,seq=w,ack=u+1),发送完毕后,服务器端进入LAST_ACK 状态,等待来自客户端的最后一个 ACK。
- 第四次挥手(ACK=1,seq=u+1,ack=w+1),客户端接收到来自服务器端的关闭请求,发送一个确认包,并进入 TIME_WAIT 状态,等待了某个固定时间(两个最大段生命周期,2MSL,2 Maximum Segment Lifetime)之后,没有收到服务器端的 ACK ,认为服务器端已经正常关闭连接,于是自己也关闭连接,进入 CLOSED 状态。服务器端接收到这个确认包之后,关闭连接,进入 CLOSED 状态。
TCP 挥手为什么需要四次呢?
思路: TCP 挥手为什么需要四次呢?为了方便大家理解,再举个生活的例子吧。
小明和小红打电话聊天,通话差不多要结束时,小红说,“我没啥要说的了”。小明回答,“我知道了”。但是小明可能还有要说的话,小红不能要求小明跟着她自己的节奏结束通话,于是小明可能又叽叽歪歪说了一通,最后小明说,“我说完了”,小红回答,“我知道了”,这样通话才算结束。
TCP 四次挥手过程中,为什么需要等待 2MSL,才进入CLOSED 关闭状态
2MSL,two Maximum Segment Lifetime,即两个最大段生命周期。
假设主动发起挥手的是客户端,那么需要 2MSL 的原因是:
- 为了保证客户端发送的最后一个 ACK 报文段能够到达服务端。 这个 ACK 报
文段有可能丢失,因而使处在 LAST-ACK 状态的服务端就收不到对已发送的
FIN + ACK 报文段的确认。服务端会超时重传这个 FIN+ACK 报文段,而客
户端就能在 2MSL 时间内(超时 + 1MSL 传输)收到这个重传的 FIN+ACK
报文段。接着客户端重传一次确认,重新启动 2MSL 计时器。最后,客户端和
服务器都正常进入到 CLOSED 状态。 - 防止已失效的连接请求报文段出现在本连接中。客户端在发送完最后一个
ACK 报文段后,再经过时间 2MSL,就可以使本连接持续的时间内所产生的所
有报文段都从网络中消失。这样就可以使下一个连接中不会出现这种旧的连接
请求报文段。
说说 TCP 是如何确保可靠性的呢?
- 首先,TCP 的连接是基于三次握手,而断开则是基于四次挥手。确保连接和断开的
可靠性。 - 其次,TCP 的可靠性,还体现在有状态;TCP 会记录哪些数据发送了,哪些数据被接收了,哪些没有被接受,并且保证数据包按序到达,保证数据传输不出差错。
- 再次,TCP 的可靠性,还体现在可控制。它有数据包校验、ACK 应答、超时重传(发送方)、失序数据重传(接收方)、丢弃重复数据、流量控制(滑动窗口)和拥塞控制等机制。
请简述 TCP 和 UDP 的区别
- TCP 面向连接(如打电话需要先拨号),UDP 面向无连接(即发送数据前不需要建立连接)。
- TCP 提供可靠的服务,UDP 无法保证。
- TCP 面向字节流,而 UDP 面向报文。
- TCP 数据传输慢,UDP 数据传输快
- TCP 是点对点连接的,UDP 可以一对一,一对多,多对多都可以。
- TCP 适用于邮件、网页等,UDP 适用于语音广播等。
Java
String,Stringbuffer,StringBuilder 的区别
- String:
- String 类是一个不可变的类,一旦创建就不可以修改。
- String 是 final 类,不能被继承
- String 实现了 equals()方法和 hashCode()方法
- StringBuffer:
- 继承自 AbstractStringBuilder,是可变类。
- StringBuffer 是线程安全的
- 可以通过 append 方法动态构造数据。
- StringBuilder:
- 继承自 AbstractStringBuilder,是可变类。
- StringBuilder 是非线性安全的。
- 执行效率比 StringBuffer 高。
Java中的几种基本数据类型是什么,各自占用多少字节呢
- byte:占用1个字节(8位),取值范围从-128到127。
- short:占用2个字节(16位),取值范围从-32768到32767。
- int:占用4个字节(32位),取值范围从-2147483648到2147483647。
- long:占用8个字节(64位),取值范围从-9223372036854775808到9223372036854775807。
- float:占用4个字节(32位),取值范围从-3.4e+38到3.4e+38。
- double:占用8个字节(64位),取值范围从-1.7e+308到1.7e+308。
- char:占用2个字节(16位),用于表示Unicode字符,取值范围从’\u0000’到’\uffff’。
- boolean:虽然理论上占用1位(1/8字节),在实际应用中通常按1个字节处理
JDK 9版本前后,双亲委派模型的变化
在JDK 9版本前后,双亲委派模型的变化主要体现在类加载器的结构调整和模块化系统的引入。具体如下:
- 类加载器结构调整:在JDK 9之前,Java的类加载器通常分为三种:Bootstrap ClassLoader、Extension ClassLoader和Application ClassLoader。其中,Bootstrap ClassLoader负责加载核心的Java类库,Extension ClassLoader负责加载扩展类库,而Application ClassLoader则负责加载应用程序级别的类。在JDK 9之后,Extension ClassLoader被移除,取而代之的是Platform ClassLoader和Application ClassLoader。
- 模块化系统的引入:JDK 9引入了Java模块化系统(Java Platform Module System),这是为了实现更好的封装隔离机制。模块化系统的引入也意味着JVM对类加载架构进行了调整,这在一定程度上改变了双亲委派模型的工作方式。
- 安全性考虑:双亲委派模型的主要目的是为了保证Java官方类库的安全性,防止被开发者覆盖。在JDK 9之前,<JAVA_HOME>lib和<JAVA_HOME>lib\ext中的类库不会被破坏,而在JDK 9之后,由于模块化系统的引入,这种保护机制有所改变,但仍然保持了对核心类库的保护。
- 破坏双亲委派的方式:虽然双亲委派模型旨在保证安全性,但在某些情况下,如SPI机制和OSGi热替换机制,双亲委派模型会被破坏。JDK 9之后的模块化系统也可以看作是对双亲委派模型的一种破坏,因为它允许更多的灵活性和可配置性。
总结来说,JDK 9的发布对双亲委派模型带来了显著的变化,这些变化旨在提高系统的模块化和可维护性,同时也考虑到了安全性和兼容性的需求。
可以打破双亲委派模型吗
可以打破双亲委派模型。
双亲委派模型是Java类加载器的一个核心概念,它确保了类加载的层次性和安全性。但在某些情况下,开发者可能会出于特定需求打破这一模型。以下是一些打破双亲委派模型的常见做法:
- 自定义类加载器:通过重写ClassLoader类的findClass()方法,可以实现对无法被父类加载器加载的类进行加载。如果需要直接打破双亲委派模型,可以重写loadClass()方法,但这通常涉及到更复杂的实现和潜在的风险。
- SPI机制:Service Provider Interface(SPI)机制允许第三方提供的JAR文件中的类可以被加载,即使它们位于CLASSPATH中。这是通过线程上下文类加载器来实现的,它可以在运行时决定使用哪个类加载器来加载类,从而绕过双亲委派模型的限制。
- 模块化系统:Java 9引入的模块化系统允许更加灵活的类可见性和加载策略,这在一定程度上也改变了双亲委派模型的工作方式。模块系统可以指定模块间的依赖关系和访问权限,从而实现对类加载流程的更精细控制。
- OSGi平台:OSGi服务平台是一个支持模块化的Java框架,它允许在运行时动态加载、更新和卸载模块。这种动态性要求能够打破双亲委派模型,以便在不同模块间隔离类加载过程。
- 应用服务器:许多应用服务器(如Tomcat)为了实现热部署和隔离不同的应用程序,也会打破双亲委派模型。它们通常会使用自定义的类加载器来加载应用程序的类,而不是依赖于系统的类加载器。
需要注意的是,打破双亲委派模型可能会导致一些问题,例如安全问题和类版本冲突等。因此,在决定打破双亲委派模型时,应当仔细评估潜在的风险和收益。
Tomcat为什么打破双亲委派模型
Tomcat打破双亲委派模型的原因主要是为了实现不同Web应用程序之间的隔离性。具体原因如下:
- 应用隔离性:Tomcat作为一个Web服务器,通常会部署多个Web应用程序。这些应用程序可能会包含相同类名的类或引用不同版本的同一个JAR包。如果遵循双亲委派模型,一个类只能被加载一次,这会导致潜在的冲突和版本控制问题。为了确保每个Web应用程序能够在自己的类加载器环境中独立运行,避免类版本冲突和类名冲突,Tomcat需要打破双亲委派模型。
- 热部署:Tomcat支持热部署,即在不重启服务器的情况下部署或更新Web应用程序。为了实现这一点,每个Web应用程序必须能够在不同的类加载器中独立加载和卸载,这也要求打破双亲委派模型。
此外,Tomcat通过使用自定义的WebAppClassLoader来实现这一机制。WebAppClassLoader会先于父类加载器尝试加载类,这样就能保证Web应用程序的类加载优先级高于系统类加载器,从而实现了应用程序间的隔离。
综上所述,Tomcat打破双亲委派模型是为了提供更好的Web应用程序隔离性和灵活性,这对于运行多版本的JAR包和实现热部署等功能至关重要。