《2021最新Java面试题全集-2021年第二版》不断更新完善!

    

第十五章 分布式架构

1:大型网站在架构上应当考虑哪些问题?

1)分层:

分层是处理任何复杂系统最常见的手段之一,将系统横向切分成若干个层面,每个层面只承担单一的职责,然后通过下层为上层提供的基础设施和服务以及上层对下层的调用来形成一个完整的复杂的系统。

计算机网络的开放系统互联参考模型(OSI/RM)和InternetTCP/IP模型都是分层结构,大型网站的软件系统也可以使用分层的理念将其分为持久层(提供数据存储和访问服务)、业务层(处理业务逻辑,系统中最核心的部分)和表示层(系统交互、视图展示)。

需要指出的是:(1)分层是逻辑上的划分,在物理上可以位于同一设备上也可以在不同的设备上部署不同的功能模块,这样可以使用更多的计算资源来应对用户的并发访问;(2)层与层之间应当有清晰的边界,这样分层才有意义,才更利于软件的开发和维护。

2)分割:

分割是对软件的纵向切分。我们可以将大型网站的不同功能和服务分割开,形成高内聚低耦合的功能模块(单元)。在设计初期可以做一个粗粒度的分割,将网站分割为若干个功能模块,后期还可以进一步对每个模块进行细粒度的分割,这样一方面有助于软件的开发和维护,另一方面有助于分布式的部署,提供网站的并发处理能力和功能的扩展。

3)分布式:

网站的静态资源(JavaScriptCSS、图片等)也可以采用独立分布式部署并采用独立的域名,这样可以减轻应用服务器的负载压力,也使得浏览器对资源的加载更快。

数据的存取也应该是分布式的,传统的商业级关系型数据库产品基本上都支持分布式部署,而新生的NoSQL产品几乎都是分布式的。

当然,网站后台的业务处理也要使用分布式技术,例如查询索引的构建、数据分析等,这些业务计算规模庞大,可以使用Hadoop以及MapReduce分布式计算框架来处理。

4)集群:

集群使得有更多的服务器提供相同的服务,可以更好的提供对并发的支持,并能大大提高系统的可用性,也就是做高性能、高可用。

5)缓存:

所谓缓存就是用空间换取时间的技术,将数据尽可能放在距离计算最近的位置。使用缓存是网站优化的第一定律。我们通常说的CDN、反向代理、热点数据都是对缓存技术的使用。

6)异步:

异步是实现软件实体之间解耦合的又一重要手段。异步架构是典型的生产者消费者模式,二者之间没有直接的调用关系,只要保持数据结构不变,彼此功能实现可以随意变化而不互相影响,这对网站的扩展非常有利。

使用异步处理还可以提高系统可用性,加快网站的响应速度(用Ajax加载数据就是一种异步技术),同时还可以起到削峰作用(应对瞬时高并发)。

能推迟处理的都要推迟处理"是网站优化的第二定律,而异步是践行网站优化第二定律的重要手段。

7)冗余:

各种服务器都要提供相应的冗余服务器以便在某台或某些服务器宕机时还能保证网站可以正常工作,同时也提供了灾难恢复的可能性。冗余是网站高可用性的重要保证。

 

2:你用过的网站前端优化的技术有哪些?

1)浏览器访问优化:

- 减少HTTP请求数量:合并CSS、合并JavaScript、合并图片(CSS Sprite

- 使用浏览器缓存:通过设置HTTP响应头中的Cache-ControlExpires属性,将CSSJavaScript、图片等在浏览器中缓存,当这些静态资源需要更新时,可以更新HTML文件中的引用来让浏览器重新请求新的资源

- 启用压缩

- CSS前置,JavaScript后置

- 减少Cookie传输

2CDN加速:

CDNContent Distribute Network)的本质仍然是缓存,将数据缓存在离用户最近的地方,CDN通常部署在网络运营商的机房,不仅可以提升响应速度,还可以减少应用服务器的压力。当然,CDN缓存的通常都是静态资源。

3)反向代理:

