0

1

2

3

4

5

6

7

8

9

0

1

{{ noReadMessageTotal }}

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

尚硅谷Java技术之北京高频面试题:第九章 分布式技术篇

晴天 晴天 | 238 | 686天前

尚硅谷Java技术之北京高频面试题

版本:V6.0

尚硅谷Java技术中心

第九章 分布式技术篇

1、消息队列的作用与使用场景?

消息队列在实际应用中常用的使用场景。异步处理,应用解耦,流量削锋和消息通讯四个场景

2.1异步处理

场景说明:用户注册后,需要发注册邮件和注册短信。传统的做法有两种
1.串行的方式;2.并行方式

(1)串行方式:将注册信息写入数据库成功后,发送注册邮件,再发送注册短信。以上三个任务全部完成后,返回给客户端

image.png

(2)并行方式:将注册信息写入数据库成功后,发送注册邮件的同时,发送注册短信。以上三个任务完成后,返回给客户端。与串行的差别是,并行的方式可以提高处理的时间

image.png

假设三个业务节点每个使用50毫秒钟,不考虑网络等其他开销,则串行方式的时间是150毫秒,并行的时间可能是100毫秒。

因为CPU在单位时间内处理的请求数是一定的,假设CPU1秒内吞吐量是100次。则串行方式1秒内CPU可处理的请求量是7次(1000/150)。并行方式处理的请求量是10次(1000/100)

小结:如以上案例描述,传统的方式系统的性能(并发量,吞吐量,响应时间)会有瓶颈。如何解决这个问题呢?

引入消息队列,将不是必须的业务逻辑,异步处理。改造后的架构如下:

image.png

按照以上约定,用户的响应时间相当于是注册信息写入数据库的时间,也就是50毫秒。注册邮件,发送短信写入消息队列后,直接返回,因此写入消息队列的速度很快,基本可以忽略,因此用户的响应时间可能是50毫秒。因此架构改变后,系统的吞吐量提高到每秒20
QPS。比串行提高了3倍,比并行提高了两倍

2.2应用解耦

场景说明:用户下单后,订单系统需要通知库存系统。传统的做法是,订单系统调用库存系统的接口。如下图

image.png

传统模式的缺点:

假如库存系统无法访问,则订单减库存将失败,从而导致订单失败

订单系统与库存系统耦合

如何解决以上问题呢?引入应用消息队列后的方案,如下图:

image.png

订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功

库存系统:订阅下单的消息,采用拉/推的方式,获取下单信息,库存系统根据下单信息,进行库存操作

假如:在下单时库存系统不能正常使用。也不影响正常下单,因为下单后,订单系统写入消息队列就不再关心其他的后续操作了。实现订单系统与库存系统的应用解耦

2.3流量削锋

流量削锋也是消息队列中的常用场景,一般在秒杀或团抢活动中使用广泛

应用场景:秒杀活动,一般会因为流量过大,导致流量暴增,应用挂掉。为解决这个问题,一般需要在应用前端加入消息队列。

可以控制活动的人数

可以缓解短时间内高流量压垮应用

image.png

用户的请求,服务器接收后,首先写入消息队列。假如消息队列长度超过最大数量,则直接抛弃用户请求或跳转到错误页面

秒杀业务根据消息队列中的请求信息,再做后续处理

2.4日志处理

日志处理是指将消息队列用在日志处理中,比如Kafka的应用,解决大量日志传输的问题。

日志采集客户端,负责日志数据采集,定时写受写入Kafka队列

Kafka消息队列,负责日志数据的接收,存储和转发

日志处理应用:订阅并消费kafka队列中的日志数据

