每日一问记录

【每日一问:20260320】问:MySQL选择InnoDB作为引擎,它有什么优势?

答案:

MySQL 默认的存储引擎是 InnoDB,这是因为 InnoDB 在性能、事务支持和容错能力等方面具有较好的特性,适合大多数应用场景。下面是一些原因:
●支持事务:InnoDB 是一个支持事务的存储引擎。事务是一组数据库操作的原子性执行,可以保证操作的一致性和完整性。
●并发控制:InnoDB 支持行级锁定, 在高并发环境下可以最大程度地减少锁冲突,提高并发性能。相比之下,MySQL 的另一个存储引擎 MyISAM 只支持表级锁定,并发性能较低。
●外键约束:InnoDB 支持外键约束,可以保证数据的完整性。外键用于建立表与表之间的连接,通过外键约束可以实现数据之间的关联和参照完整性。
●崩溃恢复:InnoDB 具有自动崩溃恢复的能力。即使在发生意外故障或系统崩溃时,InnoDB 引擎也能够自动进行崩溃恢复,保障数据的一致性。
●支持热备份:InnoDB 支持在线热备份,可以在不停止数据库服务的情况下进行备份操作。这对于需要实时运行且对数据可用性要求高的应用程序非常重要。
●需要注意的是,虽然 InnoDB 是 MySQL 默认的存储引擎,但在某些场景下,可以根据实际需求选择其他存储引擎,如 MyISAM、Memory 等。不同的存储引擎适用于不同的应用场景和需求。

 

 

【每日一问:20260322】问:小明公司的数据库用的MySQL 引擎是Innodb。小明创建了一张表,忘记给这张表添加主键,请问这边表有没有聚簇索引?如果有的话聚簇索引是什么样的?
聚簇索引创建的原则:
●主键存在:如果表中定义了主键,主键即为聚簇索引。
●没有主键时:如果没有定义主键,InnoDB 会选择第一个唯一且非空的索引作为聚簇索引。
●既没有主键也没有唯一索引时:如果既没有主键也没有合适的唯一索引,InnoDB 会自动创建一个隐藏的6字节的行 ID (ROWID) 用作聚簇索引。这个是内部管理的,对用户不可见。

官网参考:https://dev.mysql.com/doc/refman/8.0/en/innodb-index-types.html
相关源码: https://github.com/mysql/mysql-server/blob/8.0/storage/innobase/handler/ha_innodb.cc  @所有人

 

【每日一问:20260201】SQL场景实战问题
https://docs.qq.com/doc/DVUZGZlhUQklMbURB

问题: 请问事务1 的第二次查询结果是什么?为什么?

答案: 4条数据。 事务2新增数据提交之后,事务1更新的当前读是可以操作成功,根据可见性规则:当前事务更新的数据对当前事务是可见的。所以事务1的第2个查询是可以看到id=20这条数据,再加上快照里面的3条数据,一共就是4条数据。
 @所有人

 

【每日一问:20260325】  想清理一下这张订单表(mysql数据库)历史数据,请问这三种

truncate、delete、drop方式哪种更好些?有什么区别吗?
truncate、delete、drop方式 在面试中问得很多,也是工作经常使用的SQL语句,他们的特性如下:
一、DELETE语句
特点:
1. 可以根据条件删除部分数据
2. 支持事务,可以回滚
3. 删除数据时会记录日志
4. 删除速度较慢
5. 不会释放表空间
6. 会一行一行地删除
适用场景:
1. 需要删除部分数据
2. 需要保留删除记录
3. 需要事务支持
4. 表数据量不大
二、TRUNCATE语句
特点:
1. 删除速度快
2. 会释放表空间
3. 表的自增ID重置为1
4. 不能根据条件删除
5. 不支持事务回滚
6. 不记录日志
适用场景:
1. 需要删除全表数据
2. 需要重置自增ID
3. 对删除速度有要求
4. 不需要保留删除记录
三、DROP语句
特点:
1. 执行速度最快
2. 完全释放表空间
3. 删除表结构和索引
4. 删除整个表定义
5. 不支持事务回滚
6. 需要重建表结构
适用场景:
1. 需要删除整个表
2. 表结构需要重建
3. 不需要保留任何信息
【实用建议】
1. 如果只是想清理部分历史数据:
使用DELETE,可以指定条件
示例:DELETE FROM orders WHERE create_time < '2024-01-01';
2. 如果想清空整个表但保留表结构:
使用TRUNCATE,速度快且释放空间
示例:TRUNCATE TABLE orders;
3. 如果要完全废弃这个表:
使用DROP,删除整个表
示例:DROP TABLE orders;
【安全建议】
1. 执行删除操作前先备份数据
示例:CREATE TABLE orders_backup AS SELECT * FROM orders;
2. 对于大表删除,建议分批执行
示例:DELETE FROM orders WHERE create_time < '2024-01-01' LIMIT 10000;
3. 在业务低峰期执行删除操作
4. 删除前先确认是否有外键关联
【性能对比】
1. 执行速度:DROP > TRUNCATE > DELETE
2. 空间释放:DROP = TRUNCATE > DELETE
3. 安全性:DELETE > TRUNCATE > DROP
4. 灵活性:DELETE > TRUNCATE > DROP
【总结】
选择合适的删除方式主要取决于:
1. 是否需要保留表结构
2. 是否需要部分删除
3. 是否需要事务支持
4. 是否需要删除日志
5. 表的大小和业务影响
选择合适的删除方式可以大大提高效率,同时避免不必要的风险。在实际操作中,建议先在测试环境验证,确保操作安全无误后再在生产环境执行。 @所有人

 

 

【每日一问:20260327】订单表3年积累了5000万条数据,需要删除1年前的历史订单(约3000万条),要求不影响线上业务,请说下你的方案?
先不关注订单在业务上是否允许,生产上也有类似场景,比如一些日志表。重点关注方案设计。
先分析这个场景:
●数据量大(3000万条)
●不能影响线上业务(不能长时间锁表)
●需要保证数据安全
一般有以下几个方案,大家可以对比下:
方案1:直接DELETE(这个是不推荐的),主要的问题会有:
●会产生巨大的锁,阻塞其他业务操作
●回滚日志(undo log)巨大,可能导致磁盘空间不足
●主从复制延迟严重
●执行时间可能数小时,期间数据库压力极大
方案2:分批删除,可以按照主键分批删除,通过批处理或者存储过程实现。这种方案的特点: 每次只锁少量数据
可以在业务低峰期分多批次多天执行,每个批次大小:5000-10000条(这个要根据自己项目的服务器性能调整)
出问题可以随时停止
对主从复制影响小

方案3:先做归档后删除,这种方案数据可追溯,万一需要还能找回更安全,不用担心误删。但是需要需要额外存储空间,耗时也可能更长。

方案4:按时间分区表。这个需要提前做好设计,如果经常需要清理历史数据,建议改造成分区表。我们可以按照分区删除历史数据,性能快,不产生锁,也不影响其他分区查询。

所以推荐相对安全和完善的方案: 提前做好数据备份+分批删除+业务低峰期执行+做好数据监控以及验证,有条件改造成分区表最好,另外记得做完这类大范围删除操作,要OPTIMIZE TABLE 回收空间,减少内存碎片。

 

 

 

 

【每日一问:20260330】面试官问他mysql查询的时候使用where 1=1会不会影响性能?你的答案是什么呢?说说你的看法。

很多小伙伴会在where后面跟上1=1的保证语,经常看网上的八股文说1=1会影响性能, 建议用Mybatis的<where>标签。两种方案,该如何选择?
●如果 MySQL Server版本小于 5.7,用了 MyBatis的话,建议使用<where> 标签。

●如果 MySQL版本大于等于 5.7,两个随便选;因为在MySQL5.7后,有一个所谓的(常量折叠优化)可以在编译期消除重言式表达式。什么是重言式表达式,就是任何时候永远都为true的结果, 就会被优化器识别并优化掉,好奇的话你可以通过show warnings;查看,就会发现1=1没有了。并且我也在一张100多万的表里面把1=1 和<where>标签分别做了100次查询, 耗时时间相差无几。 所以5.7后两种方式随便选。
当然现在 MySQL Server版本基本都是 5.7以上了,不是的话那赶紧升级吧。
 @所有人

 

 

【每日一问:20260402】学员的一个面试问题:一张表有500万数据,100多个字段,请问如何快速把数据查出来?
在处理大数据量和多字段的表时,优化查询性能是至关重要的。以下是一些策略,可以帮助你快速查询数据:

1. 选择必要的字段:
● 只选择你需要的字段,而不是使用`SELECT *`。这可以减少数据传输量和内存使用。

2. 使用索引:
● 确保查询条件中的列(如`WHERE`子句中的列)上有适当的索引。
● 使用覆盖索引(即索引包含所有查询的字段)可以避免回表查询。

3. 分页查询:
● 如果需要处理大量数据,考虑使用分页查询(如`LIMIT`和`OFFSET`)来分批获取数据。
● 注意:对于大偏移量的分页,`OFFSET`可能会导致性能问题,可以考虑使用“延续键”分页。

4. 优化查询条件:
● 使用高效的查询条件,避免在`WHERE`子句中使用函数或计算。
● 尽量使用等值查询而不是范围查询。

5. 数据库配置优化:
● 调整数据库配置参数,如`innodb_buffer_pool_size`,以便更好地利用内存。
● 确保数据库服务器有足够的内存和CPU资源。

6. 分区表:
● 对于非常大的表,可以考虑使用表分区,将数据按某个字段(如日期)分割成多个物理部分。

7. 缓存机制:
● 使用缓存机制(如Redis)来存储常用查询的结果,减少数据库的负载。

8. 分析执行计划:
● 使用`EXPLAIN`命令分析查询的执行计划,找出潜在的性能瓶颈。

9. 批量处理:
● 如果需要对大量数据进行批量处理,考虑使用批量操作而不是逐行处理。

通过结合这些策略,可以显著提高查询性能,尤其是在处理大数据量和多字段的复杂查询时。
 @所有人

 

 

【每日一问:20260403】假如今天不小心把数据库删了,领导让你把数据恢复出来,请问应该怎么做?
如果因为某些原因误删误删数据或者数据库,需要面对如何快速恢复问题。关于这个问题的解决总结如下:
一、恢复数据的方法:
(1). 误删数据表或数据库:
使用 `DROP TABLE`、`TRUNCATE TABLE` 或 `DROP DATABASE` 误删数据时,binlog 记录的是 statement 格式,可能不能通过 binlog 恢复。
恢复方法:依赖全量备份和增量日志。这个需要定期的全量备份和实时备份的 binlog。
(2) 误删数据行:
使用 `DELETE` 语句误删数据行,可以通过 Flashback或者美团闪回工具Myflash进行恢复数据。
美团Myflash详细介绍:

二、提升数据恢复效率:
MySQL 5.6 之后引入了延迟复制的备库,防止错误操作快速蔓延到从库,导致主从数据都被删除。具体方法: 通过命令 CHANGE MASTER TO MASTER_DELAY = N ,可以指定这个备库持续保持跟主库有 N 秒的延迟。这样可以缩短了整个数据恢复需要的时间,提高恢复数据的效率。

三、预防数据误删的方法
(1)) 规范数据库脚本审计以及脚本发布上线的流程,出现问题能够及时撤销和回退
(2) 数据库账号权限的管理,特别是Drop/Truncate权限必须严格管理
(3) 数据库配置层面也需要做好约束规范,sql_safe_updates 参数设置为 on。 delete 或者 update 语句中没有 where 条件,或者 where 条件里面没有包含索引字段的话,这条语句的执行就会报错
(4)为了防止集群集体删除的极端情况,我们做数据库备份的时候有条件尽量选择跨机房或者跨城市都可以,提高数据库可用性,降低数据丢失的损失。
 @所有人

 

【每日一问:20260405】 开放性讨论题:你知道MySQL常用的查询优化分析方法有哪些?说下你的看法和理解
(1)EXPLAIN分析执行计划,如果要看得更加详细清晰可以使用EXPLAIN FORMAT=JSON
或者EXPLAIN FORMAT=TREE,EXPLAIN ANALYZE,注意后面两个是mysql 8之后的新增功能。
(2) Optimizer Trace
MySQL Optimizer Trace功能会跟踪MySQL优化器对查询优化过程的关键信息,比如扫描的行数,成本,以及为什么选择哪个执行计划,都是有明确的指示。
使用方法:

-- 开启optimizer trace
set optimizer_trace='enabled=on';
-- query;
执行你的SQL;
-- 查看optimizer trace记录
select * from information_schema.optimizer_trace;
(3)Profiling。最新的 MySQL 版本是默认开启 Show Profile 功能的。Show Profiles 只显示最近发给服务器的 SQL 语句,默认情况下是记录最近已执行的 15 条记录,我们可以重新设置 profiling_history_size 增大该存储记录,最大值为 100。获取到 Query_ID 之后,我们再通过 Show Profile for Query ID 语句,就能够查看到对应 Query_ID 的 SQL 语句在执行过程中线程的每个状态所消耗的时间。

