(一)Elasticsearch 安装(docker方式)
1. 环境准备
# 创建文件
mkdir -p /mydata/elasticsearch/{config,data,plugins}
vi /mydata/elasticsearch/config/elasticsearch.yml
# elasticsearch.yml 内容(7.x)
cluster.name: "es-cluster"
network.host: 0.0.0.0
# 跨域
http.cors.allow-origin: "*"
http.cors.enabled: true
# elasticsearch.yml 内容(8.x)
cluster.name: "docker-cluster"
network.host: 0.0.0.0
# Enable security features
xpack.security.enabled: false // 是true时需要账号和密码登录
xpack.security.enrollment.enabled: true
# Enable encryption for HTTP API client connections, such as Kibana, Logstash, and Agents
xpack.security.http.ssl:
enabled: true
keystore.path: certs/http.p12
# Enable encryption and mutual authentication between cluster nodes
xpack.security.transport.ssl:
enabled: true
verification_mode: certificate
keystore.path: certs/transport.p12
truststore.path: certs/transport.p12
# 修改文件夹权限
chmod -R 777 /mydata/elasticsearch
# 分词器下载
wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.9.0/elasticsearch-analysis-ik-7.9.0.zip
# 解压到plugins目录下面
unzip elasticsearch-analysis-ik-7.9.0.zip -d elasticsearch-analysis-ik
2. 启动容器
# 拉取镜像
docker pull elasticsearch:7.9.0
# 启动容器
docker run --name elasticsearch --restart always -p 9200:9200 -p 9300:9300 \
-e "discovery.type=single-node" -e ES_JAVA_OPTS="-Xms256m -Xmx512m" \
-v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
-v /mydata/elasticsearch/data/:/usr/share/elasticsearch/data \
-v /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
-d elasticsearch:7.9.0
# 访问 http://ip:9200
(二)kibana 安装
# 拉取镜像
docker pull kibana:7.9.0
# 启动 kibana
docker run --name kibana -e ELASTICSEARCH_HOSTS=http://127.0.0.1:9200 -p 5601:5601 -d kibana:7.9.0
# 访问 http://ip:5601
(三)SpringBoot整合Elasticsearch(仿京东搜索)
1、后端
1.1 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.coffee</groupId>
<artifactId>es-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>es-demo</name>
<description>es-demo</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
<jsoup.version>1.15.3</jsoup.version>
<fastjson.version>2.0.14.graal</fastjson.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>${jsoup.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</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>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.7.RELEASE</version>
<configuration>
<mainClass>com.coffee.es.EsDemoApplication</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
1.2 构建实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Content implements Serializable {
private static final long serialVersionUID = -8049497962627482693L;
private String name;
private String price;
private String img;
}
1.3 爬取页面工具类
public class HtmlParseUtil {
public static List<Content> parseJD(String keywords) throws Exception {
// 请求url
String url = "http://search.jd.com/search?keyword=" + keywords;
//1.解析网页(jsoup 解析返回的Document对象 就是 浏览器Document对象)
Document document = Jsoup.parse(new URL(url), 30000);
// 使用document可以使用在js对document的所有操作
// 2.获取元素(通过id) J_goodsList是通过F12审查该页面源代码得到的
Element goodsList = document.getElementById("J_goodsList");
// 3.获取J_goodsList 中的 ul里的 每一个 li 标签
assert goodsList != null;
Elements lis = goodsList.getElementsByTag("li");
List<Content> contents = new ArrayList<>();
// 4.获取li下的 img、price、name 标签
for (Element li : lis) {
String img = li.getElementsByTag("img").eq(0).attr("data-lazy-img");
String name = li.getElementsByClass("p-name").eq(0).text();
String price = li.getElementsByClass("p-price").eq(0).text();
//封装为对象
contents.add(new Content(name, price, img));
}
return contents;
}
}
1.4 创建 RestHighLevelClient
@Configuration
public class ElasticSearchConfig {
@Value("${es.hostname}")
private String hostname;
@Value("${es.port}")
private Integer port;
@Value("${es.scheme}")
private String scheme;
@Bean
public RestHighLevelClient restHighLevelClient() {
return new RestHighLevelClient(
//如果是集群就构建多个
RestClient.builder(new HttpHost(hostname, port, scheme))
);
}
}
1.5 编写 ContentService
@Service
public class ContentService {
@Autowired
private RestHighLevelClient restHighLevelClient;
//把数据放入 es 索引中
public Boolean parseContent(String keywords) throws Exception {
//1、获取内容
List<Content> contents = HtmlParseUtil.parseJD(keywords);
//2、内容放入 es 中 (批量存入到ES中)
BulkRequest bulkRequest = new BulkRequest();
//超时时间,可根据实际业务来设置
bulkRequest.timeout("2m");
for (Content content : contents) {
//jd_goods 为索引库名,所以需要事先创建好
bulkRequest.add(new IndexRequest("jd_goods")
.source(JSON.toJSONString(content), XContentType.JSON)
);
}
BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
//restHighLevelClient.close();
return !bulk.hasFailures();
}
//根据keywords分页查询结果
public List<Map<String, Object>> search(String keywords, Integer pageIndex, Integer pageSize) throws IOException {
if (pageIndex <= 1){
pageIndex = 1;
}
//1、创建搜索请求
SearchRequest jd_goods = new SearchRequest("jd_goods");
//2、创建搜索源建造者对象,来构造查询条件
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//-精确查询 通过keyword查字段name
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", keywords);
searchSourceBuilder.query(termQueryBuilder);
searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
//分页
searchSourceBuilder.from(pageIndex);
searchSourceBuilder.size(pageSize);
//3、搜索条件放入搜索请求中
jd_goods.source(searchSourceBuilder);
//4、执行查询,返回结果
SearchResponse searchResponse = restHighLevelClient.search(jd_goods, RequestOptions.DEFAULT);
//restHighLevelClient.close();
//5、解析结果
SearchHits hits = searchResponse.getHits();
List<Map<String,Object>> results = new ArrayList<>();
for (SearchHit documentFields : hits.getHits()) {
Map<String, Object> sourceAsMap = documentFields.getSourceAsMap();
results.add(sourceAsMap);
}
// 返回查询的结果
return results;
}
//实现搜索高亮功能
public List<Map<String, Object>> searchHighlight(String keywords, Integer pageIndex, Integer pageSize) throws IOException {
if (pageIndex <= 0){
pageIndex = 0;
}
//1、创建搜索请求
SearchRequest jd_goods = new SearchRequest("jd_goods");
//2、创建搜索源建造者对象,来构造查询条件
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//-精确查询 通过keyword查字段name
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", keywords);
searchSourceBuilder.query(termQueryBuilder);
searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
//-分页
searchSourceBuilder.from(pageIndex);
searchSourceBuilder.size(pageSize);
//-高亮
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("name");
// 多个高亮显示
highlightBuilder.requireFieldMatch(false);
highlightBuilder.preTags("<span style='color:red'>");
highlightBuilder.postTags("</span>");
searchSourceBuilder.highlighter(highlightBuilder);
//3、搜索条件放入搜索请求中
jd_goods.source(searchSourceBuilder);
//4、执行查询,返回结果
SearchResponse searchResponse = restHighLevelClient.search(jd_goods, RequestOptions.DEFAULT);
//restHighLevelClient.close();
//5、解析结果
SearchHits hits = searchResponse.getHits();
List<Map<String,Object>> results = new ArrayList<>();
for (SearchHit documentFields : hits.getHits()) {
Map<String, Object> sourceAsMap = documentFields.getSourceAsMap();
//获取高亮字段
Map<String, HighlightField> highlightFields = documentFields.getHighlightFields();
HighlightField name = highlightFields.get("name");
// 替换
if (name != null){
Text[] fragments = name.fragments();
StringBuilder newName = new StringBuilder();
for (Text text : fragments) {
newName.append(text);
}
sourceAsMap.put("name", newName);
}
results.add(sourceAsMap);
}
// 返回查询的结果
return results;
}
}
1.6 编写ContentController
@RestController
public class ContentController {
@Autowired
private ContentService contentService;
//把数据放入 es 索引中
@GetMapping("/parse/{keywords}")
public Boolean parse(@PathVariable("keywords") String keywords) throws Exception {
return contentService.parseContent(keywords);
}
//查询
@GetMapping("/search/{keywords}/{pageIndex}/{pageSize}")
public List<Map<String, Object>> parse(@PathVariable("keywords") String keywords,
@PathVariable("pageIndex") Integer pageIndex,
@PathVariable("pageSize") Integer pageSize) throws IOException {
return contentService.searchHighlight(keywords,pageIndex,pageSize);
}
}
1.7 主启动类
@SpringBootApplication
public class EsJdApplication {
public static void main(String[] args) {
SpringApplication.run(EsJdApplication.class, args);
}
}
1.8 application.properties
spring.application.name=es-demo
server.port=9090
spring.thymeleaf.cache=false
es.hostname=127.0.0.1
es.port=9200
es.scheme=http
2、前端
2.1 引入css、js等文件
在resources/static目录下分别创建css、js、images文件夹并引入对应的文件,源文件可通过获取
2.2 编写 index.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
<meta charset="utf-8"/>
<title>ES仿京东实战</title>
<link rel="stylesheet" th:href="@{/css/style.css}"/>
</head>
<body class="pg">
<div class="page" id="app">
<div id="mallPage" class=" mallist tmall- page-not-market ">
<!-- 头部搜索 -->
<div id="header" class=" header-list-app">
<div class="headerLayout">
<div class="headerCon ">
<!-- Logo-->
<h1 id="mallLogo">
<img th:src="@{/images/jdlogo.png}">
</h1>
<div class="header-extra">
<!--搜索-->
<div id="mallSearch" class="mall-search">
<form name="searchTop" class="mallSearch-form clearfix">
<fieldset>
<legend>天猫搜索</legend>
<div class="mallSearch-input clearfix">
<div class="s-combobox" id="s-combobox-685">
<div class="s-combobox-input-wrap">
<input v-model="keyword" type="text" autocomplete="off" value="dd"
id="mq"
class="s-combobox-input" aria-haspopup="true">
</div>
</div>
<button type="submit" @click.prevent="searchKey" id="searchbtn">搜索</button>
</div>
</fieldset>
</form>
<ul class="relKeyTop">
<li><a>Java</a></li>
<li><a>前端</a></li>
<li><a>Linux</a></li>
<li><a>大数据</a></li>
<li><a>GO</a></li>
</ul>
</div>
</div>
</div>
</div>
</div>
<!-- 商品详情页面 -->
<div id="content">
<div class="main">
<!-- 品牌分类 -->
<form class="navAttrsForm">
<div class="attrs j_NavAttrs" style="display:block">
<div class="brandAttr j_nav_brand">
<div class="j_Brand attr">
<div class="attrKey">
品牌
</div>
<div class="attrValues">
<ul class="av-collapse row-2">
<li><a href="#"> Vue </a></li>
<li><a href="#"> Java </a></li>
</ul>
</div>
</div>
</div>
</div>
</form>
<!-- 排序规则 -->
<div class="filter clearfix">
<a class="fSort fSort-cur">综合<i class="f-ico-arrow-d"></i></a>
<a class="fSort">人气<i class="f-ico-arrow-d"></i></a>
<a class="fSort">新品<i class="f-ico-arrow-d"></i></a>
<a class="fSort">销量<i class="f-ico-arrow-d"></i></a>
<a class="fSort">价格<i class="f-ico-triangle-mt"></i><i class="f-ico-triangle-mb"></i></a>
</div>
<!-- 商品详情 -->
<div class="view grid-nosku">
<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><b>¥</b> {{result.price}} </em>
</p>
<!--标题-->
<p class="productTitle">
<a v-html="result.name"></a>
</p>
<!-- 店铺名 -->
<div class="productShop">
<span>店铺: 蜡笔小新 </span>
</div>
<!-- 成交信息 -->
<p class="productStatus">
<span>月成交<em>999笔</em></span>
<span>评价 <a>3</a></span>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!--前端使用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/20').then(response => {
console.log(response.data);
//绑定数据
this.results = response.data;
})
}
}
});
</script>
</body>
</html>
3、效果演示
3.1 准备数据
访问 http://localhost:9090/parse/{keywords} 将数据存储到elasticsearch中!!
3.2 首页
访问 http://localhost:9090/ 即可看到首页,具体如下图:
3.3 搜索
搜索栏中输入 java,即可以看到搜索结果显示,具体如下图:
(四)Elasticsearch优化
- 硬件优化:Elasticsearch会重度使用磁盘,磁盘处理的吞吐量越大,节点越稳定,使用SSD
- 分片策略:合理设置分片数目、适当延长分片再均衡时间
- 路由选择:文档存储于分片中时会通过路由计算来决定最终数据存储于哪个分片上,所以使用带routing查询
- 写入速度优化:加大Translog Flush、增加Index Refresh间隔、调整Bulk线程池和队列
- 内存设置:不要超过物理内存的 50%,堆内存的大小最好不要超过 32GB(-Xms31g -Xmx31g)