《最少须要面试题》第一版

打印 上一主题 下一主题

主题 877|帖子 877|积分 2631

《最少须要面试题》第一版

相信大家都会有种及眼熟又陌生的感觉、看过可能在短暂的面试后又马上忘记了。JavaPub 在这里整理这些容易忘记的重点知识及 解答,发起收藏,经常温习查阅。
点击在线阅读《最少须要面试题》
更多
作者:JavaPub2024

目录

缓存

1. 什么是缓存?

缓存,就是数据交换的缓冲区,针对服务对象的差别(本质就是差别的硬件)都可以构建缓存。而我们平时说的缓存,大多是指内存。
目的是, 把读写速度【慢】的介质的数据保存在读写速度【快】的介质中,从而提高读写速度,减少时间斲丧。 比方:

  • CPU 高速缓存 :高速缓存的读写速度远高于内存。

    • CPU 读数据时,如果在高速缓存中找到所需数据,就不需要读内存
    • CPU 写数据时,先写到高速缓存,再回写到内存。

  • 磁盘缓存:磁盘缓存其实就把常用的磁盘数据保存在内存中,内存读写速度也是远高于磁盘的。

    • 读数据,时从内存读取。
    • 写数据时,可先写到内存,定时或定量回写到磁盘,或者是同步回写。

2. 为什么要用缓存?

利用缓存的目的,就是提拔读写性能。而实际业务场景下,更多的是为了提拔读性能,带来更好的性能,更高的并发量。
日常业务中,我们利用比力多的数据库是 MySQL ,缓存是 Redis 。Redis 比 MySQL 的读写性能好许多。那么,我们将 MySQL 的热点数据,缓存到 Redis 中,提拔读取性能,也减小 MySQL 的读取压力。比方说:

  • 论坛帖子的访问频率比力高,且要实时更新阅读量,利用 Redis 记录帖子的阅读量,可以提拔性能和并发。
  • 商品信息,数据更新的频率不高,但是读取的频率很高,特别是热门商品。
3. 请说说有哪些缓存算法?是否能手写一下 LRU 代码的实现?

缓存算法,比力常见的是三种:

  • LRU(least recently used ,近来最少利用)
  • LFU(Least Frequently used ,最不经常利用)
  • FIFO(first in first out ,先进先出)
这里我们可以借助 LinkedHashMap 实现
  1. public class LRULinkedMap<K,V> {
  2.     /**
  3.      * 最大缓存大小
  4.      */
  5.     private int cacheSize;
  6.     private LinkedHashMap<K,V> cacheMap ;
  7.     public LRULinkedMap(int cacheSize) {
  8.         this.cacheSize = cacheSize;
  9.         cacheMap = new LinkedHashMap(16,0.75F,true){
  10.             @Override
  11.             protected boolean removeEldestEntry(Map.Entry eldest) {
  12.                 if (cacheSize + 1 == cacheMap.size()){
  13.                     return true ;
  14.                 }else {
  15.                     return false ;
  16.                 }
  17.             }
  18.         };
  19.     }
  20.     public void put(K key,V value){
  21.         cacheMap.put(key,value) ;
  22.     }
  23.     public V get(K key){
  24.         return cacheMap.get(key) ;
  25.     }
  26.     public Collection<Map.Entry<K, V>> getAll() {
  27.         return new ArrayList<Map.Entry<K, V>>(cacheMap.entrySet());
  28.     }
  29. }
复制代码
利用案例:
  1.     @Test
  2.     public void put() throws Exception {
  3.         LRULinkedMap<String,Integer> map = new LRULinkedMap(3) ;
  4.         map.put("1",1);
  5.         map.put("2",2);
  6.         map.put("3",3);
  7.         for (Map.Entry<String, Integer> e : map.getAll()){
  8.             System.out.print(e.getKey() + " : " + e.getValue() + "\t");
  9.         }
  10.         System.out.println("");
  11.         map.put("4",4);
  12.         for (Map.Entry<String, Integer> e : map.getAll()){
  13.             System.out.print(e.getKey() + " : " + e.getValue() + "\t");
  14.         }
  15.     }
  16.    
  17. //输出
  18. 1 : 1        2 : 2        3 : 3       
  19. 2 : 2        3 : 3        4 : 4         
复制代码
4. 常见的常见的缓存工具和框架有哪些?

