本笔记基于【狂神说Java】ElasticSearch7.6.x最新完整教程通俗易懂
本笔记参考 ElasticSearch7.6入门学习笔记
JDK版本:1.8以上
ES,Head,Kibana,IK分词器版本:均为7.6.1
在学习ElasticSearch之前,先简单了解一下Lucene:
Lucene和ElasticSearch的关系:
ElasticSearch是基于Lucene 做了一下封装和增强
Elaticsearch,简称为es, es是一个开源的高扩展的分布式全文检索引擎,它可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上百台服务器,处理PB级别(大数据时代)的数据。es也使用java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单。
Elasticsearch
维基百科
使用Elasticsearch提供全文搜索并高亮关键字,以及输入实时搜索(search-asyou-type)和搜索纠错(did-you-mean)等搜索建议功能。英国卫报
使用Elasticsearch结合用户日志和社交网络数据提供给他们的编辑以实时的反馈,以便及时了解公众对新发表的文章的回应。StackOverflow
结合全文搜索与地理位置查询,以及more-like-this功能来找到相关的问题和答案。Github
使用Elasticsearch检索1300亿行的代码。DataDog
以及Klout
这样的创业公司将最初的想法变成可扩展的解决方案。Solr
总结
1、es基本是开箱即用(解压就可以用!) ,非常简单。Solr安装略微复杂一丢丢!
2、Solr 利用Zookeeper进行分布式管理,而Elasticsearch自身带有分布式协调管理功能。
3、Solr 支持更多格式的数据,比如JSON、XML、 CSV ,而Elasticsearch仅支持json文件格式。
4、Solr 官方提供的功能更多,而Elasticsearch本身更注重于核心功能,高级功能多有第三方插件提供,例如图形化界面需要kibana友好支撑
5、Solr 查询快,但更新索引时慢(即插入删除慢) ,用于电商等查询多的应用;
6、Solr比较成熟,有一个更大,更成熟的用户、开发和贡献者社区,而Elasticsearch相对开发维护者较少,更新太快,学习使用成本较高。
.bat
文件bin 启动文件目录
config 配置文件目录
1og4j2 日志配置文件
jvm.options java 虚拟机相关的配置(默认启动占1g内存,内容不够需要自己调整)
elasticsearch.ym1 elasticsearch 的配置文件! 默认9200端口!跨域!
1ib
相关jar包
modules 功能模块目录
plugins 插件目录
ik分词器
http://localhost:9200/
http.cors.enabled: true
http.cors.allow-origin: "*"
CMD
运行cnpm install
npm run start
启动http://localhost:9100/
i18n.locale: "zh-CN
"图1 设置Kibana为中文
.bat
文件http://localhost:5601/
如果你是初学者
- 索引 可以看做 “数据库”
- 类型 可以看做 “表”
- 文档 可以看做 “库中的数据(表中的行)”
这个head,我们只是把它
当做可视化数据展示工具
,之后
所有的查询都在kibana中进行
- 因为不支持json格式化,不方便
概述
1、索引(ElasticSearch)
2、字段类型(映射)
3、文档
4、分片(Lucene索引,倒排索引)
ElasticSearch是面向文档,关系行数据库和ElasticSearch客观对比!一切都是JSON!
elasticsearch(集群)中可以包含多个索引(数据库) ,每个索引中可以包含多个类型(表) ,每个类型下又包含多个文档(行) ,每个文档中又包含多个字段(列)
图2 检测插件安装是否成功
如上图所示表示插件安装成功
图3 各个软件位置
GET _analyze
{
"analyzer": "ik_smart",
"text": "中国共产党"
}
GET _analyze
{
"analyzer": "ik_max_word",
"text": "中国共产党"
}
图4 最粗粒度拆分ik_smart
图5 最细粒度划分ik_max_word
两次查询结果的不同在于analyzer
属性的不同ik_smart
和ik_max_word
ik_max_word
会将文本做最细粒度的拆分,比如会将“中华人民共和国人民大会堂”拆分为“中华人民共和国、中华人民、中华、华人、人民共和国、人民、共和国、大会堂、大会、会堂等词语。
ik_smart
会做最粗粒度的拆分,比如会将“中华人民共和国人民大会堂”拆分为中华人民共和国、人民大会堂。
GET _analyze
{
"analyzer": "ik_smart",
"text": "赵富强编程Java"
}
GET _analyze
{
"analyzer": "ik_max_word",
"text": "赵富强编程Java"
}
图6 添加分词器之前的ik_smart查询
图7 添加分词器之前的ik_max_word查询
图8 添加分词器过程
图9 es启动窗口看到了ccy.idc文件配置成功
可以看到ccy.dic已经配置成功
图10 添加分词器后的ik_smart查询
图11 添加分词器后的ik_max_word查询
https://www.elastic.co/guide/en/elasticsearch/reference/current/keyword.html
图12 rest风格
text,keyword
long,integer,short,byte,double,float,half float,scaled float
date
boolean
binary
PUT /索引名/类型名/文档ID
{请求体}
图13 添加test索引
实际效果
图14 创建test索引效果
DELETE test2 // DELETE 索引名称
这里test3索引是用另一种方式创建的
图15 用另一种方式创建索引
PUT
PUT /test3/_doc/1
{
"name": "CCY123",
"age": 20,
"birthday": "2001-03-25"
}
图16 默认类型
图17 更新一次version自增1
可以执行更新操作之后,version会发生了变化
POST
POST /test3/_doc/1/_update
{
"doc": {
"name": "赵富双"
}
}
图18 POST更新方式
GET test3
基本操作
PUT /ccy/usr/3
{
"name": "ZFS",
"age": 21,
"desc": "最怕你一生碌碌无为还安慰自己平凡可贵",
"tags": ["用友","字节跳动","努力"]
}
图19 添加文档
后续又添加了两条数据
GET /ccy/usr/1
图20 获得文档
PUT
图21 通过PUT更新文档
如果修改的数据不全面,其他的文档内容就会被置空所以推荐使用POST
POST
POST ccy/usr/2/_update{
"doc": {
"name": "Ccy" }}
如果去除_update
,那么除了修改的文档,其他的内容也会被置空
加上_update,自由度很高,除了修改的文档,其他内容不会改变
GET ccy/usr/_search?q=name:赵富双GET ccy/usr/_search // 上面的全限定名称{ "query": { "match": { "name": "赵富双" } }}
图22 查询文档
复杂查询文档
过滤结果查询
GET ccy/usr/_search{
"query": {
"match": {
"name": "赵富双" } }, "_source": ["name","age"]}
图23 过滤结果查询
GET ccy/usr/_search{
"query": {
"match": {
"name": "赵富双" } }, "sort": [ {
"age": {
"order": "asc" //升序 } } ]}
GET ccy/usr/_search{
"query": {
"match": {
"name": "赵富双" } }, "sort": [ {
"age": {
"order": "asc" } } ], "from": 0, // 初始 "size": 1 // 单页面数据条数}
must
查询,must中的条件必须全部满足
GET ccy/usr/_search{
"query": {
"bool": {
"must": [ {
"match": {
"name": "双" } }, {
"match": {
"age": "23" } } ] } }}
should
查询,should中的条件只要有满足就可以
GET ccy/usr/_search{
"query": {
"bool": {
"should": [ {
"match": {
"name": "双" } }, {
"match": {
"age": "23" } } ] } }}
filter
过滤器GET ccy/usr/_search{
"query": {
"bool": {
"must": [ {
"match": {
"name": "双" } } ], "filter": {
"range": {
"age": {
"gt": 1, "lt": 30 // lt小于 lte小于等于 gt大于 gte 大于等于 } } } } }}
tags
查询GET ccy/usr/_search{
"query": {
"match": {
"tags": "JAVA 宅男" // 中间拿空格隔开 } }}
图24 tags查询多个tag用空格间隔
term查询时通过倒排索引指定的词条进行精确查找到的
关于分词
keyword
关键字不能被分词器解析
GET ccy/usr/_search
{
"query": {
"match": {
"name": "双"
}
},
"highlight": {
"pre_tags": ""
, // 自定义标签默认为
"post_tags": "",
"fields": {
"name":{
}
}
}
}
<properties>
<java.version>1.8java.version>
<elasticsearch.version>7.6.1elasticsearch.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-elasticsearchartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-configuration-processorartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
@Configurationpublic class ElasticSearchClientConfig {
@Bean public RestHighLevelClient restHighLevelClient() {
RestHighLevelClient client = new RestHighLevelClient( RestClient.builder( new HttpHost("127.0.0.1", 9200, "http"))); return client; }}
@SpringBootTestclass EsApiApplicationTests {
@Autowired public RestHighLevelClient restHighLevelClient; // 创建索引 @Test void test1() throws IOException {// 创建索引请求 CreateIndexRequest request = new CreateIndexRequest("ccy_index");// 执行创建请求 CreateIndexResponse createIndexResponse = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT); System.out.println(createIndexResponse); } // 获得索引,只能判断索引是否存在 @Test void test2() throws IOException { GetIndexRequest request = new GetIndexRequest("ccy_index"); boolean exists = restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT); System.out.println(exists); } // 删除索引 @Test void test3() throws IOException { DeleteIndexRequest request = new DeleteIndexRequest("ccy_index"); AcknowledgedResponse delete = restHighLevelClient.indices().delete(request, RequestOptions.DEFAULT); System.out.println(delete.isAcknowledged()); }}
// 添加文档
@Test
void test4() throws IOException {
// 创建对象
User user = new User("CCY", 3);
// 创建请求
IndexRequest request = new IndexRequest("ccy_index");
// 规则 put/ccy_index/_doc/1
request.id("1");
request.timeout(TimeValue.timeValueSeconds(1));
request.timeout("1s");
// 将我们的数据放到请求 json
request.source(JSON.toJSONString(user), XContentType.JSON);
// 客户端发生请求,获取相应的结果
IndexResponse indexResponse = restHighLevelClient.index(request, RequestOptions.DEFAULT);
System.out.println(indexResponse.toString());
System.out.println(indexResponse.status());
}
// 获取文档判断是否存在
@Test
void test5() throws IOException {
GetRequest request = new GetRequest("ccy_index", "1");
// 不获取返回的_source
request.fetchSourceContext(new FetchSourceContext(false));
request.storedFields("_none_");
boolean exists = restHighLevelClient.exists(request, RequestOptions.DEFAULT);
System.out.println(exists);
}
// 获取文档信息
@Test
void test6() throws IOException {
GetRequest request = new GetRequest("ccy_index", "1");
GetResponse getResponse = restHighLevelClient.get(request, RequestOptions.DEFAULT);
System.out.println(getResponse.getSourceAsString());
System.out.println(getResponse);
}
// 更新文档的信息
@Test
void test7() throws IOException {
UpdateRequest updateRequest = new UpdateRequest("ccy_index", "1");
updateRequest.timeout("1s");
User user = new User("赵富双", 20);
updateRequest.doc(JSON.toJSONString(user), XContentType.JSON);
UpdateResponse response = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
System.out.println(response.status());
}
// 删除文档记录
@Test
void test8() throws IOException {
DeleteRequest request = new DeleteRequest("ccy_index", "1");
request.timeout("1s");
DeleteResponse deleteResponse = restHighLevelClient.delete(request, RequestOptions.DEFAULT);
System.out.println(deleteResponse.status());
}
// 特殊的,真的项目一般都会批量插入数据
@Test
void test9() throws IOException {
BulkRequest bulkRequest = new BulkRequest();
bulkRequest.timeout("10s");
ArrayList<User> userList = new ArrayList<>();
userList.add(new User("ccy1", 21));
userList.add(new User("ccy2", 22));
userList.add(new User("ccy3", 23));
for (int i = 0; i < userList.size(); i++) {
bulkRequest.add(
// 批量更新和批量删除,修改对应的请求就可以了
new IndexRequest("ccy_index")
.id("" + (i + 1))
.source(JSON.toJSONString(userList.get(i)), XContentType.JSON));
}
BulkResponse bulkResponse = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
System.out.println(bulkResponse.hasFailures()); // 判断是否失败
}
// 查询
// 搜索请求 searchRequest
// 条件构造 SearchSourceBuilder
// 构建精确查询
@Test
void test10() throws IOException {
SearchRequest searchRequest = new SearchRequest("ccy_index");
// 构建搜索条件
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
// 查询条件,我们可以使用querybuilders 工具来实现
// querybuilders.termquery 精准
// querybuilders.matchallquery() 匹配所有
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", "ccy1");
// MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
sourceBuilder.query(termQueryBuilder);
sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
searchRequest.source(sourceBuilder);
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
System.out.println(JSON.toJSONString(searchResponse.getHits()));
System.out.println("==============");
for (SearchHit documentFields : searchResponse.getHits().getHits()
) {
System.out.println(documentFields.getSourceAsString());
}
}
图25 相关依赖
<properties> <java.version>1.8java.version> <elasticsearch.version>7.6.1elasticsearch.version> properties><dependency> <groupId>com.alibabagroupId> <artifactId>fastjsonartifactId> <version>1.2.62version>dependency>
@Controllerpublic class IndexController {
@GetMapping({
"/", "/index"}) public String index() {
return "index"; }}
图26 页面展示
爬取数据:获取请求返回的页面信息,筛选出我们想要的数据就可以了
<dependency>
<groupId>org.jsoupgroupId>
<artifactId>jsoupartifactId>
<version>1.10.2version>
dependency>
@Component
public class HtmlParseUtil {
public static List<Content> parseJD(String keywords) throws Exception {
// 获取请求 https://search.jd.com/Search?keyword=java
String url = "https://search.jd.com/Search?keyword=" + keywords;
// 解析网页(Jsoup返回Document就是浏览器Document对象)
Document document = Jsoup.parse(new URL(url), 30000);
// 所有你在js中可以使用的方法这里都能使用
Element element = document.getElementById("J_goodsList");
// 获取所有li元素
Elements elements = element.getElementsByTag("li");
// 获取元素中的内容,这里el,就是每个li标签
ArrayList<Content> goodslist = new ArrayList<>();
for (Element el : elements
) {
String img = el.getElementsByTag("img").eq(0).attr("data-lazy-img"); // 爬取懒加载的图片
String price = el.getElementsByClass("p-price").eq(0).text();
String title = el.getElementsByClass("p-name").eq(0).text();
Content content = new Content(img, price, title);
goodslist.add(content);
}
return goodslist;
}
}
@Service
public class ContentService {
@Autowired
private RestHighLevelClient restHighLevelClient;
public Boolean parseContent(String keywords) throws Exception {
List<Content> contents = new HtmlParseUtil().parseJD(keywords);
// 把查询到的数据批量添加放到es中
BulkRequest bulkRequest = new BulkRequest();
bulkRequest.timeout("2m");
for (int i = 0; i < contents.size(); i++) {
bulkRequest
.add(new IndexRequest("jd_goods")
.source(JSON.toJSONString(contents.get(i)), XContentType.JSON));
}
BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
return !bulk.hasFailures();
}
}
@RestController
public class ContentController {
@Autowired
private ContentService contentService;
@GetMapping("/parse/{keywords}")
public Boolean parse(@PathVariable("keywords") String keywords) throws Exception {
return contentService.parseContent(keywords);
}
}
图27 添加文档成功
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Content {
private String img;
private String price;
private String title;
}
// 获取数据实现搜索功能
public List<Map<String, Object>> searchPage(String keyword, int pageNO, int pagesize) throws Exception {
if (pageNO <= 1) {
pageNO = 1;
}
// 条件搜索
SearchRequest searchRequest = new SearchRequest("jd_goods");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
// 分页
sourceBuilder.from(pageNO);
sourceBuilder.size(pagesize);
// 精确匹配
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title", keyword);
sourceBuilder.query(termQueryBuilder);
sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
// 执行搜索
searchRequest.source(sourceBuilder);
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
// 解析结果
ArrayList<Map<String, Object>> list = new ArrayList<>();
for (SearchHit documentFields : searchResponse.getHits().getHits()
) {
list.add(documentFields.getSourceAsMap());
}
return list;
}
@GetMapping("/search/{keyword}/{pageNO}/{pagesize}")public List<Map<String, Object>> search(@PathVariable("keyword") String keyword, @PathVariable("pageNO") int pageNO, @PathVariable("pagesize") int pagesize) throws Exception {
return contentService.searchPage(keyword, pageNO, pagesize);}
图28 下载vue和js代码
<!--前端使用vue,实现前后端分离-->
<script th:src="@{/js/vue.min.js}"></script>
<script th:src="@{/js/axios.min.js}"></script>
<script>
new Vue({
el: '#app', // 在最大的
data: {
keyword: '', // 搜索关键字
results: [] // 搜索结果
},
methods: {
searchKey() {
var keyword = this.keyword;
console.log(keyword);
//对接后端的接口
axios.get('search/' + keyword + "/1/10").then(response => {
console.log(response);
this.results = response.data; // 绑定数据
})
}
}
})
</script>
- 修改html代码为vue格式
<div class="product" v-for="result in results">
<div class="product-iWrap">
<div class="productImg-wrap">
<a class="productImg">
<img :src="result.img">
a>
div>
<p class="productPrice">
<em> {
{result.price}} em>
p>
<p class="productTitle">
<a> {
{result.title}} a>
p>
<div class="productShop">
<span>店铺: 狂神说Java span>
div>
<p class="productStatus">
<span>月成交<em>999笔em>span>
<span>评价 <a>3a>span>
p>
div>
div>
高亮搜索
修改service层搜索方法
// 获取数据实现高亮搜索
public List<Map<String, Object>> searchLightPage(String keyword, int pageNO, int pagesize) throws Exception {
if (pageNO <= 1) {
pageNO = 1;
}
// 条件搜索
SearchRequest searchRequest = new SearchRequest("jd_goods");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
// 分页
sourceBuilder.from(pageNO);
sourceBuilder.size(pagesize);
// 精确匹配
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title", keyword);
sourceBuilder.query(termQueryBuilder);
sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
// 高亮
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.requireFieldMatch(true); // 多个高亮显示
highlightBuilder.field("title");
highlightBuilder.preTags("");
highlightBuilder.postTags("");
sourceBuilder.highlighter(highlightBuilder);
// 执行搜索
searchRequest.source(sourceBuilder);
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
// 解析结果
ArrayList<Map<String, Object>> list = new ArrayList<>();
for (SearchHit hit : searchResponse.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 n_title = "";
for (Text text:fragments) {
n_title += text;
}
sourceAsMap.put("title",n_title); // 高亮字段替换掉原来的内容即可
}
list.add(sourceAsMap);
}
return list;
}