记一次面试腾讯全资子公司问到的问题

 1.你项目中用到了spring cloud,请问你用了哪些spring cloud组件?java

答:服务发现——Netflix Eureka,客服端负载均衡——Netflix Ribbon,node

断路器——Netflix Hystrix,服务网关——Netflix Zuul,分布式配置——Spring Cloud Confignginx

 

2.谈一谈断路器Hystrix的原理web

答:Hystrix 是一个帮助解决分布式系统交互时超时处理和容错的类库, 它一样拥有保护系统的能力。Netflix的众多开源项目之一。面试

     a. 隔离:redis

           Hystrix隔离方式采用线程/信号的方式,经过隔离限制依赖的并发量和阻塞扩散spring

            1)线程隔离docker

            Hystrix在用户请求和服务之间加入了线程池。数据库

            Hystrix为每一个依赖调用分配一个小的线程池,若是线程池已满调用将被当即拒绝,默认不采用排队.加速失败断定时间。线程数是能够被设定的。apache

            原理:用户的请求将再也不直接访问服务,而是经过线程池中的空闲线程来访问服务,若是线程池已满,则会进行降级处理,用户的请求不会被阻塞,至少能够看到一个执行结果(例如返回友好的提示信息),而不是无休止的等待或者看到系统崩溃。

隔离前:

            

     

 

