Elaticsearch,简称为es, es是一个开源的高扩展的分布式全文检索引擎,它可以近乎实时的存储、检索数据;本 身扩展性很好,可以扩展到上百台服务器,处理PB级别的数据。es也使用Java开发并使用Lucene作为其核心来实 现所有索引和搜索的功能,但是它的目的是通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得 简单。

ElasticSearch-上

Elaticsearch,简称为es, es是一个开源的高扩展的分布式全文检索引擎,它可以近乎实时的存储、检索数据;本 身扩展性很好,可以扩展到上百台服务器,处理PB级别的数据。es也使用Java开发并使用Lucene作为其核心来实 现所有索引和搜索的功能,但是它的目的是通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得 简单。

1. 使用ES

  1. 官网 https://www.elastic.co/products/elasticsearch

  2. 解压ES压缩包

    1
    2
    3
    4
    5
    6
    7
    bin		# 可执行二进制文件
    config # 配置信息目录
    lib # jar包存放目录
    logs # 日志存放目录
    modules # 模块存放目录
    plugins # 插件安装目录
    .....
  3. 启动ES访问

    Windows:elasticsearch.bat

    Linux:elasticsearch.sh

  4. 启动ES之后

    9300端口:es的tcp通讯端口,集群间和TCPClient都执行该端口。

    9200端口:http协议的RESTful接口。

    打开ES之后,在浏览器中访问127.0.0.1:9200,出现下面结果即表示启动成功。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    {
    "name": "4R2QZyN",
    "cluster_name": "elasticsearch",
    "cluster_uuid": "YQsfD7T8TgmT4tQf2RkEhQ",
    "version": {
    "number": "5.6.8",
    "build_hash": "688ecce",
    "build_date": "2018-02-16T16:46:30.010Z",
    "build_snapshot": false,
    "lucene_version": "6.6.1"
    },
    "tagline": "You Know, for Search"
    }

2. 使用ES可视化插件

  1. 下载head插件

    https://github.com/mobz/elasticsearch-head

  2. 解压缩软件

  3. 下载安装nodejs

    https://nodejs.org/zh-cn/

  4. 使用npm安装工具

    1
    2
    3
    4
    5
    # 安装grunt
    npm install -g grunt-cli
    # 在解压后的header根目录执行下面命令,启动head
    npm install
    grunt server
  5. 访问测试

    浏览器中输入:http://localhost:9100测试

  6. 配置es的config文件下yml配置文件配置跨域

    1
    2
    http.cors.enabled: true
    http.cors.allow-origin: "*"

    重启ES

