个人博客欢迎访问
微信搜索程序dunk,关注公众号,获取项目、博客源码
我们面前无所不有,我们面前一无所有 ——查尔斯·狄更斯
序号 | 内容 |
---|---|
1 | Java基础面试题 |
2 | JVM面试题 |
3 | Java并发编程面试 |
4 | 计算机网络知识点汇总 |
5 | MySQL面试题 |
6 | Mybatis源码分析 + 面试 |
7 | Spring面试题 |
8 | SpringMVC面试题 |
9 | SpringBoot面试题 |
10 | SpringCloud面试题 |
11 | Redis面试题 |
12 | Elasticsearch面试题 |
13 | Docker学习 |
14 | 消息队列 |
15 | 持续更新… |
我们面前无所不有,我们面前一无所有 ——查尔斯·狄更斯
pause可以阻止dos命令闪退
Lucene作者 Doug Cutting
深入浅出大数据:到底什么是Hadoop?
Hadoop之父:Doug Cutting
elasticsearch中文社区
正如查尔斯·狄更斯在《双城记》中所述,在信息爆炸的当下,我们面前无所不有;而个人信息过载已经成为越来越多的人的负担,我们面前一无所有
如何摆脱过载的信息束缚,高效地找到自己需要的信息呢?——答案就是搜索引擎,借助搜索引擎实现
宏观而言,搜索引擎的发展经历了五个阶段和两大类
五个阶段
两大类
站内搜索,用户行为,社交数据,异常讨论,开源代码,电商商品,日志分析,价格监控,商业智能等等
REST的英文是Representational State Transfer,中文翻译”表述性状态转移“,一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件,是所有Web应用都应该遵守的架构设计指导原则。它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。
资源其实可以看做一种看待服务器的方式
意味着所有事物链接在一起
统一接口意味着使用标准的方法
四个HTTP方法:POST、GET、PUT、DELETE
Elasticsearch中的Rest命令说明
method | url地址 | 描述 |
---|---|---|
PUT | localhost:9200/index/type/documentId | 创建文档(指定文档id) |
POST | localhost:9200/index/type/ | 创建文档(随机文档id) |
POST | localhost:9200/index/type/documentId/_update | 修改文档 |
DELETE | localhost:9200/index/type/documentId | 删除文档 |
GET | localhost:9200/index/type/documentId | 查询文档通过文档id |
POST | localhost:9200/index/type/_search | 查询所有数据 |
资源的表示是对一段资源在某个特定时刻的状态的描述。资源可以在客户端——服务器之间传递
资源的表述有很多格式:HTML、XML、JSON、文本、图片、音频、视频等
意味着无状态通信
无状态即服务器的变化 对客户端是不可见的,主要为了保证架构设计的可伸缩性和可扩展
搜索引擎主要是对数据进行检索。而研发过程中不难发现,数据有两种类型,即结构化数据和非结构化数据
搜索引擎的工作原理
搜索引擎的工作原理分为两个阶段
网络爬虫有多个不同称谓,如网络探测器、Spider蜘蛛等,取意的原因是网页爬取程序像虫子一样在网络间爬来爬取,从一个网页链接爬到另一个网页链接
我们可以基于现有的爬虫框架来实现对网络数据的爬取,java语言技术栈可以使用WebMagic、Gecco;Python语言栈使用Scrapy;GO语言使用YiSpider
在搜索引擎中,爬虫爬取了对应的网页之后,会将网页存储到服务器的原始数据库中,之后,搜索引擎会对这些网页进行分析并确定各网页的重要性,即会影响用户检索的排名结果
所以在此,我们需要对搜索引擎的网页分析算法进行简单了解
搜索引擎的网页分析算法主要分为3类:基于用户行为的网页分析算法、基于网络拓扑的网页分析算法、基于网页内容的网页分析算法
在搜索引擎中每个文件都对应一个文件ID,文件内容被表示为一系列关键词的集合(实际上在搜索引擎索引库中,关键词也已经转换为关键词ID)。例如“文档1”经过分词,提取了20个关键词,每个关键词都会记录它在文档中的出现次数和出现位置。
正向索引的结构
一般是通过key,去找value。
所以,搜索引擎会将正向索引重新构建为倒排索引,即把文件ID对应到关键词的映射转换为关键词到文件ID的映射,每个关键词都对应着一系列的文件,这些文件中都出现这个关键词。
得到倒排索引的结构如下:
基于词做索引明显比基于字做索引内容要少的多,因而查询会更高效(所以引入了中文分词器(ik分词器))
倒排序中三个名词
熟悉目录
bin
: 启动文件 config
: 配置文件 log4j
: 日志文件 jvm.options
: java 虚拟机先关的配置 elasticsearch.xml
: elasticsearch 的配置文件lib
: 相关 jar 包logs
: 日志modules
: 功能模块plugins
: 插件 ikcnpm install
npm run start
\elasticsearch-7.13.1\config\elasticsearch.yml
#开启http跨域
http.cors.enabled: true
#允许所有人可以访问
http.cors.allow-origin: "*"
\kibana-7.13.1-windows-x86_64\config\kibana.yml
i18n.locale: "zh-CN"
ElasticSearch是面向文档的,关系型数据库和ElasticSearch客观对比
Relational DB | ElasticSearch |
---|---|
数据库(Database) | 索引(index) |
表(table) | types(慢慢废弃) |
行(rows) | 文档(documents) |
字段(columns) | fields |
ElasticSearch集群中可以包含多个索引(数据库),每个索引可以包含多个类型(表),每个类型下可以包含多个文档(行),每个文档中可以包含多个字段(列)
ElasticSearch在后台吧多个所以划分成多个分片,每个分片可以在集群中的不同服务器迁移
一个类型中,包含多个文档,比如说文档1,文档2,当我们索引一篇文档时,可以通过这样的顺序找到它:索引>类型>文档ID,通过这个组合我们就能索引到某个具体的文档。注意:ID不必是整数,实际上它是个字符串
文档(Document):一般搜索引擎的处理对象是互联网网页,而文档这个概念要更宽泛些,代表以文本形式存在的存储对象,相比网页来说,涵盖更多种形式,比如Word,PDF,html,XML等不同格式的文件都可以称之为文档。再比如一封邮件,一条短信,一条微博也可以称之为文档。在本书后续内容,很多情况下会使用文档来表征文本信息
ElasticSearch中,文档有几个重要属性:
尽管我们可以随意的新增或者忽略某个字段,但是,每个字段的类型非常重要,比如一个年龄字段类型,可以是字符串也可以是整形。因为ElasticSearch会保存字段和类型之间的映射及其他的设置,这种映射具体到每个映射的每种类型,这也是为什么在ElasticSearch中,类型有时候也称为映射类型。
类型是文档的逻辑容器,就像关系型数据库一样,表格是行的容器。类型中对于字段的定义称为映射,比如name映射为字符串类型。我们说文档是无模式的,它们不需要拥有映射中所定义的所有字段,比如新增一个字段,那么 ElasticSearch是怎么做的呢?
ElasticSearch会自动的将新字段加入映射,但是这个字段的不确定它是什么类型, ElasticSearch就开始猜,如果这个值是18,那么ElasticSearch会认为它是整形,但是 ElasticSearch也可能猜不对,所以最安全的方式就是提前定义好所需要的映射,这点跟关系型数据库殊途同归了,先定义好字段,然后再使用
就是数据库
索引是映射类型的容器, ElasticSearch中的索引是一个非常大的文档集合,索引存储了映射类型的字段和其他设置。然后它们被存储到了各个分片上了。我们来研究下分片是如何工作的
一个集群至少有一个节点,而一个节点就是一个 ElasticSearch进程,节点可以有多个索引默认的,如果你创建索引,那么索引将会有个5个分片( primary shard又称主分片)构成的,每个主分片会有一个副本( replica shard,又称复制分片)
主分片和对应的复制分片都不会在同一个节点内,这样有利于某个节点挂掉了,数据也不至于丢失。实际上,一个分片是一个 Lucene索引,一个包含倒排索引的文件目录,倒排索引的结构使得 ElasticSearch在不扫描全部文档的情况下,就能告诉你哪些文档包含特定的关键字
高可用是企业级服务必须考虑的一个指标,高可用必然涉及到集群和分布式,好在ES天然支持集群模式,可以非常简单的搭建一个分布式系统
ES服务高可用要求其中一个节点如果挂掉了,不能影响正常的搜索服务。这就意味着挂掉的节点上存储的数据,必须在其他节点上留有完整的备份。这就是副本的概念
如上图所示,Node1
作为主节点,Node2
和Node3
作为副本节点保存了和主节点完全相同的数据,这样任何一个节点挂掉都不会影响业务的搜索。满足服务的高可用要求。
但是有一个致命的问题,无法实现系统扩容!即使添加另外的节点,对整个系统的容量扩充也起不到任何帮助。因为每一个节点都完整保存了所有的文档数据。
因此,ES
引入了分片(Shard
)的概念。
ES
将每个索引(ES
中一系列文档的集合,相当于MySQL
中的表)分成若干个分片,分片将尽可能平均地分配到不同的节点上。比如现在一个集群中有3台节点,索引被分成了5个分片,分配方式大致(因为具体如何平均分配取决于ES
)如下图所示。
这样一来,集群的横向扩容就非常简单了,现在我们向集群中再添加2个节点,则ES
会自动将分片均衡到各个节点之上:
副本和分片功能通力协作造就了ES
如今高可用和支持PB级数据量的两大优势。
现在我们以3个节点为例,展示一下分片数量为5
,副本数量为1
的情况下,ES
在不同节点上的分片排布情况:
ES怎么确定某个文档应该存储到哪一个分片上的呢?
通过上面的映射算法,ES
将文档数据均匀地分散在各个分片中,其中routing
默认是文档id。
此外,副本分片的内容依赖主分片进行同步,副本分片存在意义就是负载均衡、顶上随时可能挂掉的主分片位置,成为新的主分片。
客户端进行关键词搜索时,ES
会使用负载均衡策略选择一个节点作为协调节点(Coordinating Node
)接受请求,这里假设选择的是Node3
节点;
Node3
节点会在10个主副分片中随机选择5个分片(所有分片必须能包含所有内容,且不能重复),发送search request;Node3
节点;Node3
节点整合5个分片返回的结果,再次排序之后取到对应分页的结果集返回给客户端。注:实际上
ES
的搜索分为Query阶段
和Fetch阶段
两个步骤,在Query阶段
各个分片返回文档Id和排序值,Fetch阶段
根据文档Id去对应分片获取文档详情
现在考虑客户端获取990~1000
的文档时,ES
在分片存储的情况下如何给出正确的搜索结果。
获取990~1000
的文档时,ES
在每个分片下都需要获取1000
个文档,然后由Coordinating Node
聚合所有分片的结果,然后进行相关性排序,最后选出相关性顺序在990~1000
的10
条文档。
页数越深,每个节点处理的文档也就越多,占用的内存也就越多,耗时也就越长,这也就是为什么搜索引擎厂商通常不提供深度分页的原因了,他们没必要在客户需求不强烈的功能上浪费性能。
ELK是三个开源软件的缩写,分别表示:Elasticsearch , Logstash, Kibana , 它们都是开源软件。新增了一个FileBeat,它是一个轻量级的日志收集处理工具(Agent),Filebeat占用资源少,适合于在各个服务器上搜集日志后传输给Logstash,官方也推荐此工具。
Elasticsearch是个开源分布式搜索引擎,提供搜集、分析、存储数据三大功能。它的特点有:分布式,零配置,自动发现,索引自动分片,索引副本机制,restful风格接口,多数据源,自动搜索负载等。
Logstash 主要是用来日志的搜集、分析、过滤日志的工具,支持大量的数据获取方式。一般工作方式为c/s架构,client端安装在需要收集日志的主机上,server端负责将收到的各节点日志进行过滤、修改等操作在一并发往elasticsearch上去。
Kibana 也是一个开源和免费的工具,Kibana可以为 Logstash 和 ElasticSearch 提供的日志分析友好的 Web 界面,可以帮助汇总、分析和搜索重要数据日志。
Filebeat隶属于Beats。目前Beats包含四种工具:
下载IK分词器
注意版本一定要和ElasticSearch版本一致!!!!
解压放在\elasticsearch-7.13.1\plugins\ik
目录下,注意是编译打包过得文件
重启ElasticSearch,插件加载完毕
通过elasticsearch-plugin插件查看加载的插件
分词:即把一段中文或者别的划分成一个个的关键字,我们在搜索时候会把自己的信息进行分词,会把数据库中或者索引库中的数据进行分词,然后进行一个匹配操作,默认的中文分词是将每个字看成一个词,比如”我爱长安”会被分为”我”、”爱”、”长”、”安”、这显然是不符合要求的,所以我们需要安装中文分词器来解决这个问题
iK提供了两个分词算法: ik_smart(最小分词器)和 ik_max_word(最细粒度划分),其中 ik_smart为最少切分, ik_max_word为最细粒度划分
使用分词器查询程序dunk
可以看到
GET _analyze
{
"analyzer" : "ik_max_word",
"text" : "程序dunk"
}
如果想要吧程序dunk当做一个词语,那么需要在elasticsearch-7.13.1\plugins\ik\config
路径下自己扩展字典
重启服务,可以看到分词器,将程序dunk单独划分了出来
添加索引、同时添加数据
PUT test1/type1/1
{
"name" : "dunk_code",
"age" : 18
}
PUT test2
{
"mappings": {
"properties": {
"name" : {
"type": "text"
},
"age" : {
"type": "long"
},
"birthday" : {
"type": "date"
}
}
}
}
POST /test1/type1/1/_update
{
"doc" : {
"name" : "程序dunk"
}
}
DELETE test1/type1/1
//查询该索引的信息
GET test1/
//查询索引下指定文档id信息
GET test1/type1/1
//精确查询name为dunk_code的信息
GET test1/type1/_search?q=name : dunk_code
创建索引
PUT /xauat/student/1
{
"name" : "程序dunk",
"age" : 21,
"desc" : "必进大厂",
"tags" : ["编程", "篮球", "游戏"]
}
_query
GET /xauat/student/_search
{
"query" : {
"match": {
"name" : "程序"
}
}
}
_source:保留那些数据
"_source" : ["name","desc"]
sort
"sort" : [
{
"age" : {
"order" : "asc"
}
}
]
from :开始页
size:页码
"from" : 0,
"size" : 2
must : and
GET /xauat/student/_search
{
"query" : {
"bool": {
"must": [
{
"match" : {
"name" : "dunk"
}
},
{
"match": {
"age" : 30
}
}
]
}
}
}
should:or
GET /xauat/student/_search
{
"query" : {
"bool": {
"should": [
{
"match" : {
"name" : "dunk"
}
},
{
"match": {
"age" : 30
}
}
]
}
}
}
must_not:not
filter
GET /xauat/student/_search
{
"query" : {
"bool": {
"must": [
{
"match" : {
"name" : "dunk"
}
}
],
"filter": {
"range" : {
"age" : {
"gte" : 21,
"lte" : 30
}
}
}
}
}
}
term 查询是直接通过倒排索引指定的词条进行精确查找!
关于分词:
两个类型 text keyword
高亮:highlight
前缀:pre_tags
后缀:post_tags
GET /xauat/student/_search
{
"query" : {
"term": {
"name" : "dunk"
}
},
"highlight" : {
"pre_tags": ""
,
"post_tags": "",
"fields": {
"name" : {}
}
}
}
导入SpringData Elasticsearch依赖
@Test
//创建索引
void testCreateIndex() throws IOException {
CreateIndexRequest request = new CreateIndexRequest("xauat");
client.indices().create(request, RequestOptions.DEFAULT);
}
@Test
//判断是否存在索引
void isExistIndex() throws IOException {
GetIndexRequest request = new GetIndexRequest("xauat");
boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
System.out.println(exists);
}
@Test
//删除索引
void deleteIndex() throws IOException {
DeleteIndexRequest request = new DeleteIndexRequest("xauat");
AcknowledgedResponse delete = client.indices().delete(request, RequestOptions.DEFAULT);
System.out.println(delete.isAcknowledged());
}
@Test
//创建文档
void createDocument() throws IOException {
//创建请求
IndexRequest request = new IndexRequest("xauat");
//创建对象
Student student = new Student("王五",
40, "编程大佬", Arrays.asList("编程", "学习"));
//设置请求
request.id("6");
request.timeout(TimeValue.timeValueMillis(1));
//将数据转为json格式放入请求
request.source(JSON.toJSONString(student), XContentType.JSON);
//发送请求
IndexResponse indexResponse = client.index(request, RequestOptions.DEFAULT);
//获取响应结果
System.out.println(indexResponse.toString());
//响应状态
System.out.println(indexResponse.status());
}
@Test
//获取文档
void getDocument() throws IOException {
GetRequest request = new GetRequest("xauat", "6");
//判断是否存在
boolean exists = client.exists(request, RequestOptions.DEFAULT);
System.out.println(exists);
GetResponse getResponse = client.get(request, RequestOptions.DEFAULT);
//返回全部内容
System.out.println(getResponse);
//打印文档内容
System.out.println(getResponse.getSourceAsString());
}
@Test
//更新文档
void updateDocument() throws IOException {
UpdateRequest request = new UpdateRequest("xauat", "6");
Student student = new Student("王五",
40, "编程菜鸡", Arrays.asList("编程", "互啄"));
request.doc(JSON.toJSONString(student), XContentType.JSON);
UpdateResponse update = client.update(request, RequestOptions.DEFAULT);
System.out.println(update.status());
}
@Test
//删除文档
void DeleteDocument() throws IOException {
DeleteRequest request = new DeleteRequest("xauat", "3");
DeleteResponse delete = client.delete(request, RequestOptions.DEFAULT);
System.out.println(delete.status());
}
@Test
//批量插入
void addBatch() throws IOException {
List<Student> students = new ArrayList<>();
//创建请求
BulkRequest bulkRequest = new BulkRequest();
students.add(new Student("王五",
40, "编程菜鸡", Arrays.asList("编程", "互啄")));
students.add(new Student("王五",
40, "编程菜鸡", Arrays.asList("编程", "互啄")));
students.add(new Student("王五",
40, "编程菜鸡", Arrays.asList("编程", "互啄")));
students.add(new Student("王五",
40, "编程菜鸡", Arrays.asList("编程", "互啄")));
for (int i = 0; i < students.size(); i++) {
bulkRequest.add(new IndexRequest("xauat")
.id(i + 6 + "")
.source(JSON.toJSONString(students.get(i)), XContentType.JSON));
BulkResponse bulk = client.bulk(bulkRequest, RequestOptions.DEFAULT);
System.out.println(bulk.status());
}
}
@Test
//查询
void SearchDocument() throws IOException {
SearchRequest request = new SearchRequest();
//构建搜素条件
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//构建查询条件
TermQueryBuilder termQueryBuilder = new TermQueryBuilder("name", "dunk");
searchSourceBuilder.query(termQueryBuilder);
searchSourceBuilder.timeout(TimeValue.timeValueMillis(2000));
request.source(searchSourceBuilder);
SearchResponse search = client.search(request, RequestOptions.DEFAULT);
//查询结果集
SearchHits hits = search.getHits();
System.out.println(JSON.toJSONString(hits));
//循环打印每个查询结果
for (SearchHit hit : hits.getHits()) {
System.out.println(hit.getSourceAsMap());
}
}
<!-- https://mvnrepository.com/artifact/org.jsoup/jsoup -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.13.1</version>
</dependency>
/**
* @author :zsy
* @date :Created 2021/6/9 16:17
* @description:商品
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Content {
private String img;
private String price;
private String name;
}
jd图片是懒加载的,所以从
"data-lazy-img"
中获取图片url而不是src
/**
* @author :zsy
* @date :Created 2021/6/9 15:24
* @description:解析网页
*/
public class HtmlParseUtil {
public static void main(String[] args) throws IOException {
HtmlParseUtil.parseJD("java").forEach(System.out::println);
}
public static List<Content> parseJD(String keyWord) throws IOException {
String url = "https://search.jd.com/Search?keyword=" + keyWord;
Document document = Jsoup.parse(new URL(url), 30000);
Element e = document.getElementById("J_goodsList");
//System.out.println(e.html());
List<Content> list = new ArrayList<>();
Elements lis = e.getElementsByTag("li");
for (Element li : lis) {
//所有图片都是延时加载的
String img = li.getElementsByTag("img").eq(0).attr("data-lazy-img");
String price = li.getElementsByClass("p-price").eq(0).text();
String name = li.getElementsByClass("p-name").eq(0).text();
//System.out.println("http:" + img);
//System.out.println(price);
//System.out.println(name);
list.add(new Content("http:" + img, price, name));
}
return list;
}
}
前端页面如下
/**
* @author :zsy
* @date :Created 2021/6/9 16:45
* @description:jd搜索
*/
public interface JDService {
//解析数据放入es
Boolean analyticalData(String keyword) throws IOException;
}
/**
* @author :zsy
* @date :Created 2021/6/9 16:45
* @description:实现
*/
@Service
public class JDServiceImpl implements JDService {
@Autowired
RestHighLevelClient client;
@Override
public Boolean analyticalData(String keyword) throws IOException {
//获取解析数据
List<Content> contents = HtmlParseUtil.parseJD(keyword);
//判断索引是否存在
GetIndexRequest getRequest = new GetIndexRequest("jd_goods");
boolean exists = client.indices().exists(getRequest, RequestOptions.DEFAULT);
if (!exists) {
//索引不存在,创建索引
CreateIndexRequest createIndexRequest = new CreateIndexRequest("jd_goods");
CreateIndexResponse createIndexResponse = client.indices().create(createIndexRequest, RequestOptions.DEFAULT);
if (!createIndexResponse.isAcknowledged()) return false;
}
//批处理请求
BulkRequest bulkRequest = new BulkRequest();
for (int i = 0; i < contents.size(); i++) {
bulkRequest.add(new IndexRequest("jd_goods")
.source(JSON.toJSONString(contents.get(i)), XContentType.JSON));
}
bulkRequest.timeout(TimeValue.timeValueMillis(10000));
BulkResponse searchResponse = client.bulk(bulkRequest, RequestOptions.DEFAULT);
return !searchResponse.hasFailures();
}
}
/**
* @author :zsy
* @date :Created 2021/6/9 17:40
* @description:商品
*/
@Controller
public class ContentController {
@Autowired
JDService jdService;
@GetMapping("/parse/{keyword}")
@ResponseBody
public Boolean parse(@PathVariable String keyword) throws IOException {
return jdService.analyticalData(keyword);
}
}
List<Map<String, Object>> search(String keyword, int pageNo, int pageSize) throws IOException;
@Override
public List<Map<String, Object>> search(String keyword, int pageNo, int pageSize) throws IOException {
SearchRequest searchRequest = new SearchRequest();
List<Map<String, Object>> list = new ArrayList<>();
//构建搜索条件
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//分页
searchSourceBuilder.from(pageNo);
searchSourceBuilder.size(pageSize);
//精确匹配
TermQueryBuilder termQueryBuilder = new TermQueryBuilder("name", keyword);
searchSourceBuilder.query(termQueryBuilder);
searchSourceBuilder.timeout(TimeValue.timeValueMillis(10000));
//执行搜索
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
//查询结果集
SearchHits hits = searchResponse.getHits();
for(SearchHit hit : hits) {
list.add(hit.getSourceAsMap());
}
return list;
}
@ResponseBody
@GetMapping("/search/{keyword}/{pageNo}/{pageSize}")
public List<Map<String, Object>> search(
@PathVariable String keyword,
@PathVariable int pageNo,
@PathVariable int pageSize) throws IOException {
return jdService.search(keyword, pageNo, pageSize);
}
@Override
public List<Map<String, Object>> search(String keyword, int pageNo, int pageSize) throws IOException {
SearchRequest searchRequest = new SearchRequest();
List<Map<String, Object>> list = new ArrayList<>();
//构建搜索条件
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//分页
searchSourceBuilder.from(pageNo);
searchSourceBuilder.size(pageSize);
//精确匹配
TermQueryBuilder termQueryBuilder = new TermQueryBuilder("name", keyword);
searchSourceBuilder.query(termQueryBuilder);
searchSourceBuilder.timeout(TimeValue.timeValueMillis(10000));
//添加高亮
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("name");
highlightBuilder.requireFieldMatch(false);
highlightBuilder.preTags("");
highlightBuilder.postTags("");
searchSourceBuilder.highlighter(highlightBuilder);
//执行搜索
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
//查询结果集
SearchHits hits = searchResponse.getHits();
for(SearchHit hit : hits) {
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
//获取原来的结果
HighlightField name = highlightFields.get("name");
Map<String, Object> sourceAsMap = hit.getSourceAsMap();
if (name != null) {
Text[] fragments = name.fragments();
String curName = "";
for (Text fragment : fragments) {
curName += fragment;
}
sourceAsMap.put("name", curName);
}
name = highlightFields.get("name");
list.add(hit.getSourceAsMap());
}
return list;
}
系统中的数据,随着业务的发展,时间的推移,将会非常的多,而业务中往往采用模糊查询进行数据结构搜索,而模糊查询会导致查询引擎放弃索引,导致系统查询数据时都是全盘扫描,在百万级别数据库中,查询效率是非常低效的,而Es是一个全文索引,将经常查询的系统功能的某些字段,比如电商系统表中的商品名,描述、价格和id都可以放入ES索引库,提高查询效率
文档写入包含:单文档写入和批量 bulk 写入,这里只解释一下:单文档写入流程
第二步中的文档获取分片的过程
借助路由算法获取,路由算法就是根据路由和文档 id 计算目标的分片 id 的过程
传统的我们的检索是通过文章,逐个遍历找到对应关键词的位置
而倒排索引,是通过分词策略,形成了词和文章的映射关系表,这种词典+映射表即为倒排索引。有了倒排索引,就能实现 o(1)时间复杂度的效率检索文章了,极大的提高了检索效率
倒排索引,相反于一篇文章包含了哪些词,它从词出发,记载了这个词在哪些文档中出现过,由两部分组成——词典和倒排表
加分项:倒排索引的底层实现是基于:FST(Finite State Transducer)数据结构
ucene 从 4+版本后开始大量使用的数据结构是 FST。FST 有两个优点:
索引数据的规划,应在前期做好规划,正所谓“设计先行,编码在后”,这样才能有效的避免突如其来的数据激增导致集群处理能力不足引发的线上客户检索或者其他业务受到影响。
基于模板+时间+rollover api 滚动创建索引,举例:设计阶段定义:blog 索引的模板格式为:blog_index_时间戳的形式,每天递增数据。这样做的好处:不至于数据量激增导致单个索引数据量非常大,接近于上线 2 的32 次幂-1,索引存储达到了 TB+甚至更大。
一旦单个索引很大,存储等各种风险也随之而来,所以要提前考虑+及早避免。
冷热数据分离存储,热数据(比如最近 3 天或者一周的数据),其余为冷数据。
对于冷数据不会再写入新数据,可以考虑定期 force_merge 加 shrink 压缩操作,节省存储空间和检索效率。
一旦之前没有规划,这里就属于应急策略。
结合 ES 自身的支持动态扩展的特点,动态新增机器的方式可以缓解集群压力,注意:如果之前主节点等规划合理,不需要重启集群也能完成动态新增的
Elasticsearch built-in security features are not enabled. Without authentication, your cluster could be accessible to anyone
E:\course\26-elasticsearch\elasticsearch-7.13.1\config\elasticsearch.yml
xpack.security.enabled: false
参考文档:
《Elasticsearch实战与原理解析》牛冬
ElasticSearch面试题
参考视频:
仿京东实战搜索