除了以上工具以外,我们还需要去分析mysql慢查询日志,还有服务器状态指标 比如CPU ,连接数,有没有长事务,page指标等等。我们需要综合各个技术手段和查询方法找到问题的SQL。
 @所有人

 

 

 

【每日一问:20260408】 redis集群有几种模式?分别讲讲这些集群模式的基本原理是什么?
1. 主从复制模式(Replication)
基本原理
● 主从架构:由一个主节点(Master)和多个从节点(Slave)组成,主节点负责写操作,从节点通过异步复制同步主节点的数据。
● 数据同步:
1. 从节点启动后向主节点发送 `SYNC` 命令。
2. 主节点生成当前数据的快照(RDB 文件),发送给从节点。
3. 从节点加载 RDB 文件后,主节点继续将后续的写命令发送给从节点,保持数据一致性。
1. 读写分离:读请求可以分散到从节点,提升读性能;写请求仍由主节点处理。
优点
● 高可用:主节点宕机后,可以手动提升从节点为主节点。
1. 负载均衡:通过读写分离提升读吞吐量。
缺点
● 单点写入:主节点是写操作的唯一入口,可能成为性能瓶颈。
1. 数据延迟:异步复制可能导致从节点数据短暂不一致。
2. 分片集群模式(Cluster)
基本原理
● 数据分片:将数据划分为 16384 个哈希槽(Hash Slot),每个节点负责一部分槽。
● 分布式架构:
1. 客户端请求的键通过 CRC16 算法计算哈希值,再对 16384 取模,确定所属的槽。
2. 节点间通过 Gossip 协议通信,维护集群状态(如槽分配、节点故障检测)。
3. 如果客户端访问的键不在当前节点,节点会返回 `MOVED` 重定向错误,引导客户端访问正确的节点。
1. 高可用:每个分片可以配置主从复制(一主多从),主节点故障时从节点自动晋升。
优点
● 水平扩展:通过增加节点提升集群容量和性能。
1. 自动故障转移:主节点宕机时,从节点自动接管。
缺点
● 复杂度高:需要管理分片、槽分配和节点通信。
1. 事务限制:跨节点的多键操作(如事务、Lua 脚本)可能受限。
3. 哨兵模式(Sentinel)
● 定位:严格来说是主从复制的增强版,用于自动化故障转移。
● 原理:
1. 哨兵节点监控主从节点的健康状态。
2. 主节点故障时,哨兵通过投票机制选举新的主节点。
3. 客户端通过哨兵获取最新的主节点地址。
根据业务需求(如数据量、性能、复杂度)选择合适的集群模式。 @所有人

 

 

【每日一问:20260412】什么是Redis的大Key和热Key?你们的项目一般是怎么解决的?
(1)首先我们要搞清楚大key和热key是什么。
●大Key: 通常以Key的大小和Key中成员的数量来综合判定。比如Key本身的Value过大,一个String类型的Key,它的值为10 MB;Key中的成员数过多:一个ZSET类型的Key,它的成员数量为10000个。
●热key:通常以其接收到的Key被请求频率来判定,例如:QPS集中在特定的Key:Redis实例的总QPS为10000,而其中一个Key的每秒访问量达到了8000。
(2)
大Key一般产生的问题就是占用大量的带宽以及资源资源,导致系统出现OOM,访问阻塞等问题。
热Key占用大量的CPU资源,影响其他请求并导致整体性能降低。
(3)如何找到大Key和热Key呢?
通过redis-cli的bigkeys和hotkeys参数查找大Key和热Key,当然如果有第三方监控平台也是可以的,比如。
(4)解决办法
针对大key的问题:
● 我们可以对大Key进行拆分,例如将含有数万成员的一个HASH Key拆分为多个HASH Key,并确保每个Key的成员数量在合理范围。在Redis集群架构中,拆分大Key能对数据分片间的内存平衡起到显著作用。
●定期进行清理掉无效的key,腾出更多的内存空间。
针对热Key的问题:
● 在Redis集群架构中对热Key进行复制,然后改名迁移到其他分片。例如将热Key foo复制出3个内容完全一样的Key并名为foo2、foo3、foo4,将这三个Key迁移到其他数据分片来解决单个数据分片的热Key压力。
●读写分离。如果热Key的产生来自于读请求,您可以将实例改造成读写分离架构来降低每个数据分片的读请求压力,甚至可以不断地增加从节点。
(5)做好系统的监测,建立预警机制,提前做好防范。
 @所有人

 

 

【每日一问:20260414】 Redis的Key和Value的设计原则有哪些?

Key 设计原则
1.短小精炼:
●避免过长:Key 应该尽量短小,以节省内存和提高操作速度,通常不超过 256 字节。
●含义明确:使用具有清晰含义的 Key,以便于理解和维护。
2.使用命名空间:
●分隔符:使用冒号(:)作为分隔符来组织命名空间,有助于实现 Key 的层级结构管理。
●层级结构:例如 user:1001:profile,可以很好地反映数据的逻辑分层关系。
3.避免热 Key:
●负载均衡:确保 Key 的分布均匀,避免某单一 Key 承担过多的访问压力,可能需对数据进行分片处理。
4.选择唯一和通用的标识方式:
●全局唯一性:确保 Key 的唯一性,避免不同数据使用相同的 Key。
●使用业务标识:结合业务逻辑,如使用用户ID、产品ID等。
Value 设计原则
1.选择合适的数据结构:
●对应使用:根据不同的需求选择适当的数据类型,如 String、List、Set、Hash、Sorted Set 等。
●避免存储过大对象:如需存储大对象,建议先进行拆分或压缩。
2.限制单个 Value 的大小:
●分片存储:对于需要存储大量数据的 Value,可以考虑拆分成多部分存储,以降低单个操作的复杂度。
●合理设置Blob:如果需要存储Blob数据,考虑放在外部存储引擎中,只将引用或索引保存在 Redis。
3.利用压缩:
●节省空间:对数据进行压缩,以减少内存占用和网络传输时间。
4.TTL设置:
●数据过期:合理使用 TTL 来控制数据的生命周期,避免无用数据长期占用内存。
通用设计建议
●预估容量和并发:评估不同数据结构在不同容量与并发情况下的表现,选择最优的数据存储结构。
●多环境测试:在生产环境部署前,在开发和测试环境中进行充足的测试,验证 Key 和 Value 设计的有效性和可行性。
●性能监控:部署 Redis 监控工具以观察实际使用中的状态和负载,及时调整 Key 和 Value 设计。
通过遵循这些原则,可以确保 Redis 在提供高性能服务的同时,也保持良好的可扩展性和易维护性。

 @所有人

 

【每日一问:20260415】分布式锁的特性是什么?如何实现分布式锁?
分布式锁的特性和实现方法如下:
特性
1.互斥性:在任何时刻,只有一个节点可以持有锁,确保资源的独占访问。
2.不会发生死锁:如果一个节点崩溃,锁可以被其他节点获取,避免死锁。
3.公平性:如果多个节点同时申请锁,系统应该保证每个节点都有获取锁的机会。
4.可重入性:同一个节点可以多次获取同一个锁,而不会被阻塞。
5.高可用:锁服务应该是高可用的,不能因为锁服务的故障而影响整个系统的运行。
实现方法
1.基于 Redis:
使用 SETNX 命令来实现锁,确保在同一时间只有一个客户端能够获得锁。
使用 EXPIRE 命令为锁设置过期时间,避免死锁。
使用 Lua 脚本确保在释放锁时检查锁的持有者。
RedLock 算法提供了更高的安全性和容错能力。
2.基于数据库:
创建一个锁表,表中包含锁的名称和状态。
节点通过插入或更新操作来获取锁。
优点是实现简单,但性能较低。
3.基于 Zookeeper:
使用临时节点作为锁。
节点创建临时节点来获取锁,使用完后删除节点。
如果节点崩溃,Zookeeper会自动删除临时节点,避免死锁。
4.基于 Etcd:
创建一个带有TTL的键值对来实现锁。
节点创建键值对来获取锁,使用完后删除。
如果节点崩溃,Etcd会自动删除键值对,避免死锁。
选择具体的实现方式需要根据应用场景、性能需求和一致性要求来决定。 @所有人

 

 

【每日一问:20260416】说说生产环境分布式锁的常见问题和解决方案
1. 死锁问题
●问题:当一个客户端获取了锁,但由于某些原因(如程序崩溃、异常等)无法释放锁时,会导致其他客户端永远无法获取锁。
●解决方案:
设置锁的过期时间。当锁的持有者未能在过期时间内执行完毕并释放锁时,锁将自动过期,从而允许其他客户端获取锁。
2. 锁续命问题
●问题:如果一个操作需要的时间可能超过锁的过期时间,那么在操作执行过程中锁过期会导致其他客户端获取到锁,从而产生并发问题。
解决方案:
使用锁续命机制。在锁持有者执行操作期间,可以定期检查锁是否即将过期,并在适当的时候对锁进行续命,即重新设置锁的过期时间。
3. 锁释放问题
●问题:为确保数据的一致性,只有锁的持有者才能释放锁。但在实际应用中,可能会出现误解锁的情况。
●解决方案:
在设置锁时,为锁关联一个唯一的值(如UUID)。在释放锁时,先检查锁的值是否与当前客户端的值匹配,如果匹配则释放锁,否则不做任何操作。注意,锁持有人的判断和锁的释放应该在一个原子操作内完成。
4. 锁的公平性问题
●问题:在高并发环境中,如果多个节点同时请求获取锁,可能会出现“饥饿”现象,即某些节点长时间无法获取到锁。
●解决方案:
引入队列,将请求锁的节点按照顺序排队。例如,在Zookeeper中,可以使用顺序节点来实现公平锁。
5. 锁的可重入性问题
●问题:在某些场景中,一个节点可能需要多次获取同一个锁,如果锁不支持重入,可能会导致死锁。
●解决方案:
为锁添加一个拥有者的概念,只有锁的拥有者才能再次获取到锁。例如,在Redis中,可以将锁的值设置为节点的唯一标识,获取锁时检查锁的值是否为自己的标识。
6. 锁的安全性问题
●问题:如果分布式锁的存储系统(如Redis、Zookeeper等)出现故障,可能会导致锁无法正常工作。
●解决方案:
使用高可用的存储系统,如使用Redis集群或Zookeeper集群。另外,可以使用心跳机制来检测存储系统的状态,如果检测到故障,可以及时进行切换。
 @所有人

 

【每日一问:20260420】电商系统每天订单1000+,订单表可能递增到上千万,现在要导出全部的订单数据,有没有什么好的解决办法,解决导出慢和内存溢出的情况?(同学昨天面试滴滴的场景面试题)
电商系统大数据量订单导出的解决方案
典型的大数据处理方案,面试问得多,适合数据量大项目(如何金融,航空,数据处理等业务系统),参考方案:
1. 分批次异步导出
分页导出:按照ID或时间范围划分,每次导出固定数量(如5000-20000条). 如果按照ID,要避免深分页的问题,分批查询条件需要带上ID;如果按照时间导出,可以考虑按照日期进行表分区,减少查询扫描的数据总量。
任务队列:使用消息队列将导出任务拆分,提高任务吞吐量以及并行处理性能,也能在出现异常可以重试。
进度追踪:建立任务状态表,记录每个导出批次的完成情况。对于常态化且数据量这么大的任务,需要实时监控任务执行情况,在出现任务能够第一时间介入处理。
当然如果有条件可以引入任务调度系统进行处理。
2. 流式处理(主要思想是边读边写)
流式写入:采用流式写入文件(如Java的StreamingOutput)
增量写入:边查询边写入,减少内存占用
3. 服务端文件处理(主要分片写文件)
分片存储:将导出文件按批次生成多个文件。因为这么大数据量可能文件大小在几个G以上,如果同时写一个大文件,基本上会有性能问题。
是否考虑压缩:后台压缩为ZIP文件减小体积,这个可以适当考虑,主要是考虑文件下载的性能。
4. 技术选型优化
轻量级导出格式:尽量考虑使用文本文件,减少内存占用
专用ETL工具:数量特别大或者导出涉及复杂业务处理,可以考虑。如果简单导出,则不必要。
数据库优化:添加适当索引,优化导出SQL,避免全表扫描 。
5. 基础架构提升
独立导出服务:将导出功能独立部署,不影响主业务,同时考虑能够扩展和扩容。以免后期业务量加大,性能优化更加方便。
读写分离:如果是主从架构的,建议从从库读取导出数据,不影响主库性能