3. ES中的术语

  1. 概述

    ES是面向文档(Document)的,可以存储整个对象或者文档。然而他不仅存储,还可以索引每个文档的内容,使之可以被搜索。在ES中,你可以对文档进行索引、搜索、排序、过滤。

    1
    2
    3
    # ES对比传统关系型数据库
    关系型数据库 -->Databases --->Tables --->Rows --->Colums
    ES -->Indices --->Types --->Documents --->Fields
  2. ES核心概念

    索引Index

    ​ 一个索引就是一个拥有几分相似特征的文档的集合。比如说,你可以有一个客户数据的索引,另一个产品目录的索 引,还有一个订单数据的索引。一个索引由一个名字来标识(必须全部是小写字母的),并且当我们要对对应于这 个索引中的文档进行索引、搜索、更新和删除的时候,都要使用到这个名字。在一个集群中,可以定义任意多的索 引。

    类型Type

    ​ 在一个索引中,你可以定义一种或多种类型。一个类型是你的索引的一个逻辑上的分类/分区,其语义完全由你来 定。通常,会为具有一组共同字段的文档定义一个类型。比如说,我们假设你运营一个博客平台并且将你所有的数 据存储到一个索引中。在这个索引中,你可以为用户数据定义一个类型,为博客数据定义另一个类型,当然,也可 以为评论数据定义另一个类型。

    字段Field

    ​ 相当于是数据表的字段,对文档数据根据不同属性进行的分类标识

    映射mapping

    ​ mapping是处理数据的方式和规则方面做一些限制,如某个字段的数据类型、默认值、分析器、是否被索引等等, 这些都是映射里面可以设置的,其它就是处理es里面数据的一些使用规则设置也叫做映射,按着最优规则处理数据 对性能提高很大,因此才需要建立映射,并且需要思考如何建立映射才能对性能更好。

    文档document

    ​ 一个文档是一个可被索引的基础信息单元。比如,你可以拥有某一个客户的文档,某一个产品的一个文档,当然, 也可以拥有某个订单的一个文档。文档以JSON(Javascript Object Notation)格式来表示,而JSON是一个到处存 在的互联网数据交互格式。
    ​ 在一个index/type里面,你可以存储任意多的文档。注意,尽管一个文档,物理上存在于一个索引之中,文档必须 被索引/赋予一个索引的type。

    接近实时NRT

    ​ Elasticsearch是一个接近实时的搜索平台。这意味着,从索引一个文档直到这个文档能够被搜索到有一个轻微的延 迟(通常是1秒以内)

    集群cluster

    ​ 一个集群就是由一个或多个节点组织在一起,它们共同持有整个的数据,并一起提供索引和搜索功能。一个集群由 一个唯一的名字标识,这个名字默认就是“elasticsearch”。这个名字是重要的,因为一个节点只能通过指定某个集 群的名字,来加入这个集群

    节点node

    ​ 一个节点是集群中的一个服务器,作为集群的一部分,它存储数据,参与集群的索引和搜索功能。

    分片和复制 shards&replicas

    ​ 一个索引可以存储超出单个结点硬件限制的大量数据。比如,一个具有10亿文档的索引占据1TB的磁盘空间,而任 一节点都没有这样大的磁盘空间;或者单个节点处理搜索请求,响应太慢。为了解决这个问题,Elasticsearch提供 了将索引划分成多份的能力,这些份就叫做分片。当你创建一个索引的时候,你可以指定你想要的分片的数量。每 个分片本身也是一个功能完善并且独立的“索引”,这个“索引”可以被放置到集群中的任何节点上。分片很重要,主 要有两方面的原因: 1)允许你水平分割/扩展你的内容容量。 2)允许你在分片(潜在地,位于多个节点上)之上 进行分布式的、并行的操作,进而提高性能/吞吐量。

    ​ 在一个网络/云的环境里,失败随时都可能发生,在某个分片/节点不知怎么的就处于离线状态,或者由于任何原因 消失了,这种情况下,有一个故障转移机制是非常有用并且是强烈推荐的。为此目的,Elasticsearch允许你创建分 片的一份或多份拷贝,这些拷贝叫做复制分片,或者直接叫复制。

4. ES的客户端操作

操作的三种方式【在postman下测试】

es-head插件

es提供的RESTful接口直接访问

ES提供的API访问

  1. 下载、注册并使用Postman

    https://www.postman.com/downloads/

  2. 创建索引index和映射mapping【同时】

    请求url

    1
    post	http://127.0.0.1:9200/index1   -- 可近似认为是数据库名

    body

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    {	
    "mappings":{ -- mapping关键字
    "article":{ -- 可近似为数据库表名
    "properties":{ -- properties关键字
    "id":{ -- 可近似数据库类名
    "type":"long",
    "store": true,
    "index":"not_analyzed"
    },
    "title":{
    "type":"text",
    "store": true,
    "index": "analyzed",
    "analyzer": "standard"
    },
    "content":{
    "type":"text",
    "store":true,
    "index":"analyzed",
    "analyzer":"standard"
    }
    }
    }
    }
    }
  1. 先创建index再创建mapping

    先创建index,不带body

    1
    put http://127.0.0.1:9200/index1

    创建mapping

    请求url

    1
    put http://127.0.0.1:9200/index1/_mapping

    body

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    {
    "tb1":{
    "properties":{
    "id":{
    "type":"long",
    "store":"true"
    },
    "title":{
    "type":"text",
    "store":true,
    "index":true,
    "analyzer":"standard"
    },
    "content":{
    "type":"text",
    "store":true,
    "index":true,
    "analyzer:"standard"
    }
    }
    }
    }
  2. 删除index

    请求url

    1
    delete http://127.0.0.1:9200/index1  -- 删除索引index1
  1. 创建文档document

    请求url

    1
    post http://127.0.01:9200/index1/tb1/1  --近似给数据库表中插入数据

    body

    1
    2
    3
    4
    5
    {
    "id":1,
    "title":"震惊,LOL",
    "content":"LoL竟然有更新,没错,就是标题党,你来打我啊"
    }
  1. 修改文档document

    请求url

    1
    post http://127.0.01:9200/index1/tb1/1  -- 修改id为1的记录

    body

    1
    2
    3
    4
    5
    {
    "id":1,
    "title":"指定修改的标题",
    "content":"指定修改的内容"
    }
  1. 删除文档document

    请求url

    1
    delete http://127.0.01:9200/index1/tb1/1   -- 删除记录1
  1. 查询文档-根据id查询

    请求url

    1
    get http://127.0.01:9200/index1/tb1/1 
  1. 查询文档-querystring查询

    请求url

    1
    post http://127.0.01:9200/index1/tb1/_search

    body

    1
    2
    3
    4
    5
    6
    7
    8
    {
    "query":{
    "query_string":{
    "defalut_field":"title",
    "query":"英"
    }
    }
    }
  1. 查询文档-term查询

    请求url

    1
    post http://127.0.01:9200/index1/tb1/_search

    body

    1
    2
    3
    4
    5
    6
    7
    {
    "query":{
    "term":{
    "title":"震"
    }
    }
    }
  1. 总结

    对ES操作可以近似为对数据库的操作。RESTful的风格请求可以近似SQL语句。

    索引的操作—–近似—->对数据库的操作

    映射的操作—–近似—->对数据库的操作

    文档操作—–近似—->对数据库记录的操作