隔离后:

     2)信号隔离:

         信号隔离也能够用于限制并发访问,防止阻塞扩散, 与线程隔离最大不一样在于执行依赖代码的线程依然是请求线程(该线程须要经过信号申请, 若是客户端是可信的且能够快速返回,可使用信号隔离替换线程隔离,下降开销。信号量的大小能够动态调整, 线程池大小不能够。

     b. 熔断:

若是某个目标服务调用慢或者有大量超时,此时,熔断该服务的调用,对于后续调用请求,不在继续调用目标服务,直接返回,快速释放资源。若是目标服务状况好转则恢复调用。

熔断器:Circuit Breaker

      熔断器是位于线程池以前的组件。用户请求某一服务以后,Hystrix会先通过熔断器,此时若是熔断器的状态是打开(跳起),则说明已经熔断,这时将直接进行降级处理,不会继续将请求发到线程池。熔断器至关于在线程池以前的一层屏障。每一个熔断器默认维护10个bucket ,每秒建立一个bucket ,每一个blucket记录成功,失败,超时,拒绝的次数。当有新的bucket被建立时,最旧的bucket会被抛弃。

         熔断器的状态机:

           

  • Closed:熔断器关闭状态,调用失败次数积累,到了阈值(或必定比例)则启动熔断机制;
  • Open:熔断器打开状态,此时对下游的调用都内部直接返回错误,不走网络,但设计了一个时钟选项,默认的时钟达到了必定时间(这个时间通常设置成平均故障处理时间,也就是MTTR),到了这个时间,进入半熔断状态;
  • Half-Open:半熔断状态,容许定量的服务请求,若是调用都成功(或必定比例)则认为恢复了,关闭熔断器,不然认为还没好,又回到熔断器打开状态; 

 

3.说一说hashmap的扩容机制,hashmap不是线程安全的,一写多读会怎么样?

答:hashmap的扩容须要知足两个条件:当前数据存储的数量(即size())大小必须大于等于阈值;当前加入的数据是否发生了hash冲突。

由于上面这两个条件,因此存在下面这些状况

(1)、就是hashmap在存值的时候(默认大小为16,负载因子0.75,阈值12),可能达到最后存满16个值的时候,再存入第17个值才会发生扩容现象,由于前16个值,每一个值在底层数组中分别占据一个位置,并无发生hash碰撞。

(2)、固然也有可能存储更多值(超多16个值,最多能够存26个值)都尚未扩容。原理:前11个值所有hash碰撞,存到数组的同一个位置(这时元素个数小于阈值12,不会扩容),后面全部存入的15个值所有分散到数组剩下的15个位置(这时元素个数大于等于阈值,可是每次存入的元素并无发生hash碰撞,因此不会扩容),前面11+15=26,因此在存入第27个值的时候才同时知足上面两个条件,这时候才会发生扩容现象。

(3)读写不一致也就会产生线程安全问题,因此hashmap一写多读也不是线程安全的。

 

4.你项目中用到了redis,说一说redis有哪些结构?

答:字符串,哈希,列表,集合,有序集合

 

5.redis中的存储的字符串value值有啥限制

答:最大不能超过512M。

 

6.谈一谈redis的list结构

答:列表对象的编码能够是ziplist和linkedlist之一。

(1) ziplist编码

ziplist编码的哈希随想底层实现是压缩列表,每一个压缩里列表节点保存了一个列表元素。

(2)linkedlist编码

linkedlist编码底层采用双端链表实现,每一个双端链表节点都保存了一个字符串对象,在每一个字符串对象内保存了一个列表元素。

列表对象编码转换:

    • 列表对象使用ziplist编码须要知足两个条件:一是全部字符串长度都小于64字节,二是元素数量小于512,不知足任意一个都会使用linkedlist编码。
    • 两个条件的数字能够在Redis的配置文件中修改,list-max-ziplist-value选项和list-max-ziplist-entries选项。
    • 图中StringObject就是上一节讲到的字符串对象,字符串对象是惟一个在五大对象中做为嵌套对象使用的

 

7.你项目经历还写了用到了k8s,谈一谈你对k8s的认识?

答:Kubernetes,它能够帮助用户省去应用容器化过程的许多手动部署和扩展操做。也就是说,您能够将运行 Linux 容器的多组主机汇集在一块儿,由 Kubernetes 帮助您轻松高效地管理这些集群。并且,这些集群可跨公共云、私有云或混合云部署主机。所以,对于要求快速扩展的云原生应用而言(例如借助 Apache Kafka 进行的实时数据流处理)Kubernetes 是理想的托管平台。。真正的生产型应用会涉及多个容器。这些容器必须跨多个服务器主机进行部署。Kubernetes 能够提供所需的编排和管理功能,以便您针对这些工做负载大规模部署容器。借助 Kubernetes 编排功能,您能够构建跨多个容器的应用服务、跨集群调度、扩展这些容器,并长期持续管理这些容器的健康情况。而后巴拉巴拉说了一些七杂八杂的。

 

8.谈一谈K8S中的node和pod?

 除了Master,Kubernetes集群中的其余机器被称为Node节点,在较早的版本中也被称为Minion。与Master同样,Node节点能够是一台物理主机,也能够是一台虚拟机。Node节点才是Kubernetes集群中的工做负载节点,每一个Node都会被Master分配一些工做负载(Docker容器),当某个Node宕机时,其上的工做负载会被Master自动转移到其余节点上去。

 每一个Node节点上都运行着如下一组关键进程:

     kubelet:负责Pod对应的容器的建立、启停等任务,同时与Master节点密切协做,实现集群管理的基本功能。

     kube-proxy:实现Kubernetes Service的通讯与负载均衡机制的重要组件。

     Docker Engine (docker):Docker引擎,负责本机的容器建立和管理工做。

 Node节点能够在运行期间动态增长到Kubernetes集群中,前提是这个节点上已经正确安装、配置和启动了上述关键进程,在默认状况下kubelet会向Master注册本身,这也是Kubernetes推荐的Node管理方式。一旦Node被归入集群管理范围,kubelet进程就会定时向Master节点汇报自身的情报,例如操做系统、Docker版本、机器的CPU和内存状况,以及当前有哪些Pod在运行等,这样Master能够获知每一个Node的资源使用状况,并实现高效均衡等资源调度策略。而某个Node超过指定时间不上报信息时,会被Master判断为“失联”,Node的状态被标记为不可用(Not Ready),随后Master会触发“工做负载大转移”的自动流程。

 Pod是Kubernetes的最重要也最基本的概念,以下图所示是Pod的组成示意图,咱们看到每一个Pod都有一个特殊的被成为“根容器”的Pause容器。Pause容器对应的镜像属于Kubernetes平台的一部分,除了Pause容器,每一个Pod还包含一个或多个紧密相关的用户业务容器。

 

9.说一说K8S的经常使用命令?

下表包括全部kubectl操做的简短描述和通常语法:

Operation

Syntax

Description

annotate

`kubectl annotate (-f FILENAME

TYPE NAME

api-versions

kubectl api-versions [flags]

列出可用的API版本。

apply

kubectl apply -f FILENAME [flags]

对文件或标准输入流更改资源应用配置。

attach

kubectl attach POD -c CONTAINER [-i] [-t] [flags]

attach 到正在运行的容器来查看输出流或与容器(stdin)进行交互。

autoscale

`kubectl autoscale (-f FILENAME

TYPE NAME

cluster-info

kubectl cluster-info [flags]

显示有关集群中master节点和服务的端点信息。

config

kubectl config SUBCOMMAND [flags]

修改kubeconfig文件。有关详细信息,请参阅各个子命令。

create

kubectl create -f FILENAME [flags]

从文件或stdin建立一个或多个资源。

delete

`kubectl delete (-f FILENAME

TYPE [NAME

describe

`kubectl describe (-f FILENAME

TYPE [NAME_PREFIX

edit

`kubectl edit (-f FILENAME

TYPE NAME

exec

kubectl exec POD [-c CONTAINER] [-i] [-t] [flags] [-- COMMAND [args...]]

对pod中的容器执行命令

explain

kubectl explain [--include-extended-apis=true] [--recursive=false] [flags]

获取各类资源的文档。例如 pods, nodes, services 等.

expose

`kubectl expose (-f FILENAME

TYPE NAME

get

`kubectl get (-f FILENAME

TYPE [NAME

label

`kubectl label (-f FILENAME

TYPE NAME

logs

kubectl logs POD [-c CONTAINER] [--follow] [flags]

在pod的容器中打印日志。

patch

`kubectl patch (-f FILENAME

TYPE NAME

port-forward

kubectl port-forward POD [LOCAL_PORT:]REMOTE_PORT [...[LOCAL_PORT_N:]REMOTE_PORT_N] [flags]

将一个或多个本地端口转发到pod。

proxy

kubectl proxy [--port=PORT] [--www=static-dir] [--www-prefix=prefix] [--api-prefix=prefix] [flags]

运行一个代理到Kubernetes API服务器。

replace

kubectl replace -f FILENAME

从文件或stdin替换资源。

rolling-update

`kubectl rolling-update OLD_CONTROLLER_NAME ([NEW_CONTROLLER_NAME] –image=NEW_CONTAINER_IMAGE

-f NEW_CONTROLLER_SPEC) [flags]`

run

kubectl run NAME --image=image [--env="key=value"] [--port=port] [--replicas=replicas] [--dry-run=bool] [--overrides=inline-json] [flags]

在集群上运行指定的镜像。

scale

`kubectl scale (-f FILENAME

TYPE NAME

stop

kubectl stop

已弃用: 相应的, 请查看 kubectl delete.

version

kubectl version [--client] [flags]

显示在客户端和服务器上运行的Kubernetes版本。

10.你简历项目中用到了kafka,谈一谈大家为何用kafka,而不用其余消息队列?

答:

特性

MQ

ActiveMQ

RabbitMQ

RocketMQ

Kafka

生产者消费者模式

支持

支持

支持

支持

发布订阅模式

支持

支持

支持

支持

请求回应模式

支持

支持

不支持

不支持

Api完备性

多语言支持

支持

支持

java

支持

单机吞吐量

万级

万级

十万级

十万级

消息延迟

微秒级

毫秒级

毫秒级

可用性

高(主从)

高(主从)

很是高(分布式)

很是高(分布式)

消息丢失

理论上不会丢失

理论上不会丢失

文档的完备性

教高

提供快速入门

社区活跃度

商业支持

商业云

商业云

整体来讲:

ActiveMQ 历史悠久的开源项目,已经在不少产品中获得应用,实现了JMS1.1规范,能够和spring-jms轻松融合,实现了多种协议,不够轻巧(源代码比RocketMQ多),支持持久化到数据库,对队列数较多的状况支持很差。

RabbitMQ 它比Kafka成熟,支持AMQP事务处理,在可靠性上,RabbitMQ超过Kafka,在性能方面超过ActiveMQ。

RocketMQ RocketMQ是阿里开源的消息中间件,目前在Apache孵化,使用纯Java开发,具备高吞吐量、高可用性、适合大规模分布式系统应用的特色。RocketMQ思路起源于Kafka,但并非简单的复制,它对消息的可靠传输及事务性作了优化,目前在阿里集团被普遍应用于交易、充值、流计算、消息推送、日志流式处理、binglog分发等场景,支撑了阿里屡次双十一活动。 由于是阿里内部从实践到产品的产物,所以里面不少接口、API并非很广泛适用。其可靠性毋庸置疑,并且与Kafka一脉相承(甚至更优),性能强劲,支持海量堆积。

Kafka Kafka设计的初衷就是处理日志的,不支持AMQP事务处理,能够看作是一个日志系统,针对性很强,因此它并无具有一个成熟MQ应该具有的特性。Kafka的性能(吞吐量、tps)比RabbitMQ要强,若是用来作大数据量的快速处理是比RabbitMQ有优点的。

11.kafka为何比较快?

Kafka的消息是保存或缓存在磁盘上的,通常认为在磁盘上读写数据是会下降性能的,由于寻址会比较消耗时间,可是实际上,Kafka的特性之一就是高吞吐率。

即便是普通的服务器,Kafka也能够轻松支持每秒百万级的写入请求,超过了大部分的消息中间件,这种特性也使得Kafka在日志处理等海量数据场景普遍应用。

写入数据,Kafka会把收到的消息都写入到硬盘中,它绝对不会丢失数据。为了优化写入速度Kafka采用了两个技术, 顺序写入 和 MMFile 。

顺序写入,磁盘读写的快慢取决于你怎么使用它,也就是顺序读写或者随机读写。在顺序读写的状况下,某些优化场景磁盘的读写速度能够和内存持平(注:此处有疑问, 不推敲细节,参考 http://searene.me/2017/07/09/Why-is-Kafka-so-fast/
)。
由于硬盘是机械结构,每次读写都会寻址->写入,其中寻址是一个“机械动做”,它是最耗时的。因此硬盘最讨厌随机I/O,最喜欢顺序I/O。为了提升读写硬盘的速度,Kafka就是使用顺序I/O。

并且Linux对于磁盘的读写优化也比较多,包括read-ahead和write-behind,磁盘缓存等。若是在内存作这些操做的时候,一个是JAVA对象的内存开销很大,另外一个是随着堆内存数据的增多,JAVA的GC时间会变得很长,使用磁盘操做有如下几个好处:

磁盘顺序读写速度超过内存随机读写

JVM的GC效率低,内存占用大。使用磁盘能够避免这一问题

系统冷启动后,磁盘缓存依然可用

在这里插入图片描述

上图就展现了Kafka是如何写入数据的, 每个Partition其实都是一个文件 ,收到消息后Kafka会把数据插入到文件末尾(虚框部分)。

这种方法有一个缺陷—— 没有办法删除数据 ,因此Kafka是不会删除数据的,它会把全部的数据都保留下来,每一个消费者(Consumer)对每一个Topic都有一个offset用来表示 读取到了第几条数据 。

在这里插入图片描述

两个消费者,Consumer1有两个offset分别对应Partition0、Partition1(假设每个Topic一个Partition);Consumer2有一个offset对应Partition2。这个offset是由客户端SDK负责保存的,Kafka的Broker彻底无视这个东西的存在;通常状况下SDK会把它保存到zookeeper里面。(因此须要给Consumer提供zookeeper的地址)。

若是不删除硬盘确定会被撑满,因此Kakfa提供了两种策略来删除数据。一是基于时间,二是基于partition文件大小。具体配置能够参看它的配置文档。

Memory Mapped Files

即使是顺序写入硬盘,硬盘的访问速度仍是不可能追上内存。因此Kafka的数据并 不是实时的写入硬盘 ,它充分利用了现代操做系统 分页存储 来利用内存提升I/O效率。

Memory Mapped Files(后面简称mmap)也被翻译成 内存映射文件 ,在64位操做系统中通常能够表示20G的数据文件,它的工做原理是直接利用操做系统的Page来实现文件到物理内存的直接映射。完成映射以后你对物理内存的操做会被同步到硬盘上(操做系统在适当的时候)。

经过mmap,进程像读写硬盘同样读写内存(固然是虚拟机内存),也没必要关心内存的大小有虚拟内存为咱们兜底。

使用这种方式能够获取很大的I/O提高, 省去了用户空间到内核空间 复制的开销(调用文件的read会把数据先放到内核空间的内存中,而后再复制到用户空间的内存中。)也有一个很明显的缺陷——不可靠, 写到mmap中的数据并无被真正的写到硬盘,操做系统会在程序主动调用flush的时候才把数据真正的写到硬盘。 Kafka提供了一个参数——producer.type来控制是否是主动flush,若是Kafka写入到mmap以后就当即flush而后再返回Producer叫 同步 (sync);写入mmap以后当即返回Producer不调用flush叫 异步 (async)。

读取数据,Kafka在读取磁盘时作了哪些优化?

基于sendfile实现Zero Copy

传统模式下,当须要对一个文件进行传输的时候,其具体流程细节以下:

调用read函数,文件数据被copy到内核缓冲区

read函数返回,文件数据从内核缓冲区copy到用户缓冲区

write函数调用,将文件数据从用户缓冲区copy到内核与socket相关的缓冲区。

数据从socket缓冲区copy到相关协议引擎。

以上细节是传统read/write方式进行网络文件传输的方式,咱们能够看到,在这个过程中,文件数据其实是通过了四次copy操做:

硬盘—>内核buf—>用户buf—>socket相关缓冲区—>协议引擎

而sendfile系统调用则提供了一种减小以上屡次copy,提高文件传输性能的方法。
在内核版本2.1中,引入了sendfile系统调用,以简化网络上和两个本地文件之间的数据传输。 sendfile的引入不只减小了数据复制,还减小了上下文切换。

sendfile(socket, file, len);

运行流程以下:

sendfile系统调用,文件数据被copy至内核缓冲区

再从内核缓冲区copy至内核中socket相关的缓冲区

最后再socket相关的缓冲区copy到协议引擎

相较传统read/write方式,2.1版本内核引进的sendfile已经减小了内核缓冲区到user缓冲区,再由user缓冲区到socket相关缓冲区的文件copy,而在内核版本2.4以后,文件描述符结果被改变,sendfile实现了更简单的方式,再次减小了一次copy操做。

在apache,nginx,lighttpd等web服务器当中,都有一项sendfile相关的配置,使用sendfile能够大幅提高文件传输性能。

Kafka把全部的消息都存放在一个一个的文件中,当消费者须要数据的时候Kafka直接把文件发送给消费者,配合mmap做为文件读写方式,直接把它传给sendfile。

批量压缩

在不少状况下,系统的瓶颈不是CPU或磁盘,而是网络IO,对于须要在广域网上的数据中心之间发送消息的数据流水线尤为如此。进行数据压缩会消耗少许的CPU资源,不过对于kafka而言,网络IO更应该须要考虑。

若是每一个消息都压缩,可是压缩率相对很低,因此Kafka使用了批量压缩,即将多个消息一块儿压缩而不是单个消息压缩

Kafka容许使用递归的消息集合,批量的消息能够经过压缩的形式传输而且在日志中也能够保持压缩格式,直到被消费者解压缩

Kafka支持多种压缩协议,包括Gzip和Snappy压缩协议

Kafka速度的秘诀在于,它把全部的消息都变成一个批量的文件,而且进行合理的批量压缩,减小网络IO损耗,经过mmap提升I/O速度,写入数据的时候因为单个Partion是末尾添加因此速度最优;读取数据的时候配合sendfile直接暴力输出。 

12.你项目中用到了多线程,若是线上发生死锁问题,你会怎么排查?

答:在分布式部署的状况下须要根据链路追踪找到对应服务器死锁的进程,找到机器后根据jps -l 查找正在运行的java程序的pid,而后再根据jastack -pid 就能查到死锁的位置

总结:

这是今晚进行一场电话面试实际问到的问题,问的问题基本平时都遇到过,但感受回答得不够深刻,因此面试了大概40分钟就结束了。面而知不足,现整理下来记录(答案已作整理)