在 Java 后端开发中,常见的缓存工具和框架枚举如下:

  • 本地缓存:Guava LocalCache、Ehcache、Caffeine 。
    Ehcache 的功能更加丰富,Caffeine 的性能要比 Guava LocalCache 好。
  • 分布式缓存:Redis、Memcached、Tair 。
    Redis 最为主流和常用。
5. 用了缓存之后,有哪些常见问题?

常见的问题,可枚举如下:
写入问题

  • 缓存何时写入?并且写时怎样避免并发重复写入?
  • 缓存怎样失效?
  • 缓存和 DB 的同等性怎样保证?
经典三连问

  • 怎样避免缓存穿透的问题?
  • 怎样避免缓存击穿的问题?
  • 如果避免缓存雪崩的问题?
6. 怎样处理缓存穿透的问题

缓存穿透,是指查询一个一定不存在的数据,由于缓存是不掷中时被动写,并且处于容错思量,如果从 DB 查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到 DB 去查询,失去了缓存的意义。
在流量大时,可能 DB 就挂掉了,要是有人利用不存在的 key 频繁攻击我们的应用,这就是漏洞。如下图:

怎样解决
有两种方案可以解决:

  • 方案一,缓存空对象。
    当从 DB 查询数据为空,我们仍旧将这个空结果进行缓存,具体的值需要利用特殊的标识,能和真正缓存的数据区分开。别的,需要设置较短的过期时间,一般发起不要超过 5 分钟。
  • 方案二,BloomFilter 布隆过滤器。
    在缓存服务的底子上,构建 BloomFilter 数据结构,在 BloomFilter 中存储对应的 KEY 是否存在,如果存在,说明该 KEY 对应的值不为空。
怎样选择
这两个方案,各有其优缺点。
缓存空对象BloomFilter 布隆过滤器适用场景1、数据掷中不高 2、保证同等性1、数据掷中不高, 2、数据相对固定、实时性低维护成本1、代码维护简单 2、需要过多的缓存空间 3、数据差别等1、代码维护复杂,2、缓存空间占用小实际情况下,利用方案二比力多。因为,相比方案一来说,更加节省内容,对缓存的负荷更小。
7. 怎样处理缓存雪崩的问题

缓存雪崩,是指缓存由于某些原因无法提供服务( 比方,缓存挂掉了 ),全部请求全部达到 DB 中,导致 DB 负荷大增,最终挂掉的情况。
怎样解决
预防息争决缓存雪崩的问题,可以从以下多个方面进行共同着手。

  • 缓存高可用:通过搭建缓存的高可用,避免缓存挂掉导致无法提供服务的情况,从而降低出现缓存雪崩的情况。假设我们利用 Redis 作为缓存,则可以利用 Redis Sentinel 或 Redis Cluster 实现高可用。
  • 本地缓存:如果利用本地缓存时,即使分布式缓存挂了,也可以将 DB 查询到的结果缓存到本地,避免后续请求全部到达 DB 中。如果我们利用 JVM ,则可以利用 Ehcache、Guava Cache 实现本地缓存的功能。
当然,引入本地缓存也会有相应的问题,比方说:
本地缓存的实时性怎么保证?
方案一,可以引入消息队列。在数据更新时,发布数据更新的消息;而进>程中有相应的消费者消费该消息,从而更新本地缓存。
方案二,设置较短的过期时间,请求时从 DB 重新拉取。
方案三,手动过期。

  • 请求 DB 限流: 通过限制 DB 的每秒请求数,避免把 DB 也打挂了。如果我们利用 Java ,则可以利用 Guava RateLimiter、Sentinel、Hystrix 实现限流的功能。这样至少能有两个利益:

    • 可能有一部分用户,还可以利用,体系还没死透。
    • 未来缓存服务恢复后,体系立刻就已经恢复,无需再处理 DB 也挂掉的情况。

  • 提前演练:在项目上线前,演练缓存宕掉后,应用以及后端的负载情况以及可能出现的问题,在此底子上做一些预案设定。
8. 怎样处理缓存击穿的问题

缓存击穿,是指某个极度“热点”数据在某个时间点过期时,恰幸亏这个时间点对这个 KEY 有大量的并发请求过来,这些请求发现缓存过期一般都会从 DB 加载数据并回设到缓存,但是这个时候大并发的请求可能会瞬间 DB 压垮。

  • 对于一些设置了过期时间的 KEY ,如果这些 KEY 可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要思量这个问题。
  • 区别:

    • 和缓存“雪崩“”的区别在于,前者针对某一 KEY 缓存,后者则是许多 KEY 。
    • 和缓存“穿透“”的区别在于,这个 KEY 是真实存在对应的值的。

