Java面试题总结

一、mysql的读写分离

1、概念:

读写分离是指 写入操作(INSERT、UPDATE、DELETE)由主库(Master)执行,读取操作(SELECT)由从库(Slave)执行,以提高数据库的吞吐能力和负载能力。

2、原理:

·  主库将数据变更操作写入 binlog(二进制日志)。

·  从库的 I/O 线程 读取主库 binlog,并写入自己的 relay log(中继日志)。

·  从库的 SQL 线程 解析 relay log 并执行相应的 SQL 语句,使数据同步。

3、配置步骤:

主库:开启binlog,指定服务器唯一ID

从库:指定服务器唯一ID,同步主库

4、Mysql的配置方式有哪些:

·  异步复制(Asynchronous Replication):默认模式,从库可能会有数据延迟。

·  半同步复制(Semi-Synchronous Replication):主库提交事务时,至少一个从库确认接收 binlog 才算成功。

·  组复制(Group Replication):多个节点自动选主,保证一致性。

5、可能遇到的问题:

数据一致性问题、从库负载不均问题

6、数据一致性问题:

主从复制有延迟,可能导致查询到旧数据

解决: 强制查询主库、半同步复制、读后写一致性策略

7、从库负载不均:

读请求可能集中在某个从库

解决: 负载均衡(ProxySQL、MySQL Router)

8、MySQL 主从复制如何处理网络中断问题?

采用 GTID(全局事务 ID)同步,自动修复复制链路:

二、redis读写分离及哨兵

1、概念:

Redis 读写分离是指主库(Master)负责写入,从库(Slave)负责读取,通过复制机制提高系统的并发能力和高可用性。

2、原理:

主从复制:

主库处理所有写入(SET、DEL、INCR 等)请求。

从库通过 全量同步或 增量同步机制复制主库数据,只接受读取请求(GET)。

如果主库宕机,从库不会自动提升为主库(需要手动切换)。

读写分离的实现:

客户端手动分流,业务代码手动区分写入走主库,读取走从库。

代理层自动分流,使用 Twemproxy、Codis、Redis Proxy 等中间件实现自动分配请求。

3、主从复制:

主库生成二进制日志(binlog):

从库同步数据:

全量同步(Full Sync):从库与主库建立连接时,主库会将当前数据库的所有数据一次性发送到从库。此过程称为全量同步。

增量同步(Partial Sync):全量同步完成后,主库持续向从库发送增量数据(即新的写操作)。这个过程称为增量同步。

增量同步是基于 复制日志 来执行的,主库不断地向从库发送数据更新,确保从库的数据与主库保持同步。

从库执行同步操作:
从库接收到主库的操作后,会在自己的数据库中执行相同的操作,确保数据一致性。

4、配置实现:

主库从库配置,从库指定主库ip端口号,连接认证密码

5、问题:

数据一致性问题、主从复制延迟、从库负载不均、主从切换问题(引入哨兵模式)

数据一致性问题:

问题描述-由于redis是异步的,写入主库后,可能还没同步到从库,导致读取时出现数据不一致的情况

解决方案-

强制读主库(适用于重要数据,如订单、支付信息)

读后写一致性策略(使用高并发场景)。先写入主库,短时间内所有读取都从主库获取。

6、哨兵模式:

使用的原因:如果 Redis 主节点(master)出现故障,整个应用系统的读写操作将受阻,Redis 数据库的可用性就会受到严重影响。

概述:Redis 哨兵模式提供了自动故障转移功能,当主库宕机时,哨兵会自动监控到故障并选举一个新的主库,从而保证 Redis 集群的持续可用性。

原理:

哨兵会监控主库状态,并通过投票的方式选举一个从库(slave)作为新的主库。

哨兵完成故障转移后,会通知其他客户端或系统新的主库地址。

7、使用场景:

读写分离:

高并发读请求场景,读取操作远比写操作多,尤其是缓存场景。例如,社交媒体、在线新闻网站,其核心数据(如文章、商品信息、用户资料等)会被频繁读取,且写操作(如评论、订单等)相对较少。

哨兵:

电商平台:电商平台需要保证 7x24 小时的在线服务,若主节点宕机,哨兵可以自动选举新的主节点,保证业务不中断。

金融系统:金融系统需要保证 Redis 节点的高可用,哨兵可以帮助监控集群的状态,及时报警处理故障。

微服务架构:在微服务架构中,多个服务需要访问 Redis 缓存。使用 Redis 哨兵可以确保缓存服务的高可用性,自动处理主从切换。

跨国电商平台:为了提高全球用户的访问体验,跨国电商平台在多个数据中心部署 Redis,使用 Redis 哨兵进行健康监控和故障转移,确保服务的全球可用性。

三、Mysql和Redis同步的常见问题

1、数据一致性问题:

更新操作未同步到 Redis、缓存更新延迟