以下是新浪kafka日志处理应用案例:转自(http://cloud.51cto.com/art/201507/484338.htm)

image.png

(1)Kafka:接收用户日志的消息队列

(2)Logstash:做日志解析,统一成JSON输出给Elasticsearch

(3)Elasticsearch:实时日志分析服务的核心技术,一个schemaless,实时的数据存储服务,通过index组织数据,兼具强大的搜索和统计功能

(4)Kibana:基于Elasticsearch的数据可视化组件,超强的数据可视化能力是众多公司选择ELK
stack的重要原因

2.5消息通讯

消息通讯是指,消息队列一般都内置了高效的通信机制,因此也可以用在纯的消息通讯。比如实现点对点消息队列,或者聊天室等

点对点通讯:

image.png

客户端A和客户端B使用同一队列,进行消息通讯。

聊天室通讯:

客户端A,客户端B,客户端N订阅同一主题,进行消息发布和接收。实现类似聊天室效果。

以上实际是消息队列的两种消息模式,点对点或发布订阅模式。模型为示意图,供参考。

2、RabbitMQ如何保证消息的顺序性?

消息队列中的若干消息如果是对同一个数据进行操作,这些操作具有前后的关系,必须要按前后的顺序执行,否则就会造成数据异常。举例:
比如通过mysql
binlog进行两个数据库的数据同步,由于对数据库的数据操作是具有顺序性的,如果操作顺序搞反,就会造成不可估量的错误。比如数据库对一条数据依次进行了
插入->更新->删除操作,这个顺序必须是这样,如果在同步过程中,消息的顺序变成了
删除->插入->更新,那么原本应该被删除的数据,就没有被删除,造成数据的不一致问题。

举例场景:

RabbitMQ:①一个queue,有多个consumer去消费,这样就会造成顺序的错误,consumer从MQ里面读取数据是有序的,但是每个consumer的执行时间是不固定的,无法保证先读到消息的consumer一定先完成操作,这样就会出现消息并没有按照顺序执行,造成数据顺序错误。

image.png

②一个queue对应一个consumer,但是consumer里面进行了多线程消费,这样也会造成消息消费顺序错误。

image.png

解决方案:

①拆分多个queue,每个queue一个consumer,就是多一些queue而已,确实是麻烦点;这样也会造成吞吐量下降,可以在消费者内部采用多线程的方式取消费。

image.png

一个queue对应一个consumer

②或者就一个queue但是对应一个consumer,然后这个consumer内部用内存队列做排队,然后分发给底层不同的worker来处理

image.png
一个queue对应一个consumer,采用多线程

3、如何使用RabbitMQ解决分布式事务?

**分布式事务:**不同的服务操作不同的数据源(库或表),保证数据一致性的问题。

**解决:**采用RabbitMQ消息最终一致性的解决方案,解决分布式事务问题。

分布式事务场景:

1、电商项目中的商品库和ES库数据同步问题。

2、电商项目中:支付----订单—库存,一系列操作,进行状态更改等。

在互联网应用中,基本都会有用户注册的功能。在注册的同时,我们会做出如下操作:

收集用户录入信息,保存到数据库向用户的手机或邮箱发送验证码等等…

如果是传统的集中式架构,实现这个功能非常简单:开启一个本地事务,往本地数据库中插入一条用户数据,发送验证码,提交事物。

但是在分布式架构中,用户和发送验证码是两个独立的服务,它们都有各自的数据库,那么就不能通过本地事物保证操作的原子性。这时我们就需要用到
RabbitMQ(消息队列)来为我们实现这个需求。

在用户进行注册操作的时候,我们为该操作创建一条消息,当用户信息保存成功时,把这条消息发送到消息队列。验证码系统会监听消息,一旦接受到消息,就会给该用户发送验证码。

4、RabbitMQ如何确保消息可靠性?

消息可靠性一般来说由3方面来保证:

1) 生产者

RabbitMQ提供transaction事务和confirm模式来确保生产者不丢消息;

Transaction事务机制就是说:发送消息前,开启事务(channel.txSelect()),然后发送消息,如果发送过程中出现什么异常,事务就会回滚
(channel.txRollback()),如果发送成功则提交事务(channel.txCommit()),然而,这种方式有个缺点:吞吐量下降。

confirm模式用的居多:一旦channel进入confirm模式,所有在该信道上发布的消息都将会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后;

rabbitMQ就会发送一个ACK给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了;

如果rabbitMQ没能处理该消息,则会发送一个Nack消息给你,可以进行重试操作。

2) 消息队列本身

可以进行消息持久化, 即使rabbitMQ挂了,重启后也能恢复数据

如果要进行消息持久化,那么需要对以下3种实体均配置持久化

a) Exchange

声明exchange时设置持久化(durable = true)并且不自动删除(autoDelete =
false)

b) Queue

声明queue时设置持久化(durable = true)并且不自动删除(autoDelete =
false)

c) message

发送消息时通过设置deliveryMode=2持久化消息

3) 消费者

