ElasticSearch

ES概述:
	Elasticsearch,简称 ES,是一个开源的高扩展的分布式全文检索引擎,它可以近乎实时的存储、检索数据。ES也使用Java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的 RESTful API 来隐藏Lucene的复杂性,从而让全文搜索变得简单。
	
# ES 原理:
	Elasticsearch 的实现原理主要分为以下几个步骤,首先用户将数据提交到Elasticsearch 数据库中,再通过分词控制器去将对应的语句分词,将其权重和分词结果一并存入数据,当用户搜索数据时候,再根据权重将结果排名,打分,再将返回结果呈现给用户。
	
ES和solr的选型:
# 场景比较:
	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安装:
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客观的对比 如下表格: 

# 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 提供了两个分词算法:
	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>

Rest风格说明:

########## (也可在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:
# 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;
    }
}

ES文档API操作:
# 索引的创建/查询是否存在/删除

@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;
    }

你可能感兴趣的:(ES,elasticsearch,数据库)