怎样解决
有两种方案可以解决:

  • 方案一,利用互斥锁。请求发现缓存不存在后,去查询 DB 前,利用分布式锁,保证有且只有一个线程去查询 DB ,并更新到缓存。
  • 方案二,手动过期。缓存上从不设置过期时间,功能上将过期时间存在 KEY 对应的 VALUE 里。流程如下:

    • 获取缓存。通过 VALUE 的过期时间,判定是否过期。如果未过期,则直接返回;如果已过期,继续往下执行。
    • 通过一个背景的异步线程进行缓存的构建,也就是“手动”过期。通过背景的异步线程,保证有且只有一个线程去查询 DB。
    • 同时,虽然 VALUE 已经过期,还是直接返回。通过这样的方式,保证服务的可用性,虽然损失了一定的时效性。

选择
这两个方案,各有其优缺点。
利用互斥锁手动过期优点1、思路简单 2、保证同等性1、性价最佳,用户无需等待缺点1、代码复杂度增大 2、存在死锁的风险1、无法保证缓存同等性9. 缓存和 DB 的同等性怎样保证?

产生原因
重要有两种情况,会导致缓存和 DB 的同等性问题:

  • 并发的场景下,导致读取老的 DB 数据,更新到缓存中。
重要指的是,更新 DB 数据之前,先删除 Cache 的数据。在低并发量下没什么问题,但是在高并发下,就会存在问题。在(删除 Cache 的数据, 和更新 DB 数据)时间之间,恰好有一个请求,我们如果利用被动读,因为此时 DB 数据还是老的,又会将老的数据写入到 Cache 中。

  • 缓存和 DB 的操作,不在一个事务中,可能只有一个 DB 操作乐成,而另一个 Cache 操作失败,导致差别等。
当然,有一点我们要留意,缓存和 DB 的同等性,我们指的更多的是最终同等性。我们利用缓存只要是提高读操作的性能,真正在写操作的业务逻辑,还是以数据库为准。比方说,我们可能缓存用户钱包的余额在缓存中,在前端查询钱包余额时,读取缓存,在利用钱包余额时,读取数据库。
解决方案
在开始说解决方案之前,胖友先看看如下几篇文章,可能有一丢丢多,保持耐烦。
当然无论哪种方案,比力重要的就是解决两个问题:


    • 将缓存可能存在的并行写,实现串行写。


    • 实现数据的最终同等性。


  • 先淘汰缓存,再写数据库
    因为先淘汰缓存,所以数据的最终同等性是可以得到有用的保证的。为什么呢?先淘汰缓存,即使写数据库发生异常,也就是下次缓存读取时,多读取一次数据库。
那么,我们需要解决缓存并行写,实现串行写。比力简单的方式,引入分布式锁。

  • 在写请求时,先淘汰缓存之前,先获取该分布式锁。
  • 在读请求时,发现缓存不存在时,先获取分布式锁。

  • 先写数据库,再更新缓存
按照 “先写数据库,再更新缓存”,我们要保证 DB 和缓存的操作,能够在 “同一个事务”中,从而实现最终同等性
10. 什么是缓存预热?怎样实现缓存预热?

缓存预热
在刚启动的缓存体系中,如果缓存中没有任何数据,如果依靠用户请求的方式重修缓存数据,那么对数据库的压力非常大,而且体系的性能开销也是巨大的。
此时,最好的策略是启动时就把热点数据加载好。这样,用户请求时,直接读取的就是缓存的数据,而无需去读取 DB 重修缓存数据。举个例子,热门的或者推荐的商品,需要提前预热到缓存中。
怎样实现
一般来说,有如下几种方式来实现:

  • 数据量不大时,项目启动时,自动进行初始化。
  • 写个修复数据脚本,手动执行该脚本。
  • 写个管理界面,可以手动点击,预热对应的数据到缓存中。
拓展:缓存数据的淘汰策略有哪些?

除了缓存服务器自带的缓存自动失效策略之外,我们还可以根据具体的业务需求进行自界说的“手动”缓存淘汰,常见的策略有两种:

  • 定时去清理过期的缓存。
  • 当有用户请求过来时,再判定这个请求所用到的缓存是否过期,过期的话就去底层体系得到新数据并更新缓存。
