Elasticsearch,简称 ES,是一个开源的高扩展的分布式全文检索引擎,它可以近乎实时的存储、检索数据。ES也使用Java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的 RESTful API 来隐藏Lucene的复杂性,从而让全文搜索变得简单。
# ES 原理:
Elasticsearch 的实现原理主要分为以下几个步骤,首先用户将数据提交到Elasticsearch 数据库中,再通过分词控制器去将对应的语句分词,将其权重和分词结果一并存入数据,当用户搜索数据时候,再根据权重将结果排名,打分,再将返回结果呈现给用户。
# 场景比较:
1. 当单纯的对已有数据进行搜索时,Solr更快
2. 当实时建立索引时,Solr会产生io阻塞,查询性能较差,ES具有明显的优势。
3. 随着数据量的增加,Solr的搜索效率会变得更低,而ES缺没有明显的变化。
# 总结:
1. ES基本时开箱即用(解压就可用),Solr安装复杂。
2. Solr利用Zookeeper进行分布式管理,而ES自身带有分布式协调管理功能。
3. Solr支持更多格式的数据,比如JSON/XML/CSV,而ES仅支持JSON文件格式。
4. Solr官方提供的功能更多,而ES本身更注重于核心功能,高级功能多由第三方插件提供。
5. Solr查询快,但更新索引时慢(即插入删除慢),用于电商等查询多的应用;
ES建立索引快(即查询快),即实时性查询快,用于facebook/新浪等搜索。
Solr是传统搜索应用的有力解决方案,但ES更适用于新兴的实时搜索应用。
6. Solr比较成熟,社区活跃度高,而ES相对低,学习成本高。
ES客户端工具,界面工具!JDK1.8最低要求
# ES安装步骤:
1. 解压安装包
2. 双击bin目录下的 elasticsearch.bat 文件
3. 默认访问路径 http://127.0.0.1:9200
# 安装可视化界面:elasticsearch-head
1. 在github拉前端代码:https://github.com/mobz/elasticsearch-head
2. git clone 代码地址
3. cd elasticsearch-head 打开命令行
4. npm install
5. npm run start
6. open http://localhost:9100/ 访问测试
# 访问测试 9200 出现跨域问题:
需要在ES config目录下 elasticsearch.yml 加入跨域的配置:
# 开启跨域
http.cors.enabled: true
# 允许所有人访问
http.cors.allow-origin: "*"
# head 相当于一个数据展示工具,所有的查询可以Kibana做
# Kibana 安装:
1. 解压安装包
2. 开启Kibana前要先开启 ES
3. 测试访问 http://localhost:5601/
4. 如果汉化则需要在 yml 文件修改配置:
i18n.locale: "zh-CN"
# ES 是面向文档的
# 关系型数据库和ES客观的对比 如下表格:
# ES Restful API GET、POST、PUT、DELETE、HEAD含义:
1. GET:获取请求对象的当前状态。
2. POST:改变对象的当前状态。
3. PUT:创建一个对象。
4. DELETE:销毁对象。
5. HEAD:请求获取对象的基础信息。
Relational DB | Elasticsearch |
---|---|
数据库(database) | 索引(indices) |
表(tables) | types(慢慢会被弃用) |
行(rows) | documents |
字段(columns) | fields |
# 分词:
即把一段中文或其它划分成一个个关键字,因为默认的中文分词是将每个字看成一个词,所以需要安装IK分词器解决这个问题。
# IK 提供了两个分词算法:
ik_smart: 最少切分。
ik_max_word: 最细粒度划分。
# IK 分词器安装:
1. github上下载与 ES 相同版本的 IK
2. 下载完毕后解压到 ES plugins 目录下
3. 重启 ES,可以看到 IK分词器被加载!
# 在 控制台 进行测试:
# 1. 最少切分:ik_smart
GET _analyze
{
"analyzer": "ik_smart",
"text": "打工人的坚持"
}
# 2. 最细粒度划分:ik_max_word
GET _analyze
{
"analyzer": "ik_max_word",
"text": "打工人的坚持"
}
#### 自定义分词 自定义分词内容:
# 比如:唐、宇、翔 合并为 唐宇翔
1. 在 ES plugins 里找到 IK 分词器的config目录,找到 IKAnalyzer.cfg.xml 配置文件,在同级目录下创建自定义的 .dic文件,然后注入到配置文件中
<entry key="ext_dict">自定义.dic</entry>
########## (也可在postman发送url请求)
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 |
POST | localhost:9200/索引名/类型名/_search | 查询所有数据 |
# 创建一个索引:
PUT /索引名/类型名/文档id
{请求体}
PUT /test1/type1/1
{
"name":"唐宇翔",
"text":"很强"
}
# 指定字段类型:(创建规则)
PUT /test2
{
"mappings": {
"properties": {
"name":{
"type": "text"
},
"age":{
"type": "long"
},
"birthday":{
"type": "date"
}
}
}
}
# 获取相关信息:(索引)
GET /test2
# 如果没有指定字段类型,那么ES会自动给类型。
# 使用 GET _cat/... 可以看到其它配置信息
# 更新数据的方法:
1. 直接重新 PUT 数据(修改需要改变的数据)
2. POST .../_update 数据
POST /test3/_doc/1/_update
{
"doc":{
"name":"法外狂徒张三"
}
}
# 删除方法:
# DELETE /....
DELETE /test2
#### 基本操作
# 注意点:
如果没有使用 _update 修改的方式,那么没有被修改的数据将会置为空。
PUT /tang/user/1
{
"name":"大佬唐",
"age":13,
"desc":"不在沉默中死亡,就在沉默中爆发",
"tags":["大佬","自律","衣品好"]
}
# 查询 :(单字段多条件查询)
使用空格隔开 可以多条件查询
GET tang/user/_search?q=name:唐
GET tang/user/_search
{
"query":{
"match": {
"name": "3 唐"
}
}
}
#### 复杂查询操作
# hits:
包含:索引和文档的信息,查询的结果总数,查询出来的具体的文档
# 可以指定查询字段信息: _source
"_source":["name","desc"]
GET tang/user/_search
{
"query":{
"match": {
"name": "唐"
}
},
"_source":["name","desc"]
}
# 排序: sort
GET tang/user/_search
{
"query":{
"match": {
"name": "唐"
}
},
"sort":[
{
"age":"desc"
}
]
}
# 分页: from(页码) size(大小)
GET tang/user/_search
{
"query":{
"match": {
"name": "唐"
}
},
"sort":[
{
"age":"desc"
}
],
"from":0,
"size":1
}
# 多条件查询(多字段查询):
#(must 相当于 and)(should 相当于 or)
#(must_not 相当于 not)
GET tang/user/_search
{
"query":{
"bool": {
"must": [
{
"match": {
"name": "唐"
}
},
{
"match": {
"age": 23
}
}
]
}
}
}
# 过滤器:
# filter: gt大于 lt小于 gte 大于等于 lte小于等于
GET tang/user/_search
{
"query":{
"bool": {
"must": [
{
"match": {
"name": "唐"
}
}
],
"filter": [
{
"range": {
"age": {
"gt": 15
}
}
}
]
}
}
}
# 精确查询:
1. term,会直接查询精确
2. match,会使用分词器解析后查询
3. keyword类型字段 不会被分词器解析
GET tang/user/_search
{
"query":{
"term": {
"name": "大"
}
}
}
# 高亮查询: (显示红色关键字)(highlight)
pre_tags: 前置高量条件
post_tags: 后置高量条件
GET tang/user/_search
{
"query":{
"match": {
"name": "大"
}
},
"highlight":{
"pre_tags": ""
,
"post_tags": "",
"fields": {
"name":{}
}
}
}
# 高量结果:
"highlight" : {
"name" : [
"大
佬唐1"
]
}
# SpringBoot集成ES:
1. idea创建项目可以选中 ES 依赖(sb内含)
2. 需要在pom文件中指定当前本地使用的ES版本
<properties>
<java.version>1.8</java.version>
<!-- 自定义本地ES版本 -->
<elasticsearch.version>7.13.0</elasticsearch.version>
</properties>
3. 需要引入Bean
@Configuration
public class ElasticSearchClientConfig {
@Bean
public RestHighLevelClient restHighLevelClient(){
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("127.0.0.1", 9200, "http")));
return client;
}
}
# 索引的创建/查询是否存在/删除
@SpringBootTest
class ElasticsearchApplicationTests {
@Autowired
@Qualifier("restHighLevelClient")// 一个接口被多个类实现,可以使用这个注解区分(Qualifier/Resource)
private RestHighLevelClient restHighLevelClient;
@Test
// # 创建索引
void testCreateIndex() throws IOException {
// 1. 创建索引请求
CreateIndexRequest request = new CreateIndexRequest("tang_index2");
// 2. 客户端执行请求 indicesClient 并获得响应
CreateIndexResponse response =
restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
System.out.println(response);
}
@Test
// # 测试判断索引是否存在
void testExistsIndex() throws IOException {
GetIndexRequest request = new GetIndexRequest("tang_index2");
boolean exists =
restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
System.out.println(exists);
}
@Test
// # 测试删除索引
void testDeleteIndex() throws IOException {
DeleteIndexRequest request = new DeleteIndexRequest("tang_index2");
AcknowledgedResponse acknowledgedResponse =
restHighLevelClient.indices().delete(request, RequestOptions.DEFAULT);
System.out.println(acknowledgedResponse.isAcknowledged());
}
}
# 测试 添加/查询/获取/更新/删除 文档信息
@Test
// # 测试添加文档
void testAddDocument() throws IOException {
// 创建对象
User user = new User("唐宇翔", 3, "18674740387");
// 创建请求
IndexRequest request = new IndexRequest("tang_index");
// 规则 put /tang_index/_doc/1
request.id("2");
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 testIsExists() throws IOException {
GetRequest getRequest = new GetRequest("tang_index", "3");
// 不获取返回的 _source 的上下文了
getRequest.fetchSourceContext(new FetchSourceContext(false));
getRequest.storedFields("_none_");
boolean exists = restHighLevelClient.exists(getRequest, RequestOptions.DEFAULT);
System.out.println(exists);
}
@Test
// # 获取文档信息
void testGetDocument() throws IOException {
GetRequest getRequest = new GetRequest("tang_index", "1");
GetResponse getResponse = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT);
System.out.println(getResponse.getSourceAsString());
System.out.println(getResponse);
}
@Test
// # 更新文档信息
void testUpdateRequest() throws IOException {
UpdateRequest updateRequest = new UpdateRequest("tang_index", "1");
updateRequest.timeout("1s");
User user = new User("唐", 33, "222");
updateRequest.doc(JSON.toJSONString(user), XContentType.JSON);
UpdateResponse updateResponse = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
System.out.println(updateResponse.status());
}
@Test
// # 删除文档信息
void testDeleteRequest() throws IOException {
DeleteRequest deleteRequest = new DeleteRequest("tang_index", "1");
deleteRequest.timeout("1s");
DeleteResponse deleteResponse = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
System.out.println(deleteResponse.status());
}
# 测试批量插入文档信息
@Test
// # 批量插入文档信息
void testBulkRequest() throws IOException {
BulkRequest bulkRequest = new BulkRequest();
bulkRequest.timeout("10s");
ArrayList<User> arrayList = new ArrayList<>();
arrayList.add(new User("t唐宇翔1", 4, "123"));
arrayList.add(new User("t唐宇翔2", 4, "123"));
arrayList.add(new User("t唐宇翔3", 4, "123"));
arrayList.add(new User("t唐宇翔4", 4, "123"));
arrayList.add(new User("t唐宇翔5", 4, "123"));
arrayList.add(new User("t唐宇翔6", 4, "123"));
// 批处理请求
for (int i = 0; i < arrayList.size(); i++) {
// 批量 插入/更新/删除, 在这里进行操作
bulkRequest.add(
new IndexRequest("tang_index")
.id("" + (i + 1))
.source(JSON.toJSONString(arrayList.get(i)), XContentType.JSON));
}
BulkResponse bulkResponse = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
System.out.println(bulkResponse.hasFailures());
}
# 测试搜索
@Test
// # 测试搜素
void testSearch() throws IOException {
SearchRequest searchRequest = new SearchRequest("tang_index");
// 构建搜索条件
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 查询条件,可以使用 QueryBuilders 工具来实现
// QueryBuilders.termQuery 精确
// QueryBuilders.matchAllQuery 匹配所有
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", "唐");
searchSourceBuilder.query(termQueryBuilder);
searchSourceBuilder.timeout(new TimeValue(30, TimeUnit.SECONDS));
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
System.out.println(JSON.toJSONString(searchResponse.getHits()));
System.out.println("===============================================");
for (SearchHit hit : searchResponse.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
####### 实战项目地址:D:\Idea\Private\elasticSearchApi
# 1. 新建项目,建立前端资源
资源在图片路径下拿取
# 2. 爬虫爬取数据
public List<Content> parseJD(String keywords) throws IOException {
// 获取请求
String url = "https://search.jd.com/Search?keyword=" + keywords;
// 解析网页(Jsonp 返回的 Document就是浏览器 Document对象)
Document document = Jsoup.parse(new URL(url), 30000);
Element element = document.getElementById("J_goodsList");
// 获取所有的 li 元素
Elements elements = element.getElementsByTag("li");
// 存储容器
ArrayList<Content> goodList = new ArrayList<>();
// 遍历 li 便签
for (Element el : elements) {
// 网站数据特别多的情况下,图片都是延迟加载: data-lazy-img
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();
content.setTitle(title);
content.setImg(img);
content.setPrice(price);
goodList.add(content);
}
return goodList;
}
# 3. 解析数据放入 ES 索引中
public Boolean parseContent(String keywords) throws IOException {
// 爬虫爬取数据
HtmlParseUtil htmlParseUtil = new HtmlParseUtil();
List<Content> contents = htmlParseUtil.parseJD(keywords);
BulkRequest bulkRequest = new BulkRequest();
bulkRequest.timeout("10s");
// 批处理请求
for (int i = 0; i < contents.size(); i++) {
// 批量 插入/更新/删除, 在这里进行操作
bulkRequest.add(
new IndexRequest("jd_goods")
// .id("" + (i + 1))
.source(JSON.toJSONString(contents.get(i)), XContentType.JSON));
}
BulkResponse bulkResponse = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
return !bulkResponse.hasFailures();
}
# 4. 获取数据实现搜索功能
public List<Map<String, Object>> searchPage(String keyword, int pageNo, int pageSize) throws IOException {
if(pageNo <= 1) pageNo = 1;
// 条件搜索
SearchRequest searchRequest = new SearchRequest("jd_goods");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 分页
searchSourceBuilder.from(pageNo);
searchSourceBuilder.size(pageSize);
// 精准匹配
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title", keyword);
searchSourceBuilder.query(termQueryBuilder);
searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
// 搜索高量
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("title");
highlightBuilder.requireFieldMatch(false);// 多个高量显示
highlightBuilder.preTags("");
highlightBuilder.postTags("");
searchSourceBuilder.highlighter(highlightBuilder);
// 执行搜索
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
// 解析结果
ArrayList<Map<String, Object>> list = new ArrayList<>();
for (SearchHit hit : searchResponse.getHits().getHits()) {
Map<String, HighlightField> highlightFieldMap = hit.getHighlightFields();
HighlightField title = highlightFieldMap.get("title");
Map<String, Object> sourceAsMap= hit.getSourceAsMap();// 原来的结果
if(title != null){// 解析高量的字段
Text[] fragments = title.fragments();
String n_title = "";
for (Text fragment : fragments) {
n_title += fragment;
}
// 高量字段替换原来的内容
sourceAsMap.put("title", n_title);
}
list.add(hit.getSourceAsMap());
}
return list;
}