当然除此之外,可能考虑是否需要做数据归档,冷热分离,上大数据一套方案? 这个看具体业务场景和架构团队需要,总之跟随业务和公司需要进行调整即可!

 @所有人

 

 

【每日一问:20260421】说说RocketMQ的主要特性有哪些?在业务场景中作用是什么?
RocketMQ的主要特性
1.高吞吐量:RocketMQ可以处理高流量、低延迟的数据流,非常适合金融、电子商务等高并发场景。
2.高可靠性:通过消息持久化、多副本机制,RocketMQ确保消息的高度可靠性。
3.灵活的消息模型:支持多种模型,包括点对点和发布/订阅。
4.支持顺序消息:可以保证在同一主题内,消息的消费顺序。
5.事务消息:支持分布式事务,确保消息与数据库操作的一致性。
6.丰富的消息过滤机制:支持基于Tag和属性的消息过滤。

应用场景:
1.系统解耦:微服务架构中,服务间通信通过消息中间件实现解耦,降低服务间的相互依赖。
2.异步处理:非实时处理任务通过消息队列异步执行,提高系统响应速度。
3.流量削峰:在高并发情况下,消息队列可以缓冲流量,防止流量骤增导致系统崩溃。
4.事件驱动架构:实现复杂业务流程的事件驱动,推动业务中各个阶段的事件流转。
5.日志处理:收集和处理日志、监控数据等场景,尤其适合大规模数据的实时处理。

 @所有人

 

 

每日一问:20260422】RocketMQ消息是如何存储的?
RocketMQ的消息存储是一个复杂而高效的过程,设计上充分考虑了性能和扩展性。消息存储的主要组件包括 CommitLog 文件、消费队列文件(ConsumeQueue)、以及索引文件(IndexFile)。我们来详细解释一下这几个核心部分。
1. CommitLog文件
CommitLog是RocketMQ的核心存储文件,负责保存消息的完整内容。
●顺序写入:所有的消息都顺序写入CommitLog文件,这种方式减少了磁盘寻道时间,提高了写入性能。
●文件滚动:CommitLog按照固定大小(比如1GB)进行分片。当一个文件写满后,会创建一个新的文件。
●存储所有数据:包括消息体、主题、队列ID等。
2. ConsumeQueue文件
ConsumeQueue是针对消息的逻辑视图,旨在加快消费者对消息的访问速度。
●条目固定:每个ConsumeQueue条目固定为20字节,包含消息在CommitLog中的偏移量、消息大小、Tag哈希值。
●独立文件:每个主题的每个队列都有独立的ConsumeQueue文件,文件路径为store/consumequeue/{topic}/{queueId}。
●快速定位:通过ConsumeQueue,消费者无需扫描整个CommitLog即可快速找到消息的位置。
3. IndexFile文件
IndexFile用于支持消息的快速检索。
●哈希索引:为消息的key建立哈希索引,支持通过key快速检索消息偏移。
●增强查询:IndexFile是可选的,用于需要基于消息属性进行快速查找的场景。
消息存储流程
1.接收消息:Broker接收到消息后,将其放入内存缓冲区(待写入CommitLog)。
2.写入CommitLog:
●每条消息追加到当前活跃的CommitLog文件中。使用顺序写入提升写入效率和磁盘利用率。
3.同步到ConsumeQueue:
●异步转发服务(ReputMessageService)从CommitLog读取新写入的消息。
●将消息的偏移量和其他元数据(如大小和Tag哈希值)存储到相应的ConsumeQueue文件中。
4.更新IndexFile(可选):
●若消息带有key(如业务ID),则将其哈希和偏移量存入IndexFile。
●这样,可以通过该key快速查找消息。 @所有人

 

【每日一问:20260423】讲讲RocketMQ的Broker有哪几种集群模式?
RocketMQ 4.x和5.x的集群模式发生了很大的变化,这里分别说下它们的原理以及区别:
一、RocketMQ 4.x支持以下几种集群部署模式:
1.单Master模式
a.最简单的部署方式,只有一个Master节点
b.优点:部署简单,适合开发测试环境
c.缺点:一旦Broker宕机,整个服务不可用
2.多Master模式
a.所有Broker都是Master,无Slave
b.优点:无单点故障,任一Master宕机,其他Master仍可对外提供服务
c.缺点:Master宕机期间,该Master上未被消费的消息在节点恢复前不可用 [ref:11,15]
3.多Master多Slave模式-异步复制
a.每个Master配置一个或多个Slave,Master与Slave之间异步复制
b.优点:即使Master宕机,消费者仍可从Slave读取消息,降低消息丢失风险
c.缺点:在Master宕机期间,可能会有少量消息丢失(因为异步)
4.多Master多Slave模式-同步复制
a.每个Master配置一个或多个Slave,Master与Slave之间同步复制
b.优点:数据零丢失
c.缺点:性能较低,同步复制会增加数据写入延迟
5.Dledger模式 (4.5版本引入)
a.基于Raft协议的高可用集群模式
b.提供自动故障转移能力,但配置较复杂
二、RocketMQ 5.x集群模式
RocketMQ 5.x在保留4.x集群模式的基础上,引入了新的集群部署模式:
1.Controller模式
a.5.x最重要的新增特性之一
b.基于Raft协议实现的轻量级元数据中心
c.负责Broker主从角色的选举和切换
d.可以内嵌在NameServer中部署,也可以独立部署
2.Dledger Controller模式
a.改进版的Dledger模式,结合了Controller的优势
b.提供更高效的自动故障转移能力
c.可以利用RocketMQ原生存储和复制机制
3.Proxy模式
a.5.x新引入的代理组件,支持多种协议(gRPC、MQTT等)
b.将协议层与业务逻辑分离,简化客户端实现
c.提供了轻量级客户端和多语言支持
4.混合部署模式
a.可以根据业务需求,组合使用多种部署模式
b.例如:Controller + Proxy的组合部署
三、主要区别与改进
1.高可用性:
a.4.x:主要依赖多Master多Slave架构,故障恢复需手动干预
b.5.x:引入Controller机制,实现自动故障检测和角色切换
2.协议支持:
a.4.x:主要支持自身协议
b.5.x:通过Proxy支持多协议(gRPC、MQTT、JMS等)
3.客户端实现:
a.4.x:较重的客户端实现
b.5.x:轻量级客户端,支持多语言
4.部署复杂性:
a.5.x提供了更多部署选项,但也增加了部署的复杂性
b.提供了更强大的集群管理和监控能力
总结来说,RocketMQ 5.x在4.x的基础上引入了更多集群部署模式,特别是Controller和Proxy模式,大大提升了集群的高可用性、可扩展性和协议兼容性。
 @所有人

 

 

【每日一问:20260424】讲讲RocketMQ的事务消息的原理是什么?
RocketMQ事务消息是一种用于确保分布式系统中消息一致性的机制,主要是利用了消息中间件的二阶段提交机制,可以解决分布式系统中由于网络、系统故障等原因导致的消息不一致问题。以下是RocketMQ事务消息的基本原理:

事务消息的基本流程

1. 消息发送阶段:
● 发送方首先发送一个“半消息”(Half Message)到RocketMQ的Broker。此时,这个消息对消费者是不可见的。
● 发送方会收到一个确认,表示半消息已经成功存储。

2. 本地事务执行:
● 发送方在收到半消息确认后,开始执行本地事务操作(例如,数据库操作)。
● 本地事务的结果有两种:成功或失败。

3. 事务状态提交:
● 如果本地事务成功,发送方提交一个“提交”请求给Broker,Broker将半消息标记为可投递状态,消费者可以消费该消息。
● 如果本地事务失败,发送方提交一个“回滚”请求给Broker,Broker将删除该半消息。

4. 事务状态回查:
● 在某些情况下,发送方可能由于网络问题或系统故障未能及时提交事务状态。
● Broker会定期回查发送方以获取事务状态。发送方需要实现一个回查接口,以便Broker询问本地事务的最终状态。
● 根据回查结果,Broker决定是提交还是回滚该消息。

关键点

● 幂等性:由于网络问题,事务消息可能会被重复发送,因此消费者需要确保消息处理的幂等性。
● 事务回查:发送方需要实现事务回查接口,以便Broker在需要时能够获取本地事务的状态。
● 消息状态:事务消息有三种状态:Prepared(半消息)、Commit(提交)、Rollback(回滚)。

优势

● 一致性:通过事务消息,RocketMQ能够在分布式系统中提供最终一致性。
● 可靠性:即使在网络故障或系统崩溃的情况下,事务消息机制也能确保消息的可靠传递。 @所有人

 

 

 

【每日一问:20260426】说说你的项目RocketMQ如何保证消息不丢失?
RocketMQ通过多层面的机制来确保消息的可靠性,包括生产者端、broker端和消费者端。
1. 生产者端保证
a. 同步发送
同步发送是最可靠的发送方式,它会等待broker的确认响应。
b. 异步发送 + 重试机制
异步发送通过回调来处理发送结果,并可以设置重试次数。

2.Broker端保证
a. 同步刷盘
通过配置broker.conf文件,可以启用同步刷盘:
brokerRole = SYNC_MASTER
3. 消费者端保证
a. 手动提交消费位移
使用手动提交可以确保消息被正确处理后再提交位移。
b. 幂等性消费
在消费端实现幂等性处理,确保重复消费不会导致业务问题。

通过这些机制的组合,RocketMQ能够在各个环节保证消息的可靠性,极大地降低了消息丢失的风险。在实际应用中,可以根据业务需求选择合适的配置和实现方式,以在可靠性和性能之间取得平衡。 @所有人

 

 

【每日一问:20260427】RocketMQ延迟消息是如何实现的?
RocketMQ 4.x和5.x延时消息原理有非常大的改变,分别说下它们之间的原理以及差异:
1. RocketMQ 4.x 延时消息原理
(1)实现机制:使用特殊的主题 SCHEDULE_TOPIC_XXXX 存储延时消息,每个延时级别对应一个队列
(2)工作流程: 将消息转存到特殊主题,记录原始主题信息 ;Broker定时扫描特殊主题队列,到期后转发到原始主题
(3)局限性:仅支持18个固定延时级别(1s至2h),不支持自定义时间点
2.RocketMQ 5.x 延时消息原理
(1)实现机制:基于时间轮算法和定时日志(TimerLog)
(2)工作流程: 消息存储在TimerLog中,并在TimerWheel对应时间槽建立索引 ;定时服务周期性检查当前时间槽,将到期消息投递到目标主题
(3)优势:支持任意时间点的延时设置,毫秒级精度,适用于精确时间调度场景
 @所有人

 

 

【每日一问:20260428】请问RocketMQ消息积压一般产生原因是什么?如何解决消息积压问题呢?
一般消息出现堆积原因有:
●消费者消息处理逻辑异常,导致消息无法正常消费。
●消息生产应用出现突发流量,消息生产速度远大于消费速度,消息来不及消费出现堆积。
●消费者依赖的下游服务耗时变长,消费线程阻塞等。
●消费线程不够,消费并发度较小,消费速度跟不上生产速度。

解决方案有:
(1)确认消息的消费耗时是否合理,通过打印堆栈日志信息分析如果是消费耗时较长,可以参考出来解决方案:
1. 分析和优化业务逻辑
●简化逻辑:仔细分析业务逻辑,去除不必要的步骤和复杂计算。
●分解任务:将复杂的任务分解为多个简单的子任务,逐步处理。
●异步处理:对于不需要立即完成的任务,考虑使用异步处理,将其放到后台执行。
2. 使用并行和并发技术
●多线程处理:在消费者内部使用多线程来并行处理消息。
●批量处理:如果业务允许,合并多条消息进行批量处理,减少处理次数。
3. 优化I/O操作
●数据库优化:优化数据库查询,使用索引、减少查询次数或使用批量操作。
●缓存使用:对于频繁访问的数据,使用缓存来减少数据库或外部服务的访问次数。
●网络优化:减少网络请求的次数和延迟,使用更高效的协议或配置

(2)如果消费耗时正常,则有可能是因为消费并发度不够导致消息堆积,需要逐步调大消费线程或扩容节点来解决。
(3)设置消息过期时间
在消息发送时设置TTL,消息在队列中超过一定时间后自动过期并被丢弃。这样可以确保系统不会处理过期的消息。这个要看具体的业务场景。
 @所有人

 

 