反向代理相当于应用服务器的一个门面,可以保护网站的安全性,也可以实现负载均衡的功能,当然最重要的是它缓存了用户访问的热点资源,可以直接从反向代理将某些内容返回给用户浏览器。

 

3:你使用过的应用服务器优化技术有哪些?

1:分布式缓存:

缓存的本质就是内存中的哈希表,如果设计一个优质的哈希函数,那么理论上哈希表读写的渐近时间复杂度为O(1)

缓存主要用来存放那些读写比很高、变化很少的数据,这样应用程序读取数据时先到缓存中读取,如果没有或者数据已经失效再去访问数据库或文件系统,并根据拟定的规则将数据写入缓存。

对网站数据的访问也符合二八定律(Pareto分布,幂律分布),即80%的访问都集中在20%的数据上,如果能够将这20%的数据缓存起来,那么系统的性能将得到显著的改善。当然,使用缓存需要解决以下几个问题:

1)频繁修改的数据

2)数据不一致与脏读

3)缓存雪崩(可以采用分布式缓存服务器集群加以解决)

4)缓存预热

5)缓存穿透(恶意持续请求不存在的数据)。

 

2:异步操作:

可以使用消息队列将调用异步化,通过异步处理将短时间高并发产生的事件消息存储在消息队列中,从而起到削峰作用。

电商网站在进行促销活动时,可以将用户的订单请求存入消息队列,这样可以抵御大量的并发订单请求对系统和数据库的冲击。目前,绝大多数的电商网站即便不进行促销活动,订单系统都采用了消息队列来处理。

 

3:集群部署

   

4:代码优化:

1)多线程:

基于JavaWeb开发基本上都通过多线程的方式响应用户的并发请求,使用多线程技术在编程上要解决线程安全问题,主要可以考虑以下几个方面:

A:将对象设计为无状态对象(这和面向对象的编程观点是矛盾的,在面向对象的世界中被视为不良设计),这样就不会存在并发访问时对象状态不一致的问题。

B:在方法内部创建对象,这样对象由进入方法的线程创建,不会出现多个线程访问同一对象的问题。使用ThreadLocal将对象与线程绑定也是很好的做法。

C:对资源进行并发访问时应当使用合理的锁机制。

2)非阻塞I/O

使用单线程和非阻塞I/O是目前公认的比多线程的方式更能充分发挥服务器性能的应用模式。JavaJDK 1.4中就引入了NIONon-blocking I/O,Servlet 3规范中又引入了异步Servlet的概念,这些都为在服务器端采用非阻塞I/O提供了必要的基础。

3)资源复用:

资源复用主要有两种方式,一是单例,二是对象池,我们使用的数据库连接池、线程池都是对象池化技术,这是典型的用空间换取时间的策略,另一方面也实现对资源的复用,从而避免了不必要的创建和释放资源所带来的开销。

 

 

4:谈谈TCC(分布式事务的解决方案之一)?

TCC 其实就是采用的补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。它分为三个阶段:

Try 阶段主要是对业务系统做检测及资源预留

Confirm 阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认 Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。

Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。

 

举个例子,假入 Bob 要向 Smith 转账,思路大概是: 我们有一个本地方法,里面依次调用

 

首先在 Try 阶段,要先调用远程接口把 Smith Bob 的钱给冻结起来。

Confirm 阶段,执行远程调用的转账的操作,转账成功进行解冻。

如果第2步执行成功,那么转账成功,如果第二步执行失败,则调用远程冻结接口对应的解冻方法 (Cancel)

优点: 跟2PC比起来,实现以及流程相对简单了一些,但数据的一致性比2PC也要差一些

缺点: 缺点还是比较明显的,在2,3步中都有可能失败。TCC属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码,在一些场景中,一些业务流程可能用TCC不太好定义及处理。

 

5:谈谈本地消息表(分布式事务的解决方案之一)?

解决分布式事务,目前业界最常用的方案就是基于消息队列的最终一致性方案,该方案是通过消息中间件保证上、下游应用数据操作的一致性。

基本思路是将本地消息表与业务数据表于同一个数据中,这样就能利用本地事来保对这两个表的操作足事特性,并且使用了消息列来保一致性。