5. 使用中文分词器

ES默认的分词器是不支持中文分词的,如果想给中文分词要是用IKAnalyzer分词器进行分词。

  1. 下载IKAnalyzer

    https://github.com/medcl/elasticsearch-analysis-ik/

  2. 解压并安装

    将解压之后的elasticsearch文件夹放在ES安装目录下的plugins,重启服务。

  3. 测试中文分词器

    请求url

    1
    GET localhost:9200/_analyze?analyzer=ik_smart&pretty=true&text=广东省位于中国东南部

    如果结果出现广东等词语则表明使用中文分词器成功。

6. ES集群

ES集群是一个P2P类型的分布式系统,除了集群状态管理以外,其他所有请求都可以发送到集群内的任意一台节点上,这个节点可以找到需要转发给哪些节点,并且直接跟这些节点通信。

  1. 集群的相关概念

    集群cluster:一个集群由一个或者多个节点组织在一起,它们共同持有整个的数据,并一起提供索引和搜索功能。一个集群由一个唯一的名字标识,这个名字默认就是“elasticsearch”。这个名字是重要的,因为一个节点只能通过指定某个集群的名字,来加入这个集群

    节点node:一个节点是集群中的一个服务器,作为集群的一部分,它存储数据,参与集群的索引和搜索功能。和集群类似,一个节点也是由一个名字来标识的,默认情况下,这个名字是一个随机的漫威漫画角色的名字,这个名字会在启动的时候赋予节点

    分片和复制:Lucene中有介绍

  2. 搭建集群

    【1】创建文件夹、复制三份es服务。

    ​ 复制时要确保已经把索引库删除完毕。

    【2】给每台服务配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # 集群名称,保证唯一
    cluster.name: my-elasticsearch
    # 节点名称,必须不一样
    node.name: node-1
    # 所在主机的IP地址
    network.host: 127.0.0.1
    # 服务端口号,同一机器下是,必须不一样
    http.port: 9200/9201/9202
    # 集群间通信端口,同一机器必须不一样
    transport.tcp.port: 9300/9301/9302
    # 设置集群发现机器ip集合
    discovery.zen.ping.unicast.hosts: ["127.0.0.1:9300","127.0.0.1:9301","127.0.0.1:9302"]

    【3】启动每台服务

    【4】使用postman添加索引

    url

    1
    PUT		localhost:9200/blog1

    body

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    {
    "mappings": {
    "article": {
    "properties": {
    "id": {
    "type": "long",
    "store": true,
    "index":"not_analyzed"
    },
    "title": {
    "type": "text",
    "store": true,
    "index":"analyzed",
    "analyzer":"standard"
    },
    "content": {
    "type": "text",
    "store": true,
    "index":"analyzed",
    "analyzer":"standard"
    }
    }
    }
    }
    }

    【5】使用header查看效果