【每日一问:20260430】 如果要你对生产环境的RocketMQ进行调优,请问你的思路是什么?
这个一个综合性问题,RocketMQ在生产环境中的优化是一个多方面的任务,涉及到系统配置、应用代码、监控和运维等多个层面。主要优化思路有:
1. 生产者优化
a) 批量发送消息
批量发送可以提高吞吐量,减少网络开销。
b) 异步发送
对于非关键消息,使用异步发送可以提高发送效率。
c) 合理设置发送超时时间
2. 消费者优化
a) 增加消费并发度
通过增加消费线程数来提高消费能力。
b) 批量消费
配置批量消费可以减少网络交互,提高效率。
c) 合理设置消费重试次数
3. Broker配置优化
需要在broker.conf中配置:
brokerRole = ASYNC_MASTER #主从异步复制消息
flushDiskType = ASYNC_FLUSH #开启异步刷盘
4. 合理设置Topic的队列数
小规模系统:对于一般的小型应用,可以从 4 到 8 个队列开始,根据具体的消息负载和性能监控结果再行调整。
中等到大型系统:对于大型系统或高吞吐量需求,默认 16 到 32 个队列 是常见的设置。
5. 消费设置长轮询模式
长轮询可以减少无效的轮询请求,在消费者端配置:
6. JVM优化
a) 设置合适的堆内存大小
b) 使用G1垃圾收集器
7. 消息存储优化
a) 启用文件预热
在broker.conf中开启文件预热配置,将warmMapedFileEnable这个参数设置为 true 时,RocketMQ 会在创建映射文件时对其进行预热。具体的实现方式可能包括写入一些数据以触发操作系统将文件的物理页与虚拟内存页面相关联,可以减少I/O延迟和提高文件读写效率。
8. 监控和告警
使用RocketMQ的Dashboard或者其他监控工具(如Prometheus + Grafana)来监控系统状态,并设置适当的告警阈值。
9. 消息压缩
对于大消息,可以考虑进行压缩。
这些优化策略涵盖了RocketMQ的多个方面,包括生产者、消费者、Broker配置、网络、JVM、存储等。在实际应用中,需要根据具体的业务场景和硬件环境来选择合适的优化策略。同时,优化是一个持续的过程,需要不断监控、测试和调整,以达到最佳的性能表现。
 @所有人

 

【每日一问:20260501】请问如何实现RocketMQ 顺序消息?
RocketMQ提供了两种级别的顺序消息:全局顺序消息和分区顺序消息。
1. 全局顺序消息
全局顺序消息确保一个主题内的所有消息都按照发送顺序被消费。这通常通过将所有消息路由到同一个队列来实现。
2. 分区顺序消息
分区顺序消息保证具有相同分区键的消息按顺序被消费。这允许更高的并行度,因为不同分区键的消息可以并行处理。
关键点解释
1.消息队列选择:
●全局顺序:所有消息都发送到同一个队列。
●分区顺序:使用自定义的队列选择算法,确保相同分区键的消息进入同一队列。
2.MessageListenerOrderly:
3.使用 MessageListenerOrderly 接口来确保消息的有序消费。RocketMQ 保证同一个队列的消息会被同一个线程顺序处理。
ConsumeOrderlyStatus:
4.消费者返回 ConsumeOrderlyStatus.SUCCESS 表示消息已成功处理。
锁定机制:
5.RocketMQ 内部使用锁定机制确保同一时间只有一个消费者线程在处理特定队列的消息。
重试机制:
6.如果消息处理失败,RocketMQ 会自动重试,同时保持消息的顺序。负载均衡: 分区顺序消息允许更好的负载均衡,因为不同的分区可以并行处理。注意事项 1.全局顺序消息可能会限制系统的吞吐量,因为所有消息都经过单一队列。 2.分区顺序消息在保证局部顺序的同时提供了更好的并行性。 3.选择合适的分区键对于分区顺序消息至关重要,以确保相关消息进入同一队列。 4.消费者端需要正确处理并发和重试逻辑,以维护消息顺序。 通过这些机制和实践,RocketMQ 能够在不同场景下有效地保证消息的顺序性,同时兼顾系统的性能和可扩展性。  @所有人

 

【每日一问:20260502】RocketMQ的消息存储如何进行清理和归档?
RocketMQ 提供了消息存储清理和归档的机制,以便管理消息存储空间,删除过期消息,并将历史消息归档到其他存储介质中。这些功能有助于维护消息队列的性能和可用性。以下是关于 RocketMQ 消息存储清理和归档的主要方面:
1.消息文件删除策略: RocketMQ 支持多种消息文件删除策略,可以在配置文件中进行设置。以下是一些常见的策略:
●定时删除策略: 您可以配置 RocketMQ 定期删除过期的消息文件和索引文件。这样,一旦消息文件中的消息过期,RocketMQ 将自动删除它们。
●空间满策略: 如果存储磁盘空间达到一定限制,RocketMQ 可以自动删除最早的消息文件,以释放磁盘空间。这个策略确保了存储空间不会无限制地增长。
●指定时间段删除策略: 您可以配置 RocketMQ 只删除特定时间段内的消息文件,以保留历史消息。
2.消息归档: RocketMQ 允许您将历史消息归档到其他存储介质中,以减小消息服务器的存储负担。归档通常涉及将消息转移到长期存储(如云存储或本地归档系统)中。归档可以手动触发,也可以自动触发,具体取决于您的需求。
3.历史消息访问: 尽管消息被归档,RocketMQ 仍然提供了访问历史消息的机制。通过合适的归档系统或者存储介质,您可以检索和访电商系统每天订单1000+问历史消息,以满足合规性要求或其他业务需求。
需要注意的是,清理和归档消息不是 RocketMQ 的核心功能,而是辅助功能。您需要根据自己的需求和业务场景来配置和管理消息的清理和归档策略。确保配置合理的清理策略以防止存储空间耗尽,并根据业务需求进行消息的归档操作,以保留历史消息数据。同时,归档后的消息可以根据需要进行合适的检索和恢复,以满足特定的数据需求。
 @所有人

 

【每日一问:20260503】讲讲生产环境亿级数据在不停服务的情况如何实现平滑迁移?

这个面试问到非常多场景问题,可以把这个作为项目亮点加入你的项目中,主要设计方案如下:
一、技术方案选择
1.双写+增量同步组合方案‌
通过(MyBatis拦截器/Canal订阅Binlog)两个方案实现新旧库并行写入,确保迁移期间数据完整性
2.采用分阶段流量切换(先读后写),降低业务风险
3.关键设计:
• 雪花算法生成分布式ID避免主键冲突
• 动态数据源路由(如AbstractRoutingDataSource)
二、实施步骤
1.全量同步‌
(1)使用mysqldump分批次导出数据
(2)基于GTID建立主从复制,实时同步增量数据
2.双写改造‌
(1)非核心业务灰度开启双写,观察3-7天
(2)每小时CRC32分块校验数据一致性
3.最终切换‌
(1)低峰期停写源库,binlog追平最后差异
(3)DNS权重调整实现流量渐进切换

三、双写+增量同步是亿级数据迁移的核心方案,可以重点介绍下

1、双写机制实现

(1)代码层改造‌

通过MyBatis拦截器实现动态数据源路由,所有写操作同时写入新旧库
采用配置中心(如Nacos)动态控制双写开关,支持灰度发布


(2)一致性保障‌

使用分布式事务或最终一致性补偿(如MQ重试机制)
通过CRC32分块校验每日比对数据差异
2、增量同步技术

(1)日志解析方案‌

基于MySQL的binlog或CDC工具(如Canal)捕获变更
通过Kafka中转增量数据,消费端按分库分表规则写入新库

(2)追平策略‌

全量迁移完成后,需短暂停写旧库(通常<5分钟)确保binlog消费完毕
采用GTID复制保证数据连续性,监控Seconds_Behind_Master指标
3、切换流程

(1)渐进式切流‌

先切读流量(按用户ID分片灰度),再切写流量
通过DNS权重调整或负载均衡策略实现平滑过渡

(2)异常处理‌

双写失败时优先保证旧库成功,记录差异日志异步修复
回滚方案:立即关闭双写,DNS切回旧库+反向同步补偿
 @所有人

 

【每日一问:20260504】为什么都说Redis是高性能的?说一下你的理解
Redis 的高性能主要源于以下 5 个核心设计:

1. 内存存储架构
● 全内存操作避免磁盘 I/O 瓶颈
● 异步持久化机制(RDB/AOF)不阻塞主线程
● 内存访问速度比磁盘快 5 个数量级(100ns vs 10ms)

2. 高效数据结构
● 底层实现优化:
● SDS 动态字符串(预分配+惰性删除)
● 压缩列表(ziplist)节省内存
● 跳跃表(zskiplist)实现 O(logN) 查询

.3. 单线程模型
● 避免上下文切换和锁竞争
● 基于事件循环的 Reactor 模式
● 顺序执行命令保证原子性

4. IO多路复用
● 使用 epoll/kqueue 实现
● 单线程处理 10 万级并发连接
● 典型吞吐量可达 10万 QPS

5. 协议优化
● RESP 协议简单高效
● 批量命令减少网络开销(如 pipeline)
● 二进制安全的字节流处理
 @所有人

 

【每日一问:20260505】JAVA创建一个空对象,需要占多少空间?说说你的计算方法

 对象组成: Java对象结构包括三部分:对象头、对象体、对齐字节(保证8的整数倍)。
其中对象头: 
  (1)markword : 32位4字节,64位8字节
  (2)class指针:  开启指针压缩4字节 ,未开启指针压缩 8字节
  (3)数组长度(可选):4字节

  32位操作系统:4+4=8
  64位操作系统:
       未开启压缩=8+8=16
       开启压缩= 8+4+4(padding)=16
 @所有人

 

【每日一问:20260506】说说JVM对象创建与内存分配的流程 ?
创建一个对象的流程

1.类加载检查
2.判断是否加载过、解析、初始化过。 
●如果没有加载会去加载类
3.分配内存 
●指针碰撞
●空闲列表
4.初始化:给成员变量设置默认初始值
5.设置对象头
6.执行<init>方法

分配内存方法

●指针碰撞:内存空间分配是规则的,有一个已使用和未使用的临界指针,正常情况下是,把指针往后挪一定的空间,空出来的位置存放对象,这个叫做指针碰撞
●空闲列表:内存分配不规则,已被使用的内存和空闲的内存相互交错在一起,虚拟机维护一个列表,记录可用的内存空间,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,叫做空闲列表

不管是哪种内存划分,都会有并发的问题,每个线程创建对象都会在堆中存放,会对内存做争抢,解决并发问题有两种方式

●CAS+失败重试:大家一起抢空间,只能有一个线程抢到,没抢到的再去其他空间抢
●本地线程分配缓冲(Thread Local Allocation Buffer,TLAB):给每一个线程在堆里事先规划一块独立的线程专属内存空间,线程在new对象时,只向自己的内存空间存放。可以通过-XX:+/-UseTLAB参数来设定是否使用TLAB,-XX:TLABSize 指定TLAB大小,有默认大小通常不需要指定

对象内存分配流程

1.new一个对象,对象逃逸分析,非逃逸的对象尝试在栈上分配,如果栈放不下还是在堆上分配。
2.如果在堆上分配,优先放入Eden区 
3.判断是否是大对象,如果是大对象,直接进入老年代
4.做TLAB线程分配缓冲,然后放入Eden区
5.长期存活的对象进入老年代 
●从Eden出生的对象,经过一次minor gc存活,并且可以被Survivor区容纳,会被放入Survivor区,对象分代年龄设为1,每熬过一次minor gc,分代年龄就会+1,当分代年龄达到一定程度(最大是15,可以配置)时,就会晋升到老年代。对象晋升到老年代的阈值可通过参数-XX:MaxTenuringThreshold设置


 @所有人

 

【每日一问:20260507】ThreadLocal为什么会导致内存泄漏?如何解决的?

导致内存泄漏的原因有:
(1)线程生命周期:ThreadLocal 变量是与线程绑定的。当线程池中的线程被重用时,ThreadLocal 变量可能不会被及时清理,导致内存泄漏。
(2)弱引用机制:ThreadLocal 的键是弱引用,但值是强引用。当 ThreadLocal 对象被垃圾回收时,键会被清除,但值仍然存在于线程的 ThreadLocalMap 中,导致无法访问但无法回收的内存。
(3)未显式清理:如果在使用完 ThreadLocal 后没有显式调用 remove() 方法,可能会导致内存泄漏,尤其是在使用线程池时。
如何解决:
(1)显式清理:在使用完 ThreadLocal 变量后,显式调用 remove() 方法清理数据。
ThreadLocal<MyObject> threadLocal = new ThreadLocal<>();
try{       
     threadLocal.set(new MyObject());// 使用 threadLocal 的值  
  }finally{       
 threadLocal.remove();// 显式清理 
 }
(2)避免长生命周期的 ThreadLocal:尽量避免在长生命周期的线程中使用 ThreadLocal,如线程池中的线程
(3)平时做好监控和预防:
使用工具(如 Java VisualVM、JProfiler)监控应用程序的内存使用情况,及时发现和解决内存泄漏问题。
 @所有人

 