两者各有优劣,第一种的缺点是维护大量缓存的 key 是比力贫困的,第二种的缺点就是每次用户请求过来都要判定缓存失效,逻辑相对比力复杂!Redis 的缓存淘汰策略就是很好的实践方式。
具体用哪种方案,大家可以根据本身的应用场景来衡量。
Docker

1. 什么是 Docker 容器?

Docker 是一种流行的开源软件平台,可简化创建、管理、运行和分发应用程序的过程。它利用容器来打包应用程序及其依赖项。我们也可以将容器视为 Docker 镜像的运行时实例。
2. Docker 和虚拟机有什么差别?

Docker 是轻量级的沙盒,在其中运行的只是应用,虚拟机内里还有额外的体系。
3. 什么是 DockerFile?

Dockerfile 是一个文本文件,其中包含我们需要运行以构建 Docker 镜像的全部命令,每一条指令构建一层,因此每一条指令的内容,就是描述该层应当怎样构建。Docker 利用 Dockerfile 中的指令自动构建镜像。我们可以 docker build 用来创建按顺序执行多个命令行指令的自动构建。
一些最常用的指令如下:
  1. FROM :使用 FROM 为后续的指令建立基础映像。在所有有效的 Dockerfile 中, FROM 是第一条指令。
  2. LABEL: LABEL 指令用于组织项目映像,模块,许可等。在自动化布署方面 LABEL 也有很大用途。在 LABEL 中指定一组键值对,可用于程序化配置或布署 Docker 。
  3. RUN: RUN 指令可在映像当前层执行任何命令并创建一个新层,用于在映像层中添加功能层,也许最来的层会依赖它。
  4. CMD: 使用 CMD 指令为执行的容器提供默认值。在 Dockerfile 文件中,若添加多个 CMD 指令,只有最后的 CMD 指令运行。
复制代码
4. 利用Docker Compose时怎样保证容器A先于容器B运行?

Docker Compose 是一个用来界说和运行复杂应用的Docker工具。一个利用Docker容器的应用,通常由多个容器构成。利用Docker Compose不再需要利用shell脚本来启动容器。Compose 通过一个设置文件来管理多个Docker容器。简单理解:Docker Compose 是docker的管理工具。
Docker Compose 在继续下一个容器之前不会等待容器准备就绪。为了控制我们的执行顺序,我们可以利用“取决于”条件,depends_on 。这是在 docker-compose.yml 文件中利用的示例
  1. version: "2.4"
  2. services:
  3. backend:
  4.    build: .    # 构建自定义镜像
  5.    depends_on:
  6.      - db
  7. db:
  8.    image: mysql
复制代码
用 docker-compose up 命令将按照我们指定的依赖顺序启动和运行服务。
5. 一个完整的Docker由哪些部分构成?


  • DockerClient 客户端
  • Docker Daemon 守护历程
  • Docker Image 镜像
  • DockerContainer 容器
6. docker常用命令

命令发起在本地安装做一个实操,记忆会更深刻。
也可以克隆基于docker的俩万(springboot+vue)项目练手,提供视频+完善文档。地址:https://gitee.com/rodert/liawan-vue

  • 查看本田主机的所用镜像:`docker images``
  • 搜刮镜像:`docker search mysql``
  • 下载镜像:docker pull mysql,没写 tag 就默认下载最新的 lastest
  • 下载指定版本的镜像:`docker pull mysql:5.7``
  • 删除镜像:`docker rmi -f 镜像id 镜像id 镜像id``
7. 描述 Docker 容器的生命周期。

Docker 容器经历以下阶段:

  • 创建容器
  • 运行容器
  • 停息容器(可选)
  • 取消停息容器(可选)
  • 启动容器
  • 停止容器
  • 重启容器
  • 杀死容器
  • 销毁容器
8. docker容器之间怎么隔离?

这是一道涉猎很广泛的标题,理解性阅读。
Linux中的PID、IPC、网络等资源是全局的,而Linux的NameSpace机制是一种资源隔离方案,在该机制下这些资源就不再是全局的了,而是属于某个特定的NameSpace,各个NameSpace下的资源互不干扰。
Namespace实际上修改了应用历程看待整个盘算机“视图”,即它的“视线”被操作体系做了限制,只能“看到”某些指定的内容​​。对于宿主机来说,这些被“隔离”了的历程跟其他历程并没有区别。
虽然有了NameSpace技术可以实现资源隔离,但历程还是可以不受控的访问体系资源,比如CPU、内存、磁盘、网络等,为了控制容器中历程对资源的访问,Docker接纳control groups技术(也就是cgroup),有了cgroup就可以控制容器中历程对体系资源的斲丧了,比如你可以限制某个容器利用内存的上限、可以在哪些CPU上运行等等。
有了这两项技术,容器看起来就真的像是独立的操作体系了。
强烈发起大家实操,才能更好的理解docker。
低谷蓄力
ElasticSearch