7. Java操作ES

  1. 依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    <dependencies>
    <dependency>
    <groupId>org.elasticsearch</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>5.6.8</version>
    </dependency>
    <dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>transport</artifactId>
    <version>5.6.8</version>
    </dependency>
    <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-to-slf4j</artifactId>
    <version>2.9.1</version>
    </dependency>
    <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.24</version>
    </dependency>
    <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>1.7.21</version>
    </dependency>
    <dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.12</version>
    </dependency>
    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    </dependency>
    </dependencies>
  2. 创建索引

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    /**
    * 创建索引
    * @throws UnknownHostException
    */
    @Test
    public void testCreateIndex() throws UnknownHostException {
    //创建Client对象
    Settings settings = Settings.builder()
    .put("cluster.name","my-elasticsearch").build();
    TransportClient client = new PreBuiltTransportClient(settings)
    .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("127.0.0.1"),9300));
    //创建名称为index1的索引
    client.admin().indices().prepareCreate("index2").get();
    //释放资源
    client.close();
    }
  1. 创建映射

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    @Test
    public void testCreateMapping() throws IOException, ExecutionException, InterruptedException {
    XContentBuilder builder = XContentFactory.jsonBuilder()
    .startObject()
    .startObject("article")
    .startObject("properties")
    .startObject("id")
    .field("type","long").field("store","yes")
    .endObject()
    .startObject("title")
    .field("type","string").field("store","yes").field("analyzer","ik_smart")
    .endObject()
    .startObject("content")
    .field("type","string").field("store","yes").field("analyzer","ik_smart")
    .endObject()
    .endObject()
    .endObject()
    .endObject();
    // 创建mapping
    PutMappingRequest mapping = Requests.putMappingRequest("index2")
    .type("article").source(builder);
    client.admin().indices().putMapping(mapping).get();
    client.close();

    }
  1. 创建文档

    通过XcontentBuilder创建

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Test
    public void createDocument() throws IOException {
    XContentBuilder xContentBuilder = XContentFactory.jsonBuilder()
    .startObject()
    .field("id",1)
    .field("title","震惊!一拳超人竟然....")
    .field("content","一拳超人竟然出了第二季度动漫,每次就是来蹭热点的,不服来打我啊!哈哈哈")
    .endObject();
    client.prepareIndex("index2","article","1").setSource(xContentBuilder).get();
    //释放资源
    client.close();
    }

    通过Jackson转换实体类创建文档

    1)创建实体类

    1
    2
    3
    4
    5
    6
    public class Article {
    private Long id;
    private String title;
    private String content;
    // getter and setter
    }

2)添加Jackson坐标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!--Jackson-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.8.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.8.1</version>
</dependency>

3)代码实现

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void createDocumentByJackson() throws JsonProcessingException {
Article article = new Article();
article.setId(2L);
article.setTitle("新添加内容标题");
article.setContent("新添加的内容!新添加的内容");
ObjectMapper objectMapper = new ObjectMapper();
client.prepareIndex("index2","article",
article.getId().toString()).setSource(objectMapper.writeValueAsString(article).getBytes(),
XContentType.JSON).get();
client.close();
}
  1. 查询文档

    根据id查询

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Test
    public void queryDocumentById() {
    SearchResponse response = client.prepareSearch("index2")
    .setTypes("article", "content")
    .setQuery(QueryBuilders.idsQuery().addIds("1"))
    .get();

    SearchHits hits = response.getHits();
    System.out.println("总记录数:" + hits.getTotalHits());
    for (SearchHit hit : hits.getHits()) {
    System.out.println(hit.getSourceAsString());
    }

    }

    根据关键词查找

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Test
    public void queryDocByKeyWord(){
    SearchResponse response = client.prepareSearch("index2")
    .setTypes("article")
    .setQuery(QueryBuilders.termQuery("title", "震惊"))
    .get();
    SearchHits hits = response.getHits();

    System.out.println("总记录数:" + hits.getTotalHits());
    Iterator<SearchHit> iterator = hits.iterator();
    while (iterator.hasNext()){
    SearchHit next = iterator.next();
    System.out.println(next.getSourceAsString());
    }
    client.close();
    }

    根据字符串模糊查询

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @Test
    public void queryDocByString(){
    SearchResponse response = client.prepareSearch("index2")
    .setTypes("article")
    .setQuery(QueryBuilders.queryStringQuery("震惊党")).get();

    SearchHits hits = response.getHits();
    System.out.println("总记录数:" + hits.getTotalHits());
    Iterator<SearchHit> iterator = hits.iterator();
    while (iterator.hasNext()){
    SearchHit next = iterator.next();
    System.out.println(next.getSourceAsString());
    System.out.println("-----------优雅的分割线------------");
    }
    }

评论