消费者丢数据一般是因为采用了自动确认消息模式,消费者在收到消息之后,处理消息之前,会自动回复RabbitMQ已收到消息;如果这时处理消息失败,就会丢失该消息;改为手动确认消息即可!手动确认模式下消费失败时,不将其重新放入队列(确认重试也不会成功的情形),打印错误信息后,通知相关人员,人工介入处理。

5、如何防止RabbitMQ消息重复消费?

保证消息幂等性。幂等性概念:一个请求,不管重复来多少次,结果是不会改变的。

RabbitMQ、RocketMQ、Kafka等任何队列不保证消息不重复,如果业务需要消息不重复消费,则需要消费端处理业务消息要保持幂等性

方式一:Redis的setNX() , 做消息id去重 java版本目前不支持设置过期时间

//Redis中操作,判断是否已经操作过 TODO

boolean flag = jedis.setNX(key);

if(flag){

//消费

}else{

//忽略,重复消费

}

方式二:redis的 Incr 原子操作:key自增,大于0
返回值大于0则说明消费过,(key可以是消息的md5取值,
或者如果消息id设计合理直接用id做key)

int num = jedis.incr(key);

if(num == 1){

//消费

}else{

//忽略,重复消费

}

方式三:数据库去重表

设计一个去重表,某个字段使用Message的key做唯一索引,因为存在唯一索引,所以重复消费会失败

