Java开发面试题

本文探讨了Redis事务的ACID特性,特别是隔离性和持久性,以及如何排查Redis中的慢查询问题,包括大key、内存限制和分布式锁的设计。还涉及了MySQL的日志类型、主从延迟解决方案和Zookeeper的一主多从设计,以及ThreadLocal可能导致的内存泄露和幂等性概念的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、Redis事务是否满足ACID?

Redis的事务

实现方式:multi(开启事务,将命令都放进队列里),EXEC(执行事务),DISCARD(取消事务,清空队列)。
不支持回滚:在语法正确的情况下,Redis事务一定会执行成功。只有语法错误时才会导致事务失败,而语法问题应该在开发时就避免,所以为了提高性能,Redis事务不支持回滚。事务是一个原子操作,要么全部执行,要么全不执行。不完全满足ACID特性:Redis只满足隔离性和持久性,不满足原子性和一致性。

原子性:事务的所有操作,要么全部成功,要么全部失败。Redis不满足原子性,单个 Redis 命令的执行是原子性的,但事务失败后无法回滚。
一致性:事务前后,数据库的约束没有被破坏,保持前后一致。Redis连约束这个概念都没有。
隔离性:操作同一资源的并发事务之间相互隔离,不会互相干扰。Redis满足隔离性,因为redis server是单线程的,串行化执行事务,肯定是满足隔离性的。
持久性:事务的结果最终一定会持久化到数据库,宕机等故障也无法影响。Redis在开启aof并指定立刻持久化命令时,满足持久性。rdb模式会丢失部分数据,不满足持久性。

2、如何排查Redis中的慢查询?慢查询原因主要分为以下几个

1、大key导致淘汰耗时长:Redis实例里存了大key(string类型值大于10kb、其他类型元素个数超过5000个)。大key在申请内存和释放内存时,都比较耗时。
解决办法:传输类瘦身(去除冗余字段、@JsonProperty缩短字段名)、Redis 4.0开启懒释放机制(lazyfree-lazy-user-del)使删除key在后台进行。

2、内存上限导致写时数据淘汰:Redis实例设置了内存上限maxmemory,并且内容已满,导致每次写数据时都要淘汰数据,从而影响查询性能。
解决办法:加大maxmemory和服务器内存。

3、集中过期:定期删除策略占用内存和性能导致查询变慢。Redis采用惰性+定期的过期淘汰策略,如果大量key过期,Redis会不断重复用贪心算法打捞删除key,直到过期比例低于25%。
解决办法:过期时间加随机值、Redis 4.0开启lazy-free机制。

4、开启内存大页导致CopyOnWrite耗时:操作系统常规分配的内存页是2KB,开启内存大页后就支持分配2M的内存页。Redis在bg save时如果有更改请求,主进程不会立刻改数据(防止子线程写rdb时混乱),而会采用CopyOnWrite(写时复制)技术拷贝一份待更改数据到内存大页(如果开启了的话)里,再进行更改。拷贝过程会耗费内存和性能。
解决办法:关闭了内存大页,将只拷贝到2KB的常规内存页。

5、网络带宽过载:Redis的瓶颈是内存大小和网络带宽,网络过载直接导致Redis读写性能差。
解决办法:提高网络带宽、排除不必要的网络IO。

慢查询解决步骤:

慢查询报警:监控工具(如普罗米修斯+Grafana)监控到慢查询报警;
检查慢查询日志:慢查询阈值:slowlog-log-slower-than,默认10ms。慢查询日志存储记录的最大数量:slowlog-max-len;
排查大key:bigkeys命令查看各类型最大的key
排查服务器、配置原因:检查服务器内存量和带宽负载、Redis最大内存、大key、操作系统是否开启了内存大页

3、锁

需要锁的条件:
1、多任务环境下(进程,线程)
2、任务都对同一共享资源进行写操作。
3、对资源的访问是互斥的。

一、分布式锁

1、最简单的实现方式,直接用Redis的setnx命令,这个命令的语法是:
setnx key value

2、问题:如果获取锁的服务挂掉了,那么锁就一直得不到释放,需要一个超时来兜底
命令:set key value nx ex seconds

3、问题:会存在服务A释放掉服务B的锁的可能
服务A获取了锁,由于业务流程比较长,或者网络延迟、 GC卡顿等原因,导致锁过期,而业务还会继续进行。这时候,业务B已经拿到了锁,准备去执行,这个时候服务A恢复过来并做完了业务,就会释放锁,而B却还在继续执行
在真实的分布式场景中,可能存在几十个竞争者,那么上述情况发生概率就很高,导致同一份资源频繁被不同竞争者同时访问,分布式锁也就失去了意义
解决:分布式锁需要满足谁申请谁释放原则,不能释放别人的锁,也就是说,分布式锁,是要有归属的
方案:引入 Lua

问题:
到这一步其实还存在一个小问题,我们完整的流程是竞争者获取锁执行任务,执行完毕后检查锁是不是自己的,最后进行释放。流程一梳理,你们肯定明白了,执行完毕后,检查锁,再释放,这些操作不是原子化的。
可能锁获取时还是自己的,删除时却已经是别人的了。这可怎么办呢?Redis 可没有直接提供这种场景原子化的操作啊。遇事不要慌,仔细想一想,Redis 是不是还有个特性,专门整合原子操作,对,就是它——Lua。

Redis+Lua,可以说是专门为解决原子问题而生。就有了Lua的特性,Redis才真正在分布式锁、秒杀等场景,有了用武之地。

