一、【每日一问:20250525】
问:一、MySQL选择InnoDB作为引擎,它有什么优势?
答:参考答案:
MySQL 默认的存储引擎是 InnoDB,这是因为 InnoDB 在性能、事务支持和容错能力等方面具有较好的特性,适合大多数应用场景。下面是一些原因:
●支持事务:InnoDB 是一个支持事务的存储引擎。事务是一组数据库操作的原子性执行,可以保证操作的一致性和完整性。
●并发控制:InnoDB 支持行级锁定, 在高并发环境下可以最大程度地减少锁冲突,提高并发性能。相比之下,MySQL 的另一个存储引擎 MyISAM 只支持表级锁定,并发性能较低。
●外键约束:InnoDB 支持外键约束,可以保证数据的完整性。外键用于建立表与表之间的连接,通过外键约束可以实现数据之间的关联和参照完整性。
●崩溃恢复:InnoDB 具有自动崩溃恢复的能力。即使在发生意外故障或系统崩溃时,InnoDB 引擎也能够自动进行崩溃恢复,保障数据的一致性。
●支持热备份:InnoDB 支持在线热备份,可以在不停止数据库服务的情况下进行备份操作。这对于需要实时运行且对数据可用性要求高的应用程序非常重要。
●需要注意的是,虽然 InnoDB 是 MySQL 默认的存储引擎,但在某些场景下,可以根据实际需求选择其他存储引擎,如 MyISAM、Memory 等。不同的存储引擎适用于不同的应用场景和需求。
二、【每日一问:20250526】
问:小明公司的数据库用的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
三、【每日一问:20250527】
问:公司创建了一张订单表(mysql数据库),关于订单金额字段类型的设计争论不休, 依你之见应该使用什么类型呢?
答:参考答案:
在 MySQL 中,记录货币值时一般使用的字段类型有:
1. DECIMAL 类型:
推荐使用:DECIMAL 类型是最适合存储货币数据的,因为它是一个定点数,有助于避免精度问题。
定义方法:通常用 `DECIMAL(M, D)` 来定义,其中 `M` 是总位数,`D` 是小数位数。例如,`DECIMAL(10, 2)` 可以储存最大值为 99999999.99 的货币数。
优点:提供高精度的数值存储,适合精确计算。
2. BIGINT 类型:
场景:在某些情况下,货币可以以最小单位存储(比如分或厘),然后使用 BIGINT 类型进行存储,这样可以避免浮点运算的问题。
优点:使用整数可以提高计算性能,避免小数精度问题。
实现方式:将货币值以最小单位进行存储,例如将 $10.23 存为 1023(以分为单位)。
3. FLOAT/DOUBLE 类型:
不推荐:这些浮点类型可能会由于精度问题导致金额计算不准确,因此不建议用于存储货币数据。
四、【每日一问:20250528】
学员的一个面试问题:一张表有500万数据,100多个字段,请问如何快速把数据查出来?
在处理大数据量和多字段的表时,优化查询性能是至关重要的。以下是一些策略,可以帮助你快速查询数据:
1. 选择必要的字段:
● 只选择你需要的字段,而不是使用`SELECT *`。这可以减少数据传输量和内存使用。
2. 使用索引:
● 确保查询条件中的列(如`WHERE`子句中的列)上有适当的索引。
● 使用覆盖索引(即索引包含所有查询的字段)可以避免回表查询。
3. 分页查询:
● 如果需要处理大量数据,考虑使用分页查询(如`LIMIT`和`OFFSET`)来分批获取数据。
● 注意:对于大偏移量的分页,`OFFSET`可能会导致性能问题,可以考虑使用“延续键”分页。
4. 优化查询条件:
● 使用高效的查询条件,避免在`WHERE`子句中使用函数或计算。
● 尽量使用等值查询而不是范围查询。
5. 数据库配置优化:
● 调整数据库配置参数,如`innodb_buffer_pool_size`,以便更好地利用内存。
● 确保数据库服务器有足够的内存和CPU资源。
6. 分区表:
● 对于非常大的表,可以考虑使用表分区,将数据按某个字段(如日期)分割成多个物理部分。
7. 缓存机制:
● 使用缓存机制(如Redis)来存储常用查询的结果,减少数据库的负载。
8. 分析执行计划:
● 使用`EXPLAIN`命令分析查询的执行计划,找出潜在的性能瓶颈。
9. 批量处理:
● 如果需要对大量数据进行批量处理,考虑使用批量操作而不是逐行处理。
五、 【每日一问:20250529】
想清理一下这张订单表(mysql数据库)历史数据,请问这三种truncate、delete、drop方式哪种更好些?有什么区别吗?
一、DELETE语句
特点:
- 可以根据条件删除部分数据
- 支持事务,可以回滚
- 删除数据时会记录日志
- 删除速度较慢
- 不会释放表空间
- 会一行一行地删除
适用场景:
- 需要删除部分数据
- 需要保留删除记录
- 需要事务支持
- 表数据量不大
二、TRUNCATE语句
特点:
- 删除速度快
- 会释放表空间
- 表的自增ID重置为1
- 不能根据条件删除
- 不支持事务回滚
- 不记录日志
适用场景:
- 需要删除全表数据
- 需要重置自增ID
- 对删除速度有要求
- 不需要保留删除记录
三、DROP语句
特点:
- 执行速度最快
- 完全释放表空间
- 删除表结构和索引
- 删除整个表定义
- 不支持事务回滚
- 需要重建表结构
适用场景:
- 需要删除整个表
- 表结构需要重建
- 不需要保留任何信息
【实用建议】
- 如果只是想清理部分历史数据: 使用DELETE,可以指定条件 示例:DELETE FROM orders WHERE create_time < '2024-01-01';
- 如果想清空整个表但保留表结构: 使用TRUNCATE,速度快且释放空间 示例:TRUNCATE TABLE orders;
- 如果要完全废弃这个表: 使用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
【总结】 选择合适的删除方式主要取决于:
- 是否需要保留表结构
- 是否需要部分删除
- 是否需要事务支持
- 是否需要删除日志
- 表的大小和业务影响
选择合适的删除方式可以大大提高效率,同时避免不必要的风险。在实际操作中,建议先在测试环境验证,确保操作安全无误后再在生产环境执行。
六、【每日一问参考答案:20250530】
生产环境需要使用模糊查询MySQL数据库的数据,根据业务场景前缀匹配和后缀匹配都可能用到, 请问这个模糊查询该如何优化?
我们实际的开发过程可能碰到这类模糊匹配的场景,模糊匹配使用不当就会导致全表扫描引发慢查询的问题。问题严重的甚至会导致系统崩溃。针对模糊匹配的优化方案,总结为以下五种,大家可以参考下:
1. 针对前缀匹配 使用 name LIKE 'prefix%'的形式,这种情况只要name建了索引,或者说使用name前缀列的联合索引,一般还是会用到索引或者部分索引。
2. 使用后缀匹配使用反向索引 对于需要匹配后缀的情况(即LIKE '%suffix'),可以创建一个辅助列存储反转字符串,并基于此列进行前缀匹配。这个时候就和第一种方式类似了。
3. 限制扫描范围 在LIKE查询中,如果可以通过其他条件进一步缩小搜索范围,请尽量加入这些条件。 SELECT * FROM users WHERE created_at >= '2023-01-01' AND username LIKE 'John%'; 4. 使用全文索引
● myISAM支持全文索引,Innodb 5.6之后也开始支持全文索引。所以针对这种情况处理方案: 针对有这类搜索的业务单独使用myISAM就可以轻松解决;
● 如果版本在5.6之后,也可以使用Innodb创建全文索引,5.7.6之后甚至开始支持中文的全文索引。 下面是关于这个全文索引的案例: -- 在创建表时添加全文索引 CREATE TABLE articles ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(255), body TEXT, FULLTEXT (title, body) ); -- 在已有表上添加全文索引 ALTER TABLE articles ADD FULLTEXT INDEX ft_index (title, body); 查询时,使用 MATCH 关键字与 AGAINST 子句来指定搜索词。 SELECT * FROM articles WHERE MATCH (title, body) AGAINST ('search terms');
5. 使用搜索引擎类中间件 对于数据量非常庞大的业务量,比如亿级数据,可以使用外部全文搜索引擎如Elasticsearch、Solr等代替MySQL的LIKE操作。
其实具体使用哪种方案,还得看你的业务场景的需要,秉持简单适用的原则就行。
七、【每日一问参考答案:20250531】
面试官问他mysql查询的时候使用where 1=1会不会影响性能?你的答案是什么呢?说说你的看法。
很多小伙伴会在where后面跟上1=1的保证语,经常看网上的八股文说1=1会影响性能, 建议用Mybatis的<where>标签。两种方案,该如何选择?
不会影响性能,优化器会自动给移除1=1恒真条件
● 如果 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以上了。
八、【每日一问:20250601】
MVCC的原理是什么?谈谈你的理解!(热点面试题)
【每日一问参考答案:20250601】MVCC的原理是什么?谈谈你的理解!(热点面试题)
关于MVCC的原理可以理解如下:
(1)undolog多版本链。MVCC通过为每行数据项维护多个版本来实现并发控制。当一个事务对数据进行修改时,它不会直接覆盖现有的数据,而是创建一个新的版本。这些不能数据版本形成了单向版本链
(2) readview视图。
readview组成:
当前活跃的事务ID数组
最小事务min_id
最大事务max_id
当前事务creator_trx_id
(3) readview创建时机
● START TRANSACTION WITH CONSISTENT SNAPSHOT; 立即创建快照
● Start Transaction 对于RR来说第一个select语句,对于RC来说每条select语句都会创建。
(4)可见性规则
● 版本的事务ID小于min_ids:该版本在Read View创建之前已经提交,因此对当前事务可见。
● 版本的事务ID大于max_id:该版本在Read View创建之后生成,因此对当前事务不可见。
● 版本的事务ID在m_ids和max_id之间:如果版本的事务ID不在活跃事务列表中,则该版本已经提交,对当前事务可见。如果版本的事务ID在活跃事务列表中,则该版本尚未提交,对当前事务不可见特别备注: 如果是当前事务creator_trx_id操作的数据,也是可见的!!
(5)
快照读:查询select语句属于快照读
当前读: 删除更新类语句属于当前读,读的是数据库已经提交的数据
(6)select数据是如何可见或者不可见的?
从undolog版本链的头部开始遍历,根据每个数据版本的事务ID,结合readview以及可见性的规则,来判定数据是否可见。
九、【每日一问:20250603】
问:Mysql(Innodb引擎)加索引的时候会锁表吗?
【每日一问参考答案:20250603】Mysql(Innodb引擎)加索引的时候会锁表吗?
回答这个问题需要搞清楚以下几个技术点:
1、 MDL元数据锁。DDL操作加的MDL写锁,DML操作加的MDL读锁。 读读之间是并行,其他都会有冲突。所以加索引这类操作从正常来看加的MDL写锁。
2、 Online DDL 。从 MySQL 5.6 开始,InnoDB 支持Online DDL操作。意味着5.6之前DDL操作加MDL写锁,那就是锁表了,其他数据库操作都做做不了。5.6之后,会有所不同。
3、 添加索引的Online DDL的过程。
(1)建立临时索引文件,扫描原始表索引。
(2)原始索引复制到临时索引中去,同时记录原表的更改操作记录
(3)将操作更改记录应用到 临时索引中
(4)切换索引
(5)删除旧索引 释放资源
4、 添加索引的最开始加的MDL写锁,再开始复制数据到临时文件的时候就会降级为MDL读锁,这个加锁的过程很短。降级为读锁之后,是可以允许DML操作了。 所以对我们来说是Online DDL.
5、 虽然Online DDL是可以允许DML增删改查操作。但是创建索引时,涉及到扫描原表数据和构建临时索引,这里有大量的IO操作。如果是大表的是话,还是会有性能影响,不是万能的。
综上,我们在生产环境做DDL操作的时候一要看数据库的版本是否支持Online DDL;二要看业务情况. 如果数据库支持Online DDL, 在生产环境业务不忙的时候可以做这类DDL操作,同时还能允许增删改查,一定程度上确实提高了数据库的并发性和可用性。 你是不是学废了。
十、【每日一问:20250604】假如今天不小心把数据库删了,领导让你把数据恢复出来,请问应该怎么做?
如果因为某些原因误删误删数据或者数据库,需要面对如何快速恢复问题。关于这个问题的解决总结如下:
一、恢复数据的方法:
(1). 误删数据表或数据库:
使用 `DROP TABLE`、`TRUNCATE TABLE` 或 `DROP DATABASE` 误删数据时,binlog 记录的是 statement 格式,可能不能通过 binlog 恢复。
恢复方法:依赖全量备份和增量日志。这个需要定期的全量备份和实时备份的 binlog。
(2) 误删数据行:
使用 `DELETE` 语句误删数据行,可以通过 Flashback或者美团闪回工具Myflash进行恢复数据。
美团Myflash详细介绍:
https://github.com/Meituan-Dianping/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) 为了防止集群集体删除的极端情况,我们做数据库备份的时候有条件尽量选择跨机房或者跨城市都可以,提高数据库可用性,降低数据丢失的损失。
十一、【每日一问:20250606】 开放性讨论题:你知道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。
十二、【每日一问:20250607】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
十三、【每日一问:20250608】说说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设置
十四、【每日一问:20260609】ThreadLocal为什么会导致内存泄漏?如何解决的?
【每日一问参考答案:20260609】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)监控应用程序的内存使用情况,及时发现和解决内存泄漏问题。
十五、【每日一问:20250610】谈谈什么是三色标记?
【每日一问参考答案:20250610】谈谈什么是三色标记?
三色标记法是一种用于垃圾回收算法中的对象标记方法,特别用于标记-清除型垃圾回收器。这种方法通过使用三种颜色(白色、灰色和黑色)来跟踪对象的可达性和垃圾回收状态,以避免对象的重复回收和丢失。
三色标记的基本概念
● 白色:表示对象尚未被检查。白色对象可能是垃圾,直到证明它们是可达的。
● 灰色:表示对象被检查过,并且其本身是可达的,但其引用的对象还未全部检查。
● 黑色:表示对象和它所有引用的对象都已检查且是可达的。
三色标记步骤
1. 初始化:所有对象开始时都是白色。
2. 标记开始:从GC Roots开始,根对象标记为灰色。
3. 扫描灰色对象:
● 将灰色对象引用的所有白色对象标记为灰色。
● 然后将该灰色对象标记为黑色。
4. 重复步骤3直到没有更多的灰色对象。
5. 清除:未标记为黑色的对象为白色,即垃圾,可被回收。
使用三色标记的好处
● 避免循环引用:能够正确识别循环引用对象不被误判为不可回收。
● 并发性增强:支持并发标记,减少“Stop-the-World”时间。
● 渐进性:可以执行增量垃圾回收,减小程序暂停。
在实际的垃圾回收算法实现中,如G1和CMS,三色标记法被广泛应用,用于高效地管理内存和提高程序运行的性能。
@所有人
十六、【每日一问:20250611】说说你们生产环境遇到的哪些JVM内存问题?是怎么解决的?
【每日一问参考答案:20250611】说说你们生产环境遇到的哪些JVM内存问题?是怎么解决的?
这是面试热门的项目场景问题,大家可以举一些自己项目中出现过的一些JVM相关的问题进行阐述。讲这个场景问题的方法是: 问题背景,问题原因,解决方案以及最后问题最后出来结果。要求言简意赅,有具体数据和场景做支撑。 我这里列出一些常见的生产环境关于JVM内存问题的场景点:
1. 关于内存泄漏。
● 线程池使用ThreadLocal,这个每日一问有讲过。解决思路手动remove.
● 集合存储数据,没有手动清空。解决思路 weakHashmap或者手动清理内存。
2. 内存溢出。大数量的文件读写,产生大对象导致内存溢出,比如10G大文件,2G内存怎么出来?解决思路文件切片,Stream流处理等。
3. 频繁full GC问题。一个高并发的系统,由于老年代内存不足,导致频繁的 Full GC。解决思路就是:
● 调整堆内存配置,增加老年代的内存大小。
● 使用 CMS 或 G1 垃圾回收器来减少 Full GC 的频率和停顿时间。
● 优化对象的生命周期,减少不必要的对象创建。
4. 线程池参数不合理导致的阻塞队列过载,引发内存溢出的问题。
解决方法:使用合适的线程池配置,限制最大线程数。
定期监控线程池的状态,及时发现和处理异常线程。
5. 内存碎片的问题。批处理调度,频繁创建和释放大对象,导致内存碎片化。
解决方法:使用 G1 垃圾回收器,它具有更好的内存压缩能力。
● 调整堆内存配置,增加连续内存块的大小。
● 尽量重用大对象,减少频繁的分配和释放
做银行项目时需要对接政务征信系统,其中遇到了一个这样的JVM内存问题:
问题:大JSON解析导致的老年代堆积问题
在对接征信系统时,接口返回的JSON数据平均大小达到5-8MB,一开始使用JSON.parseObject()
全量解析方式会导致:老年代内存每周增长15-20%、频繁触发Full GC、90%的GC时间消耗在标记阶段
解决方案:1)改成JsonReader流式解析,内存占用下降60%
// 改造后(流式处理)
try (JsonReader reader = new JsonReader(new StringReader(jsonString))) {
reader.beginObject();
while (reader.hasNext()) {
String field = reader.nextName();
switch (field) {
// 字段...
}
}
reader.endObject();
}
2)并且配合JVM参数调整:
-XX:+UseG1GC
-XX:G1HeapRegionSize=8M #避免大对象分配时跨Region(减少内存碎片)
-XX:InitiatingHeapOccupancyPercent=40
-XX:G1NewSizePercent=40 # 增大新生代防止大对象直接进入老年代
十七、【每日一问:20250612】Redis常用的数据类型有哪些?Zset的底层数据结构是什么?
补下昨天的每日答案
【每日一问参考答案:20250612】Redis常用的数据类型有哪些?Zset的底层数据结构是什么?
Redis常用数据类型面试问的毕竟多,常问的是数据类型,还有问string和zset的原理。参考答案如下:
String(字符串) - 最基本类型,存储文本、数字、二进制数据; 底层底层数据结构SDS。
Hash(哈希) - 键值对集合,适合存储对象
List(列表) - 有序字符串列表,支持双端操作
Set(集合) - 无序唯一元素集合,支持集合运算
1.Zset(有序集合) - 按分数排序的唯一元素集合
(1)Zset底层数据结构
核心结构:跳跃表 + 哈希表
跳跃表(Skip List):维护有序性,支持O(log n)的范围查询
哈希表(Hash Table):存储元素→分数映射,支持O(1)的分数获取
(2)内存优化结构
a.Redis 7.2之前:压缩列表(ZipList)。元素少时使用ZipList节省内存 。存在级联更新问题,影响性能 。
b.Redis 7.2之后:列表包(ListPack),替代ZipList,解决级联更新问题 。
优势: 避免连锁更新,插入/删除性能更稳定
支持从尾部遍历,提升某些操作效率
内存布局更优化
触发条件(默认配置): 元素个数 ≤ 128 且 每个元素长度 ≤ 64字节 ;超过阈值自动转换为跳跃表+哈希表 @所有人
Redis常用的数据类型有哪些?Zset的底层数据结构是什么?
Redis常用的数据类型有:
1、String(字符串)常用于单值缓存、对象缓存分布式锁等场景
2、Hash(哈希):键值对集合,适合对象缓存、电商购物车等场景;Hash将同类数据归类整合储存,方便数据管理
相比String操作消耗内存与cpu更小、储存更节省空间
3、List(列表):有序可重复的字符串集合,常用于视频列表 、签到列表、排队机等场景
4、Set(集合):无序且唯一的字符串集合,常用于抽奖小程序,微信微博点赞、 收藏、 标签,以及共同关注的人、朋友圈、推荐好友 等场景
5、Zset(有序集合):有序且唯一的字符串集合,Zset集合操作常用于实现排行榜等场景
Zset的底层数据结构:
Zset的底层实现采用了两种数据结构的组合:跳跃表(Skip List) + 哈希表(Hash Table)
跃表维护有序性,支持高效的范围查询(ZRANGE等),哈希表保证快速判断成员是否存在(ZSCORE等)
两者结合既保证了有序性又保证了查询效率
十八、【每日一问:20250613】为什么都说Redis是高性能的?说一下你的理解?
【每日一问:20250613】
问:为什么都说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)
● 二进制安全的字节流处理
十九、【每日一问:20250614】
问:如果你的生产环境Redis实例CPU使用很高,比如达到90%以上,请问可能产生的原因有哪些? 如何解决?
引发Redis实例CPU飙高的因素可能有:
● 使用高风险的Redis命令。例如KEYS、HGETALL或使用MGET、MSET、HMSET、HMGET一次操作大量Key等。通常情况下,命令的时间复杂度越高,在执行时会消耗越多的资源,从而导致CPU使用率上升。由于命令执行单元为单线程的特性,Redis在执行高消耗命令时会引发排队导致应用响应变慢。极端情况下,甚至可能导致实例被整体阻塞,引发应用超时中断或流量跳过缓存层直接到达后端的数据库侧,引发雪崩效应。
● 热Key:某个或某部分Key的请求访问次数显著超过其他Key时,代表此时可能产生了热Key。热Key将会消耗Redis的大量CPU资源,从而影响其他Key的访问时延。并且,在集群架构中,如果热Key较为集中地分布在部分数据分片节点,可能会导致CPU使用率倾斜(个别分片的CPU使用率远超其他分片)。
● 大Key:大Key会占用更多的内存,同时,对大Key的访问会显著增加Redis的CPU负载和流量。大Key在一定程度上更容易形成热点从而造成CPU使用率高。如果大Key较为集中地分布在部分数据分片节点,可能会导致CPU使用率倾斜、带宽使用率倾斜及内存使用率倾斜。
● 频繁建立短连接:频繁地建立连接,导致Redis实例的大量资源消耗在连接处理上。
● AOF频繁写磁盘:AOF的写盘行为将会导致CPU使用率升高及实例整体的响应时延增加。
● Lua 脚本执行:
运行复杂或长时间的 Lua 脚本可能导致 CPU 使用率升高。
● 慢查询:
某些查询操作耗时较长,导致 Redis 处理效率下降。
如何解决:
● 首先是要有Redis监控平台,能够及时预测问题发生,也能提供问题排查的数据以及日志。
● 禁用高风险命令和高消耗命令,例如FLUSHALL、KEYS、HGETALL等。对于这些命令上线需要评估和审核。,非必要可以禁用。
● 尽量避免通过短连接访问Redis,推荐使用线程池连接,比如Lettuce,Jedis等客户端。
● 排查并优化大Key,根据业务的实际情况,将大Key拆分为小Key,以分散请求压力。
● 调整写磁盘的频率,在访问高峰期可以暂时禁止写日志,在流量不高再开启写磁盘.不过这个要相机行事,存在丢失数据的风险
● 在硬件资源也可以做一定的优化,推荐使用 SSD(固态硬盘)可以显著提高 Redis 写入磁盘的性能
二十、【每日一问:20250615】 redis集群有几种模式?分别讲讲这些集群模式的基本原理是什么?
@所有人
【每日一问参考答案:20250615】 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. 客户端通过哨兵获取最新的主节点地址。
根据业务需求(如数据量、性能、复杂度)选择合适的集群模式。
@所有人
二十一、【每日一问:20250616】如何处理Redis集群数据倾斜问题?
【每日一问参考答案:20250616】如何处理Redis集群数据倾斜?
Redis数据倾斜问题也是面试热门场景技术题。在正常情况下,各数据分片节点的Key数量是均匀分布的,同时内存使用率、CPU使用率等性能指标也是相近的。一般是在使用Redis的过程中,设计考虑不周、不规范的数据写入及突发的访问量,造成redis个别的节点数据量倾斜或数据访问倾斜,最终引起数据倾斜。常见倾斜场景有: (1)内存倾斜。一般由于大key问题或者使用hash Tages集中到某个节点。
(2)带宽倾斜。大key 热key 高消耗命令造成 访问节点占用带宽
(3)CPU倾斜。大key 热key 高消耗命令造成CPU使用率偏高
明白了场景和问题原因之后,解决方案主要有:
(1)减少大key的使用,或者对大key进行拆分成多个 hash key
(2) 禁止使用高消耗命令,或者流量高峰期禁止使用。这个我们之前有讲过。
(3) 减少Hash Tags的使用
(4) 增加本地缓存,减少热key对Redis的压力
(5) 提高内存配置和带宽等资源,从硬件角度减少数据倾斜带来的问题
@所有人
二十二【每日一问:20250617】Redis内存使用率在95%以上,请问是什么原因?
Redis内存飙高的问题在生产环境屡见不鲜,这个也是面试试重点考察的技术,经常是和我们项目场景有直接关系。
内存使用率在生产环境一般会有三种场景:
(1)一直都很高 比如95%以上
(2)突然飙高,一般和瞬时流量有关,内存使用率达到100%
(3)只是某个节点飙高 ,内存使用率达到100%
场景不一样,问题的原因也可能不一样,解决方案也会不一样。具体的问题原因以及解决方案如下:
● 内存使用率长期处于高水位的解决办法
(1)盘点下key的使用情况,清理掉无用的key
(2) 看下key是否设置了合适的TTL策略,避免一直存在redis中
(3) 检查是否有大key,大key很多的话,就会占用很多内存
(4) 根据业务需求,设置合理的数据逐出策略,主要是调整maxmemory-policy参数的值。关于逐出策略参考官网:https://redis.io/blog/cache-eviction-strategies/
(5) 根据业务需求,设置合理的过期Key主动删除的执行频率(即调整hz参数的值)。这个大家可以了解下 https://redis.io/docs/latest/operate/oss_and_stack/management/config-file/
(6) 经过上述步骤优化后,内存使用率依旧较高,可以考虑从硬件配置上入手加大内存。
● 内存使用率突然上升的解决办法
问题原因
内存使用率突然升高一般和瞬时并发流量有关,具体表现为:
(1)短时间内大量写入新数据。
(2)短时间内大量创建新连接。
(3)突发访问产生大量流量超过网络带宽,导致输入缓冲区和输出缓冲区积压。
(4)客户端处理速度跟不上Redis的处理速度,导致输出缓冲区积压。
具体解决方案有:
(1)Redis性能监控的入流量与写QPS一致的话,也就是说流量可以承接,可能是写数据太大导致,这个需要升级下redis内存配置,也可以清理下无效的key,腾出更多的空间。
(2)如果连接数突增的话,首先排除链接是否泄漏,正常关闭;或者设置连接超时时间(timeout 参数),自动关闭空闲连接
(3)执行MEMORY STATS命令,看下clients.normal占用的内存是否过多。如果是的话,排查业务流量突发原因;或者提升带宽。
(4)执行MEMORY DOCTOR命令,查看big_client_buf的值。当big_client_buf=1时,代表至少有一个客户端的输出缓冲区占用内存较大。执行CLIENT LIST命令,查看哪个客户端的输出缓冲区内存占用量(omem)较大。排查该客户端应用是否存在性能问题。
● 单个节点内存飙高
这种情况一般都是hash到这个分片大key或者热点key 有关系,一般解决办法是:
(1)拆分key,分散单个节点流量,使数据更加均匀地分布在不同的数据分片节点上。
(2) 增加redis实例的内存,进行扩容。
二十三【每日一问:20250618】什么是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,当然如果有第三方监控平台也是可以的,比如Redis-rdb-tools。
(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)做好系统的监测,建立预警机制,提前做好防范。
@所有人
什么是Redis的大Key和热Key?你们的项目一般是怎么解决的?
1.大Key:单个Key存储的Value过大,超过Redis设计的最佳实践阈值(通常认为String > 10KB,Hash/List/Set/ZSet > 1000个元素)
在银行项目中通常在客户征信报告原始JSON(单个String达50KB+)中
大Key解决方案:征信报告分片存储,例如解析JSON按章节分片存储
2. 热Key:被高频访问的Key,会引发缓存击穿、单节点CPU负载过高.
例如:月末季度末某产品高频访问
解决方法:多级缓存架构
二十四、【每日一问:20250619】如何保证Redis缓存和数据库数据的一致性?
保证Redis缓存和数据库数据的一致性是面试的热门场景题,也是很多项目经常面临的问题。关于一致性方案很多,比较常见的如下:
1. Cache Aside Pattern(旁路缓存模式):
读取操作:先从缓存中读取数据。
如果缓存未命中,则从数据库中读取数据,并将数据写入缓存。
写入操作:更新数据库中的数据。
成功更新数据库后,再删除或更新缓存中的数据。
这种模式的关键在于确保数据库更新成功后再更新缓存,以避免缓存中的数据过时。
2. Write Through Cache(写直达缓存):
所有写操作同时更新数据库和缓存。这种方法可以确保缓存和数据库的一致性,但可能会增加写操作的延迟。
3. Write Behind Cache(写回缓存):
写操作首先更新缓存,然后异步地将数据写入数据库。这种方法可以提高写操作的性能,但在系统崩溃时可能导致数据丢失。
4. 使用消息队列:
在数据库更新后,发送一条消息到消息队列,消费者监听队列并更新缓存。这种方法可以解耦数据库和缓存的更新操作,并提高系统的可扩展性。
5. 分布式锁:
在更新数据库和缓存时使用分布式锁(如Redis的Redlock)来确保操作的原子性,避免并发写入导致的不一致。
6. TTL(Time to Live)策略:
为缓存数据设置TTL,确保数据在一段时间后自动过期,从而减少缓存和数据库不一致的时间窗口。
7. 延时双删:
● 对于不可变操作(读取):
缓存命中:直接从 Redis 返回数据,不需要查询 MySQL;
缓存未命中:查询 MySQL 获取数据(可以使用只读副本来提高性能),将返回的数据保存到 Redis,将结果返回给客户端。
● 对于可变操作(创建、更新、删除):
删除Redis中的条目;
向MySQL创建、更新或删除数据;
睡眠一段时间(比如500ms);
再次删除Redis中的条目。
8. 定期同步:
定期从数据库同步数据到缓存,确保缓存中的数据与数据库保持一致。
每种方法都有其优缺点,选择合适的方法需要根据具体的应用场景、性能要求和一致性需求来决定。在实际应用中,可能需要结合多种方法来实现最佳效果
9、 通过Cannal订阅Binlog日志,然后同步到Redis。这种场景也是很多互联网项目使用比较多的解决方案。
关于缓存与DB一致性的方案有很多,没有一种方案是银弹。究竟选择哪一种这要看业务场景,开发维护成本以及公司战略层面等诸多因素进行综合考虑。
二十五、【每日一问:2025620】 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 在提供高性能服务的同时,也保持良好的可扩展性和易维护性。
二十六【每日一问:20250621】
问:分布式锁的特性是什么?如何实现分布式锁?
分布式锁的特性和实现方法如下:
特性
1. 互斥性:在任何时刻,只有一个节点可以持有锁,确保资源的独占访问。
2. 不会发生死锁:如果一个节点崩溃,锁可以被其他节点获取,避免死锁。
3. 公平性:如果多个节点同时申请锁,系统应该保证每个节点都有获取锁的机会。
4. 可重入性:同一个节点可以多次获取同一个锁,而不会被阻塞。
5. 高可用:锁服务应该是高可用的,不能因为锁服务的故障而影响整个系统的运行。
实现方法
1. 基于 Redis:
使用 SETNX 命令来实现锁,确保在同一时间只有一个客户端能够获得锁。
使用 EXPIRE 命令为锁设置过期时间,避免死锁。
使用 Lua 脚本确保在释放锁时检查锁的持有者。
RedLock 算法提供了更高的安全性和容错能力。
2. 基于数据库:
创建一个锁表,表中包含锁的名称和状态。
节点通过插入或更新操作来获取锁。
优点是实现简单,但性能较低。
3. 基于 Zookeeper:
使用临时节点作为锁。
节点创建临时节点来获取锁,使用完后删除节点。
如果节点崩溃,Zookeeper会自动删除临时节点,避免死锁。
4. 基于 Etcd:
创建一个带有TTL的键值对来实现锁。
节点创建键值对来获取锁,使用完后删除。
如果节点崩溃,Etcd会自动删除键值对,避免死锁。
选择具体的实现方式需要根据应用场景、性能需求和一致性要求来决定。
二十七、【每日一问:20250622】说说生产环境分布式锁的常见问题和解决方案?
1. 死锁问题
● 问题:当一个客户端获取了锁,但由于某些原因(如程序崩溃、异常等)无法释放锁时,会导致其他客户端永远无法获取锁。
● 解决方案:
设置锁的过期时间。当锁的持有者未能在过期时间内执行完毕并释放锁时,锁将自动过期,从而允许其他客户端获取锁。
2. 锁续命问题
● 问题:如果一个操作需要的时间可能超过锁的过期时间,那么在操作执行过程中锁过期会导致其他客户端获取到锁,从而产生并发问题。
解决方案:
使用锁续命机制。在锁持有者执行操作期间,可以定期检查锁是否即将过期,并在适当的时候对锁进行续命,即重新设置锁的过期时间。
3. 锁释放问题
● 问题:为确保数据的一致性,只有锁的持有者才能释放锁。但在实际应用中,可能会出现误解锁的情况。
● 解决方案:
在设置锁时,为锁关联一个唯一的值(如UUID)。在释放锁时,先检查锁的值是否与当前客户端的值匹配,如果匹配则释放锁,否则不做任何操作。注意,锁持有人的判断和锁的释放应该在一个原子操作内完成。
4. 锁的公平性问题
● 问题:在高并发环境中,如果多个节点同时请求获取锁,可能会出现“饥饿”现象,即某些节点长时间无法获取到锁。
● 解决方案:
引入队列,将请求锁的节点按照顺序排队。例如,在Zookeeper中,可以使用顺序节点来实现公平锁。
5. 锁的可重入性问题
● 问题:在某些场景中,一个节点可能需要多次获取同一个锁,如果锁不支持重入,可能会导致死锁。
● 解决方案:
为锁添加一个拥有者的概念,只有锁的拥有者才能再次获取到锁。例如,在Redis中,可以将锁的值设置为节点的唯一标识,获取锁时检查锁的值是否为自己的标识。
6. 锁的安全性问题
● 问题:如果分布式锁的存储系统(如Redis、Zookeeper等)出现故障,可能会导致锁无法正常工作。
● 解决方案:
使用高可用的存储系统,如使用Redis集群或Zookeeper集群。另外,可以使用心跳机制来检测存储系统的状态,如果检测到故障,可以及时进行切换。
二十八、【每日一问:20250623】Redis事务是如何实现的?它和关系型数据库的事务区别是什么?
在 Redis 中事务是通过 MULTI/EXEC 命令实现,相对简单,有几个特点:
1. 命令队列化
在执行事务时,Redis 会先通过 MULTI 命令打开事务,之后的所有命令会被放入事务队列中,直到遇到 EXEC 命令才会一次性地执行这些命令。
2. 不支持传统“部分回滚”
如果事务执行过程出现错误,例如命令语法错误,Redis 只会跳过有问题的命令继续执行后续命令或整个事务会中断。然而,并不会像关系型数据库那样进行部分的自动回滚。要么所有命令都执行成功(有语法错误时跳过该条命令),要么在 EXEC 前可以通过 DISCARD 取消整个事务。
3. 原子性保障来自单线程模型
Redis 的原子性更多是由单线程模型保证:当一个事务内的多条命令开始执行后,不会被其他客户端的命令插队。但严格来说,Redis 并没有像关系型数据库那样的 MVCC、多版本控制、隔离级别等完整事务机制。
4. 乐观锁(WATCH 机制)
Redis 提供了类似乐观锁的功能:通过对键进行 WATCH,可以监控在事务执行前该键是否被修改,如果修改了则在 EXEC 时拒绝这次事务。这在一定程度上为数据的并发写入提供了约束,但它依然不等同于关系型数据库的复杂锁机制。
与关系型数据库的事务区别
• 关系型数据库一般通过 ACID(原子性、一致性、隔离性、持久性)来严格保证事务,拥有锁机制、隔离级别、多版本并发控制(MVCC)等特性。
• Redis 主要通过单线程和乐观锁 WATCH 来实现简化的事务模型,并不支持自动的“回滚”操作,也没有复杂的隔离级别,更多适合场景是“轻事务”与快速操作。
• 因为 Redis 并非专门为复杂事务设计,通常在需要强一致性的场景中,还是会选用关系型数据库作核心事务处理,而 Redis 多用于缓存、高速读写和简单的原子性操作场景。
二十九【每日一问:20250624】说说REDIS集群一般在什么情况下会导致整个集群不可用?
Redis 集群在以下情况下可能导致整个集群不可用:
1. 多个主节点同时故障:如果多个主节点同时发生故障,而且它们的从节点无法正常升级为新的主节点,那么整个集群将无法提供读写服务。
2. 集群管理节点故障:集群管理节点负责监控集群状态和协调故障转移操作。如果集群管理节点发生故障,并且无法及时恢复或替换,那么集群将失去管理和协调能力,可能导致集群不可用。
3. 网络分区:如果集群中的节点之间发生网络分区,即无法互相通信,那么可能会引起脑裂(split-brain)问题。在这种情况下,每个分区内的节点可能会认为自己是合法的 Redis 集群,导致数据冲突和不一致性,最终导致整个集群无法正常工作。
4. 配置错误:如果 Redis 集群的配置出现错误或者某些节点的配置不一致,可能导致集群无法正常运行。
5. 内存不足:如果集群中的某个节点的内存不足以容纳当前处理的数据量,可能会导致该节点性能下降甚至崩溃,从而影响整个集群的可用性。
为避免整个集群不可用,建议采取以下措施:
1. 配置正确的主从复制和故障转移机制,确保每个主节点都有足够的从节点,并定期进行故障转移测试。
2. 部署多个独立的集群管理节点,以确保高可用性和决策一致性。
3. 定期检查和监控集群配置,确保各个节点之间的配置一致性。
4. 实施网络分区容忍策略,例如使用网络拓扑结构和分布式一致性协议,以减少脑裂问题的发生。
5. 监控集群节点的内存使用情况,及时扩容或优化内存管理,避免内存不足问题。
三十【每日一问:20250625】电商系统每天订单1000+,订单表可能递增到上千万,现在要导出全部的订单数据,有没有什么好的解决办法,解决导出慢和内存溢出的情况?
【每日一问参考答案:20250625】电商系统每天订单1000+,订单表可能递增到上千万,现在要导出全部的订单数据,有没有什么好的解决办法,解决导出慢和内存溢出的情况?(同学昨天面试滴滴的场景面试题)
电商系统大数据量订单导出的解决方案
典型的大数据处理方案,面试问得多,适合数据量大项目(如何金融,航空,数据处理等业务系统),参考方案:
1. 分批次异步导出
分页导出:按照ID或时间范围划分,每次导出固定数量(如5000-20000条). 如果按照ID,要避免深分页的问题,分批查询条件需要带上ID;如果按照时间导出,可以考虑按照日期进行表分区,减少查询扫描的数据总量。
任务队列:使用消息队列将导出任务拆分,提高任务吞吐量以及并行处理性能,也能在出现异常可以重试。
进度追踪:建立任务状态表,记录每个导出批次的完成情况。对于常态化且数据量这么大的任务,需要实时监控任务执行情况,在出现任务能够第一时间介入处理。
当然如果有条件可以引入任务调度系统进行处理。
2. 流式处理(主要思想是边读边写)
流式写入:采用流式写入文件(如Java的StreamingOutput)
增量写入:边查询边写入,减少内存占用
3. 服务端文件处理(主要分片写文件)
分片存储:将导出文件按批次生成多个文件。因为这么大数据量可能文件大小在几个G以上,如果同时写一个大文件,基本上会有性能问题。
是否考虑压缩:后台压缩为ZIP文件减小体积,这个可以适当考虑,主要是考虑文件下载的性能。
4. 技术选型优化
轻量级导出格式:尽量考虑使用文本文件,减少内存占用
专用ETL工具:数量特别大或者导出涉及复杂业务处理,可以考虑。如果简单导出,则不必要。
数据库优化:添加适当索引,优化导出SQL,避免全表扫描 。
5. 基础架构提升
独立导出服务:将导出功能独立部署,不影响主业务,同时考虑能够扩展和扩容。以免后期业务量加大,性能优化更加方便。
读写分离:如果是主从架构的,建议从从库读取导出数据,不影响主库性能
当然除此之外,可能考虑是否需要做数据归档,冷热分离,上大数据一套方案? 这个看具体业务场景和架构团队需要,总之跟随业务和公司需要进行调整即可!@所有人
三十一、【每日一问:20250626】说说RocketMQ的主要特性有哪些?在业务场景中作用是什么?
RocketMQ的主要特性
1. 高吞吐量:RocketMQ可以处理高流量、低延迟的数据流,非常适合金融、电子商务等高并发场景。
2. 高可靠性:通过消息持久化、多副本机制,RocketMQ确保消息的高度可靠性。
3. 灵活的消息模型:支持多种模型,包括点对点和发布/订阅。
4. 支持顺序消息:可以保证在同一主题内,消息的消费顺序。
5. 事务消息:支持分布式事务,确保消息与数据库操作的一致性。
6. 丰富的消息过滤机制:支持基于Tag和属性的消息过滤。
应用场景:
1. 系统解耦:微服务架构中,服务间通信通过消息中间件实现解耦,降低服务间的相互依赖。
2. 异步处理:非实时处理任务通过消息队列异步执行,提高系统响应速度。
3. 流量削峰:在高并发情况下,消息队列可以缓冲流量,防止流量骤增导致系统崩溃。
4. 事件驱动架构:实现复杂业务流程的事件驱动,推动业务中各个阶段的事件流转。
5. 日志处理:收集和处理日志、监控数据等场景,尤其适合大规模数据的实时处理。