2 minutes
Java面试中常问的问题
Java面试中常问的问题
- Java HashMap 两次put会发生什么
- springboot的starter是如何实现自动装配的?
- redis为什么快?
- redis常用的数据结构是什么,
- redis如何实现分布式锁,
- redis实现分布式锁有什么问题
- redis如果系统错误释放锁和业务执行超时释放锁,会发生什么?
- 分布式事务如何保证一致性
- 服务发现是怎么回事
- kafka消息队列为何要引入组?解决了什么问题
- mysql ABC索引, 实际用到了AB,没用到C,是什么情况会发生?
1. Java HashMap 两次put会发生什么
我们要了解HashMap的结构,并了解put时候发生了什么。
HashMap是键值对结构, 计算key的hash值来找对对应桶的位置,然后放进去。所以put(key, value) 基本可以认为 先 hash(key), 找到位置,放进去,再put同样的 key, 位置是一样的, 那就把原有值覆盖掉。
如果是put的是不同的key, hash(key)后找到桶位置,如果桶里没有值,直接就放进去,有的话用equals一个个比较,找到就替换值,找不到就拼到链表结尾。
2. springboot的starter是如何实现自动装配的?
我们知道springboot由于有很多starter和注解的存在大大简化了spring的使用,减少了很多xml配置,那么它是如何做到的?
其实是由springboot自定义starter的配置来完成的,springboot 通过在WEB-INF目录下spring.factories 配置自己定义注解,然后引入到工程里。 在2.7.11 里, META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
改这个文件就可以达到相同的结果。
3. redis为什么快?
数据结构简单:Redis中的数据结构相对简单,只包含基本的数据结构,例如字符串、列表、集合、哈希表和有序集合等,相比其他的数据库,Redis需要更少的时间用于数据结构实现和维护。
内存操作:Redis是高速的内存数据库,数据存储在内存中,所有操作都是内存操作,不存在硬盘IO等慢速操作。
单线程模型:Redis采用单独的IO线程,使得应用程序并发访问时需要的锁或者同步操作少很多,提高了系统的吞吐量。
异步非阻塞IO:Redis采用异步非阻塞IO模型,使得服务器能够处理成百上千个客户端的请求,而且不会由于单个客户端请求阻塞而导致服务器无法处理其他请求。
综上所述,Redis之所以快就是因为其采用了简单的数据结构、内存操作、单线程模型和异步非阻塞IO等技术,从而大幅提升了数据读写的速度和系统吞吐量。
4. redis数据结构有哪些
字符串(Strings):存储字符串、整数或者浮点数。
列表(Lists):一个列表结构,通过一个键可以对一个字符串列表进行操作,支持从列表两端(左端/右端)添加或者删除数据。
集合(Sets):一个无序的、元素不重复的集合,支持对集合进行添加、删除和判断两个集合的交集、并集和差集等操作。
哈希表(Hashes):一个键值对的集合,键和值都是字符串类型,支持添加、修改、删除和查询具体的某个键值对。
有序集合(Sorted Sets):与集合类似,是一个无序的、元素不重复的集合,但不同的是,每一个元素都会附带一个权重(score),支持根据权重快速查询元素。
其中,字符串、列表、集合、哈希表和有序集合都具有很好的应用场景,应用广泛,并且在实现上都相对简单,易于理解和使用。
5. 如何用redis实现分布式锁
redis的原子操作 setnx来实现
6. redis实现分布式锁有什么问题
redis 分布式锁有以下几个问题需要注意:
- 锁失效 - 如果持有锁的客户端崩溃而没有主动释放锁,会导致锁失效。为了解决这个问题,通常会设置锁的过期时间。客户端申请锁之后需要在过期时间内完成操作并释放锁,否则锁会被自动删除,其他客户端可以再申请锁。.
- 锁的安全性 - 如果多个客户端同时申请一把锁,Redis 会随机将锁给一个客户端。这可能导致获取锁的客户端并不是最先申请锁的那个。为了解决这个问题,可以使用 Redis 的 Redlock 算法来提高获取锁的安全性。
- 死锁 - 如果一个客户端持有锁 A 并申请锁 B,而另一个客户端持有锁 B 并申请锁 A,这会导致两个客户端相互等待而产生死锁。为了避免这种情况,应该确保所有的客户端以同样的顺序申请多个锁。
- 锁的选举 - 在主从复制的环境下,如果客户端将锁申请在从节点上,当主节点下线时,新的主节点上不会存在这个锁,会导致锁的选举问题。为此,客户端应该始终将锁申请到主节点上,而不是从节点。
- 释放锁失败 - 当客户端持有锁并完成操作后,如果在释放锁时客户端崩溃,会导致锁一直无法释放而永久失效。为了避免这种情况,可以为锁设置过期时间,即使客户端崩溃,锁也会在一定时间后自动释放。
以上是 Redis 分布式锁需要考虑和解决的几个问题,通过设置锁的过期时间、使用 Redlock 算法、设置加锁的顺序、将锁申请到主节点等方式可以避免或者解决这些问题,实现安全高效的 Redis 分布式锁。
7. redis如果系统错误释放锁和业务执行超时释放锁,会发生什么?
如果应用在持有 Redis 分布式锁的过程中,锁因为超时而被自动释放,会发生以下问题:
- 资源被其他客户端修改 - 在锁释放之后,其他等待锁的客户端会获得到锁,并对共享资源进行修改。这会导致先前持有锁的客户端操作共享资源时,资源处于不一致的状态。
- 产生脏数据 - 如果一个客户端 half-done 就释放了锁,数据可能处于中间状态,这会产生脏数据。其他客户端看到这些脏数据,并基于这些数据进行决策,会造成错误。
- 并发操作混乱 - 多个客户端并发对同一个资源进行操作,顺序混乱,会导致最终结果错误。特别是一些需要顺序执行的操作。
为了解决超时释放锁带来的问题,可以采取以下措施:
- 锁续期 - 客户端在持有锁的过程中,需要不断地续期锁的有效期,防止锁被自动释放。当操作完成后,客户端主动释放锁。
- 操作超时 - 为整个业务操作设置一个超时时间,这个超时时间要比锁的超时时间长。如果在操作超时时间内,客户端未完成操作并释放锁,则认为该操作已经超时失败。这可以避免产生脏数据。
- 锁标识 - 每次获取锁时,都在 value 中保存一个锁标识,包括请求 ID、时间戳等。当锁超时释放后,如果 value 发生变化,表示该锁已经被其他客户端获取。原客户端需根据锁标识判断是否是自己持有的锁,如果不是则退出操作。
- 操作幂等 - 使共享资源操作变得幂等,这样无论客户端对资源操作多少次,结果都是一致的。这可以避免并发操作导致的结果错误。
综上,通过设置锁和业务操作的超时时间,使用锁标识判断锁的持有者,采取操作幂等设计等措施,可以尽量减少和避免Redis超时释放锁带来的问题
8. 分布式事务如何保证一致性
分布式事务要保证一致性,需要解决几个问题:
- 全局序列化 - 要保证所有节点在同一时间内只能有一个事务被执行,否则会发生写写冲突。通常使用分布式锁来实现全局序列化。
- 原子提交 - 要保证在所有节点上,事务的提交或者回滚是原子的,不会处于中间状态。通常使用两阶段提交协议来实现原子提交。
- 隔离级别 - 要确保事务内的读数据是事务开始时的最新数据版本(串行化),不会读取到其他并发事务修改的数据(避免脏读)。
- 补偿机制 - 如果在提交过程中,某些节点发生故障,需要补偿机制来确保所有节点数据的一致性。通常通过回滚已经执行的节点来实现补偿。
- 超时处理 - 要考虑在执行过程中某些节点超时的情况,并在超时后采取恢复措施保证一致性。
常见的一致性实现方案有:
- 两阶段提交(2PC)协议:使用prepare和commit两个阶段来控制所有的节点要么全部commit,要么全部rollback。
- 三阶段提交(3PC)协议:在两阶段提交的基础上增加了一个预提交确认阶段,可以避免脏写。
- PAXOS 算法:PAXOS 是一个比较复杂的多轮投票过程,可以在分布式系统中达成一致并选择出一个值。
- TCC 模式:TCC 模式分为 Try、Confirm、Cancel 三个阶段。Try 阶段执行业务操作,Confirm 阶段提交确认,Cancel 阶段执行补偿操作。
- XA 接口:XA 接口是 JTA 规范中定义的分布式事务处理接口,其主要目的是统一接口,实现异构数据库之间的分布式事务处理。
总之,要实现分布式事务的一致性,需要遵循 ACID 原则,解决全局序列化、原子提交、隔离级别、补偿机制和超时处理等问题。并且需要选择两阶段提交、三阶段提交、PAXOS 算法、TCC 模式或者 XA 接口等算法和协议来控制分布式事务的执行。
9. 服务发现是怎么回事
微服务架构中,服务发现是很关键的一个部分。它的主要作用是管理各个微服务实例的信息,并将这些信息提供给其他微服务或者客户端使用。常见的服务发现机制有:
- 客户端服务发现 - 客户端直接查询注册中心获取服务实例信息。这种方式的实现简单,但是会造成注册中心的压力增大,并且一旦注册中心宕机,服务将不可用。
- 服务端服务发现 - 服务启动时直接从注册中心获取其它服务的实例信息,并缓存在本地。这种方式可以减轻注册中心压力,但是当服务实例发生变化时,需要更新所有依赖该服务的其它服务的缓存,较为麻烦。
- 客户端缓存服务发现 - 客户端先从注册中心获取服务实例信息并缓存在本地,后续会定期从注册中心更新数据。这种方式可以减轻注册中心压力,并且当注册中心宕机时,服务仍可用。这是一种较好的服务发现方式。
常用的服务注册中心有:
- Eureka:Netflix 开源的一款服务注册中心,用于服务的注册、发现和负载均衡。
- Consul:HashiCorp 开源的服务发现和配置管理系统。支持健康检查、KV 存储、多数据中心等功能。
- Etcd:CoreOS 开源的一个分布式键值存储系统,可用于服务发现、共享配置以及分布式锁等。
- Zookeeper:Apache 开源的一个分布式协调工具,支持服务发现、配置管理等功能。
具体的服务发现流程是:
- 服务启动时向注册中心注册自己的网络地址和端口等信息。
- 服务消费者从注册中心获取提供者的网络地址和端口等信息。
- 消费者使用提供者的信息与其建立连接,发起 RPC 请求。
- 注册中心定期检查各服务实例是否可用,如果某实例故障,则从注册列表中删除。
- 消费者定期从注册中心更新服务提供者的列表,并刷新本地缓存。
- 服务提供者定期向注册中心发送心跳,以续约在注册中心的注册信息。
以上就是微服务架构中的服务发现机制和基本流程,用来实现服务实例的动态注册与发现。
10. kafka消息队列为何要引入组?解决了什么问题
Kafka 引入消费组的主要目的是为了实现消息的负载均衡,以及在消费者不可用的情况下实现消息的高可用。
没有消费组的情况下,每条消息会被所有的消费者消费一次,导致消息被重复消费。而引入消费组后,属于同一个组的消费者会负责消费不同分区的数据,从而实现负载均衡,并且同一个消息也只会被组内的一个消费者消费,避免了消息重复消费的问题。
另外,当某个消费者不可用时,其所属分区的数据可以被 group 中其他消费者消费,保证消息不会丢失,实现了高可用。每个分区的数据只能被组内的一个消费者消费,而不是所有消费者。
举个例子,有 3 个 Topic,每个 Topic 有 3 个分区。有两个消费组,每个消费组有两个消费者。那么:- 消费组 1 - 消费者 1-1 消费 Topic 1 分区 0 和 Topic 2 分区 0 的数据 - 消费者 1-2 消费 Topic 1 分区 1 和 Topic 2 分区 1 的数据- 消费组 2 - 消费者 2-1 消费 Topic 1 分区 2 和 Topic 3 分区 0 的数据 - 消费者 2-2 消费 Topic 2 分区 2 和 Topic 3 分区 1 的数据
这样,每个 Topic 的每个分区的数据只会被一个消费组中的一个消费者消费,实现了负载均衡和消息高可用。
所以,Kafka 消费组的主要作用是:
- 实现消息的负载均衡,通过给不同的消费者分配不同的分区的数据,达到均衡的消费效果。
- 实现消息的高可用,当某个消费者不可用时,其分区的数据可以被组内其他消费者消费,保证消息不丢失。
- 避免消息被多个消费者重复消费的问题。
11. mysql ABC索引, 实际用到了AB,没用到C,是什么情况会发生?
B 是范围查询