【每日一问:20260508】谈谈什么是三色标记?
三色标记法是一种用于垃圾回收算法中的对象标记方法,特别用于标记-清除型垃圾回收器。这种方法通过使用三种颜色(白色、灰色和黑色)来跟踪对象的可达性和垃圾回收状态,以避免对象的重复回收和丢失。
三色标记的基本概念
●白色:表示对象尚未被检查。白色对象可能是垃圾,直到证明它们是可达的。
●灰色:表示对象被检查过,并且其本身是可达的,但其引用的对象还未全部检查。
●黑色:表示对象和它所有引用的对象都已检查且是可达的。
三色标记步骤
1.初始化:所有对象开始时都是白色。
2.标记开始:从GC Roots开始,根对象标记为灰色。
3.扫描灰色对象:
●将灰色对象引用的所有白色对象标记为灰色。
●然后将该灰色对象标记为黑色。
4.重复步骤3直到没有更多的灰色对象。
5.清除:未标记为黑色的对象为白色,即垃圾,可被回收。
使用三色标记的好处
●避免循环引用:能够正确识别循环引用对象不被误判为不可回收。
●并发性增强:支持并发标记,减少“Stop-the-World”时间。
●渐进性:可以执行增量垃圾回收,减小程序暂停。
在实际的垃圾回收算法实现中,如G1和CMS,三色标记法被广泛应用,用于高效地管理内存和提高程序运行的性能。 @所有人

 

【每日一问:20260509】说说你们生产环境遇到的哪些JVM内存问题?是怎么解决的?
   这是面试热门的项目场景问题,大家可以举一些自己项目中出现过的一些JVM相关的问题进行阐述。讲这个场景问题的方法是: 问题背景,问题原因,解决方案以及最后问题最后出来结果。要求言简意赅,有具体数据和场景做支撑。 我这里列出一些常见的生产环境关于JVM内存问题的场景点:
1.关于内存泄漏。 
●    线程池使用ThreadLocal,这个每日一问有讲过。解决思路手动remove.
●    集合存储数据,没有手动清空。解决思路 weakHashmap或者手动清理内存。
2.内存溢出。大数量的文件读写,产生大对象导致内存溢出,比如10G大文件,2G内存怎么出来?解决思路文件切片,Stream流处理等。
3.频繁full GC问题。一个高并发的系统,由于老年代内存不足,导致频繁的 Full GC。解决思路就是:
●调整堆内存配置,增加老年代的内存大小。
●使用 CMS 或 G1 垃圾回收器来减少 Full GC 的频率和停顿时间。
●优化对象的生命周期,减少不必要的对象创建。
4.线程池参数不合理导致的阻塞队列过载,引发内存溢出的问题。
解决方法:使用合适的线程池配置,限制最大线程数。
        定期监控线程池的状态,及时发现和处理异常线程。
5.内存碎片的问题。批处理调度,频繁创建和释放大对象,导致内存碎片化。
解决方法:使用 G1 垃圾回收器,它具有更好的内存压缩能力。
●调整堆内存配置,增加连续内存块的大小。
●尽量重用大对象,减少频繁的分配和释放  @所有人

 

【每日一问:20260511】聊聊Java线程池的工作原理
线程池是一种用于管理和重用线程的机制,其底层工作原理涉及线程的创建、调度、执行以及回收等关键过程。线程池的底层工作原理可以分为以下几个关键步骤:
1.线程池的创建: 在使用线程池之前,需要首先创建一个线程池。通常,线程池会根据配置参数(如核心线程数、最大线程数、队列类型等)来初始化线程池的基本属性。
2.任务提交: 当有任务需要执行时,将任务提交给线程池。任务可以是Runnable或Callable对象,表示需要在一个独立线程中执行的工作单元。
3.线程分配: 线程池内部维护了一组工作线程,这些线程会被动态分配来执行任务。线程池首先会尝试将任务分配给核心线程,如果核心线程数没有达到上限,就创建一个新的核心线程来执行任务。
如果核心线程已满,任务会被放入任务队列中等待执行。
4.任务执行: 分配给线程的任务会被执行。每个工作线程会不断地从任务队列中获取任务并执行它们。一旦任务执行完成,线程可以选择等待新任务或被回收,具体取决于线程池的配置和实现方式。 5.线程回收: 线程池内的线程可能会被回收,这可以是根据一些策略,如闲置时间超过一定阈值或线程数超过最大线程数等。回收的线程会释放资源,如内存和CPU,以便在需要时重新使用。 6.任务完成和结果返回: 任务执行完成后,可以将执行结果返回给调用者。如果任务是通过Callable提交的,线程池会返回Future对象,通过该对象可以获取任务的执行结果。 7.异常处理: 线程池通常会处理任务执行过程中抛出的异常,可以将异常信息记录下来或采取适当的措施,以确保线程池的稳定性。 总的来说,线程池的底层工作原理是通过管理一组工作线程、任务队列和任务分配策略来实现任务的调度和执行。这种机制可以提高线程的重用性、提高程序性能,并有效地控制线程的生命周期,
使得线程池成为多线程编程中的重要工具。 @所有人

 

【每日一问:20260512】ReentrantLock中的公平锁和非公平锁的底层是怎么实现的?  @所有人
ReentrantLock 的公平锁和非公平锁,底层都是基于 AQS(抽象队列同步器) 实现的。它们最核心的区别在于:当锁处于空闲状态时,新来的线程是否有资格直接‘插队’抢占锁。
1、具体来说: • 非公平锁(默认): 它的策略是‘能抢就抢,抢不到再排队’。当线程调用 lock() 时,它根本不管队列里有没有人在排队,上来就直接通过 CAS 尝试修改 state 状态去抢占锁。只有抢占失败了,它才会老老实实进入 CLH 等待队列去排队。 • 公平锁: 它的策略是‘先来后到,严禁插队’。当线程调用 lock() 时,它会直接进入 acquire 流程。即使在尝试获取锁时发现 state 为 0(锁是空闲的),它也必须先调用 hasQueuedPredecessors() 方法,去检查一下等待队列里有没有比自己来得更早的线程。如果有,它就必须放弃抢占,乖乖去队尾排队。 2、从优缺点来看: • 非公平锁的吞吐量更高,因为它减少了线程挂起和唤醒的开销,但可能会导致队列中的线程长期拿不到锁,产生‘饥饿’现象。 • 公平锁保证了线程严格按照 FIFO(先进先出)的顺序获取锁,避免了饥饿,但因为频繁的线程上下文切换,整体性能会比非公平锁低一些。 3、在实际开发中,除非有严格的顺序需求,否则我们通常默认使用性能更好的非公平锁。

 

【每日一问:20260514】Synchronized与ReentrantLock的区别?
Synchronized和ReentrantLock都是Java中用于实现线程同步的机制,它们各有优缺点和适用场景。以下是它们的比较和适用示例:
相似之处
1.线程安全:两者都可以用于实现对临界区的保护,从而实现线程安全。
2.可重入性:两者都支持可重入性,允许一个线程多次获取同一把锁而不会发生死锁。
3.互斥锁:本质上都是互斥锁,确保在同一时刻只有一个线程能执行被同步的代码块。
区别
1.灵活性:
●synchronized是Java语言的内置特性,使用简单,但功能有限。
●ReentrantLock是一个类,提供了更高级的锁功能,例如:可中断的锁获取、超时获取锁、非阻塞尝试获取锁以及可实现更复杂的同步结构。
2.性能:
●在较低竞争时,synchronized会自动使用优化,比如锁消除和锁粗化,使得它的性能在某些情况下可能高于ReentrantLock。
●ReentrantLock可能在高竞争下表现更好,因为它可以提供非公平和公平锁模式,公平模式会严格按照请求锁的顺序来分配锁。
3.实现的功能:
●ReentrantLock提供了更多控制功能,如lock()、unlock()方法,可在任何位置灵活调用。而synchronized在语法上是强制块结束时锁自动释放。
●ReentrantLock提供tryLock()和lockInterruptibly()方法,以响应中断和超时。
4.条件变量:
●ReentrantLock具有与之关联的Condition对象,可以搭配lock来更细粒度的控制线程通信。
●synchronized配合Object的wait()和notify()/notifyAll()来进行线程之间的通信,但不如Condition灵活。
适用场景
●synchronized:适用于简单的同步需求。由于其语法简单且嵌入在Java语言中,特别适合锁定范围与方法等价的情况。小规模、多线程竞争不高的情况下表现优异。适合开发者不想处理锁的复杂生命周期时使用。
●ReentrantLock:适用于需要更高级的同步控制,或者锁定范围与方法不同时。特别是在需要公平锁、可中断锁操作、尝试获取带超时功能的锁,或者需要多个条件等待时,应选择ReentrantLock。当系统规模较大、线程数较多,且具有复杂同步需求的情境时表现突出。
选择一个合适的锁机制,将有助于提升应用程序的性能并简化并发代码的编写。 @所有人

 

【每日一问:20260515】说说你对JMM内存模型的理解?
Java 内存模型(Java Memory Model, JMM)是 Java 语言规范的一部分,它定义了多线程程序中变量(包括实例字段、静态字段和数组元素)的访问规则。JMM 主要解决了在多线程环境下,如何保证可见性、有序性和原子性的问题。以下是对 JMM 的一些关键概念的理解:

1. 可见性

可见性指的是一个线程对变量的修改,何时对其他线程可见。在 Java 中,JMM 通过以下机制来保证可见性:

● `volatile` 关键字:当一个变量被声明为 `volatile` 时,JMM 保证对该变量的写操作会立即刷新到主内存中,而读操作会从主内存中读取。这确保了所有线程看到的 `volatile` 变量的值是一致的。
● 锁(synchronized):锁机制不仅保证了互斥性,还保证了可见性。一个线程释放锁之前对变量的修改,对接下来获得同一个锁的线程是可见的。

2. 有序性

有序性是指程序执行的顺序。JMM 允许编译器和处理器对指令进行重排序以优化性能,但它提供了以下机制来保证有序性:

● `volatile`:对 `volatile` 变量的读写操作不会被重排序。
● `synchronized`:进入和退出同步块时,JMM 会插入内存屏障,确保同步块内的代码按顺序执行。
● 程序次序规则:在单线程环境下,程序按代码顺序执行。

3. 原子性

原子性指的是一个操作不可被中断。JMM 保证基本数据类型的读写是原子操作,但对于复合操作(如 `i++`),需要使用同步机制来保证原子性。

Happens-Before 原则

JMM 使用 Happens-Before 原则来定义操作之间的顺序关系。Happens-Before 是一种偏序关系,满足以下规则:

Happens-Before 原则在 Java 内存模型中定义了一组规则,用于确定多线程程序中操作的执行顺序和内存可见性。以下是主要的 Happens-Before 规则:

●1. 程序次序规则:在一个线程内,按照程序代码顺序,前面的每一个操作 Happens-Before 于后续的每一个操作。
●2. 监视器锁规则:对一个锁的解锁操作 Happens-Before 于随后对这个锁的加锁操作。
●3. `volatile` 变量规则:对一个 `volatile` 变量的写操作 Happens-Before 于后续对这个 `volatile` 变量的读操作。
●4. 线程启动规则:在一个线程中对 `Thread.start()` 方法的调用 Happens-Before 于该线程的每一个动作。
●5. 线程终止规则:一个线程中的所有操作 Happens-Before 于其他线程检测到该线程已经终止(可以通过 `Thread.join()` 方法或 `Thread.isAlive()` 方法来检测)。
●6. 线程中断规则:对线程的 `interrupt()` 方法的调用 Happens-Before 于被中断线程检测到中断事件的发生(通过 `Thread.interrupted()` 或 `Thread.isInterrupted()`)。
●7. 对象终结规则:一个对象的构造函数的结束 Happens-Before 于该对象的 `finalize()` 方法的开始。
●8. 传递性:如果操作 A Happens-Before 操作 B,且操作 B Happens-Before 操作 C,那么操作 A Happens-Before 操作 C。 @所有人

 

每日一问:20260517】要实现线程安全有哪种几种方式?
确保线程安全可以通过多种方法和技术来实现。以下是一些常用的方法:
1.使用synchronized关键字:synchronized关键字可以确保同一时刻只有一个线程可以执行某个代码块,从而避免了多个线程同时访问和修改共享资源的问题。
2.使用Atomic类:Java提供了多个原子类,如AtomicInteger、AtomicLong等,它们可以保证对基本数据类型的原子性操作,避免了使用synchronized关键字和volatile关键字的限制。
3.使用ReentrantLock类:ReentrantLock类是Java提供的一种可重入锁,与synchronized关键字类似,但它提供了更多的灵活性和功能。
4.使用线程安全的数据结构:Java提供了多种线程安全的数据结构,如ConcurrentHashMap、CopyOnWriteArrayList等,这些数据结构内部已经实现了线程安全,可以直接使用。
5.使用线程池:线程池可以避免创建和销毁线程的开销,并且可以有效地控制并发量,保证线程安全。
6.避免共享状态: 如果可能,尽量避免多个线程共享状态。将数据封装在线程内部,减少共享数据的需求。
7.使用线程安全的设计模式: 了解并应用线程安全的设计模式,如单例模式中的双重检查锁定等。
在实现线程安全时,还需要考虑其他问题,如避免死锁、合理地控制并发量等。同时,应该避免使用非线程安全的数据结构和方法,避免使用可能会引起竞态条件和数据不一致的操作。

 @所有人

 

