分布式搜索引擎ElasticSearch运用 - 快速入门使用

1. 基本介绍

  • ElasticSearch特色

    Elasticsearch不仅仅是Lucene和全文搜索, 还包括:

    • 分布式的实时文件存储,每个字段都被索引并可被搜索。
    • 分布式的实时分析搜索引擎。
    • 可以扩展到上百台服务器,处理PB级结构化或非结构化数据。
    • 各节点组成对等的网络结构,某些节点出现故障时会自动分配其他节点代替其进行工作。
  • ElasticSearch使用场景

    ElasticSearch广泛应用于各行业领域, 比如维基百科, GitHub的代码搜索,电商网站的大数据日志统计分析, BI系统报表统计分析等。

    • 提供分布式的搜索引擎和数据分析引擎

      比如百度,网站的站内搜索,IT系统的检索, 数据分析比如热点词统计, 电商网站商品TOP排名等。

    • 全文检索,结构化检索,数据分析

      支持全文检索, 比如查找包含指定名称的商品信息; 支持结构检索, 比如查找某个分类下的所有商品信息;

      还可以支持高级数据分析, 比如统计某个商品的点击次数, 某个商品有多少用户购买等等。

    • 支持海量数据准实时的处理

      采用分布式节点, 将数据分散到多台服务器上去存储和检索, 实现海量数据的处理, 比如统计用户的行为日志, 能够在秒级别对数据进行检索和分析。

  • ElasticSearch基本概念介绍