CREATE TABLE `message_record` ( `id` int(11) unsigned NOT NULL
AUTO_INCREMENT, `key` varchar(128) DEFAULT NULL, `create_time`
datetime DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `key`
(`key`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

6、什么是ElasticSearch? 怎么解决中文分词?

Elasticsearch是一个基于Lucene的搜索引擎。它提供了具有HTTP
Web界面和无架构JSON文档的分布式,多租户能力的全文搜索引擎。Elasticsearch是用Java开发的,根据Apache许可条款作为开源发布。

采用IK中文分词器插件进行中文分词处理。

7、Elasticsearch中的倒排索引是什么?

倒排索引是搜索引擎的核心。搜索引擎的主要目标是在查找发生搜索条件的文档时提供快速搜索。倒排索引是一种像数据结构一样的散列图,可将用户从单词导向文档或网页。它是搜索引擎的核心。其主要目标是快速搜索从数百万文件中查找数据。

8、Dubbo执行流程是什么样的?各个组件作用

image.png

它的架构主要有五个角色/核心组件,分为是Container(容器)、Provider(服务的提供方)、Registry(注册中心)、Consumer(服务的消费方)、Monitor(监控中心)。

容器主要负责启动、加载、运行服务提供者;

同时服务提供者在启动时,向注册中心注册自己提供的服务;

消费者向注册中心订阅自己的服务;

注册中心返回服务提供者列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者;

对于服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另外一台调用;

服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心;

注册中心:

  1. 注册中心只负责地址的注册和查找,相当于我们的目录服务,只有在容器启动时,服务提供者和调用者与注册中心交互,整个过程,注册中心不参与数据传输,不转发请求,压力较小
    (“两不一小”);

  2. 注册中心宕机问题:(Dubbo挂了怎么办?)

注册中心会部署集群,

如果集群中的任意一台宕机之后,将自动切换到另外一台,不会影响已运行的提供者和消费者;

如果集群中所有注册中心全部宕机之后,服务提供者和服务消费者仍能通过本地缓存通讯;注册中心仍能通过缓存提供服务列表查询,但不能注册新服务;

监控中心:

  1. 监控中心负责统计各服务调用次数、调用时间等,统计先在内存汇总后每分钟一次发送到监控中心服务器,并以报表展示

  2. 监控中心宕机不影响使用,只是丢失部分的采样数据;

  3. dubbo-admin可以通过监控中心的可视化界面,进行禁止服务和截止消费者(大量恶意访问的ip);

11、ZooKeeper是什么?

ZooKeeper是一个开放源码的分布式协调服务,它是集群的管理者,监视着集群中各个节点的状态根据节点提交的反馈进行下一步合理操作。最终,将简单易用的接口和性能高效、功能稳定的系统提供给用户。

分布式应用程序可以基于Zookeeper实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master选举、分布式锁和分布式队列等功能。

Zookeeper保证了如下分布式一致性特性:

  • 顺序一致性

  • 原子性

  • 单一视图

  • 可靠性

  • 实时性(最终一致性)

客户端的读请求可以被集群中的任意一台机器处理,如果读请求在节点上注册了监听器,这个监听器也是由所连接的zookeeper机器来处理。对于写请求,这些请求会同时发给其他zookeeper机器并且达成一致后,请求才会返回成功。因此,随着zookeeper的集群机器增多,读请求的吞吐会提高但是写请求的吞吐会下降。

有序性是zookeeper中非常重要的一个特性,所有的更新都是全局有序的,每个更新都有一个唯一的时间戳,这个时间戳称为zxid(Zookeeper
Transaction
Id)。而读请求只会相对于更新有序,也就是读请求的返回结果中会带有这个zookeeper最新的zxid。

12、Zookeeper Watcher 机制 – 数据变更通知

Zookeeper允许客户端向服务端的某个Znode注册一个Watcher监听,当服务端的一些指定事件触发了这个Watcher,服务端会向指定客户端发送一个事件通知来实现分布式的通知功能,然后客户端根据Watcher通知状态和事件类型做出业务上的改变。

工作机制:

  • 客户端注册watcher

  • 服务端处理watcher

  • 客户端回调watcher

Watcher特性总结:

(1)一次性
无论是服务端还是客户端,一旦一个Watcher被触发,Zookeeper都会将其从相应的存储中移除。这样的设计有效的减轻了服务端的压力,不然对于更新非常频繁的节点,服务端会不断的向客户端发送事件通知,无论对于网络还是服务端的压力都非常大。

(2)客户端串行执行
客户端Watcher回调的过程是一个串行同步的过程。

(3)轻量

Watcher通知非常简单,只会告诉客户端发生了事件,而不会说明事件的具体内容。

客户端向服务端注册Watcher的时候,并不会把客户端真实的Watcher对象实体传递到服务端,仅仅是在客户端请求中使用boolean类型属性进行了标记。

watcher
event异步发送watcher的通知事件从server发送到client是异步的,这就存在一个问题,不同的客户端和服务器之间通过socket进行通信,由于网络延迟或其他因素导致客户端在不通的时刻监听到事件,由于Zookeeper本身提供了ordering
guarantee,即客户端监听事件后,才会感知它所监视znode发生了变化。所以我们使用Zookeeper不能期望能够监控到节点每次的变化。Zookeeper只能保证最终的一致性,而无法保证强一致性。

注册watcher getData、exists、getChildren

触发watcher create、delete、setData

当一个客户端连接到一个新的服务器上时,watch将会被以任意会话事件触发。当与一个服务器失去连接的时候,是无法接收到watch的。而当client重新连接时,如果需要的话,所有先前注册过的watch,都会被重新注册。通常这是完全透明的。只有在一个特殊情况下,watch可能会丢失:对于一个未创建的znode的exist
watch,如果在客户端断开连接期间被创建了,并且随后在客户端连接上之前又删除了,这种情况下,这个watch事件可能会被丢失。

13、ZooKeeper分布式锁的实现原理

使用zookeeper创建临时序列节点来实现分布式锁,适用于顺序执行的程序,大体思路就是创建临时序列节点,找出最小的序列节点,获取分布式锁,程序执行完成之后此序列节点消失,通过watch来监控节点的变化,从剩下的节点的找到最小的序列节点,获取分布式锁,执行相应处理。

14、Nginx原理

Nginx内部进程模型:

image.png

1.在nginx启动后,会有一个master进程和多个worker进程,master进程主要用来管理worker进程,包括:接受信号,将信号分发给worker进程,监听worker进程工作状态,当worker进程退出时(非正常),启动新的worker进程。基本的网络事件会交给worker进程处理。多个worker进程之间是对等的,他们同等竞争来自客户端的请求,各进程互相之间是独立的 。一个请求,只可能在一个worker进程中处理,一个worker进程,不可能处理其它进程的请求。 worker进程的个数是可以设置的,一般我们会设置与机器cpu核数一致,这里面的原因与nginx的进程模型以及事件处理模型是分不开的 。

2.当master接收到重新加载的信号会怎么处理(./nginx -s
reload)?,master会重新加载配置文件,然后启动新的进程,使用的新的worker进程来接受请求,并告诉老的worker进程他们可以退休了,老的worker进程将不会接受新的,老的worker进程处理完手中正在处理的请求就会退出。

3.worker进程是如何处理用户的请求呢?首先master会根据配置文件生成一个监听相应端口的socket,然后再faster出多个worker进程,这样每个worker就可以接受从socket过来的消息(其实这个时候应该是每一个worker都有一个socket,只是这些socket监听的地址是一样的)。当一个连接过来的时候,每一个worker都能接收到通知,但是只有一个worker能和这个连接建立关系,其他的worker都会连接失败,这就是所谓的惊群现在,为了解决这个问题,nginx提供一个共享锁accept_mutex,有了这个共享锁后,就会只有一个worker去接收这个连接。当一个worker进程在accept这个连接之后,就开始读取请求,解析请求,处理请求,产生数据后,再返回给客户端,最后才断开连接,这样一个完整的请求就是这样的了。

image.png

master-workers的机制的好处

首先,对于每个worker进程来说,独立的进程,不需要加锁,所以省掉了锁带来的开销,同时在编程以及问题查找时,也会方便很多。

其次,采用独立的进程,可以让互相之间不会影响,一个进程退出后,其它进程还在工作,服务不会中断,master进程则很快启动新的worker进程。

当然,worker进程的异常退出,肯定是程序有bug了,异常退出,会导致当前worker上的所有请求失败,不过不会影响到所有请求,所以降低了风险。

需要设置多少个worker

Nginx
同redis类似都采用了io多路复用机制,每个worker都是一个独立的进程,但每个进程里只有一个主线程,通过异步非阻塞的方式来处理请求,
即使是千上万个请求也不在话下。每个worker的线程可以把一个cpu的性能发挥到极致。

所以worker数和服务器的cpu数相等是最为适宜的。设少了会浪费cpu,设多了会造成cpu频繁切换上下文带来的损耗。

#设置worker数量

worker_processes 4

#work绑定cpu(4 work绑定4cpu)。

worker_cpu_affinity 0001 0010 0100 1000

#work绑定cpu (4 work绑定8cpu中的4个) 。

worker_cpu_affinity 00000001 00000010 00000100 00001000 00010000
00100000 01000000 10000000

#连接数

worker_connections 1024

这个值是表示每个worker进程所能建立连接的最大值,所以,一个nginx能建立的最大连接数,应该是worker_connections
*
worker_processes。当然,这里说的是最大连接数,对于HTTP请求本地资源来说,能够支持的最大并发数量是worker_connections
*
worker_processes,如果是支持http1.1的浏览器每次访问要占两个连接,所以普通的静态访问最大并发数是:
worker_connections * worker_processes
/2,而如果是HTTP作为反向代理来说,最大并发数量应该是worker_connections
* worker_processes/4。

15、Nginx作用以及常见配置

**作用:**反向代理、负载均衡、动静分离(静态资源服务器,可以进行图片等资源管理)

具体配置实现:

反向代理:

在 nginx.conf 配置文件中增加如下配置

image.png

负载均衡:

常见策略

**权重:**weight代表权,重默认为1,权重越高被分配的客户端越多

指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况。
例如:

upstream server_pool{   
server 192.168.5.21 weight=1;    
server 192.168.5.22 weight=2; 
server 192.168.5.23 weight=3;   
}

**ip_hash:**每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session的问题。
例如:

upstream server_pool{

ip_hash;

server 192.168.5.21:80;

server 192.168.5.22:80;

}

**fair(第三方):**按后端服务器的响应时间来分配请求,响应时间短的优先分配。

upstream server_pool{

server 192.168.5.21:80;

server 192.168.5.22:80;

fair;

}

轮询(默认)

image.png

image.png

静态资源(动静分离):

image.png

相关文章:
第一章 面试技巧篇
第二章 数据结构、设计模式与手写代码
第三章 Java基础篇
第四章 Java高级篇
第五章 MySQL数据库篇
第六章 Java Web篇
第七章 Java框架篇
第八章 Redis数据库篇
第九章 分布式技术篇
第十章 Git与Linux篇
第十一章 电商项目篇之尚品汇商城

真诚点赞 诚不我欺~

{{ praiseUserVoList.length }}人点赞

item.nickname

尚硅谷Java技术之北京高频面试题:第九章 分布式技术篇

{{ isPraise ? '已点赞' : '点赞'}}
{{ isCollect ? '已收藏' : '收藏'}}
评论
gOod mornIng
没有更多啦~ 加载中...

关于作者

晴天
晴天

人因梦想而伟大!