【每日一问:20260518】线程池核心线程数5,最大线程数20,工作队列500,问进来510个任务是否会执行拒绝策略? 
 答案:
不会执行拒绝策略。
解析:
当任务提交到线程池时,线程池会按照以下步骤执行:
1、判断提交当前线程池中的线程数量是否小于核心线程数
如果是,创建新线程执行任务。如果不是,进入下一步骤。
前5个任务会直接创建核心线程,核心线程数=5,余下505。
2、判断工作队列是否已满
如果未满,将任务添加到队列中等待执行。如果已满,进入下一步骤。
从余下505取500个放入队列,队列满,余下5个。
3、判断提交当前线程池中的线程数量是否小于最大线程数
如果是,创建新线程执行任务。如果不是,进入下一步骤。
最大线程数20,因此该步骤能创建20-5(核心线程数)=15个新线程。最后余下10个线程。
4、执行拒绝策略
只有如果当前线程数达到最大线程数,且工作队列满,执行拒绝策略处理新提交的任务。
所以,不会执行拒绝策略。 @所有人

 

【每日一问:20260519】为什么 wait 和 notify 方法要在同步块中调用?
当使用 wait() 和 notify() 方法时,需要将它们放在同步块内,这是因为:
1.互斥性: 多线程环境下,我们希望在同一时刻只有一个线程能够执行 wait()、notify() 或 notifyAll() 方法。使用同步块(synchronized)提供了这种互斥性,避免多线程并发修改的问题。
2.上下文切换: 当一个线程调用 wait() 时,它会暂时放弃执行权并释放对象的锁。如果不在同步块内调用 wait(),线程可能在不合适的时机被唤醒,导致混乱。同步块内的 wait() 确保线程在正确的上下文中被唤醒,可以继续执行并获取锁。
3.安全性: 如果不在同步块内使用 wait()、notify() 或 notifyAll(),多个线程可能同时访问和修改同一个共享对象的状态,可能引发竞态条件,导致程序行为不确定。同步块(synchronized)可以确保对这些方法的访问是原子的,避免了潜在的并发问题。
简而言之,将 wait() 和 notify() 方法包裹在同步块内,有助于确保线程间的协同和同步工作正确,避免了多线程问题,提高了程序的可靠性和安全性。
 @所有人

 

【每日一问:20260520】说下JAVA CAS的实现原理?
CAS(Compare-And-Swap)是一种用于多线程编程中的原子操作,它通过硬件支持的指令来实现无锁同步。在多线程环境下,CAS操作通过比较和交换来更新变量的值,是一种乐观锁的实现方式,
可以避免锁机制带来的开销。 CAS工作原理 CAS操作涉及三个操作数:
1.内存位置V:需要修改的变量的内存地址。 2.预期值A:期望变量当前持有的值。 3.新值B:需要更新到变量的新值。 CAS工作过程如下: ●比较内存位置V的当前值是否等于预期值A。 ●如果是,则用新值B更新内存位置中的值。 ●如果不是,则不更新值,并返回当前实际值。 这个操作是原子的,硬件保证比较并更新的操作不会被中断。 CAS在Java中的实现 在Java中,CAS操作主要通过java.util.concurrent.atomic包中的类来实现。例如,AtomicInteger、AtomicBoolean、AtomicReference等。通过这些类的操作,
Java应用可以在多线程环境下安全地对基本数据类型进行操作,而无需显式锁定。 优势与考虑 ●无锁机制:CAS允许许多线程尝试更新同一个变量,而无需锁定,大大减小了锁竞争。 ●性能提升:在无锁的情况下,通常会有更好的性能表现,适合高并发场景。 ●ABA问题:CAS无法直接解决ABA问题(一个值从A变到B,又变回A),可以使用带版本号的变量如AtomicStampedReference来解决这个问题。 ●活锁:在高争用环境下,如果许多线程不断重试更新操作,可能会导致活锁,程序一直循环重试而得不到进展。 CAS是实现乐观并发控制的基础技术,它在Java的并发编程中起着非常重要的作用,尤其是在实现无锁算法时。通过适当使用CAS,可以在保证线程安全的同时,减少锁竞争,提高程序性能。  @所有人

 

【每日一问:20260522】Redisson解锁失败,WatchDog会不会一直续期下去?

Redisson如果解锁失败,WatchDog一般不会一直续期下去。有几个关键的保护机制:
(一)WatchDog的停止条件
1. 线程生命周期绑定
WatchDog与持有锁的线程绑定,当线程结束时,WatchDog会自动停止续期。
2. 锁持有状态验证
WatchDog在每次续期时会验证:
●锁是否仍然被当前客户端持有
●如果锁已经不存在或被其他客户端获取,续期会失败并停止
3. 客户端连接状态
如果Redis连接断开或客户端异常,续期操作会失败,WatchDog会停止工作。
(二)核心安全机制
1.TTL兜底机制
即使WatchDog出现异常,锁也会在TTL到期后自动释放(默认30秒),这是最重要的安全保障。
2.客户端标识验证
每个锁都有唯一的客户端标识,防止其他客户端错误续期。
常见的解锁失败场景
(1)锁已过期:其他线程已获取锁
(2)网络异常:Redis连接中断
(3)线程异常:持有锁的线程已死亡

WatchDog不会在解锁失败后一直续期。正常使用时应通过代码确保锁正确释放,即使出现异常,Redisson的内部机制也会在释放锁时停止WatchDog。客户端崩溃时,锁会自动超时释放,不会永久驻留。

 

每日一问:20250524】mysql索引失效的原因都有哪些?

1. 联合索引,未遵循最左匹配原则。比如联合索引a-b-c, 查询条件是b-c。
2. 联合索引部分失效,遵循了最左匹配,但是中间跳过字段,或者出现范围查询,例如a-b-c 查询 a-c 或者a - b > *
3.索引存在隐式转化。比如查询传值与数据库字段类型不匹配。表关联时候,关联索引类型不匹配、编码不一致。
4. 索引存在计算。查询条件是 date(a)或者字段截取等操作
5. 索引列上查询使用了模糊或者范围查询   or (or中有一个字段不是索引)、 not in 、 like'%1'  、not nullis nullin(里面的数据过多,执行计划选择全表扫描)
6. 索引列的区分度太低,或者表数据很少。优化器通过计算成本,选择不走索引
7.连表查询时,关联字段如果使用索引,需要保证索引的类型是一致的。比如一个是b-tree, 一个hash 在进行范围查询的时候,hash 就会失效。
 @所有人

 

【每日一问:20260525】Redis中有一批key瞬间过期,为什么其它key的读写效率会降低?如何解决?

一、核心原因解析
1. 过期 Key 集中清理引发主线程阻塞
Redis 过期策略: 
惰性删除(Lazy Free):访问 Key 时触发过期检查。
定期删除(Active Expire):默认每 100ms 随机扫描部分 Key(受 hz 参数控制)。
问题场景:瞬时大量 Key 过期时,定期删除扫描会一次性清理大量 Key,导致线程阻塞。
2. 内存回收代价
当大量 Key 突然释放内存时,需要调整内存页分配器(jemalloc/jemalloc),可能引发 CPU 突增。
持久化模式下(如 AOF/RDB),大量 Key 删除会触发内存快照调整,加剧延迟。
3. 过期 Key 触发连锁操作
如果 Key 过期操作(如 DEL 命令)连带触发其他事件(如发布订阅、Lua 脚本),会产生雪崩效应。
二、解决方案
1.分散 Key 过期时间,可以通过加上随机过期时间进行分散。
2.启用异步删除(Redis 4.0+)
自动替换:配置 lazyfree-lazy-expire yes,过期 Key 自动异步删除。
手动指定:显式调用 UNLINK key 替代 DEL key
3.如果无法技术优化,考虑使用cluster 模式,分散key 过期对单个节点的压力

 @所有人

 

 

【每日一问:20260526】mq 上游发消息失败,或者没有发消息,下游怎么感知到?
针对MQ消息队列中上游可能发送失败或未发送消息的情况,下游可通过以下方案感知并确保消息可靠性:

方案设计:基于「事务消息 + 异步对账 + 主动探测」的综合机制

 1. 事务消息机制(确保消息发送与业务一致性)
● 原理:  
  使用MQ的事务消息功能(如RocketMQ的事务消息),将消息发送与业务操作绑定,确保两者同时成功或回滚。
● 步骤:
  1. 生产者发送半消息到MQ(消息暂存,不可被消费)。
  2. MQ确认半消息接收后,生产者执行本地业务逻辑。
  3. 业务成功:生产者提交事务,MQ将消息投递到队列;业务失败:生产者回滚事务,MQ丢弃消息。
● 优点:  
  避免业务成功但消息未发送,或消息发送但业务失败的情况。

 2. 生产者确认与重试(确保消息到达MQ)
● 原理:  
  通过MQ的ACK机制(如RabbitMQ的Publisher Confirms、Kafka的ACK配置)确认消息是否成功写入。
● 步骤:
  1. 生产者发送消息后,等待MQ的ACK确认。
  2. 未收到ACK时,启用重试机制(指数退避重试,避免雪崩)。
  3. 重试超过阈值后,记录异常并触发告警。
● 优点:  
  解决网络抖动、MQ短暂不可用等问题,确保消息到达MQ。

 3. 异步对账系统(检测消息丢失与状态一致性)
● 原理:  
  通过上下游状态记录与定时对账,发现未发送或未消费的消息。
● 步骤:
  1. 生产者侧:  
● 业务操作完成后,记录一条“待发送消息”到数据库(含业务ID、状态、时间等)。
● 消息成功发送后,更新状态为“已发送”。
  2. 消费者侧:  
● 消费成功后,记录“已处理消息”到数据库。
  3. 对账服务:  
● 定时扫描生产者库中的“待发送消息”与消费者库中的“已处理消息”。
● 发现生产者标记为“已发送”但消费者无记录的,触发补偿(如重发消息)。
● 发现生产者标记为“待发送”但长期未更新的,触发告警(可能上游未发送)。
● 优点:  
  覆盖所有异常场景(如消息未发送、MQ丢失消息、消费失败),确保最终一致性。

 4. 主动探测与超时告警(实时性补充)
● 原理:  
  针对关键业务消息,下游设置超时阈值,主动探测缺失消息。
● 步骤:
  1. 生产者发送消息时,携带业务时间戳或唯一ID。
  2. 下游监听消息时,记录接收时间。
  3. 若在预期时间内未收到消息(如订单支付后10分钟无物流消息),触发主动查询:
● 调用生产者API查询业务状态。
● 确认业务是否成功,决定是否补发消息。
● 优点:  
  结合业务逻辑,实时性高,避免对账延迟。

实施建议
1. 核心业务:优先使用事务消息 + 异步对账,确保高可靠性。
2. 普通业务:采用生产者确认重试 + 异步对账,平衡性能与可靠性。
3. 补偿策略:消费者需实现幂等性,避免重复消费问题。

 

 

 每日一问:20260527】HashMap 是怎么解决哈希冲突的?

所谓 hash 冲突,是由于哈希算法被计算的数据是无限的,而计算后的结果范围有限,所以总会存在不同的数据经过计算后得到的值相同,这就是哈希冲突。

通常解决 hash 冲突的方法有

1.开放定址法,也称为线性探测法,就是从发生冲突的那个位置开始,按照一定的次序从hash 表中找到一个空闲的位置,然后把发生冲突的元素存入到这个空闲位置中。ThreadLocal 就用到了线性探测法来解决 hash 冲突的。

2.链式寻址法,这是一种非常常见的方法,简单理解就是把存在 hash 冲突的 key,以单向链表的方式来存储,比如 HashMap 就是采用链式寻址法来实现的。

3.再 hash 法,就是当通过某个 hash 函数计算的 key 存在冲突时,再用另外一个 hash 函数对这个 key 做 hash,一直运算直到不再产生冲突。这种方式会增加计算时间,性能影响较大。

HashMap 在 JDK1.8 版本中,通过链式寻址法+红黑树的方式来解决 hash 冲突问题,其中红黑树是为了优化 Hash 表链表过长导致时间复杂度增加的问题。当链表长度大于 8 (默认值)时HashMap会将链表转换为红黑树。  @所有人

 

 【每日一问:20260528】微博排名用zset为什么不用map?--来做同学面试真题