更新操作未同步到 Redis:当 MySQL 数据被修改时,Redis 中的缓存可能未能立即同步更新。这导致数据不一致,即在查询缓存时可能获取到过时或错误的数据。

解决:删除旧缓存可以解决

缓存更新延迟:当 MySQL 数据发生变化时,缓存数据可能存在一定的延迟,导致在更新后短时间内查询到不一致的结果。

解决:延迟双删、分布式锁

在更新数据库和删除缓存时,使用分布式锁来保证只有一个请求在某一时刻可以删除缓存并更新数据,从而减少并发带来的数据不一致问题。

2、缓存击穿:

指缓存中的某一数据因失效或过期而被删除,但在这个时刻有大量请求同时访问该缓存,导致它们直接查询数据库,进而引发数据库压力,造成系统性能下降。

解决方案:

加锁机制:在缓存失效时,可以用分布式锁(如 Redis 分布式锁)保证只有一个请求去查询数据库并更新缓存,其他请求可以等待。

双重检查缓存:在查询缓存之前,先检查缓存是否存在,若不存在再查询数据库并设置缓存。

延迟加载:避免所有请求直接访问数据库,采用延迟加载方式,缓存过期时延迟加载数据。

3、缓存穿透:

指查询的数据在缓存中不存在,同时数据在数据库中也不存在。这种情况会导致每次查询都绕过缓存直接访问数据库,从而加重数据库负担,影响系统性能。

解决方案:

  • 请求合法性校验:通过对请求参数进行有效性校验,避免非法请求直接访问数据库。
  • 空对象缓存:当缓存中查询不到数据时,可以缓存一个空对象(例如返回一个空的列表或特定的标志值),并设置过期时间,这样同样的请求不会再次访问数据库。
  • 布隆过滤器:使用布隆过滤器在访问缓存之前检查数据是否存在于数据库中,如果不存在,直接返回,不访问数据库。

4、缓存雪崩:

定义:
缓存雪崩是指缓存中的大量数据在同一时刻失效或被删除,导致大量请求同时访问数据库,造成数据库的极大压力,甚至出现数据库崩溃的情况。

解决方案:

  • 不同步过期时间:将缓存的过期时间进行随机化,避免大量数据同时过期。
  • 预热缓存:在缓存过期之前,提前加载一些数据到缓存中,防止过期后大规模的缓存失效。
  • 过期时间设置合适:合理设计缓存的过期时间和策略,使得缓存失效是平滑的,而非集中式的爆发。
  • 异步加载和降级:在缓存失效时,采用异步更新缓存的方式,防止同步操作造成大量数据库压力。同时,当数据库不可用时可以进行降级处理,返回一些默认的数据或缓存备用数据。

四、mq消息可靠性

1、生产者消息确认:

通过生产者确认机制来避免消息发送到MQ过程中丢失,这种机制必须给每个消息指定一个唯一ID,消息发送到MQ以后,会返回一个结果给发送者,表示消息是否处理成功。

返回结果有两种方式:

发送者确认:

  消息成功投递到交换机,返回ack

  消息未投递到交换机,返回nack

发送者回执

  消息投递到交换机,没有路由到队列,返回ACK,及路由失败原因

2、消息持久化

RabbitMQ中交换机默认是非持久化的,mq重启后就丢失。

由SpringAMQP声明的交换机、队列,发出的消息都是持久化的。

3、消费者消息确认

RabbitMQ是阅后即焚机制,RabbitMQ确认消息被消费者消费后会立刻删除。

消费者获取消息后,应该向RabbitMQ发送ACK回执,表明自己已经处理消息。

SpringAMQP则允许配置三种确认模式:

•manual:手动ack,需要在业务代码结束后,调用api发送ack。

•auto:自动ack,由spring监测listener代码是否出现异常,没有异常则返回ack;抛出异常则返回nack

•none:关闭ack,MQ假定消费者获取消息后会成功处理,因此消息投递后立即被删除

4、消费失败重试机制

问题:当消费者出现异常后,消息会不断requeue(重入队)到队列,再重新发送给消费者,然后再次异常,再次requeue,无限循环,导致mq的消息处理飙升,带来不必要的压力:

解决方案:我们可以利用Spring的retry机制,在消费者出现异常时利用本地重试,而不是无限制的requeue到mq队列。

失败策略:重试次数耗尽,如果消息依然失败,需要有MessageRecovery接口来处理,

它包含三种不同的实现:

重试耗尽后,直接reject,丢弃消息。默认就是这种方式

重试耗尽后,返回nack,消息重新入队

重试耗尽后,将失败消息投递到指定的交换机

可以将失败后将消息投递到死信交换机,绑定死信队列,后续由人工集中处理

五、锁

Sychnorized、reentranlock、原理

Cas、aqs

1、Sychnorized关键字

原理:

synchronized 通过 JVM 内置的 Monitor(监视器) 机制来实现同步,底层依赖于 对象头(Object Header)中的 Mark Word 来存储锁的状态。其实现方式包括:

偏向锁:

线程首次获取锁时,会在对象头中记录该线程的ID,以后该线程进入同步块时,无需CAS(Compare And Swap)操作,直接进入,减少开销。

发生其他线程尝试竞争锁时,偏向锁会被撤销并升级为轻量级锁。

轻量级锁:

适用于多个线程交替进入同步块的情况,但没有竞争。

线程尝试获取锁时,会在当前线程栈中创建锁记录(Lock Record),并通过CAS尝试把对象头的Mark Word替换成指向锁记录的指针。

重量级锁:

适用于多个线程频繁竞争的情况。竞争失败的线程会被阻塞,并由OS调度恢复,开销较大。

JVM使用**操作系统的互斥锁(Mutex)**进行管理,线程进入等待队列,避免过度消耗CPU。

锁状态变化:无锁 → 偏向锁 → 轻量级锁 → 重量级锁

如何影响锁的升级:

关闭偏向锁(减少撤销开销)

调整自选次数(避免频繁升级)

尽量减少锁竞争(使用lock代替Sychnorized)

2、AQS

AbstractQueuedSynchronizer(抽象队列同步器)是java并发包中的核心框架,用于构建同步器(如:锁、信号量)。

①AQS 内部维护了一个同步标志位 state,用来实现同步加锁控制:

初始值state为0,表示没有获取锁,state等于1的时候表明获取到了锁。state 实际上表示的是已获得锁的线程进行加锁操作的次数

②在它的内部还提供了基于 FIFO(先进先出) 的等待队列(CLH队列),来表示排队等待锁的线程,当线程争抢锁失败后会封装成 Node 节点加入 CLH 队列中去。

是一个双向列表,有两个节点

tail 指向队列最后一个元素

head 指向队列中最久的一个元素

3、ReentrantLock

介绍:可重入锁,获得锁的线程在释放锁之前再次调用一个需要获取锁的方法时,不需要加锁,直接统计加锁次数。避免死锁

分布式可重入锁思路:统计加锁次数
一个线程获取锁时,如果第一次获取锁成功 锁重入次数为1
如果该线程调用了另一个需要获取锁的方法 锁获取的次数再+1
如果该线程释放锁,对获取锁的次数-1,减完后的值为0表示锁完全释放 否则释放失败

实现原理:

ReentrantLock主要利用CAS+AQS队列来实现。它支持公平锁和非公平锁,两者的实现类似

构造方法接受一个可选的公平参数(默认非公平锁),当设置为true时,表示公平锁,否则为非公平锁。公平锁的效率往往没有非公平锁的效率高,在许多线程访问的情况下,公平锁表现出较低的吞吐量。

4、ConcurrentHashMap

JDK1.7的底层采用是分段的数组+链表 实现

JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。

在jdk1.7中 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似,是一 种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构 的元素,每个 Segment 守护着一个HashEntry数组里的元素,当对 HashEntry 数组的数据进行修 改时,必须首先获得对应的 Segment的锁。

Segment 是一种可重入的锁 ReentrantLock,每个 Segment 守护一个HashEntry 数组里得元 素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment 锁

在jdk1.8中的ConcurrentHashMap 做了较大的优化,性能提升了不少。首先是它的数据结构与jdk1.8的hashMap数据结构完全一致。其次是放弃了Segment臃肿的设计,取而代之的是采用Node + CAS + Synchronized来保 证并发安全进行实现,synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲 突,就不会产生并发 , 效率得到提升

六、分库分表

链接:https://www.cnblogs.com/wuer888/p/14524303.html#autoid-1-0-0

1、为什么要分库分表?(设计高并发系统的时候,数据库层面该如何设计?)

单一数据库无法承载高并发的请求:随着业务增长,数据库的请求量会不断增加,单一的数据库可能无法承载如此高的并发压力。查询速度变慢,响应时间增加,甚至可能出现数据库崩溃、超时等问题。

IO 瓶颈:单个数据库服务器的磁盘 IO 能力有限,随着数据量的增加,磁盘读写压力加大,导致性能下降。

2、现在有一个未分库分表的系统,未来要分库分表,如何设计才可以让系统从未分库分表动态切换到分库分表上?

停机迁移方案、双写迁移方案

3、如何设计可以动态扩容缩容的分库分表方案?

一开始上来就是 32 个库,每个库 32 个表,那么总共是 1024 张表。

4、分库分表之后,id 主键如何处理?

Snowflack算法

七、Nacos原理

八、设计模式

策略模式、工厂模式、抽象工厂、单例、代理模式

九、排序算法

快速排序

冒泡、插入、选择、希尔、堆、归并排序、桶排序

时间复杂度比较

原理及实现代码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值