问题:redis可靠性如何保证,比如服务挂了不影响正常业务
方案:主从容灾和多机部署

1、主从容灾
最简单的一种方式,就是为 Redis 配置从节点,当主节点挂了,用从节点顶包。但是主从切换,需要人工参与,提高人力成本。不过Redis 已经有成熟的解决方案,也就是哨兵模式,可以灵活自动切换,不再需要人工介入。

缺点:通过增加从节点的方式,虽然一定程度解决了单点的容灾问题,但并不是尽善尽美的,由于同步有时延,Slave可能会损失掉部分数据,分布式锁可能失效,这就会发生短暂的多机获取到执行权限。

2、多机部署RedLock(多master)也叫分布式红锁
如果对一致性的要求高一些,可以尝试多机部署,比如Redis的RedLock,大概的思路就是多个机器,通常是奇数个,达到一半以上同意加锁才算加锁成功,这样可靠性会向ETCD靠近。

同步方法执行流程如下:
同步方法的时候,一旦执行到这个方法,就会先判断是否有标志位,然后,
ACC_SYNCHRONIZED会去隐式调用刚才的两个指令:monitorenter和monitorexit 所以归根究底,还是monitor对象的争夺

redis的线程模型是怎么样的

模型 1:传统阻塞式 I/O 模型
Reactor 模型
单线程模型:没有多线程通信时的竞争和锁问题,全部工作都在一个线程中完成。
多线程模型:将业务逻辑的处理工作交给了 worker 线程池,而其他部分与 Reactor 单线程模型是相同的。
主从多线程模型:Reactor 进一步拆分成了用于处理“连接建立”的主 Reactor ,用于处理“I/O 读写事件和事件分发”的从 Reactor :

1、Redis 早在 4.0 版本中就已经引入了多线程,但这里的“多线程”是指用于处理一些特定任务的线程,而不是网络模型上的多线程。
2、Redis 在 6.0 版本中正式引入多线程网络模型,在这之前,其网络模型一直是单线程模式的。

https://zhuanlan.zhihu.com/p/639750217

mysql有哪些日志类型,分别是怎么作用的;id自增,有哪些实现方案

自增主键的实现和二进制日志文件(Binary Log)有关

Binary Log和undo log、redo log区别

Binary Log(二进制日志):
作用: 记录对数据库的【结构】或【数据】的更改操作,如插入、更新、删除等。
用途: 主要用于主从复制(主从同步)和点对点复制,以及数据库的恢复和备份。

Undo Log(回滚日志):
作用: 记录【事务发生之前】的数据值,用于回滚操作。
格式: 记录了事务对数据所做的修改的逆操作。
用途: 在事务回滚或事务提交后提供数据的一致性,用于撤销事务所做的修改。

Redo Log(重做日志):
作用: 记录事务对数据库所做的修改,用于恢复数据库状态。
格式: 记录了事务对数据所做的修改的正向操作。
用途: 在数据库崩溃后,通过重放 Redo Log 来恢复未完成的事务,确保数据库的一致性。

数据库的恢复用的是Redo Log日志

Zookeeper 为什么要设计成一主多从

MySQL 是怎么分库分表的、主从延迟是怎么解决的、查询优化的方式有哪些

mysql主从延迟是怎么解决的

1、调整同步线程的数量
MySQL使用【I/O 线程】负责读取主库的二进制日志并传输到从库,
使用【SQL 线程】负责在从库执行这些 SQL 语句。
你可以通过配置主从库的 slave_parallel_workers 参数来调整【SQL 线程的并行执行数量】。增加[并行线程]提高同步速度,但也可能增加从库的负载。

2、优化网络和硬件
确保主从库之间的网络连接是稳定的,并且具有足够的带宽。如果从库性能较低,可以考虑升级硬件或优化配置,以提高数据同步的速度。

3、使用半同步复制
MySQL 支持半同步复制,即主库在提交事务时等待至少一个【从库接收并确认该事务】。
这可以减少主从延迟,但也会增加主库的响应时间

4、使用多源复制:
MySQL8.0及以上版本支持多源复制,允许一个从库连接到多个主库进行复制。这意味着一个从库可以同时从多个不同的 MySQL 主库接收并复制数据

ThreadLocal是怎么导致内存泄露的

ThreadLocal是Java提供的一个线程安全类,其实就是每个线程Thread里都有一个ThreadLocalMap类\n用于存储变量值,都是操作各自线程里的hreadLocalMap类,互不影响,从而达到的线程安全
ThreadLocal底层都是用的Thread里的ThreadLocalMap类,其实ThreadLocal就是一个工具类而已,底层都是操作的Thread里的ThreadLocalMap类
但是如果ThreadLocal使用不当,会引发内存溢出,作者意识到了这点,例如使用弱引用等,但是依然会有内存溢出的可能。

1、ThreadLocalMap里面的value在线程(Thread)不死亡时可能存在一条强引用链
2、由于value是强引用,只要Thread不死亡时,例如线程池,这条强引用链就会存在,那么value就不会回收,可能造成内存溢出

什么是幂等性?

幂等性是一个数学概念,用在接口上:用在接口上就可以理解为:同一个接口,多次发出同一个请求,请求的结果是一致的。
需要幂等的场景:

1、用户在填写某些form表单时,保存按钮不小心快速点了两次
2、接口超时问题,然后多次重试,导致多笔交易。
3、mq消息处理是,出现相同的消息。

解决:

1、先select在insert
2、幂等参数
3、分布式锁
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

信仰_273993243

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值