(1)在分布式事操作的一方完成写业务数据的操作之后向本地消息表送一个消息,本地事能保证这个消息一定会被写入本地消息表中。

(2)之后将本地消息表中的消息转发 Kafka 等消息列中,如果转发成功将消息从本地消息表中除,否则继续重新转发

(3)在分布式事操作的另一方从消息列中取一个消息,并行消息中的操作。

 

 

 

 

但是可能会出现异常的情况:

1)直接无法到达消息队列

       网络断了,抛出异常,上游业务直接回滚即可。

2)消息已经到达消息队列,但返回的时候出现异常

       MQ提供了确认ack机制,可以用来确认消息是否有返回。因此我们可以在发送前在数据库中先存一下消息,如果ack异常则进行重发

3)消息送达后,消息服务自己挂了

       先操作数据库,然后再往消息队列发送

4)未送达消费者

       消息队列收到消息后,消费者去消费,此时消息队列会处于"UNACK"的状态,直到客户端确认消息

5)确认消息丢失

       消息返回时假设确认消息丢失了,那么消息队列认为消息没有到达消费者会重发消息。

6)消费者业务处理异常

       消费者接受消息并处理,假设抛异常了,先重试,重试到一定的次数之后进行返回事务执行失败

 

 

 

 

 

6:谈谈MQ事务消息(分布式事务的解决方案之一)?

有一些第三方的MQ是支持事务消息的,比如RocketMQ,他们支持事务消息的方式也是类似于采用的二阶段提交,但是市面上一些主流的MQ都是不支持事务消息的,比如 RabbitMQ Kafka 都不支持。

以阿里的 RocketMQ 中间件为例,其思路大致为:

第一阶段Prepared消息,会拿到消息的地址。 第二阶段执行本地事务,第三阶段通过第一阶段拿到的地址去访问消息,并修改状态。

也就是说在业务方法内要想消息队列提交两次请求,一次发送消息和一次确认消息。如果确认消息发送失败了RocketMQ会定期扫描消息集群中的事务消息,这时候发现了Prepared消息,它会向消息发送者确认,所以生产方需要实现一个check接口,RocketMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。

 

优点: 实现了最终一致性,不需要依赖本地数据库事务。

缺点: 实现难度大,主流MQ不支持,RocketMQ事务消息部分代码也未开源。

 

7:谈谈分布式锁的实现?

关于分布式锁的设计,从以下四个角度考虑:

第一:互斥性。在任意时刻,只有一个客户端能持有锁。

第二:不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。

第三:具有容错性。只要大部分的节点正常运行,客户端就可以加锁和解锁。

第四:加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。

 

常见的,分布式锁有三种实现方式:

 

第一:数据库乐观锁;

利用表的唯一索引行级锁进行加解锁,加锁:

加锁:insert into methodLock(method_name,desc) values (method_name,desc)

解锁:delete from methodLock where method_name ='method_nam

 

第二:基于Redis的分布式锁;

jedis.setnx(String key, String value, String nxxx, String expx, int time)

 

第三:基于ZooKeeper的分布式锁

zk 是一种提供配置管理、分布式协同以及命名的中心化服务,用于集群配置中心管理,服务的注册监听等。zookeeper 分布式锁的实现利用zookeeper 管理配置中心的watcher机制(观察者模式),对竞争分布式锁的客户端维护了一张临时顺序表。表中每个节点代表一个客户端。

 

8:谈谈CAP理论?

CAP理论作为分布式系统的基础理论,它描述的是一个分布式系统在以下三个特性中:

 

