ToB企服应用市场:ToB评测及商务社交产业平台

标题: 《最少须要面试题》第一版 [打印本页]

作者: 水军大提督    时间: 2024-6-3 09:06
标题: 《最少须要面试题》第一版
《最少须要面试题》第一版

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

目录

缓存

1. 什么是缓存?

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

利用缓存的目的,就是提拔读写性能。而实际业务场景下,更多的是为了提拔读性能,带来更好的性能,更高的并发量。
日常业务中,我们利用比力多的数据库是 MySQL ,缓存是 Redis 。Redis 比 MySQL 的读写性能好许多。那么,我们将 MySQL 的热点数据,缓存到 Redis 中,提拔读取性能,也减小 MySQL 的读取压力。比方说:
3. 请说说有哪些缓存算法?是否能手写一下 LRU 代码的实现?

缓存算法,比力常见的是三种:
这里我们可以借助 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 后端开发中,常见的缓存工具和框架枚举如下:
5. 用了缓存之后,有哪些常见问题?

常见的问题,可枚举如下:
写入问题
经典三连问
6. 怎样处理缓存穿透的问题

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

怎样解决
有两种方案可以解决:
怎样选择
这两个方案,各有其优缺点。
缓存空对象BloomFilter 布隆过滤器适用场景1、数据掷中不高 2、保证同等性1、数据掷中不高, 2、数据相对固定、实时性低维护成本1、代码维护简单 2、需要过多的缓存空间 3、数据差别等1、代码维护复杂,2、缓存空间占用小实际情况下,利用方案二比力多。因为,相比方案一来说,更加节省内容,对缓存的负荷更小。
7. 怎样处理缓存雪崩的问题

缓存雪崩,是指缓存由于某些原因无法提供服务( 比方,缓存挂掉了 ),全部请求全部达到 DB 中,导致 DB 负荷大增,最终挂掉的情况。
怎样解决
预防息争决缓存雪崩的问题,可以从以下多个方面进行共同着手。
当然,引入本地缓存也会有相应的问题,比方说:
本地缓存的实时性怎么保证?
方案一,可以引入消息队列。在数据更新时,发布数据更新的消息;而进>程中有相应的消费者消费该消息,从而更新本地缓存。
方案二,设置较短的过期时间,请求时从 DB 重新拉取。
方案三,手动过期。
8. 怎样处理缓存击穿的问题

缓存击穿,是指某个极度“热点”数据在某个时间点过期时,恰幸亏这个时间点对这个 KEY 有大量的并发请求过来,这些请求发现缓存过期一般都会从 DB 加载数据并回设到缓存,但是这个时候大并发的请求可能会瞬间 DB 压垮。
怎样解决
有两种方案可以解决:
选择
这两个方案,各有其优缺点。
利用互斥锁手动过期优点1、思路简单 2、保证同等性1、性价最佳,用户无需等待缺点1、代码复杂度增大 2、存在死锁的风险1、无法保证缓存同等性9. 缓存和 DB 的同等性怎样保证?

产生原因
重要有两种情况,会导致缓存和 DB 的同等性问题:
重要指的是,更新 DB 数据之前,先删除 Cache 的数据。在低并发量下没什么问题,但是在高并发下,就会存在问题。在(删除 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由哪些部分构成?

6. docker常用命令

命令发起在本地安装做一个实操,记忆会更深刻。
也可以克隆基于docker的俩万(springboot+vue)项目练手,提供视频+完善文档。地址:https://gitee.com/rodert/liawan-vue
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
写入调优
查询调优
2. elasticsearch 的倒排索引是什么

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

ElasticSearch 的选主是 ZenDiscovery 模块负责,源码分析将首发在。 https://gitee.com/rodert/JavaPub
5. 描述一下 Elasticsearch 索引文档的过程


一图胜千文,记住这幅图,上面是文档在节点间分发的过程,接着说一下文档从接收到写入磁盘过程。
协调节点默认利用文档 ID 参与盘算(也支持通过 routing),以便为路由提供符合的分片。
shard = hash(document_id) % (num_of_primary_shards)
  1. 1. translog 可以理解为就是一个文件,一直追加。
  2. 2. MemoryBuffer 应用缓存。
  3. 3. Filesystem Cache 系统缓冲区。
复制代码
延伸阅读:Lucene 的 Segement:
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的查询流程如下:

查询阶段包含以下三个步调:
4.2 fetch - 读取阶段 / 取回阶段

分布式阶段由以下步调构成:
协调节点首先决定哪些文档 确实 需要被取回。比方,如果我们的查询指定了 { "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 的设置有哪些优化方法

原因:大多数操作体系会将内存利用到文件体系缓存,会将应用程序未用到的内存交换出去。会导致jvm的堆内存交换到磁盘上。交换会导致性能问题。会导致内存垃圾回收延伸。会导致集群节点相应时间变慢,或者从集群停止开。
后俩点不懂可以先说有一定了解,关注JavaPub会做具体解说。
https://www.elastic.co/cn/blog/how-to-design-your-elasticsearch-data-storage-architecture-for-scale#raid56
6. Elasticsearch 中的节点(比如共 20 个),其中的 10 个选了一个 master,别的 10 个选了另一个 master,怎么办?

7. 客户端在和集群连接时,怎样选择特定的节点执行请求的?

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

旧版本的文档依然能匹配查询,但是会在结果中被过滤掉。
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),最多只能同时满足其中两项。

11. 介绍一下你们的个性化搜刮方案?

如果你没有许多实战经验,可以基于 word2vec 做一些练习,我的博客提供了 word2vec Java版的一些Demo。
基于 word2vec 和 Elasticsearch 实现个性化搜刮,它有以下优点:
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自动装箱和拆箱。
自动装箱 ----- 基本类型的值 → 包装类的实例
自动拆箱 ----- 基本类型的值 ← 包装类的实例
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




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4