| ElasticSearch | Relational Database |
| --------------------- | ---------------------- |
| Index | Database |
| Type | Table |
| Document | Row |
| Field | Column |
| Mapping | Schema |
| Everything is indexed | Index |
| Query DSL | SQL |
| GET http://... | SELECT * FROM table... |
| PUT http://... | UPDATE table SET... |

    • 1) 索引(Index)

      相比传统的关系型数据库,索引相当于SQL中的一个【数据库】,或者一个数据存储方案(schema)。

    • 2) 类型(Type)

      一个索引内部可以定义一个或多个类型, 在传统关系数据库来说, 类型相当于【表】的概念。

    • 3) 文档(Document)

      文档是Lucene索引和搜索的原子单位,它是包含了一个或多个域的容器,采用JSON格式表示。相当于传统数据库【行】概念。

    • 4) 集群(Cluster)

      集群是由一台及以上主机节点组成并提供存储及搜索服务, 多节点组成的集群拥有冗余能力,它可以在一个或几个节点出现故障时保证服务的整体可用性。

    • 5) 节点(Node)

      Node为集群中的单台节点,其可以为master节点亦可为slave节点(节点属性由集群内部选举得出)并提供存储相关数据的功能。

    • 6) 切片(shards)

      切片是把一个大文件分割成多个小文件然后分散存储在集群中的多个节点上, 可以将其看作mysql的分库分表概念。 Shard有两种类型:primary主片和replica副本,primary用于文档存储,每个新的索引会自动创建5个Primary shard;Replica shard是Primary Shard的副本,用于冗余数据及提高搜索性能。

      注意: ES7之后Type被舍弃,只有Index(等同于数据库+表定义)和Document(文档,行记录)。

    2. ElasticSearch安装

    1. 下载ElasticSearch服务

      下载最新版ElasticSearch7.10.2, 地址入口: https://www.elastic.co/cn/start

    2. 解压安装包

      tar -xvf elasticsearch-7.10.2-linux-x86_64.tar.gz
    3. ElasticSearch不能以Root身份运行, 需要单独创建一个用户

      1) groupadd elsearch

      2) useradd elsearch -g elsearch -p elasticsearch

      3) chown -R elsearch:elsearch /usr/local/elasticsearch-7.10.2

      执行以上命令,创建一个名为elsearch用户, 并赋予目录权限。

    4. 修改配置文件

      vi config/elasticsearch.yml, 默认情况下会绑定本机地址, 外网不能访问, 这里要修改下:

      # 外网访问地址
      network.host: 0.0.0.0
    5. 关闭防火墙

      systemctl stop  firewalld.service
      systemctl disable  firewalld.service
    6. 指定JDK版本

      • 最新版的ElasticSearch需要JDK11版本, 下载JDK11压缩包, 并进行解压。
      • 修改环境配置文件

        vi bin/elasticsearch-env

        参照以下位置, 追加一行, 设置JAVA_HOME, 指定JDK11路径。

        JAVA_HOME=/usr/local/jdk11
        # now set the path to java
        if [ ! -z "$JAVA_HOME" ]; then
          JAVA="$JAVA_HOME/bin/java"
        else
          if [ "$(uname -s)" = "Darwin" ]; then
            # OSX has a different structure
            JAVA="$ES_HOME/jdk/Contents/Home/bin/java"
          else
            JAVA="$ES_HOME/jdk/bin/java"
          fi
        fi
    7. 启动ElasticSearch

      • 切换用户

        su elsearch
      • 以后台常驻方式启动

        bin/elasticsearch -d 
    8. 问题FAQ记录

      如果出现max virtual memory areas vm.max_map_count [65530] is too low, increase to at least 错误信息

      修改系统配置:

      • vi /etc/sysctl.conf

        添加

        vm.max_map_count=655360

        执行生效

        sysctl -p
      • vi /etc/security/limits.conf

        在文件末尾添加

        * soft nofile 65536
        
        * hard nofile 131072
        
        * soft nproc 2048
        
        * hard nproc 4096
        
        elsearch soft nproc 125535
        
        elsearch hard nproc 125535

        重新切换用户即可生效:

        su - elsearch
    9. 访问验证

      访问地址: http://10.10.20.28:9200/_cat/health

      file

      启动状态有green、yellow和red。 green是代表启动正常。

    3. Kibana服务安装

    Kibana是一个针对Elasticsearch的开源分析及可视化平台,用来搜索、查看交互存储在Elasticsearch索引中的数据。

    1. 到官网下载, Kibana安装包, 与之对应7.10.2版本, 选择Linux 64位版本下载,并进行解压。

      tar -xvf kibana-7.10.2-linux-x86_64.tar.gz
    2. Kibana启动不能使用root用户, 使用上面创建的elsearch用户, 进行赋权:

      chown -R elsearch:elsearch kibana-7.10.2-linux-x86_64
    3. 修改配置文件

      vi config/kibana.yml , 修改以下配置:

      # 服务端口
      server.port: 5601
      # 服务地址
      server.host: "0.0.0.0"
      # elasticsearch服务地址
      elasticsearch.hosts: ["http://10.10.20.28:9200"]
    4. 启动kibana

      ./kibana -q

      看到以下日志, 代表启动正常

      log   [01:40:00.143] [info][listening] Server running at http://0.0.0.0:5601

      如果出现启动失败的情况, 要检查集群各节点的日志, 确保服务正常运行状态。

    5. 访问服务

      验证地址: http://10.10.20.28:5601/app/home#/

    4. ES的基本操作

    1. 进入Kibana管理后台

      地址: http://10.10.20.28:5601

      进入"Dev Tools"工具栏:

      file
      在Console中可以直接输入命令操作, 比较便捷, 不需要再去模拟http请求。

    2. 分片设置:

      这里增加名为orders的索引, 因为是单节点, 如果副本数, 是会出现错误。

      PUT /orders
      {
        "settings":{
            "index":{
              "number_of_shards": 2, 
              "number_of_replicas": 2
              }
            }
      }

      查看索引信息, 会出现yellow提示:

      file

      因为单节点模式, 只有主节点信息:

      file

      删除重新创建:

      PUT /orders
      {
        "settings":{
            "index":{
              "number_of_shards": 2, 
              "number_of_replicas": 0
              }
            }
      }
      

      将分片数设为0, 再次查看, 则显示正常:

      file

    3. 创建索引(非结构化方式):

      新建索引, 会自动创建account索引:

      PUT /orders/_doc/1
      {
        "name":"Mirson", 
        "mobile": "13800138000",
        "address":"China"
      }

      通过查询命令, 能查看到对应信息, 默认分片数和副本数都为1:

      "number_of_shards" : "1",
      "number_of_replicas" : "1",

      ES会自动为属性设置对应类型:

      file

    4. 创建索引(结构化方式):

      结构化方式创建索引:

      PUT /orders
      {
        "settings":{
            "index":{
              "number_of_shards": 1, 
              "number_of_replicas": 0
            }
          },
        "mappings": {
          "properties": {
            "name": {"type": "text"},
            "mobile": {"type": "long"},
            "address": {"type": "text"}      
          }
        }
      }

      查看index的mappings信息, 可以看到类型已生效:

      file

    5. 查看索引设置信息

      查看创建的orders索引设置信息:

      GET /orders/_settings

      返回:

      {
        "orders" : {
          "settings" : {
            "index" : {
              "routing" : {
                "allocation" : {
                  "include" : {
                    "_tier_preference" : "data_content"
                  }
                }
              },
              "number_of_shards" : "1",
              "provided_name" : "orders",
              "creation_date" : "1613801650135",
              "number_of_replicas" : "0",
              "uuid" : "ZvmjsAxRTpi1PLF6t6mlyA",
              "version" : {
                "created" : "7100299"
              }
            }
          }
        }
      }
      
    6. 根据ID查看文档

      get /orders/_doc/1

      返回:

      {
        "_index" : "orders",
        "_type" : "_doc",
        "_id" : "1",
        "_version" : 1,
        "_seq_no" : 0,
        "_primary_term" : 1,
        "found" : true,
        "_source" : {
          "name" : "Mirson",
          "mobile" : "13800138000",
          "address" : "China"
        }
      }
      
    7. 更新文档

      • 覆盖式更新

        PUT /orders/_doc/1
        {
          "name": "Mirson1"
        }

        查看信息:

        GET /orders/_doc/1

        返回结果:

        {
          "_index" : "orders",
          "_type" : "_doc",
          "_id" : "1",
          "_version" : 2,
          "_seq_no" : 1,
          "_primary_term" : 1,
          "found" : true,
          "_source" : {
            "name" : "Mirson1"
          }
        }
        

        从_source中可以看到, 之前创建的balance信息已经被更新覆盖。

      • 指定字段更新

        POST /orders/_update/1
        {
          "doc": {
            "mobile": 13700137000
          }
        }

      采用POST方式, 附带_update标识。

    8. 删除文档

      删除某一个文档,采用DELETE方式操作, 后面要附带删除的数据ID:

      DELETE /orders/_doc/1

      返回结果:

      {
        "_index" : "orders",
        "_type" : "_doc",
        "_id" : "1",
        "_version" : 5,
        "result" : "deleted",
        "_shards" : {
          "total" : 1,
          "successful" : 1,
          "failed" : 0
        },
        "_seq_no" : 4,
        "_primary_term" : 1
      }
      

      根据指定条件删除文档信息:

      POST /orders/_delete_by_query
      {
        "query": { 
          "match": {
            "address": "China"
          }
        }
      }

      返回结果:

      {
        "took" : 8,
        "timed_out" : false,
        "total" : 2,
        "deleted" : 2,
        "batches" : 1,
        "version_conflicts" : 0,
        "noops" : 0,
        "retries" : {
          "bulk" : 0,
          "search" : 0
        },
        "throttled_millis" : 0,
        "requests_per_second" : -1.0,
        "throttled_until_millis" : 0,
        "failures" : [ ]
      }

    5. ES数据类型解析

    整体数据类型结构:

    file

    1. String 类型

      主要分为text与keyword两种类型。两者区别主要在于能否分词。

      • text类型

        会进行分词处理, 分词器默认采用的是standard。

      • keyword类型

        不会进行分词处理。在ES的倒排索引中存储的是完整的字符串。

    2. Date时间类型

      数据库里的日期类型需要规范具体的传入格式, ES是可以控制,自适应处理。

      传递不同的时间类型:

      PUT my_date_index/_doc/1
      { "date": "2021-01-01" } 
      
      PUT my_date_index/_doc/2
      { "date": "2021-01-01T12:10:30Z" } 
      
      PUT my_date_index/_doc/3
      { "date": 1520071600001 } 
      

      查看日期数据:

      "hits" : [
            {
              "_index" : "my_date_index",
              "_type" : "_doc",
              "_id" : "1",
              "_score" : 1.0,
              "_source" : {
                "date" : "2021-01-01"
              }
            },
            {
              "_index" : "my_date_index",
              "_type" : "_doc",
              "_id" : "2",
              "_score" : 1.0,
              "_source" : {
                "date" : "2021-01-01T12:10:30Z"
              }
            },
            {
              "_index" : "my_date_index",
              "_type" : "_doc",
              "_id" : "3",
              "_score" : 1.0,
              "_source" : {
                "date" : 1520071600001
              }
            }
          ]
        }

      ES的Date类型允许可以使用的格式有:

      yyyy-MM-dd HH:mm:ss
      yyyy-MM-dd
      epoch_millis(毫秒值)
    3. 复合类型

      复杂类型主要有三种: Array、object、nested。

      • Array类型: 在Elasticsearch中,数组不需要声明专用的字段数据类型。但是,在数组中的所有值都必须具有相同的数据类型。举例:

        POST /orders/_doc/1
        {
          "goodsName":["足球","篮球","兵乓球"]
        }
      • object类型: 用于存储单个JSON对象, 类似于JAVA对的对象类型, 可以有多个值, 比如LIST,可以包含多个对象。

        但是LIST只能作为整体, 不能独立的索引查询。举例:

        # 新增第一组数据, 组别为美国,两个人。
        POST /my_index/_doc/1
        {    
          "group" : "america",    
          "users" : [    
            {    
              "name" : "John",    
              "age" :  "22"    
            },    
            {    
              "name" : "Alice",    
              "age" :  "21"    
            }    
          ]    
        }    
        # 新增第二组数据, 组别为英国, 两个人。
        POST /my_index/_doc/2
        {    
          "group" : "england",    
          "users" : [    
            {    
              "name" : "lucy",    
              "age" :  "21"    
            },    
            {    
              "name" : "John",    
              "age" :  "32"    
            }    
          ]    
        }

        这两组数据都包含了name为John,age为21的数据,

        采用这个搜索条件, 实际结果:

        GET /my_index/_search?pretty    
        {    
          "query": {    
            "bool": {    
              "must": [    
                {    
                  "match": {    
                    "users.name": "John"
                    
                  }    
                },
                {
                  "match": {    
                    "users.age":     21    
                  }    
                }    
              ]    
            }    
          }    
        }

        结果可以看到, 这两组数据都能找出,因为每一组数据都是作为一个整体进行搜索匹配, 而非具体某一条数据。

      • Nested类型

        用于存储多个JSON对象组成的数组,nested 类型是 object 类型中的一个特例,可以让对象数组独立索引和查询。

        举例:

        创建nested类型的索引:

        PUT /my_index
        {    
          "mappings": {    
            "properties": {    
              "users": {    
                "type": "nested"    
              }    
            }    
          }    
        }

        发出查询请求:

        GET /my_index/_search?pretty    
        {    
          "query": {    
            "bool": {    
              "must": [    
                {    
                  "nested": {    
                    "path": "users",    
                    "query": {    
                      "bool": {    
                        "must": [    
                          {    
                            "match": {    
                              "users.name": "John"    
                            }    
                          },    
                          {    
                            "match": {    
                              "users.age": 21    
                            }    
                          }    
                        ]    
                      }    
                    }    
                  }    
                }    
              ]    
            }    
          }    
        }

        采用以前的条件, 这个时候查不到任何结果, 将年龄改成22, 就可以找出对应的数据:

        "hits" : [
              {
                "_index" : "my_index",
                "_type" : "_doc",
                "_id" : "1",
                "_score" : 1.89712,
                "_source" : {
                  "group" : "america",
                  "users" : [
                    {
                      "name" : "John",
                      "age" : "22"
                    },
                    {
                      "name" : "Alice",
                      "age" : "21"
                    }
                  ]
                }
              }
            ]
        1. GEO地理位置类型

          现在大部分APP都有基于位置搜索的功能, 比如交友、购物应用等。这些功能是基于GEO搜索实现的。

          对于GEO地理位置类型,分为地图:Geo-point, 和形状:Geo-shape 两种类型。

          | 经纬度 | 英文 | 简写 | 正数 | 负数 |
          | :----- | :-------- | :------- | :--- | ---- |
          | 维度 | latitude | lat | 北纬 | 南纬 |
          | 经度 | longitude | lon或lng | 东经 | 西经 |

          创建地理位置索引:

          PUT /my_locations
          {
            "mappings": {
              "properties": {
                "location": {
                  "type": "geo_point"
                }
              }
            }
          }

          添加地理位置数据:

          # 采用object对象类型
          PUT my_locations/_doc/1
          {
            "text": "Geo-point as an object",
            "location": { 
              "lat": 41.12,
              "lon": -71.34
            }
          }
          # 采用string类型
          PUT my_locations/_doc/2
          {
            "text": "Geo-point as a string",
            "location": "45.12,-75.34" 
          }
          # 采用geohash类型(geohash算法可以将多维数据映射为一串字符)
          PUT my_locations/_doc/3
          {
            "text": "Geo-point as a geohash",
            "location": "drm3btev3e86" 
          }
          # 采用array数组类型
          PUT my_locations/_doc/4
          {
            "text": "Geo-point as an array",
            "location": [ -80.34, 51.12 ] 
          }

          给出某一个地理位置, 搜索方圆200km的地址信息:

          GET /my_locations/_search
          {
              "query": {
                  "bool" : {
                      "must" : {
                          "match_all" : {}
                      },
                      "filter" : {
                          "geo_distance" : {
                              "distance" : "200km",
                              "location" : {
                                  "lat" : 40,
                                  "lon" : -70
                              }
                          }
                      }
                  }
              }
          }
        本文由mirson创作分享, 感谢大家的支持, 希望对大家有所收获!
        入群申请,请加WX号:woodblock99

        你可能感兴趣的:(java)