一致性(Consistency

可用性(Availability

分区容错性(Partition tolerance

最多满足其中的两个特性。也就是下图所描述的。分布式系统要么满足CA,要么CP,要么AP。无法同时满足CAP

 

 

分区容错性:指的分布式系统中的某个节点或者网络分区出现了故障的时候,整个系统仍然能对外提供满足一致性和可用性的服务。也就是说部分故障不影响整体使用。

事实上我们在设计分布式系统是都会考虑到bug,硬件,网络等各种原因造成的故障,所以即使部分节点或者网络出现故障,我们要求整个系统还是要继续使用的。

一般来说,分区容错无法避免,因此可以认为 CAP P 总是成立。CAP 定理告诉我们,剩下的 C A 无法同时做到。

一致性和可用性,为什么不可能同时成立?答案很简单,因为可能通信失败(即出现分区容错)

 

可用性: 一直可以正常的做读写操作。简单而言就是客户端一直可以正常访问并得到系统的正常响应。用户角度来看就是不会出现系统操作失败或者访问超时等问题。

 

一致性:在分布式系统完成某写操作后任何读操作,都应该获取到该写操作写入的那个最新的值。相当于要求分布式系统中的各节点时时刻刻保持数据的一致性。

 

9:谈谈BASE理论?

BASE理论是Basically Available(基本可用)Soft State(软状态)和Eventually Consistent(最终一致性)三个短语的缩写。

其核心思想是:既是无法做到强一致性(Strong consistency),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)。

 

BASE理论的内容

 

基本可用(Basically Available

软状态(Soft State

最终一致性(Eventually Consistent

 

1) 基本可用

什么是基本可用呢?假设系统,出现了不可预知的故障,但还是能用,相比较正常的系统而言:

响应时间上的损失:正常情况下的搜索引擎0.5秒即返回给用户结果,而基本可用的搜索引擎可以在2秒作用返回结果。

功能上的损失:在一个电商网站上,正常情况下,用户可以顺利完成每一笔订单。但是到了大促期间,为了保护购物系统的稳定性,部分消费者可能会被引导到一个降级页面。

 

2)软状态

什么是软状态呢?相对于原子性而言,要求多个节点的数据副本都是一致的,这是一种“硬状态”。

软状态指的是:允许系统中的数据存在中间状态,并认为该状态不影响系统的整体可用性,即允许系统在多个不同节点的数据副本存在数据延时。

 

3)最终一致性

上面说软状态,然后不可能一直是软状态,必须有个时间期限。在期限过后,应当保证所有副本保持数据一致性,从而达到数据的最终一致性。这个时间期限取决于网络延时、系统负载、数据复制方案设计等等因素。

而在实际工程实践中,最终一致性分为5种:

A:因果一致性(Causal consistency

    因果一致性指的是:如果节点A在更新完某个数据后通知了节点B,那么节点B之后对该数据的访问和修改都是基于A更新后的值。于此同时,和节点A无因果关系的节点C的数据访问则没有这样的限制。

 

B:读己之所写(Read your writes

    读己之所写指的是:节点A更新一个数据后,它自身总是能访问到自身更新过的最新值,而不会看到旧值。其实也算一种因果一致性。

 

C:会话一致性(Session consistency

    会话一致性将对系统数据的访问过程框定在了一个会话当中:系统能保证在同一个有效的会话中实现 “读己之所写” 的一致性,也就是说,执行更新操作之后,客户端能够在同一个会话中始终读取到该数据项的最新值。

 

D:单调读一致性(Monotonic read consistency

    单调读一致性指的是:如果一个节点从系统中读取出一个数据项的某个值后,那么系统对于该节点后续的任何数据访问都不应该返回更旧的值。

 

E:单调写一致性(Monotonic write consistency

    单调写一致性指的是:一个系统要能够保证来自同一个节点的写操作被顺序的执行。

 

在实际的实践中,这5种系统往往会结合使用,以构建一个具有最终一致性的分布式系统。

 

实际上,不只是分布式系统使用最终一致性,关系型数据库在某个功能上,也是使用最终一致性的。比如备份,数据库的复制过程是需要时间的,这个复制过程中,业务读取到的值就是旧的。当然,最终还是达成了数据一致性。这也算是一个最终一致性的经典案例。

 

总体来说BASE理论面向的是大型高可用、可扩展的分布式系统。与传统ACID特性相反,不同于ACID的强一致性模型,BASE提出通过牺牲强一致性来获得可用性,并允许数据段时间内的不一致,但是最终达到一致状态。同时,在实际分布式场景中,不同业务对数据的一致性要求不一样。因此在设计中,ACIDBASE理论往往又会结合使用。