1. 说说你们公司 es 的集群架构,索引数据大小,分片有多少,以及一些调优手段 。

节点数、分片数、副本数,只管根据本身公司利用情况回答,当然适当放大也可行。
调优手段是现在很常见的面试题,下面这几种调优手段一定要了解懂。当然,下面的每一条都可以当做调优的一部分。
计划调优
参考:
https://www.cnblogs.com/sanduzxcvbnm/p/12084012.html
a. 根据业务增量需求,接纳基于日期模板创建索引,通过 rollover API 滚动索引;(rollover API我会单独写一个代码案例做解说,公众号:JavaPub)
b. 利用别名进行索引管理;(es的索引名不能改变,提供的别名机制利用非常广泛。)
c. 天天凌晨定时对索引做force_merge操作,以开释空间;
d. 接纳冷热分离机制,热数据存储到SSD,提高检索效率;冷数据定期进行shrink操作,以缩减存储;
e. 接纳curator进行索引的生命周期管理;
f. 仅针对需要分词的字段,合理的设置分词器;
g. Mapping阶段充分结合各个字段的属性,是否需要检索、是否需要存储等。
进100+原创文章:https://gitee.com/rodert/JavaPub
写入调优

  • 写入前副本数设置为0;
  • 写入前关闭refresh_interval设置为-1,禁用刷新机制;
  • 写入过程中:接纳bulk批量写入;
  • 写入后恢复副本数和刷新间隔;
  • 只管利用自动生成的id。
查询调优

  • 禁用wildcard;(通配符模式,类似于%like%)
  • 禁用批量terms(成百上千的场景);
  • 充分利用倒排索引机制,能keyword类型只管keyword;
  • 数据量大时候,可以先基于时间敲定索引再检索;
  • 设置合理的路由机制。
2. elasticsearch 的倒排索引是什么

倒排索引也就是单词到文档的映射,当然不但是存里文档id这么简单。还包括:词频(TF,Term Frequency)、偏移量(offset)、位置(Posting)。
3. elasticsearch 是怎样实现 master 选举的

ElasticSearch 的选主是 ZenDiscovery 模块负责,源码分析将首发在。 https://gitee.com/rodert/JavaPub

  • 对全部可以成为 Master 的节点(node.master: true)根据 nodeId 排序,每次选举每个节点都把本身所知道节点排一次序,然后选出第一个(第0位)节点,临时以为它是 Master 节点。
  • 如果对某个节点的投票数达到一定的值(可以成为master节点数n/2+1)并且该节点本身也选举本身,那这个节点就是master。否则重新选举。
    (当然也可以本身设定一个值,最小值设定为超过能成为Master节点的n/2+1,否则会出现脑裂问题。discovery.zen.minimum_master_nodes)
5. 描述一下 Elasticsearch 索引文档的过程



  • 客户端向 Node 1 发送新建、索引或者删除请求。
  • 节点利用文档的 _id 确定文档属于分片 0 。请求会被转发到 Node 3,因为分片 0 的主分片目前被分配在 Node 3 上。
  • Node 3 在主分片上面执行请求。如果乐成了,它将请求并行转发到 Node 1 和 Node 2 的副本分片上。一旦全部的副本分片都报告乐成, Node 3 将向协调节点报告乐成,协调节点向客户端报告乐成。
