ElasticSearch 学习
ElasticSearch 学习 =ELK= 初级
版本:ElasticSearch7.8版本
主要内容
以前:SQL like查询
现在:如果是大数据,就速率十分慢了---->写索引,但是也无法根本解决。
ElasticSearch、Solr:搜索!(百度、GitHub、淘宝)
1、聊一个人
2、货比三家
3、安装
4、生态圈
5、分词器ik
6、RestFul操作 ES
7、CRUD ES
8、SpringBoot 集成 ElasticSearch(从原理分析!)
9、爬虫爬取数据!京东、淘宝
10、实战、模拟检索
以后如果需要使用到搜索,就可以使用ES!(大数据量的情况下使用)
主题
Lucene 是一套信息检索工具包!jar包!不包含 搜索引擎系统!
包含的:索引结构!读写索引的工具!排序,搜索规则。。。工具类!
Lucene 和 ElasticSearch 关系:
ElasticSearch 是 基于Lucene 做了一些封装和增强(我们上手是十分简单的)
ElasticSearch,简称ES,是一个开源的高扩展的分布式全文检索引擎,它可以近乎实时的存储、检索数据;
ElasticSearch
声明:JDK1.8,elasticsearch客户端,界面工具
JAVA开发,ElasticSearch的版本和我们之后对应的JAVA的核心jar包!版本对应!JDK环境正常!
ElasticSearch安装
安装过程
1、下载压缩包:
2、解压出来;
3、目录熟悉:
bin 启动文件
config 配置文件
log4j2 日志配置文件
jvm.options java虚拟机相关配置
elasticsearch.yml ES相关配置
lib 相关jar包
modules 功能模块
plugins 插件! ik
4、启动:访问9200
安装可视化界面 es head的插件
1、下载:https://github.com/mobz/elasticsearch-head
2、启动:
npm install
npm run start
遭遇跨域问题
解决
在elasticsearch.yml中进行配置跨域问题。
继续:连接成功~
es可以当作一个数据库,可以创建索引(数据库中的库),文档(库中的数据)。
Kibana安装
ELK: elasticsearch、Logstash、Kibana三大开源框架;
收集清洗数据—>搜索—>Kibana
Kibana 是一个针对ElasticSearch的开源分析及可视化平台,用来搜索、查看交互存储在ElasticSearch索引中的数据。
1、下载:
版本必须对应
2、进行解压,解压需要很长的时间,是一个标准的工程!
好处:ELK基本上都是拆箱即用的。
解压完毕:
点击运行:
访问5601页面:
4、开发工具!(Post、curl、head、谷歌浏览器插件测试!)
进行查询:
我们之后的所有的操作都可以在这里进行编写!
汉化:修改配置文件为zh-CN
ES核心概念
1、索引
2、字段(文档)
3、类型
集群、节点、索引、类型mapping、文档documents、分片、映射
elasticsearch是面向文档
DBMS | elasticsearch |
---|---|
数据库 | 索引 |
表 | types |
行 | documents |
字段 | fields |
物理设计:
elasticsearch在后台把每个索引划分为多个分片,每个分片可以在集群中的不同服务器间迁移。
一个人就是一个集群:默认的集群名字为elasticsearch
文档
就是我们的一条条数据就是文档
user
1 zhangsan 18
2 wangmazi 3
elasticsearch是面向文档的,那就意味着索引和搜索数据的最小单位是文档;
- 自我包含,一篇文章同时包含字段和对应的值,也就是同时包含key:value!
- 可以是层次型的,一个文档中包含自文档,复杂的逻辑实体就是这样来的!(就是一个json对象!,我们可以使用fastjson进行自动转换!)
- 灵活的结构,文档不依赖预先定义的模式,我们知道关系型数据库中,要提前定义字段才能使用,在elasticsearch中,对于字段是非常灵活的,有时候,我们可以忽略该字段,或者动态的添加一个新的字段。
尽管我们可以随意的新增或者忽略某个字段,但是,每个字段的类型非常重要,比如一个年龄字段类型,可以是字符串 也可以是整形。因为elasticsearch会保存字段和类型之间的映射及其他的设置。这种映射具体到每个映射的每种类型,这也是为什么在elasticsearch中,类型有时候也会称为映射类型。
类型
类型是文档的逻辑容器,就像关系数据库一样,表格是行的容器。类型中对于字段的定义称为映射,比如name映射为字符串类型。我们说文档是无模式的,它们不需要拥有映射中所定义的所有字段,比如新增一个字段那么elasticsearch是怎么做的呢?elasticsearch会自动的将新字段加入映射,但是这个字段的不确定它是什么类型,elasticsearch就开始猜,如果这个值是18,那么elasticsearch会认为它是整形。但是elasticsearch也可能猜不到,所以最安全的方式就是提前定义好所需要的映射,这点跟关系数据库殊途同归了,先定义好字段,然后再使用。
索引
就是数据库!
索引是映射类型的容器,elasticsearch中的索引是一个非常大的文档集合。索引存储了映射类型的字段和其他设置。然后它们被存储到了各个分片上了。我们来研究下分片是如何工作的。
物理设计:节点和分片 如何工作
一个集群至少有一个节点,而一个节点就是一个elasticsearch进程,节点可以有多个索引默认的,如果你创建索引,那么索引将会有5个分片,每个分片会一个副本;
我们可以看到主分片和对应的复制分片都不会在同一个节点内,这样有利于某个节点挂掉了,数据也不至于丢失。实际上,一个分片是一个Lucene索引,一个包含倒排索引的文件目录,倒排索引的结构使得elasticsearch在不扫描全部文档的情况下,就能告诉你哪些文档包含特定的关键字。
倒排索引
elasticsearch使用的是一种称为倒排索引的结构;
为了创建倒排索引,我们首先要将每个文档拆分成独立的词或者(token),然后创建一个包含所有不重复的词条的排序列表,然后列出每个词条出现在哪个文档:
现在,我们试图搜索to forever,只需要查看包含每个词条的文档;
第一个文档满足两个,所以分数会更高,这个文档就会在搜索的时候放在前面。
例子(倒排索引):
如果我们要搜索linux,我们就不会去查找 1 2的文档了,我们只会去3 4 进行查找,这样我们的效率会调高很多。完全过滤掉无关的所有数据去提高效率。
elasticsearch的索引和lucene的索引进行对比:
在elasticsearch中,索引这个词被频繁使用,这就是术语的使用。在elasticsearch中,索引被分为多个分片,每份分片是一个Lucene的索引。所以一个elasticsearch索引是由多个Lucene索引组成。
IK分词器插件
什么是ik分词器
分词:就是把一段中文或者其他语言划分为一个个的关键字;
ik分词器提供了两种分词算法:ik_smart和ik_max_word 其中ik_smart为最少切分,ik_max_word为最细粒度切分!
安装
1、下载IK elasticsearch分词器
https://github.com/medcl/elasticsearch-analysis-ik
编译打包为jar包:
2、放入到elasticsearch插件中;
如果以上的编译出现问题:
官方编译好的jar文件;
3、重启观察ES
可以看到:已经加载ik分词器插件了。
4、elasticsearch-plugin
该命令可以查看加载进来的插件
5、使用Kinbana
ik_smart最小划分:
ik_max_word最大粒度划分:把该词划分为全部的词语,穷尽词库的所有可能!字典中!
测试超级喜欢狂猫说Java
发现问题:狂猫说被拆开了!
这种如果是我们自己设定的词,需要我们自己加入到我们的分词器的字典中!
ik分词器增加自己的配置
新建一个dic字典:
再加入到配置中:
重启ES
加载了我们自定义的dic
重启kibana进行测试:
这就没有问题了,对于我们自定义的词也没有再次划分。
Rest风格说明
基本Rest命令说明:
method | url地址 | 描述 |
---|---|---|
PUT | localhost:9200/索引名称/类型名称/文档id | 创建文档(指定文档id) |
POST | localhost:9200/索引名称/类型名称 | 创建文档(随机文档id) |
POST | localhost:9200/索引名称/类型名称/文档id/_update | 修改文档 |
DELETE | localhost:9200/索引名称/类型名称/文档id | 删除文档 |
GET | localhost:9200/索引名称/类型名称/文档id | 查询文档通过文档id |
GET | localhost:9200/索引名称/类型名称/_search | 查询所有数据 |
基本测试
1、创建一个索引
PUT /索引名/~类型名~/文档id
{
请求体
}
查看是否创建成功:
完成自动增加了索引!数据也就添加成功!
3、name这个字段我们没有对 它进行类型指定,那么类型有哪些呢?
- 字符串类型
- 数值类型
- 日期类型
- te布尔值类型
- 二进制类型
- 等等…
4、指定字段的类型:
相当于创建表的字段类型!
获得这个规则:直接GET请求就 可以获取具体的信息
5、我们再来看看默认的信息:
GET方式请求得到默认信息:
如果自己的文档字段是没有指定,那么ES就会给我们默认配置字段类型!
扩展:通过命令elasticsearch索引情况!通过get _cat/ 可以获得es当前很多的信息
1、health
2、indices
修改 提交还可以使用PUT 即可! 然后覆盖即可!
也可以使用update:
删除索引
DELETE test1
DELETE test1/_doc/1
关于文档的基本操作
基本操作
1、添加数据:PUT
PUT /kuangshen/user/1
{
"name": "狂猫说",
"age":23,
"desc": "一顿操作猛如虎,一看战绩0/5",
"tags": ["技术宅","温暖","直男"]
}
2、获取数据:GET
3、更新数据:PUT
4、Post命令: _update 更新(推荐使用)
也可以更新部分数据:
简单的搜索!
id查询:
GET kuangshen/user/1
条件查询:
GET kuangshen/user/_search?q=name:狂神说
复杂查询:
select(排序、分页、高亮、模糊、精准)
查询的参数体一般实验JSON构建
GET kuangshen/user/_search
{
"query":{
"match":{
"name": "狂神"
}
}
}
1、简单query查询
现在我们再put一个狂猫前端:
我们进行查询:
这里我们可以看到一个hits:里面包含了两个对象。hits:索引+文档信息+查询结果+分数
然后每个对象里面有一个_score分数,然后在hits中也有一个max_score的分数,存在一个最大分值item。
2、只查询需要查询的字段:
select name desc from tables
##### 3、排序查询:
GET kuangshen/user/_search
{
"query":{
"match": {
"name": "狂猫"
}
},
"sort":[
{
"age":{
"order": "asc"
}
}
]
}
asc 是升序排序、desc是降序排序
4、分页查询
GET kuangshen/user/_search
{
"query":{
"match": {
"name": "狂猫"
}
},
"sort":[
{
"age":{
"order": "asc"
}
}
],
"from": 0,
"size": 2
}
from 是从第几个数据开始,size是页面大小。
前端发来的一般是:/search/{current}/pagesize
布尔值查询
多条件精确查询
must(and),所有的条件都要符合 where name="" and age=""
GET kuangshen/user/_search
{
"query":{
"bool": {
"must":[
{
"match": {
"name": "狂猫说"
}
},
{
"match": {
"age": "23"
}
}
]
}
}
}
should(or),只要有一个符合即可。
GET kuangshen/user/_search
{
"query":{
"bool": {
"should":[
{
"match": {
"name": "狂猫说"
}
},
{
"match": {
"age": "23"
}
}
]
}
}
}
not 使用 must_not即可
GET kuangshen/user/_search
{
"query":{
"bool": {
"must_not":[
{
"match": {
"age": "23"
}
}
]
}
}
}
filter过滤
GET kuangshen/user/_search
{
"query":{
"bool": {
"must":[
{
"match": {
"name": "狂猫"
}
}
],
"filter": [
{
"range": {
"age": {
"gte": 10
}
}
}
]
}
}
}
条件查询:查询年龄大于10的元素
-
gt:>
-
gte:>=
-
lt:<
-
lte:<=
匹配多个条件!
多个条件之间用 空格分隔 就可以了。 只要满足其中一个结果就可以被查出,这个时候可以根据分值进行排序。
精确查询!
term查询是直接通过我们的倒排索引指定的词条进行精确的查找的!
关于分词:
-
term,直接查询精确的;
-
match,会使用分词解析!(先分析文档,然后通过分析的文档进行查询!)
两个类型:text keyword:
text 是会被分词解析的;keyword是不会被分词解析的!
keyword:
text:
**注意:**keyword字段类型不会被分词器解析!
高亮查询:!!!重要!
1、普通高亮查询:
2、自定义高亮格式:
集成SpringBoot
1、找到原生的依赖:
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.8.0</version>
</dependency>
2、找对象
3、分析这个类中的方法即可!
配置基本的项目
pow配置:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
注意:默认版本和我们本地版本不一致:
修改:
然后刷新依赖,就得到对应的7.8.0版本的依赖了。
4、创建config配置,把es client注入到spring中:
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ElasticSearchConfig {
@Bean
public RestHighLevelClient restHighLevelClient(){
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("127.0.0.1", 9200, "http")));
return client;
}
}
源码中提供的对象:
虽然这里导入3个类,静态内部类,核心类就一个。
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.elasticsearch.rest;
import java.time.Duration;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Elasticsearch rest client infrastructure configurations.
*
* @author Brian Clozel
* @author Stephane Nicoll
*/
class RestClientConfigurations {
@Configuration(proxyBeanMethods = false)
static class RestClientBuilderConfiguration {
//RestClientBuilder
@Bean
@ConditionalOnMissingBean
RestClientBuilder elasticsearchRestClientBuilder(RestClientProperties properties,
ObjectProvider<RestClientBuilderCustomizer> builderCustomizers) {
HttpHost[] hosts = properties.getUris().stream().map(HttpHost::create).toArray(HttpHost[]::new);
RestClientBuilder builder = RestClient.builder(hosts);
PropertyMapper map = PropertyMapper.get();
map.from(properties::getUsername).whenHasText().to((username) -> {
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
Credentials credentials = new UsernamePasswordCredentials(properties.getUsername(),
properties.getPassword());
credentialsProvider.setCredentials(AuthScope.ANY, credentials);
builder.setHttpClientConfigCallback(
(httpClientBuilder) -> httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider));
});
builder.setRequestConfigCallback((requestConfigBuilder) -> {
map.from(properties::getConnectionTimeout).whenNonNull().asInt(Duration::toMillis)
.to(requestConfigBuilder::setConnectTimeout);
map.from(properties::getReadTimeout).whenNonNull().asInt(Duration::toMillis)
.to(requestConfigBuilder::setSocketTimeout);
return requestConfigBuilder;
});
builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return builder;
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestHighLevelClient.class)
static class RestHighLevelClientConfiguration {
//RestHighLevelClient 高级客户端,后面项目会用到的客户端
@Bean
@ConditionalOnMissingBean
RestHighLevelClient elasticsearchRestHighLevelClient(RestClientBuilder restClientBuilder) {
return new RestHighLevelClient(restClientBuilder);
}
@Bean
@ConditionalOnMissingBean
RestClient elasticsearchRestClient(RestClientBuilder builder,
ObjectProvider<RestHighLevelClient> restHighLevelClient) {
RestHighLevelClient client = restHighLevelClient.getIfUnique();
if (client != null) {
return client.getLowLevelClient();
}
return builder.build();
}
}
@Configuration(proxyBeanMethods = false)
static class RestClientFallbackConfiguration {
//普通的客户端RestClient
@Bean
@ConditionalOnMissingBean
RestClient elasticsearchRestClient(RestClientBuilder builder) {
return builder.build();
}
}
}
具体的Api测试
1、创建索引
2、判断索引是否存在
3、删除索引
4、创建文档
5、crud文档
@Autowired
private RestHighLevelClient restHighLevelClient;
//es API 7.8.0高级客户端api测试
//测试 索引的创建
@Test
void testCreate() throws IOException {
//1、创建索引请求 PUT
CreateIndexRequest request = new CreateIndexRequest("kuang_index");
//2、执行创建请求 IndicesClient 获得响应
CreateIndexResponse createIndexResponse =
restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
System.out.println(createIndexResponse);
}
//测试 获取索引 判断是否其是否存在
@Test
void getIndexText() throws IOException {
GetIndexRequest getIndexRequest = new GetIndexRequest("kuang_index");
boolean exists = restHighLevelClient.indices().exists(getIndexRequest, RequestOptions.DEFAULT);
System.out.println(exists);
}
//测试 删除索引
@Test
void DeleteIndex() throws IOException {
DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest("kuang_index");
AcknowledgedResponse delete = restHighLevelClient.indices().delete(deleteIndexRequest, RequestOptions.DEFAULT);
System.out.println(delete.isAcknowledged());
}
实战
最终效果:
爬虫
数据问题?数据库获取,消息队列中获取,爬虫获取数据!;
爬取数据:(获取请求返回的页面信息,筛选出我们想要的数据就可以了!)
需要的包:jsoup
前后端分离
搜索高亮
//3、获取这些数据 实现高亮功能
public List<Map<String,Object>> searchPageHighlight(String keywords,int pageNo,int pageSize) throws IOException {
if(pageNo<=1){
pageNo=1;
}
//条件搜索
SearchRequest searchResult = new SearchRequest("jd_goods");
SearchSourceBuilder builder = new SearchSourceBuilder();
//分页
builder.from(pageNo);
builder.size(pageSize);
//精准匹配关键字
// TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title", keywords);
MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("title", keywords);
builder.query(matchQuery);
builder.timeout(new TimeValue(60, TimeUnit.SECONDS));
//构建高亮
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("title");
highlightBuilder.requireFieldMatch(false); //关闭多个高亮显示
highlightBuilder.preTags("<span style='color:red'>");
highlightBuilder.postTags("</span>");
builder.highlighter(highlightBuilder);
//执行搜索
searchResult.source(builder);
SearchResponse response = restHighLevelClient.search(searchResult, RequestOptions.DEFAULT);
//解析结果
ArrayList<Map<String,Object>> mapList = new ArrayList<>();
for (SearchHit hit : response.getHits().getHits()) {
//解析高亮的字段
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
HighlightField title = highlightFields.get("title");
//获取初始的结果
Map<String, Object> sourceAsMap = hit.getSourceAsMap();
//置换高亮字段
if(title!=null){
Text[] fragments = title.fragments();
//将高亮字段替换之前未高亮字段
String new_title = "";
for (Text fragment : fragments) {
new_title += fragment;
}
sourceAsMap.put("title",new_title); //替换
}
mapList.add(sourceAsMap);
}
return mapList;
}
实现效果:
整个项目代码gitee地址:elasticsearch_code
学习视频:狂神