在实现微博排名或类似的排行榜功能时,使用 Redis 的 `zset`(有序集合)而不是 `map`(哈希表)有几个关键的原因:

1. 自动排序

● zset 的特性:`zset` 是一种有序集合,支持根据分数自动排序。每个元素都有一个关联的分数,Redis 会根据这个分数自动对元素进行排序。

● 排序操作:使用 `zset` 可以直接通过分数获取排名前 N 的元素,或者获取某个元素的排名,而不需要额外的排序操作。

2. 高效的排名操作

● 获取排名:`zset` 提供了高效的命令来获取某个元素的排名(如 `ZRANK`),以及获取某个分数范围内的元素(如 `ZRANGE`)。

● 更新分数:更新某个元素的分数(如点赞数、热度等)时,`zset` 可以在 O(log N) 的时间复杂度内完成,并自动调整排序。

3. 范围查询

● 范围操作:`zset` 支持按分数范围查询元素(如 `ZRANGEBYSCORE`),这对于实现按热度、时间等维度的范围查询非常有用。

● 灵活性:可以根据需要灵活地获取不同范围的排名数据。

4. 内存效率

● 内存使用:`zset` 在存储大量元素时,内存使用相对高效,特别是在需要频繁更新和查询的场景下。

● 压缩存储:Redis 对 `zset` 进行了优化,支持压缩存储以减少内存占用。

5. 复杂度

● 操作复杂度:`zset` 的大多数操作(如插入、删除、更新分数)都具有较低的时间复杂度,适合高并发的实时应用场景。

6. 应用场景

● 排行榜需求:在排行榜应用中,通常需要频繁地更新分数和获取排名,`zset` 的特性非常适合这种需求。

● 动态变化:`zset` 能够高效地处理动态变化的数据集,适合实时更新的场景。 相比之下,`map`(哈希表)虽然可以存储键值对,但不具备自动排序和高效排名操作的能力。因此,在需要实现排名和排序功能时,`zset` 是更合适的选择。  @所有人

 

 每日一问:20260531】Spring IoC 的实现机制是什么?

Spring的IoC底层实现机制主要依赖于以下几个关键组件和技术:

1.反射:Spring使用Java的反射机制来实现动态创建和管理Bean对象。通过反射,Spring可以在运行时动态地实例化Bean对象、调用Bean的方法和设置属性值。

2.配置元数据:Spring使用配置元数据来描述Bean的定义和依赖关系。配置元数据可以通过XML配置文件、注解和Java代码等方式进行定义。Spring在启动时会解析配置元数据,根据配置信息创建和管理Bean对象。

3.Bean定义:Spring使用Bean定义来描述Bean的属性、依赖关系和生命周期等信息。Bean定义可以通过XML配置文件中的<bean>元素、注解和Java代码中的@Bean注解等方式进行定义。Bean定义包含了Bean的类名、作用域、构造函数参数、属性值等信息。

4.Bean工厂:Spring的Bean工厂负责创建和管理Bean对象。Bean工厂可以是BeanFactory接口的实现,如DefaultListableBeanFactory。Bean工厂负责解析配置元数据,根据Bean定义创建Bean对象,并将其放入容器中进行管理。

5.依赖注入:Spring使用依赖注入来解决Bean之间的依赖关系。通过依赖注入,Spring容器负责将Bean所依赖的其他Bean实例注入到它们之中。Spring使用反射和配置元数据来确定依赖关系,并在运行时进行注入。 总结起来,Spring的IoC底层实现机制主要依赖于反射、配置元数据、Bean定义、Bean工厂和依赖注入等技术和组件。通过这些机制,Spring实现了Bean的创建、配置和管理,以及Bean之间的解耦和依赖注入

 

 【每日一问:20260601】介绍下SpringAop的底层实现?

Spring AOP 通过 JDK 动态代理 或 CGLIB 动态代理 创建代理对象,并结合 拦截器链 实现通知逻辑的增强,最终在不修改目标类的情况下实现横切关注点的分离

1. 动态代理:

● JDK 动态代理:目标类实现接口时使用,基于 `Proxy` 和 `InvocationHandler`。

● CGLIB 动态代理:目标类未实现接口时使用,基于字节码生成子类。

● 默认优先 JDK 动态代理,可通过配置强制使用 CGLIB。

2. 拦截器链:

● 将通知(Advice)封装为拦截器(`MethodInterceptor`)。

● 方法调用时,代理对象通过拦截器链按顺序执行增强逻辑,最终调用目标方法。

3. 执行流程: ● 创建代理对象 → 拦截方法调用 → 执行拦截器链 → 调用目标方法 → 返回结果。 一句话总结:Spring AOP 通过动态代理(JDK 或 CGLIB)和拦截器链实现方法增强,完成横切关注点的分离。

 

 每日一问:20260602】JDK动态代理和CGLIB动态代理的区别?

从性能上特性对比:

JDK动态代理要求目标对象必须实现至少一个接口,因为它基于接口生成代理类。而CGLIB动态代理不依赖于目标对象是否实现接口,可以代理没有实现接口的类,它通过继承或者代理目标对象的父类来实现代理。

从创建代理时的性能对比:

JDK动态代理通常比CGLIB动态代理创建速度更快,因为它不需要生成字节码文件。而CGLIB动态代理的创建速度通常比较慢,因为它需要生成字节码文件。另外,JDK代理生成的代理类较小,占用较少的内存,而CGLIB生成的代理类通常较大,占用更多的内存。

从调用时的性能对比:

JDK动态代理在方法调用时需要通过反射机制来调用目标方法,因此性能略低于CGLIB,尽管JDK动态代理在Java 8中有了性能改进,但CGLIB动态代理仍然具有更高的方法调用性能。CGLIB动态代理在方法调用时不需要通过反射,直接调用目标方法,通常具有更高的方法调用性能,同时无需类型转换

选择使用JDK动态代理还是CGLIB动态代理取决于具体需求。

如果目标对象已经实现了接口,并且您更关注创建性能和内存占用,那么JDK动态代理可能是一个不错的选择。

如果目标对象没有实现接口,或者您更关注方法调用性能,那么CGLIB动态代理可能更合适。综上所述,这两种代理方式各有优势,根据实际情况进行选择是明智的, Spring默认情况如果目标类实现了接口用JDK代理否则用CGLIB。 而SpringBoot默认用CGLIB,所以用哪个问题都不大。  @所有人

 

 【每日一问:20260603】为什么数据库(mysql)主键不建议使用UUID ,原因是什么?谈谈你的理解?

1.性能影响:

a.索引效率低:UUID 是随机生成的,插入数据时不会按顺序排列,导致 B+ 树索引频繁分裂,降低插入性能。

b.存储空间大:UUID 占用更多的存储空间(通常为128位),导致索引和数据表变大,从而影响查询性能。

2.可读性差:

a.UUID 是一串复杂的字符,不如自增 ID 直观,调试和日志记录时不易识别和追踪。

3.排序和查询效率:

a.由于 UUID 的无序性,无法自然排序,这对需要顺序的查询操作(如分页)会带来额外的复杂性和性能开销。

4.生成复杂性:

a.生成 UUID 需要额外的逻辑和库支持,相比自增 ID 更复杂。

 

【每日一问:20260604】SpringBoot的启动原理?
1.运行Main方法: 应用程序启动始于Main方法的执行。在Main方法中,创建了一个SpringApplication实例,用于引导应用程序的启动。同时,SpringApplication会根据spring.factories文件加载并注册监听器、ApplicationContextInitializer等扩展接口实现。
2.运行run方法: 运行SpringApplication的run方法是应用程序启动的入口。在这一步,Spring Boot会 启动Spring进而创建内置tomcat,进去run方法后还做了很多其他事:
3. Spring Boot会读取和解析环境变量、配置文件(如application.properties或application.yml)等,以获取应用程序的配置信息。
4.之后再创建ApplicationContext也就是我们熟知的Spring上下文: 在这一步,Spring Boot会根据应用程序的类型(例如,Web应用程序)创建相应的ApplicationContext。对于Web应用程序,通常创建的是ServletWebServerApplicationContext。
5.预初始化上下文: Spring Boot会将启动类作为配置类,读取并注册为BeanDefinition,这使得Spring容器可以识别应用程序的配置。
6.调用refresh: 此时,Spring Boot调用了refresh方法来加载和初始化Spring容器。在这一过程中,会执行一系列操作,包括解析@Import注解以加载自动配置类,创建和注册BeanDefinition等。
7.创建内置servlet容器: 如果应用程序是一个Web应用程序,Spring Boot会在这一步创建内置的servlet容器(例如Tomcat),以便应用程序可以接受HTTP请求。这个容器将被Spring Boot自动配置,并且可以通过配置进行自定义。
8.监听器和扩展点: 在整个启动过程中,Spring Boot会调用各种监听器和扩展点,这些组件可以用来对应用程序进行扩展和定制。例如,您可以使用监听器来处理应用程序启动和关闭事件,或者使用ApplicationContextInitializer来自定义ApplicationContext的初始化。
总的来说,Spring Boot的启动过程是一个复杂的流程,它从Main方法开始,经过一系列步骤来初始化Spring容器和启动内置tomcat。
 @所有人

 

【每日一问:20260605】一同学面试场景题:外卖订单每天有1000万笔的订单, 卖家也要查近30天查订单数据, 买家也要查找近30天的订单数据。请说下你的设计方案?
这个场景题主要考察点:高效存储、快速查询、高并发支持和扩展性这几个方面。
1.首先对业务量进行整体评估:
● 每天订单量:1000万笔。
● 30天订单量:1000万 × 30 = 3亿笔。 一年订单量36亿,考虑到20%业务增长,可能达到50亿。
● 假设每条订单数据大小为1KB,则30天的订单数据量为:3亿 × 1KB = 300GB。

2.基于业务场景,需要满足的功能需求:
高效查询:支持买家和卖家快速查询近30天的订单数据。
高并发支持:满足买家和卖家同时查询的高并发需求。
3. 设计方案
1). 数据存储设计需要做冷热分离
热数据:存储最近30天的订单数据,存储在mysql数据库或者ES
冷数据:超过30天的历史订单数据,可以存储在Hadoop或者其他存储,用于离线分析。
2). 买家订单和卖家订单数据可以考虑分开存储,提高查询效率查询,不过增加存储成本。
3). 分库分表
分库:可以考虑按照月分库
分表:可以考虑按天/用户id取模/日期+用户ID取模,保证单表1000-2000w数据即可
4)查询优化
可以考虑Redis缓存热点查询数据,避免直接访问数据库
对于买家或卖家查询订单列表,采用分页查询,避免一次性返回大量数据。
5)数据同步
热数据同步:数据库与redis需要做同步,可以考虑定时任务或者canal
冷数据同步:定时时任务或流式处理(如Kafka + Flink)将超过30天的订单数据从MySQL迁移到冷数据存储。
6). 高并发支持
使用负载均衡(如Nginx)分发请求。
查询服务支持水平扩展,部署多个实例。
 @所有人

 

 

【每日一问:20260607】 讲讲Mybatis 的一级、二级缓存?
MyBatis提供了两种级别的缓存:一级缓存(本地缓存)和二级缓存(全局缓存)。它们分别位于不同的作用范围,有不同的特性和使用场景。
一级缓存(本地缓存):
1. 作用范围: 一级缓存是在SqlSession的生命周期内有效,也就是说,每个SqlSession拥有独立的一级缓存
2. 默认开启: 一级缓存在MyBatis中默认是开启的,无需额外配置。
3. 特点: 当执行查询操作时,查询的结果会被缓存在当前SqlSession中。如果再次执行相同的查询,MyBatis会首先尝试从缓存中获取数据,而不再访问数据库。
4. 自动刷新: MyBatis会在执行insert、update、delete等写操作时自动清空一级缓存,以保持数据的一致性。
二级缓存(全局缓存):
1. 作用范围: 二级缓存是在多个SqlSession之间共享的,即多个SqlSession可以共享同一个二级缓存。
2. 配置开启: 二级缓存需要手动配置开启,需要在映射文件的<mapper>标签下添加<cache>元素。
<cache eviction="LRU" flushInterval="60000" size="1024" readOnly="true"/>
3. 特点: 二级缓存能够跨SqlSession共享查询结果,有效减少数据库访问次数。它的数据存储在全局范围的缓存中,可以由多个SqlSession访问。
4. 缓存策略: 你可以根据需求选择不同的缓存策略(例如LRU、FIFO等),以及配置缓存的大小、刷新间隔等参数。
5. 注意事项: 二级缓存可以缓存的对象需要是可序列化的,要确保对象可以正确地序列化和反序列化。另外,对于关联数据的更新操作,需要手动清除相关的二级缓存,以避免脏数据的问题。