一图胜千文,记住这幅图,上面是文档在节点间分发的过程,接着说一下文档从接收到写入磁盘过程。
协调节点默认利用文档 ID 参与盘算(也支持通过 routing),以便为路由提供符合的分片。
shard = hash(document_id) % (num_of_primary_shards)

  • 当分片所在的节点接收到来自协调节点的请求后,会将请求写入到 MemoryBuffer,然后定时(默认是每隔 1 秒)写入到 Filesystem Cache,这个从 MomeryBuffer 到 Filesystem Cache 的过程就叫做 refresh;
  • 当然在某些情况下,存在 Momery Buffer 和 Filesystem Cache 的数据可能会丢失,ES 是通过 translog 的机制来保证数据的可靠性的。其实现机制是接收到请求后,同时也会写入到 translog 中,当 Filesystem cache 中的数据写入到磁盘中时,才会清除掉,这个过程叫做 flush;
  • 在 flush 过程中,内存中的缓冲将被清除,内容被写入一个新段,段的 fsync将创建一个新的提交点,并将内容刷新到磁盘,旧的 translog 将被删除并开始一个新的 translog。
  • flush 触发的时机是定时触发(默认 30 分钟)或者 translog 变得太大(默以为 512M)时;
  1. 1. translog 可以理解为就是一个文件,一直追加。
  2. 2. MemoryBuffer 应用缓存。
  3. 3. Filesystem Cache 系统缓冲区。
复制代码
延伸阅读:Lucene 的 Segement:

  • Lucene 索引是由多个段构成,段本身是一个功能齐全的倒排索引。
  • 段是不可变的,允许 Lucene 将新的文档增量地添加到索引中,而不消从头重修索引。
  • 对于每一个搜刮请求而言,索引中的全部段都会被搜刮,并且每个段会斲丧CPU 的时钟周、文件句柄和内存。这意味着段的数目越多,搜刮性能会越低。
  • 为了解决这个问题,Elasticsearch 会合并小段到一个较大的段,提交新的合并段到磁盘,并删除那些旧的小段。
4. 具体描述一下 Elasticsearch 搜刮的过程?

es作为一个分布式的存储和检索体系,每个文档根据 _id 字段做路由分发被转发到对应的shard上。
搜刮执行阶段过程分俩个部分,我们称之为 Query Then Fetch。
4.1 query-查询阶段
当一个search请求发出的时候,这个query会被广播到索引内里的每一个shard(主shard或副本shard),每个shard会在本地执行查询请求后会生成一个掷中文档的优先级队列。
这个队列是一个排序好的top N数据的列表,它的size等于from+size的和,也就是说如果你的from是10,size是10,那么这个队列的size就是20,所以这也是为什么深度分页不能用from+size这种方式,因为from越大,性能就越低。
es内里分布式search的查询流程如下:

查询阶段包含以下三个步调:

  • 客户端发送一个 search 请求到 Node 3 , Node 3 会创建一个大小为 from + size 的空优先队列。
  • Node 3 将查询请求转发到索引的每个主分片或副本分片中。每个分片在本地执行查询并添加结果到大小为 from + size 的本地有序优先队列中。
  • 每个分片返回各自优先队列中全部文档的 ID 和排序值给协调节点,也就是 Node 3 ,它合并这些值到本身的优先队列中来产生一个全局排序后的结果列表。
4.2 fetch - 读取阶段 / 取回阶段

分布式阶段由以下步调构成:

  • 协调节点辨别出哪些文档需要被取回并向相干的分片提交多个 GET 请求。
  • 每个分片加载并 丰富 文档,如果有需要的话,接着返回文档给协调节点。
  • 一旦全部的文档都被取回了,协调节点返回结果给客户端。
协调节点首先决定哪些文档 确实 需要被取回。比方,如果我们的查询指定了 { "from": 90, "size": 10 } ,最初的90个结果会被扬弃,只有从第91个开始的10个结果需要被取回。这些文档可能来自和最初搜刮请求有关的一个、多个乃至全部分片。
协调节点给持有相干文档的每个分片创建一个 multi-get request ,并发送请求给同样处理查询阶段的分片副本。
分片加载文档体-- _source 字段—​如果有需要,用元数据和 search snippet highlighting 丰富结果文档。 一旦协调节点接收到全部的结果文档,它就组装这些结果为单个相应返回给客户端。
  1. 拓展阅读:
  2. 深翻页(Deep Pagination)
  3. ---
  4. 先查后取的过程支持用 from 和 size 参数分页,但是这是 有限制的 。 要记住需要传递信息给协调节点的每个分片必须先创建一个 from + size 长度的队列,协调节点需要根据 number_of_shards * (from + size) 排序文档,来找到被包含在 size 里的文档。
  5. 取决于你的文档的大小,分片的数量和你使用的硬件,给 10,000 到 50,000 的结果文档深分页( 1,000 到 5,000 页)是完全可行的。但是使用足够大的 from 值,排序过程可能会变得非常沉重,使用大量的CPU、内存和带宽。因为这个原因,我们强烈建议你不要使用深分页。
  6. 实际上, “深分页” 很少符合人的行为。当2到3页过去以后,人会停止翻页,并且改变搜索标准。会不知疲倦地一页一页的获取网页直到你的服务崩溃的罪魁祸首一般是机器人或者web spider。
  7. 如果你 确实 需要从你的集群取回大量的文档,你可以通过用 scroll 查询禁用排序使这个取回行为更有效率,我们会在 later in this chapter 进行讨论。
  8. 注:https://www.elastic.co/guide/cn/elasticsearch/guide/current/scroll.html
