Java面试问题

1. 什么是分布式系统?
分布式系统旨在支持应用程序和服务的开发,可以利用物理架构由多个自治的处理元素,不共享主内存,但通过网络发送消息合作。

三个特点 三个容易混淆的概念
多节点:服务的各节点 集群:部署于多台服务器的服务,相同业务功能的节点
消息通信:各节点通过发送消息来通信 分布式系统:强调不同功能模块的节点
不共享内存: 分布式计算:

2. session
会话控制,http协议是无状态的,对于一个url请求没有上下文关系,当用户完成登陆之后就要有一个机制来保存用户的信息和状态,在后续的请求中验证用户的身份和检查用户的信息,这个依赖就是会话控制。
key-value机制:
session机制中的关键点,第一是如何设置和获取Key,另外就是如何保存和正确获取对应的Value。从Key的角度看,会话有两种比较常规的方式:session id和token。session id:客户端请求服务器的时候,服务的设置cookie,可以在http头里面设置JSESSIONID和对应的Value,客户端的cookie会保存,后续的请求里面自动带上。token:手动在http头里或url里设置token字段,服务器收到请求之后从head里或者url里取出token进行验证,安全验证要求比较严格的时候token会和签名一起使用。session id和token全局唯一,一个用户一个标示,其本质是一个Key,对应的用户信息就是Value。


3. 消息队列 MessageQueue
三个主要应用场景:解耦,异步,削峰

中间件模式 传统模式
解耦:将消息写入消息队列,需要消息的系统自己从消息队列中订阅,而服务系统不需要做任何更改 系统间耦合性太强,系统之间直接相互调用
异步:将消息写入消息队列,非必要的业务逻辑以异步的方式运行,加快相应速度 一些非必要的业务逻辑以同步的方式运行,耗费时间
削峰:服务系统按照数据库的能力处理并发量,在生产高峰期允许积压 并发量大的时候,所有的请求直怼数据库,造成数据库连接异常

缺点:

  • 系统可用性降低:如果消息队列挂了,那么整个系统将不可用
  • 系统复杂性增加:需要考虑很多方面的问题,如一致性问题、如何保证消息不被重复消费、如何保证消息可靠传输

如何保证消息队列高可用?
以Kafka为例,Kafka集群包括若干producer,若干broker,若干consumer group,以及一个Zookeeper集群。Kafka通过Zookeeper管理集群配置,选举leader,以及在consumer group发生变化是进行rebalance。Producer用push模式将消息发布到broker,consumer使用pull模式订阅并消费消息。
如何保证消息不被重复消费?(消息队列的幂等性)
正常情况下,消费者消费消息以后,会发送一个确认消息给消息队列,消息队列将该消息删除。因为网络传输等故障,确认消息没有传送到消息队列,导致消息队列不知道该消息已经被消费过,再次把该消息分发给其他消费者。
针对不通的业务场景,解决方式:
1)拿到这个消息后进行数据库inset操作。给消息设置一个唯一主键,重复消费的时候就会导致主键冲突,避免数据库出现脏数据。
2)拿到这个消息后进行redis的set操作。无需任何处理,因为无论set几次结果都一样,set操作本来就是幂等操作。
3)其他情况。准备一个第三方介质,如redis,给消息分配一个全局id,将以Key-Value形式写入redis,消费前先查询redis中有没有消费记录。
如何保证消息的可靠性传输?
从三个角度分析:生产者丢数据、消息队列丢数据、消费者丢数据

  • 生产者丢数据。RabbitMQ提供transaction和confirm模式确保生产者不丢消息(缺点是降低吞吐量)。transaction:发送消息前开启事物,然后发送消息,如果发送消息异常,则回滚事物,如果发送成功则提交事物。confirm:所有在该信道上发布的消息都会指派一个唯一的ID,一旦消息被投递到匹配的队列之后,就会发送一个Ack(包含消息的唯一ID)给生产者,这样生产者就知道消息已经正确到达目的队列了;如果RabbitMQ没能处理该消息,则会发送一个Nack消息给生产者,生产者可以重试操作。
  • 消息队列丢数据。一般是开启持久化磁盘的配置,和confirm配合使用。在发送Ack信号之前把消息持久化磁盘,这样如果消息在持久化之前就阵亡了,那么生产这就收不到Ack信号。
  • 消费者丢数据。一般是因为采用了自动确认消息模式。这种模式下,消费者会自动确认收到消息,这时RabbitMQ就会立即将消息删除,这种情况下如果消费者出现异常而没能处理该消息,就会丢失消息。采用手动确认消息即可。

如何保证消息的顺序性?
将消息保持先后顺序放到同一个消息队列,然后用同一个消费者去消费队列。如果要考虑吞吐量,就只保证消息有序入队,出队的顺序交给消费者自己去保证(如果出队顺序错误就会失败,消费者可以重试)。


4. 线程同步
同一进程共享同一个内存空间(堆,常量池等),多个线程并发执行,对统一资源进行访问时,会产生资源竞争问题,可能一个线程使用了旧版本的数据,而另一个线程已经修改了该数据。
Java的内存模型:分为主内存和工作内存。所有的变量都存在主内存中,每个线程有自己的工作内存,线程的工作内存中保存了该线程使用到的变量到主内存中的拷贝副本,线程对变量的所有操作都在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间无法直接访问对方工作内存中的变量,线程间变量值的传递都在主内存中完成。
synchronize:对象锁,悲观锁策略,阻塞同步。

Java面试问题_第1张图片
synchronize使用场景

CAS:乐观锁策略,非阻塞同步。当多个线程同事操作同一个变量时,只有一个线程会成功,并成功更新,其余失败,失败的线程会重试,或者选择挂起。ABA问题:变量A变成B再变成A,给变量添加版本号。自旋时间过长:使用pause指令。只能保证一个共享变量的原子性操作:利用对象整合多个变量。
锁一共有4种状态:无锁状态,偏向锁状态,轻量级锁状态,重量级锁状态
偏向锁:大多数情况下,锁不仅不存在多线程竞争,而且总是由同一个线程多次获得,为了让线程获得锁的代价更低而引入偏向锁。
重量级锁:当锁处于这个状态下,其他线程试图获取锁,都会被阻塞,当持有锁的线程释放锁之后会唤醒这些线程,进行新一轮的竞争锁,避免了自旋。

Java面试问题_第2张图片
各种锁的比较

你可能感兴趣的:(Java面试问题)