需要注意的是,虽然缓存可以提高查询性能,但不合理的使用缓存可能导致数据不一致等问题,特别是在分布式环境下。因此,在使用缓存时需要根据业务需求和性能测试结果进行合理的配置和管理。
 @所有人

 

【每日一问:20260608】如何降低或者解决高并发场景的数据库性能瓶颈问题---同学面试问到场景题 在高并发环境下,数据库性能瓶颈是一个常见的问题。

以下是一些策略和技术,可以帮助降低或解决数据库性能瓶颈:

1. 数据库优化:

● 索引优化:确保对查询频繁的字段建立合适的索引,以加快查询速度。

● 查询优化:分析和优化SQL查询,避免使用复杂的子查询和不必要的全表扫描。

● 分区和分片:将大表分区或分片,以提高查询性能和数据管理效率。

2. 缓存机制:

● 应用层缓存:使用内存缓存(如Redis、Memcached)来存储热点数据,减少数据库的直接访问。

● 数据库缓存:利用数据库自带的缓存机制,优化缓存策略。

3. 读写分离:

● 将数据库的读操作和写操作分离,使用主从复制架构,将读操作分配到从库,减轻主库的压力。

4. 连接池

● 使用数据库连接池(如HikariCP、C3P0)来管理数据库连接,减少连接建立和释放的开销,提高并发处理能力。

5. 异步处理:

● 对于不需要实时处理的操作,使用异步处理或消息队列(如RabbitMQ、Kafka)来削峰填谷,缓解数据库压力。

6. 批量操作:

● 合并多次小的数据库操作为一次批量操作,减少数据库交互次数,提高效率。

7. 垂直和水平拆分:

● 垂直拆分:根据业务功能将数据库拆分为多个独立的数据库。

● 水平拆分:将数据按某种规则(如用户ID)分布到多个数据库实例中。

8. 数据库集群: ● 使用数据库集群(如MySQL Cluster、Galera Cluster)来提高数据库的可用性和扩展性。

9. 监控和调优: ● 实施数据库监控,及时发现性能瓶颈,并进行针对性的调优。

通过这些策略,可以有效降低数据库在高并发环境下的性能瓶颈,提高系统的整体性能和响应能力。  @所有人

 

【每日一问:20260611] 我们开发的时候设计一个API接口一般需要遵循哪些原则?

API接口设计是软件开发中至关重要的部分,良好的API设计可以提高系统的可用性、可维护性和可扩展性。

以下是一些常见的API接口设计原则:

1. 清晰性和简洁性

● 易于理解:API应该是直观的,易于理解和使用。方法和参数的命名应清晰明了。

● 简洁的设计:避免复杂的结构和不必要的功能,保持API的简洁性。

2. 一致性

● 统一的风格:保持API设计的一致性,包括命名约定、参数格式和响应结构。

● 标准化:遵循行业标准和最佳实践,如RESTful设计原则。

3. 文档化

● 详细的文档:提供全面的API文档,包括端点描述、请求和响应格式、示例代码等。

● 自动化文档生成:使用工具自动生成文档,以确保文档与API实现保持同步。

4. 版本控制

● 版本管理:在API发生重大变化时,使用版本控制来管理不同版本的API,确保向后兼容性。

● 清晰的版本标识:在URL或请求头中明确标识API版本。

5. 错误处理

● 一致的错误响应:提供一致的错误响应格式,包括错误代码和详细的错误信息。

● 明确的错误信息:确保错误信息对开发人员有用,帮助他们快速定位问题。

6. 安全性

● 身份验证和授权:使用安全机制(如OAuth、API密钥)来保护API。

● 数据加密:在传输过程中加密敏感数据,确保数据安全。

7. 性能和效率

● 优化性能:设计高效的API,减少延迟和资源消耗。

● 支持缓存:利用HTTP缓存头来提高性能和减少服务器负载。

8. 可扩展性

● 灵活的设计:API设计应考虑未来的扩展需求,确保可以在不破坏现有功能的情况下进行扩展。

● 支持分页和过滤:对于返回大量数据的API,支持分页、过滤和排序功能。

9. 幂等性

● 幂等操作:确保API的幂等性,即相同的请求可以重复执行而不会产生不同的结果。

10. 日志和监控

● 实施日志和监控:记录API调用日志和监控信息,以便于故障排查和性能分析。 这些原则可以帮助开发人员设计出高质量的API接口,提高系统的整体质量和用户体验。

 

每日一问:20260614】为了实现高并发和高可用性,一般常用的负载均衡算法有哪些?

1.轮询(Round Robin):最简单的负载均衡算法,按照顺序将请求分配给每个服务器。每个请求依次分发到不同的服务器上,实现了请求的均衡分配。

2.最少连接(Least Connections):将请求分配给当前连接数最少的服务器。通过统计每个服务器的当前连接数,选择连接数最少的服务器来处理请求,以确保请求的分配更加平衡。

3.最短响应时间(Shortest Response Time):根据预测或实时统计的服务器响应时间,选择响应时间最短的服务器来处理请求。通过选择响应时间最短的服务器,可以提高用户的响应速度。

4.带权重的轮询(Weighted Round Robin):为每个服务器设置一个权重值,权重越高的服务器被选中的概率越大。可以根据服务器的配置、硬件性能等设置权重,以实现更合理地资源分配。

5.IP哈希(IP Hash):根据客户端IP地址对服务器进行哈希运算,将同一客户端的请求固定分发到同一台服务器上。这样可以实现对特定客户端的会话持久性,并保证同一个客户端的请求始终被分配到同一服务器上。

6.随机(Random):随机选择一个服务器来处理请求。通过随机分配请求,可以降低预测性能负载带来的影响,适用于负载较轻的情况。

7.粘性会话(Session Stickiness):根据请求的某一特定属性(如客户端IP、Cookie等)将请求固定分配到同一台服务器上,以保持会话的连续性。这样可以确保同一用户的请求一直被同一台服务器处理,适用于需要保持会话状态的应用场景。 以上是一些常见的负载均衡算法,每种算法都有其适用场景和优劣势。在实际应用中,可以根据系统的需求和特点,综合考虑并灵活选择合适的负载均衡算法。另外,还可以结合动态调整权重、健康检查等技术手段来进一步提高负载均衡的效果和可用性。  @所有人

 

每日一问:20260615】有一个数据库查询接口耗时200ms, QPS大约100,用线程池处理这个接口任务,请问核心线程数和最大线程数设置多少合适?

已知条件:

  ● 接口耗时:200ms(0.2秒)

  ● QPS:100(每秒100个请求)

  ● 任务类型:数据库查询(I/O密集型任务)

1. 并发量计算

并发量是指系统在同一时间需要处理的请求数,

计算公式为:并发量=QPS×接口耗时

代入已知条件:并发量=100*02.=20 也就是系统需要支持 20个并发请求。

 

2. 线程数设置原则

对于 数据库查询接口(I/O密集型任务),线程数的设置需要遵循以下原则:

1. 核心线程数:

核心线程数应接近系统的并发量(20),以确保大部分请求都能被核心线程处理。

● 对于I/O密集型任务,可以适当增加核心线程数,弥补I/O等待时间。

 

 

2. 最大线程数:

最大线程数应设置为核心线程数的 1.5到2倍,以应对突发流量。

1. 最大线程数不能超过数据库连接池的最大连接数,否则会导致线程等待连接资源,影响性能。

 

3. 计算核心线程数和最大线程数

假设数据库连接池的最大连接数为 50,并考虑I/O密集型任务的特点:

1. 核心线程数:

● 设置为并发量(20),以满足大部分请求。

● 如果数据库连接池允许,可以适当增加核心线程数到 25,以提高吞吐量。

 

 

2. 最大线程数:

1. 设置为核心线程数的 1.5到2倍

● 最大线程数不能超过数据库连接池的最大连接数(50),以免出现任务等待和堆积,导致线程池性能下降。

 

 

4. 具体建议

针对数据库查询接口的线程池配置:

● 核心线程数:20到25(根据并发量和数据库连接池限制)。

1. 最大线程数:40(核心线程数的2倍,但不超过数据库连接池的最大连接数)。

当然这个根据一般场景下预估值,具体情况还得通过压测再适当调整参数值。  @所有人

 

【每日一问:20260617】synchronized可以锁字符串吗?

●synchronized可以锁存活于字符串常量池中的值,不能锁存活于堆栈中的字符串(字符串地址要相同)

●可以使用String对象.intern()

●将该字符串放入字符串常量池中,但是常量池的回收只能依赖于fullGC,故不推荐使用推荐使用guava包下的interner类,使用弱引用的方式,在内存不足的时候自动进行垃圾回收  @所有人

 

【每日一问:20260618】Springboot的自动配置原理?--springboot面试必问题

1.引入@EnableAutoConfiguration: 在Spring Boot应用程序的主配置类(通常是带有@SpringBootApplication注解的类)中,通常会引入@EnableAutoConfiguration注解,该注解负责启动自动配置功能。

2.@EnableAutoConfiguration引入了@Import: @EnableAutoConfiguration注解实际上是通过@Import注解引入的。这意味着它会导入其他配置类,这些配置类包含了Spring Boot自动配置的逻辑。

3.解析@Import注解: 当Spring容器启动时,会解析@Import注解,并加载相应的配置。

4.deferredImportSelector: 通过@Import导入的配置类中可能包含了一个deferredImportSelector,它的作用是确保Spring Boot的自动配置类在最后加载,以便方便扩展和覆盖。

5.读取META-INF/spring.factories文件: Spring Boot通过SPI(Service Provider Interface)机制,读取类路径下的META-INF/spring.factories文件,该文件包含了各种自动配置类的配置。

6.过滤出AutoConfigurationClass: 从spring.factories文件中,Spring Boot会过滤出所有AutoConfigurationClass类型的类,这些类包含了自动配置的具体实现。

7.条件化加载: 最后,Spring Boot会根据条件(@ConditionalXXX注解)来排除或包含特定的自动配置类。这些条件会根据应用程序的环境和配置动态生效。 总结起来,Spring Boot的自动配置原理是通过@EnableAutoConfiguration注解引入自动配置逻辑,然后解析@Import注解,加载各种配置类,包括deferredImportSelector和自动配置类。通过SPI机制读取spring.factories文件,过滤出自动配置类,并根据条件化配置来动态加载这些类,从而实现自动配置的功能。这种机制使得Spring Boot应用程序可以根据环境和需求自动配置,极大地简化了开发和部署的工作。  @所有人

 

每日一问:20260621】说说Redis的过期策略和淘汰策略?

Redis的过期策略主要有三种:惰性删除、定期删除和定期淘汰。

1.惰性删除: 惰性删除是Redis默认的过期键删除策略。当客户端尝试访问一个已过期的键时,Redis会立即将该键删除,并返回空值。这种策略的优点是删除操作是在需要时进行,减少了不必要的删除开销。但是,如果大量过期键在一次性被访问之前没有被访问过,这些键会一直占据内存空间。

2.定期删除:Redis会每隔一段时间执行一次检查,删除那些已过期的键。默认情况下,Redis每秒执行10次检查。定期删除通过释放过期键所占据的内存空间,使得内存能够及时被回收。但这种方式可能会导致内存占用较高,因为Redis并不保证在每次定期删除操作中都会删除足够数量的过期键。

3.定期淘汰: 定期淘汰是Redis 4.0版本引入的一种新的过期策略。与定期删除不同的是,定期淘汰不仅删除已过期的键,而且会主动查找并淘汰一些尚未过期但是由于内存不足而需要释放的键。通过定期淘汰,Redis可以更主动地管理内存,避免因为内存持续增长而导致系统性能下降。 Redis中的内存淘汰策略用于在内存不足时选择要淘汰的键,以释放内存空间。以下是几种常见的内存淘汰策略:

  1.LRU(最近最少使用): LRU是Redis默认的内存淘汰策略。根据最近使用的时间戳来判断键的热度,将最久未被使用的键淘汰出去。这种策略保留了最近较常访问的键,适合于热点数据的场景。

   2.LFU(最不经常使用): LFU根据键被访问的频率来判断热度,淘汰访问频率最低的键。这种策略适用于访问模式稳定但不同键的访问频率差异明显的场景。

  3.Random(随机淘汰): 随机淘汰策略是一种基于概率的淘汰方法,随机选择一个键进行淘汰。这种策略简单高效,但可能导致较高的缓存命中率下降。

  4.TTL(生存时间): TTL策略基于键的过期时间,淘汰剩余生存时间最短的键。适用于关注数据实效性的场景。

  5.Maxmemory Policy(最大内存策略): Redis提供了几种最大内存策略,包括noeviction(禁止淘汰)、allkeys-lru、allkeys-random等。这些策略是在达到设定的最大内存限制后,对写操作返回错误,避免继续写入导致系统崩溃。 @所有人

posted @ 2026-03-21 13:25  OMGq  阅读(18)  评论(0)    收藏  举报