文章目录
序号 | 内容 |
---|---|
1 | 基础面试题 |
2 | JVM面试题 |
3 | 多线程面试题 |
4 | MySql面试题 |
5 | 集合容器面试题 |
6 | 设计模式面试题 |
7 | 分布式面试题 |
8 | Spring面试题 |
9 | SpringBoot面试题 |
10 | SpringCloud面试题 |
11 | Redis面试题 |
12 | RabbitMQ面试题 |
13 | ES面试题 |
14 | Nginx、Cancal |
15 | Mybatis面试题 |
16 | 消息队列面试题 |
17 | 网络面试题 |
18 | Linux、Kubenetes面试题 |
19 | Netty面试题 |
ES
开源的分布式搜索和分析引擎,构建在Lucene上。为了处理大规模数据集,并提供快速的搜索、分析和数据可视化功能
数据以文档的形式存储在ES中,每个文档是一个JSON格式的数据单元,相当于数据库中的一条数据。
ES的查询类型包括全文搜索,精确匹配,范围查询,模糊查询,聚合查询。
名词术语
索引:包含一类相似数据的逻辑存储单元,每个索引可以包含多个文档,每个文档都是一个JSON格式的数据单元。
类型:早起版本中一个索引可以有多个类型,从6.X以后只能有一个类型
文档:最小的数据单元,是一个JSON对象,存储在索引中。文档必须属于一个索引,并且有一个唯一ID。
分片:为了支持水平扩展,ES将索引划分成多个分片,每个分片是一个独立的索引单元,可以分布在集群的不同节点上。
副本:副本是分片的复制品。分布在不同的节点上。
查询:ES中,用于从索引中检索符合特定条件的文档。
聚合:对数据进行汇总和统计分析。ES支持各种聚合操作,如:求和,平均值,最大,最小值。
Kibana:数据可视化工具,
Logstash:用于搜集,处理和转发日志和事件 数据的开源工具,
索引映射(index mapping)
定义文档的数据结构和字段类型的过程。(类似mysql中创建表和表字段的操作)
type的类型
text:存储长文本数据。
keyword:存储短文本数据。例如标签、关键字。keyword字段不进行分词,整个字段内容作为一个关键词被索引。通常用于精确匹配和聚合操作。
numeric:用于存储数值类型的数据,包括整数和浮点类型。“type”:“integer”
date:用于存储日期和时间数据。
boolean:存储布尔值true,false、
binary:用于存储二进制数据,例如图像、文件。
geo:存储地理位置数据,支持点,线,多边形。“type”:“geo_point”.
object:用于存储嵌套json对象
nested:用于存储嵌套json对象。和bject的区别是嵌套对象独立存在,利于搜索。
// 创建索引(table_field_copy),字段(content1,content2)添加属性为文本txt类型。
// copy_to将多个字段的值合并到一个目标字段中,content_full本身不存储原始数据,通常用于搜索、聚合和排序
PUT table_field_copy
{
"mappings":{
"properties":{
"content1":{"type":"text","copy_to":"content_full"},
"content2":{"type":"text","copy_to":"content_full"},
"content_full":{"type":"text","store":true}
}
}
}
// 1是文档的id,手动指定的,如果不手动指定,可以默认生成一个
PUT table_field_copy/_doc/1
{
"content1":"aaa",
"content2":"bbb"
}
// 查询索引
get table_field_copy/_mapping
// 删除索引
DELETE table_field_copy
动态映射
ES可以自动检测新字段,并根据数据自动添加到映射中。根据字段的值,ES能推测出字段的类型。
索引操作
// 创建索引(表)
PUT index_crud
{
"mapping":{
"prperties":{
"name":{"type":"text"},
"age":{"type":"integer"},
"stu_no":{"type":"keyword"},
"score":{"type":"float"},
"birth":{"type":"date"},
}
}
}
// 查询数据
GET index_crud/_search
// 查询指定id的数据
GET index_crud/_doc/1
// 添加数据
POST index_crud/_doc/1
{
"name":"张三",
"age":10,
"stu_no":"1234567890",
"socre":80.0
"birth":"2024-01-01"
}
// 更新数据,这种更新是全量覆盖,会将别的字段更新没值。
PUT index_crud/_doc/1
{
"age":15,
}
// 更新指定字段的值 。
PUT index_crud/_update/1
{
"doc":{
"age":15,
}
}
// 删除数据
DELETE index_crud/_doc/1
批量提交(bulk)
每次CRUD的操作都会与ES建立一次连接,这会导致大量的请求打到ES服务器上,从而间接导致数据索引变慢。可以通过bulk批量提交请求。
批量提交中可以放 增 删 改 的操作。和mysql的insert类似
POST index_bulk/_bulk
{
"index":{"_id":"1"}
}
{
"id":"1",
"name":"测试1"
}
{
"update":{"_id":"1"}
}
{
"doc":{
"name":"测试2"
}
}
{
"delete":{"_id":"1"}
}
// index 操作添加了一个文档。
// update 操作更新了 name 字段的值。
// delete 操作删除了文档。
乐观锁
ES不支持事务管理。实现方式是在数据表中增加一个版本号字段或者时间戳字段来标识数据的版本。每次更新的时候检查版本号是否一致,一致就更新,不一致就不更新。使用_seq_no 和 _primary_term来做乐观锁。
// 如果_seq_no=1 和 _primary_term=1 就更新数据。
PUT index_crud/_update/1?if_seq_no=1&if_primary_term=1
{
"doc":{
"age":15,
}
}
索引路由规则
路由规则是控制文档如何被分配到不同的分片上,是通过指定文档的某个字段值来决定文档应该被分配到哪个分片上,
默认规则
shard = hash(_routing) % number_of_primary_shards。
shard :索引的一个分片。
_routing:文档的id。
number_of_primary_shards:分片的数量
指定路由
ES别名
一个指向一个或多个索引的逻辑名称。通过给索引创建别名,可以把相同别名的索引都查出来。
PUT index_log1/_doc/1
{
"name":"登录",
"api":"/login",
"method":"POST"
}
PUT index_log2/_doc/2
{
"name":"注销",
"api":"/logout",
"method":"POST"
}
// 不加过滤
POST /_aliases{
"actions":[
{
"index":"index_log1"
"alias":"log"
},
{
"index":"index_log2"
"alias":"log"
}
]
}
get log/_search
// 添加条件过滤
POST /_aliases{
"actions":[
{
"index":"index_log1"
"alias":"log",
"filter":{
"term":{
"methd":"post" //过滤的字段和值
}
}
},
{
"index":"index_log2"
"alias":"log",
"filter":{
"term":{
"methd":"post" //过滤的字段和值
}
}
}
]
}
GET /log/_search
{
"query": {
"bool": {
"filter": [
{
"term": {
"methd": "post"
}
}
]
}
}
}
滚动索引
滚动索引是索引的管理策略,当索引数据量逐渐增大时,可能会出现性能下降或资源压力过大。可以通过滚动索引策略来定期创建新索引,在插入新数据时,从旧索引滚动到新索引。
索引名后面必须是数字,这样ES才能自增
PUT index_log-0001{
"aliases":{
"index_log":{}
}
}
// 给别名添加滚动条件
PUT index_log/_rollover{
"conditions":{
"max_age":"7d",
"max_docs":2,
"max_size":"5gb"
}
}
//满足上述任意条件
//max_age:最长服务时间,max_docs:文档数量限制,max_size:索引大小不超过
搜索引擎
搜索引擎由众多模块构成,包括数据采集,文本分析,索引存储,搜索等模块。
数据采集
数据采集分为自动采集和应用推送。应用推送是客户端主动推送到ES上,自动采集是通过Beats或者logstashs等工具进行采集。
文本分析
将采集的内容进行分词处理,将文本划分成一系列的关键词或术语。然后建立倒排索引,
索引存储
负责将经过文本分析后的内容按照定义好的结构写入到索引
搜索模块
根据用户输入的查询文本找到索引中匹配的文档,这期间也会进行分词处理,将输入的关键词进行文本分析,得到最终的关键词去倒排索引中匹配。匹配度越高的记录越会记录在前面。
倒排索引
用来存储在全文搜索下某个单词在一个文档或者一组文档中存储位置的映射。倒排索引是通过属性记录位置。一个倒排索引由文档中所有不重复词的列表构成。
倒排索引是有term、文档ID、位置信息构成。查询的时候通过term找对应的文档id。
用户查询的时候,通过输入关键词,搜索引擎在倒排索引中找这些词,搜索引擎会收集所有与查询相关的文档ID,并进行去重和排序。根据排序后的文档ID列表,搜索引擎从文档存储中检索出响应的文档内容。
term:文档中出现的单词或者术语。
文档ID:标识文档的唯一id。
位置信息:单词在文档中的位置。可以用于高亮显示
例如:
三个文档
文档1:“This is a simple document”
文档2:“another example for testing”
文档3:“Test document for Elasticsearch”
Term | 文档id |
---|---|
This | 1 |
is | 1 |
a | 1 |
simple | 1 |
document | 1,3 |
another | 2 |
example | 2 |
for | 2,3 |
testing | 2,3 |
Test | 2 |
Elasticsearch | 3 |
分析器
当文档被检索到ES中时,分析器是将输入的文本按照一定的策略进行分解,并建立倒排索引。
中文分词器:LK
分析器策略
分词:将输入的文本按照一定的规则进行分词,将文本拆分成一个一个单独的词语或者标记
去小写化:将文本转化成小写形式,可以使搜索不区分大小写,
去除停用词:
同义词处理:
词干化:将词语转换为词根或者词干的过程
格式化:去除特殊字符,标点符号或进行其他操作
内置分析器
standerd
标准分析器,按照Unioncode文本分割方法切分单词,会删除大多标点符号,并将单词转为小写,
simple
简单分析器,在任意非字母的地方把单词切分开,并将单词转为小写。非字母或汉字字符将被丢弃。
过滤器
字符过滤器
字符过滤器用来处理原始文本数据。在文本被传递到分词器之前,字符过滤器可以执行一些操作。例如:去除HTML标签,转换大小写,删除特定字符,替换特定字符
html_strip (字符过滤器):去除HTML标签、解析转义字符串,解析 为空格。
mapping(映射字符过滤器):根据指定的映射表,替换文本中的字符。自己指定替换的内容和要替换的值。
pattern_replace(正则替换):使用正则表达式来替换文本中的字符。
分词器
分词器就是将输入的文本分割成独立的词或者词条(term)。以便更好的进行索引、搜索和分析。
标准分词器(standard tokenizer)
根据非字母字符(空格、标点符号)进行分词,删除大多数标点符号。
POST _analyze{
"tokenizer": "standard",
"text":[
"hello. my name is XXX"
]
}
空格分词器(whitespace tokenizer)
根据空格处理分词,不会处理标点符号
关键词分词器(keyword tokenizer)
将整个输入的文本视为单个词条,不进行分词。适用于需要精确匹配整个字段的情况。
字母分词器(letter tokenizer)
仅根据字母进行分词,忽略非字母字符。适用于处理只包含字母的场景。
分词过滤器
stop filter
去除掉停用词,例如 “a”,“an“ ,”the“ 等常见没有实际意义的词汇
POST _analyze{
"tokenizer": "standard",
"filter":["stop"]
"text":[
"hello. my name is a XXX"
]
}
stemmer filter
Stemmer 过滤器用于将单词还原为其词根形式(或词干),这有助于在搜索时匹配具有相同词根的变体词。例如,它可以将 “running”、“ran” 和 “runner” 等词还原为 “run” 这个词根。
其他过滤器
Lowercase Filter:将所有token转换为小写。
Stop Filter:从token流中移除停用词(如常见的“和”、“是”等)
Length Filter:移除长度超出指定范围的token
Synonym Filter:替换或添加同义词
Trim Filter:移除token前后的空白字符
NGram Filter:将token拆分为n-gram(n元组)
自定义分析器
自定义分析器就是将上面的分词器、分词过滤器、过滤器结合使用
PUT /my_index
{
"settings": {
"analysis": {
"analyzer": {
"my_custom_analyzer": {
"type": "custom", //固定类型custom
"tokenizer": "standard",
"filter": ["lowercase", "stop", "my_synonym"] //下面过滤器定义了my_synonym
}
},
"filter": {
"my_synonym": {
"type": "synonym",
"synonyms": ["iphone,apple phone", "elastic,elasticsearch"]
}
}
}
}
}
精准查询
文本不需要文本分析直接存入ES,查询时也不需要对关键词进行分词处理
Term查询:用于精确匹配不分词的字段值。它对于keyword、数值、日期和boolean等类型字段特别有用,因为它不会对搜索条件进行分词。
GET /products/_search
{
"query": {
"term": {
"name.keyword": "Laptop"
}
}
}
Range(范围查询):用于根据值的范围过滤文档,特别适用于数值类型字段。例如,你可以用它来查找价格在一定范围内的产品。
GET /products/_search
{
"query": {
"range": {
"price": {
"gte": 200,
"lte": 500
}
}
}
}
Prefix查询:用于匹配以指定前缀开头的字段值。
GET /products/_search
{
"query": {
"prefix": {
"name.keyword": "Mac"
}
}
}
Wildcard查询:使用通配符来匹配字段值。这允许你执行更复杂的模式匹配。
GET /products/_search
{
"query": {
"wildcard": {
"name.keyword": "*book*"
}
}
}
Regexp查询:使用正则表达式来匹配字段值,提供了更高级的匹配能力。
GET /products/_search
{
"query": {
"regexp": {
"name.keyword": "La.*t"
}
}
}
Ids查询:用于根据文档ID列表精确地检索文档。
GET /products/_search
{
"query": {
"ids": {
"values": ["1", "3", "5"]
}
}
}
Exists查询:用于检查字段是否存在。
GET /products/_search
{
"query": {
"exists": {
"field": "description"
}
}
}
Terms查询:是一种用于匹配字段中包含多个值的查询方式
GET /products/_search
{
"query": {
"terms": {
"name.keyword": ["Laptop", "Desktop"]
}
}
}
全文检索
匹配查询:在指定字段中查找匹配的文档.
GET /blog_posts/_search
{
"query": {
"match": {
"title": "Elasticsearch"
}
}
}
短语搜索:短语搜索用于查找包含特定短语的文档。
GET /blog_posts/_search
{
"query": {
"match_phrase": {
"title": "Elasticsearch introduction"
}
}
}
多字段查询:多字段查询允许你在多个字段上执行相同的查询
GET /blog_posts/_search
{
"query": {
"multi_match": {
"query": "Elasticsearch",
"fields": ["title", "content"]
}
}
}
查询字符串搜索:允许用户以类似搜索引擎查询语法的方式执行全文搜索。它支持通配符、正则表达式、布尔操作等高级查询功能。
GET /blog_posts/_search
{
"query": {
"query_string": {
"default_field": "content",
"query": "(Elasticsearch OR Lucene) AND introduction",
"fields": ["title", "content"]
}
}
}
经纬度搜索
圆形搜索
例如:用户想要查找离他当前位置5公里范围内的所有单车
GET /bikes/_search
{
"query": {
"function_score": {
"query": {
"bool": {
"must": {
"match_all": {}
},
"filter": {
"geo_distance": {
"distance": "5km",
"location": {
"lat": 39.9075,
"lon": 116.39723
}
}
}
}
}
}
}
}
矩形搜索
GET /houses/_search
{
"query": {
"bool": {
"must": {
"match_all": {}
},
"filter": {
"geo_bounding_box": {
"location": {
"top_left": { //表示矩形的左上角顶点
"lat": 40.0,
"lon": 116.3
},
"bottom_right": { //表示矩形的右下角顶点
"lat": 39.9,
"lon": 116.4
}
}
}
}
}
}
}
复杂查询
布尔查询
must、should、must not、filter。
同时使用must、should、filter时,会导致should失效。可以把should的条件包含在must中。
GET /products/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"title": "手机"
} ,
},
{
"range": {
"price": {
"lt": 1000
}
}
}
],
"must_not": [
{
"match": {
"description": "旧款"
}
}
],
"filter": [
{
"term": {
"manufacturer": "Apple" // 假设我们只想看到Apple制造的产品
}
}
]
}
}
}
分页搜索
基于from/size分页
使用from和size参数来控制在每个请求中要跳过的文档数量以及检索的文档数量。from/size适用于小型结果集,随着from的增加,效率会降低,在默认情况下from超过10000之后会报错。
基于search/after的分页
使用上一页最后一个文档的唯一标识符(排序值)来获取下一组文档。在不需要跳过大量文档的情况下实现一致分页。但是不能跳页,只能一页一页的翻
滚动分页
处理大量数据的有效方式,特别适用于需要深度分页的场景。使用常规的page查询时,ES有10000条数据的限制,超过这个数量就会报错。为了解决这个问题,ES提供了scroll滚动搜索的方式。
滚动分页每次只能获取一页的内容,并返回一个scroll_id。根据这个scroll_id,可以不断地获取下一页的内容,直到结果集中返回的hits字段为空,表示遍历结束。
为了使用scroll滚动搜索,需要在查询请求中设置scroll参数,并在请求体中包含查询条件。然后,ES会返回一个包含初始结果和scroll_id的响应。后续的滚动请求需要包含上一次请求返回的scroll_id,以获取下一页的数据。需要注意的是,scroll_id的生存期是有限的,如果过期,ES会返回一个新的scroll_id。
滚动搜索并不适用于跳页场景,且搜索期间的数据变更不会被用户看到。
搜索常用api
高亮
GET index_search/_serch{
"query":{
"match":{
"good_name":"test"
}
},
"heighlight":{
"pre_tags":"<span style='color:red'>",
"post_tags":"</span>",
"field":{"good_nme",{}}
}
}
提起数据中不同的值
如果想获取一堆数据中,某一个字段有哪些值。例如:统计一堆用户数据都是来自哪个省份。有distinct效果,不会重复。例子中的tag是文本中的一个键。
GET index_search/_serch{
"collapse":{
"field":"tag.keyword"
}
}
聚合
桶聚合(bucket)
按照某些条件将文档进行分组。类似SQL中的group by。可以分为根据Terms(字段值分组),Date(时间间隔分组),Range(数值字段范围分组),Histogram(数值字段按照固定值分组)。
//terms
GET index_agg/_search{
"size":0, //指定返回的文档数量,0表示不返回任何文档。
"aggs":{ //aggregations(聚合)的简写
"groupBy_tags":{ //自己定义的聚合名称
"terms":{ //是Terms聚合的类型,用于按字段的值对数据进行分组
"field":"tag.keyword" //指定要进行Terms聚合的字段。按tag.keyword字段的值分组
}
}
}
}
//range
GET index_agg/_search{
"aggs":{ //aggregations(聚合)的简写
"range_tags":{ //自己定义的聚合名称
"range":{ //是Range聚合的类型
"field":"age",
"ranges":[ //小于100一组,大于100到130一组,大于130一组
{"to":100},
{"from":100,"to":130},
{"from":130}
]
}
}
}
}
//date
GET index_agg/_search{
"aggs":{ //aggregations(聚合)的简写
"range_date":{ //自己定义的聚合名称
"date_range":{ //是Range聚合的类型
"field":"create_time",
"ranges":[ //小于100一组,大于100到130一组,大于130一组
{"to":"2024-03-14 14:29:36"},
{"from":"2024-03-14 14:29:36"}
]
}
}
}
}
指标聚合(metric)
汇总数值类型字段的信息。例如:平均值(avg),总和(sum),最大值(max),最小值(min),
stats(计算字段的平均值,总和,最大值,最小值和 文档数量。将这些值全列出来)
extended(计算字段的平均值,总和,最大值,最小值,文档数量,标准差和方差。将这些值全列出来)。
以平均值为例:
GET index_age/_search{
"aggs":{ //aggregations(聚合)的简写
"age_avg":{ //自己定义的聚合名称
"avg":{ //是average(平均值)的聚合类型。
"field":"age" //求平均值的字段,类型需要时integer 或者float
}
}
}
}
管道聚合(pipline)
在其他聚合的结果上执行进一步的计算和转换,形成聚合的管道。
bucket script:桶聚合的结果上执行自定义脚本计算。
bucket selector:根据条件选择特定桶
bucket sort:对桶进行排序
嵌套对象Nested
nested类型是对象数据类型的专用版本,它允许对象数组以可以彼此独立查询的方式进行索引。
一个对象中还有一个对象数组,需要查询数组中的数据的时候使用Nested类型创建这个数组。
例如:博客系统中可以有多个评论,在ES中可以用nested类型。
PUT /blog_posts
{
"mappings": {
"properties": {
"title": {
"type": "text"
},
"content": {
"type": "text"
},
"comments": {
"type": "nested", //类型是nested
"properties": {
"author": {
"type": "keyword"
},
"content": {
"type": "text"
},
"timestamp": {
"type": "date"
}
}
}
}
}
}
//插入数据
POST /blog_posts/_doc/1
{
"title": "My first blog post",
"content": "This is my first blog post content...",
"comments": [
{
"author": "Alice",
"content": "Great post!",
"timestamp": "2023-10-23T10:00:00Z"
},
{
"author": "Bob",
"content": "I agree, very informative.",
"timestamp": "2023-10-23T10:15:00Z"
}
]
}
//查询数据
GET /blog_posts/_search
{
"query": {
"nested": {
"path": "comments",
"query": {
"match": {
"comments.content": "Great post"
}
}
}
}
}
Join
用于表达父子数据关系。ES提供了relation类型来支持父-子数据关系操作。Join类型数据的优势在于,可以对数据关系的两头分别独立进行更新,这在某些场景下比nested类型更为方便。
PUT /products
{
"mappings": {
"properties": {
"product_id": {
"type": "keyword"
},
"product_name": {
"type": "text"
},
"relations": {
"type": "join", //类型
"relations": {
"product": "variant"
}
}
}
}
}
// 索引商品
POST /products/_doc/1?routing=product_1
{
"product_id": "product_1",
"product_name": "T-shirt",
"relations": {
"name": "product"
}
}
// 索引变种(作为商品的子文档)
POST /products/_doc/2?routing=product_1&parent=1
{
"product_id": "variant_1",
"color": "blue",
"size": "M",
"relations": {
"name": "variant",
"parent": "1"
}
}
// 查询具有蓝色变种的商品
GET /products/_search
{
"query": {
"has_child": {
"type": "variant",
"query": {
"match": {
"color": "blue"
}
}
}
}
}