复制代码
5. Elasticsearch 在摆设时,对 Linux 的设置有哪些优化方法


  • 关闭缓存swap;
原因:大多数操作体系会将内存利用到文件体系缓存,会将应用程序未用到的内存交换出去。会导致jvm的堆内存交换到磁盘上。交换会导致性能问题。会导致内存垃圾回收延伸。会导致集群节点相应时间变慢,或者从集群停止开。

  • 堆内存设置为:Min(节点内存/2, 32GB);
  • 设置最大文件句柄数;
后俩点不懂可以先说有一定了解,关注JavaPub会做具体解说。

  • 调整线程池和队列大小
  • 磁盘存储 raid 方式——存储有条件利用 RAID6,增加单节点性能以及避免单节点存储故障。
https://www.elastic.co/cn/blog/how-to-design-your-elasticsearch-data-storage-architecture-for-scale#raid56
6. Elasticsearch 中的节点(比如共 20 个),其中的 10 个选了一个 master,别的 10 个选了另一个 master,怎么办?


  • 当集群 master 候选数目不小于 3 个时,可以通过设置最少投票通过数目(discovery.zen.minimum_master_nodes)超过全部候选节点一半以上来解决脑裂问题;
  • 当候选数目为两个时,只能修改为唯一的一个 master 候选,其他作为 data节点,避免脑裂问题。
7. 客户端在和集群连接时,怎样选择特定的节点执行请求的?

client 远程连接连接一个 elasticsearch 集群。它并不加入到集群中,只是得到一个或者多个初始化的地址,并以轮询的方式与这些地址进行通讯。
8. 具体描述一下 Elasticsearch 更新和删除文档的过程。


  • 删除和更新也都是写操作,但是 Elasticsearch 中的文档是不可变的,因此不能被删除或者改动以展示其变动;(根本原因是底层lucene的segment段文件不可更新删除)
  • 磁盘上的每个段都有一个相应的 .del 文件。当删除请求发送后,文档并没有真 的被删除,而是在 .del 文件中被标志为删除。该文档依然能匹配查询,但是会在 结果中被过滤掉。当段合并时,在.del 文件中被标志为删除的文档将不会被写入 新段。
  • 在新的文档被创建时,Elasticsearch 会为该文档指定一个版本号,当执行更新 时,旧版本的文档在.del 文件中被标志为删除,新版本的文档被索引到一个新段。
旧版本的文档依然能匹配查询,但是会在结果中被过滤掉。
9. Elasticsearch 对于大数据量(上亿量级)的聚合怎样实现?

这道标题较难,相信大家看到许多类似这种回答
Elasticsearch 提供的首个近似聚合是cardinality 度量。它提供一个字段的基数,即该字段的distinct或者unique值的数目。它是基于HLL算法的。HLL 会先对我们的输入作哈希运算,然后根据哈希运算的结果中的 bits 做概率估算从而得到基数。其特点是:可设置的精度,用来控制内存的利用(更准确 = 更多内存);小的数据集精度黑白常高的;我们可以通过设置参数,来设置去重需要的固定内存利用量。无论数千还是数十亿的唯一值,内存利用量只与你设置的准确度相干。
科普&拓展
  1. HyperLogLog:
  2. 下面简称为HLL,它是 LogLog 算法的升级版,作用是能够提供不精确的去重计数。存在以下的特点:
  3. 1. 能够使用极少的内存来统计巨量的数据,在 Redis 中实现的 HyperLogLog,只需要12K内存就能统计2^64个数据。
  4. 2. 计数存在一定的误差,误差率整体较低。标准误差为 0.81% 。
  5. 3. 误差可以被设置辅助计算因子进行降低。
  6. ---
  7. 应用场景:
  8. 1. 基数不大,数据量不大就用不上,会有点大材小用浪费空间
  9. 2. 有局限性,就是只能统计基数数量,而没办法去知道具体的内容是什么
  10. 3. 和bitmap相比,属于两种特定统计情况,简单来说,HyperLogLog 去重比 bitmap 方便很多
  11. 4. 一般可以bitmap和hyperloglog配合使用,bitmap标识哪些用户活跃,hyperloglog计数
  12. ---
  13. 应用场景:
  14. 1. 基数不大,数据量不大就用不上,会有点大材小用浪费空间
  15. 2. 有局限性,就是只能统计基数数量,而没办法去知道具体的内容是什么
  16. 3. 和bitmap相比,属于两种特定统计情况,简单来说,HyperLogLog 去重比 bitmap 方便很多
  17. 4. 一般可以bitmap和hyperloglog配合使用,bitmap标识哪些用户活跃,hyperloglog计数
  18. 来源:刷刷面试
复制代码
10. 在并发情况下,Elasticsearch 如果保证读写同等?

首先要了解什么是同等性,在分布式体系中,我们一般通过CPA理论分析。
分布式体系不可能同时满足同等性(C:Consistency)、可用性(A:Availability)和分区容忍性(P:Partition Tolerance),最多只能同时满足其中两项。


  • 可以通过版本号利用乐观并发控制,以确保新版本不会被旧版本覆盖,由应用层来处理具体的辩论;
  • 别的对于写操作,同等性级别支持 quorum/one/all,默以为 quorum,即只有当大多数分片可用时才允许写操作。但即使大多数可用,也可能存在因为网络等原因导致写入副本失败,这样该副本被以为故障,分片将会在一个差别的节点上重修。
  • 对于读操作,可以设置 replication 为 sync(默认),这使得操作在主分片和副本分片都完成后才会返回;如果设置 replication 为 async 时,也可以通过设置搜刮请求参数_preference 为 primary 来查询主分片,确保文档是最新版本。
11. 介绍一下你们的个性化搜刮方案?

如果你没有许多实战经验,可以基于 word2vec 做一些练习,我的博客提供了 word2vec Java版的一些Demo。
基于 word2vec 和 Elasticsearch 实现个性化搜刮,它有以下优点:

  • 基于word2vec的商品向量还有一个可用之处,就是可以用来实现相似商品的推荐;
Java底子

1. instanceof 关键字的作用

instanceof 是 Java 的保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例,返回 boolean 的数据类型。
  1. boolean result = obj instanceof class
复制代码
当 obj 为 Class 的对象,或者是其直接或间接子类,或者是其接口的实现类,结果result 都返回 true,否则返回false。
留意一点:编译器会查抄 obj 是否能转换成右边的class类型,如果不能转换则直接报错,如果不能确定类型,则通过编译,具体看运行时定。
obj 必须为引用类型,只能作为对象的判定,不能是基本类型。
  1. int i = 0;
  2. System.out.println(i instanceof Integer);//编译不通过
  3. System.out.println(i instanceof Object);//编译不通过
复制代码
源码参考:JavaSE 8 instanceof 的实现算法:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.instanceof
2. Java自动装箱和拆箱

什么是装箱拆箱,这里不做源码层面解读,源码解读在JavaPub公众号发出。这里通过解说 int 和 Interger 区别,解答Java自动装箱和拆箱。
自动装箱 ----- 基本类型的值 → 包装类的实例
自动拆箱 ----- 基本类型的值 ← 包装类的实例

  • Integer变量必须实例化后才能利用,而int变量不需要
  • Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象;而int则是直接存储数据值 。
  • Integer的默认值是null,int的默认值是0
Java中8种基本数据类型。左边基本类型,右边包装类型。

在面试中:
下面这段代码的输出结果是什么?
  1. public class Main {
  2.     public static void main(String[] args) {
  3.          
  4.         Integer i1 = 100;
  5.         Integer i2 = 100;
  6.         Integer i3 = 200;
  7.         Integer i4 = 200;
  8.          
  9.         System.out.println(i1==i2);
  10.         System.out.println(i3==i4);
  11.     }
  12. }
  13. //true
  14. //false
复制代码
输出结果表明i1和i2指向的是同一个对象,而i3和i4指向的是差别的对象。此时只需一看源码便知毕竟,下面这段代码是Integer的valueOf方法的具体实现:
[code]public static Integer valueOf(int i) {        if(i >= -128 && i

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

水军大提督

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表