ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。
Elasticsearch的应用场景

  • 一个线上商城系统,用户需要搜索商城上的商品。在这里你可以用es存储所有的商品信息和库存信息,用户只需要输入”空调”就可以搜索到他需要搜索到的商品。
  • 一个运行的系统需要收集日志,用这些日志来分析、挖掘从而获取系统业务未来的趋势。你可以用logstash(ELK中的一个产品,elasticsearch/logstash/kibana)收集、转换你的日志,并将他们存储到es中。一旦数据到达es中,就你可以在里面搜索、运行聚合函数等操作来挖掘任何你感兴趣的信息。
  • 如果你有想基于大量数据(数百万甚至数十亿的数据)快速调查、分析并且要将分析结果可视化的需求。你可以用es来存储你的数据,用kibana构建自定义的可视化图形、报表,为业务决策提供科学的数据依据。

安装ElasticSearch

版本说明:ES5 需要 Java 8 及以上的版本。ES7 开始内置了 Java 环境

首先确认下jdk有没有安装

java -version

下载源文件,解压,重新建一个用户,将目录的所属组修改为此用户,因为 elasticsearch 无法用 root 用户启动。

wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.3.0.tar.gz
tar zxvf elasticsearch-6.3.0.tar.gz
useradd elasticsearch 
passwd elasticsearch
chown -R elasticsearch:elasticsearch elasticsearch-6.3.0
cd elasticsearch-6.3.0
./bin/elasticsearch  // 启动
./bin/elasticsearch -d //后台启动 

cd config
vim jvm.options

# 修改参数如下(服务器小,不配大容量)
-Xms256m
-Xmx256m

可能存在问题

Exception in thread "main" java.nio.file.AccessDeniedException: /usr/local/src/elasticsearch-6.3.0/config/jvm.options
        at sun.nio.fs.UnixException.translateToIOException(UnixException.java:84)
        at sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:102)
        at sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:107)
        at sun.nio.fs.UnixFileSystemProvider.newByteChannel(UnixFileSystemProvider.java:214)
        at java.nio.file.Files.newByteChannel(Files.java:361)
        at java.nio.file.Files.newByteChannel(Files.java:407)
        at java.nio.file.spi.FileSystemProvider.newInputStream(FileSystemProvider.java:384)
        at java.nio.file.Files.newInputStream(Files.java:152)
        at org.elasticsearch.tools.launchers.JvmOptionsParser.main(JvmOptionsParser.java:58)

报这个错是因为用户的权限不足,因此在配置和启动ElasticSearch节点的时候要注意给用户赋予对应的权限

chown -R myuser:myuser /usr/local/src/elasticsearch-6.3.0

连接Elasticsearch:

1、Elasticsearch开启之后,可以直接通过http://127.0.0.1:9200/查看基本信息。

{
  "name": "iEX7av1",
  "cluster_name": "elasticsearch",
  "cluster_uuid": "8TMqWFwnTLiBlRKtU7a8YQ",
  "version": {
    "number": "6.3.0",
    "build_flavor": "default",
    "build_type": "tar",
    "build_hash": "424e937",
    "build_date": "2018-06-11T23:38:03.357887Z",
    "build_snapshot": false,
    "lucene_version": "7.3.1",
    "minimum_wire_compatibility_version": "5.6.0",
    "minimum_index_compatibility_version": "5.0.0"
  },
  "tagline": "You Know, for Search"
}

安装中文分词 elasticsearch-analysis-ik

ik有两种分词模式,ik_max_word,和ik_smart模式

  • ik_max_word: 会将文本做最细粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,中华人民,中华,华人,人民共和国,人民,人,民,共和国,共和,和,国国,国歌”,会穷尽各种可能的组合;
  • ik_smart: 会做最粗粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,国歌”。

索引时,为了提供索引的覆盖范围,通常会采用ik_max_word分析器,会以最细粒度分词索引,搜索时为了提高搜索准确度,会采用ik_smart分析器,会以粗粒度分词。使用ik_max_word会占用更多的存储空间

#在线安装
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.3.0/elasticsearch-analysis-ik-6.3.0.zip

#本地安装
./bin/elasticsearch-plugin install file:///usr/local/src/elasticsearch-analysis-ik-6.3.0.zip

安装好后 重启es会看到 加载了 ik分词了

安装完毕后,发现在ES的安装目录下的plugins目录下多了一个analysis-ik目录(内容是ik的zip包解压后根目录下的所有文件,一共是5个jar文件和1个properties配置文件),另外ES的安装目录下的config目录下多了一个analysis-ik目录(内容是ik的zip包解压后根目录下的config目录下所有文件,用于放置ik的自定义词库)

可视化Elasticvue

  • 平台:Windows / Mac / Linux 三端原生桌面 App + 浏览器插件
  • 费用:完全免费、开源 MIT 协议,无任何功能阉割
  • 核心能力(完美适配你的阿里云 ES)
    • 支持账号密码认证、HTTPS、阿里云公网 ES 连接、IP 白名单环境
    • 索引可视化浏览、文档表格 / JSON 双视图、单条文档增删改查
    • DSL 编辑器语法高亮、自动格式化、保存常用查询语句
    • Mapping 查看 / 编辑、集群健康监控、分片状态、批量 Bulk 操作
    • 支持 ES 7.x/ 8.x 全版本,兼容 OpenSearch
  • 适合人群:日常查 CRM 客户文档、调试 DSL、多环境集群切换,90% 开发者首选
  • 缺点:重度集群监控图表不如 Kibana 专业

安装Kibana 

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

wget https://artifacts.elastic.co/downloads/kibana/kibana-6.3.0-linux-x86_64.tar.gz
tar -zxvf kibana-6.3.0-linux-x86_64.tar.gz
cd kibana-6.3.0-linux-x86_64
#启动Kibana
./bin/kibana

http://localhost:5601访问web页面;

使用POSTMAN工具发送PUT请求创建

ES的基本用法:ES以RESTFul风格来命名API的, 其API的基本格式如下

http://<ip>:<port>/<索引>/<类型>/<文档id>

ES的动作是以http方法来决定的: 常用的http方法: GET/PUT/POST/DELETE

es支持多字段结构,例如:我们将常用的搜索条件和排序字段(销量,时间)配置到mappings.properties中

{
	"settings":{
		"number_of_shards": 3,
		"number_of_replicas": 1
	},
	"mappings":{
		"man":{
			"properties":{
				"word_count":{
					"type": "integer"
				},
				"author":{
					 "type": "keyword"
				},
				"title":{
					"type": "text"
				},
				"publish_date":{
					"type": "date",
					"format": "yyyy-MM-dd HH:mm:ss || yyyy-MM-dd || epoch_millis"
				}
			}
		}
	}
}

如下图

观察返回结果已经成功。

插入文档:PUT 127.0.0.1:9200/people/man

{
    "name": "chenjie",
    "country": "China",
    "age": 30,
    "date": "1994-09-27"
}

修改文档:POST 127.0.0.1:9200/people/man/1/_update

{
    "doc":{
         "name":"chenjie_update"
     }
}

删除文档

DELETE 127.0.0.1:9200/people/man/1

批量操作

批量操作 bulk

  • 支持在一次 API 调用中,对不同的索引进行操作
  • 支持四种类型操作Index/Create/Update/Delete
  • 可以在 URI 中指定 Index,也可以在请求的 Payload 中进行
  • 单条操作失败,并不会影响其他操作
  • 返回结果包括了每一条操作执行的结果

测试命令:

POST _bulk
{ "index" : { "_index" : "test", "_id" : "1" } }
{ "field1" : "value1" }
{ "delete" : { "_index" : "test", "_id" : "2" } }
{ "create" : { "_index" : "test2", "_id" : "3" } }
{ "field1" : "value3" }
{ "update" : {"_id" : "1", "_index" : "test"} }
{ "doc" : {"field2" : "value2"} }

ES 三种主流分页对比

方案 1:from + size 浅分页

优点

  1. 使用最简单,和 MySQL LIMIT offset,size 逻辑一致,前端页码直接映射:from=(page-1)*size
  2. 支持跳页:直接点第 100 页、第 50 页无任何代码改动
  3. 不需要维护游标、上下文,无状态,接口好设计
  4. 小页码(前几十页)性能极好

缺点(致命)

  1. 深度分页性能雪崩 ES 需要把 from+size 条数据全部拿到内存排序、打分、截取,from=10000 之后 CPU、内存暴涨,官方默认限制 index.max_result_window:10000,超过直接报错。
  2. 排序字段无唯一值时,分页数据重复、漏数据(比如按 score 排序,多条分数相同)
  3. 不适合大数据量导出、全量遍历

方案 2:scroll 滚动分页(游标快照)

原理

第一次请求创建一个快照上下文(scroll_id),后续根据 scroll_id 持续拉取下一批数据,不会重复打分排序。

优点

  1. 可以百万级、千万级全量遍历导出,深度分页性能稳定
  2. 不需要一次性加载大量数据到内存,分片逐个读取
  3. 适合数据同步、离线 ETL、索引重建、批量计算

缺点(生产几乎不再推荐)

  1. 无法跳页,只能顺序一页一页往下翻,不能直接跳到第 N 页
  2. scroll 上下文占用内存,设置超时时间过长会堆内存泄漏,必须手动清理 clear scroll
  3. 快照是创建时的数据,遍历期间新写入 / 更新的文档看不到,非实时视图
  4. ES7 开始官方弱化 scroll,优先推荐 search_after
  5. 并发多 scroll 容易造成分片压力
  6. scroll 超时,“上下文丢失”崩溃模式 search_context_missing_exception

方案 3:search_after 游标分页(最优生产方案)

原理

依靠排序字段最后一条的唯一值作为游标,下一次传入 search_after:[值1,值2] 从该位置继续取数,本质是主键游标分页。 必须:排序字段组合全局唯一(通常 _id 兜底)

优点

  1. 深度分页性能稳定,几万、十几万页压力几乎不变,不受 max_result_window 限制
  2. 无 scroll 上下文内存开销,无过期清理问题
  3. 视图是实时的,能看到分页过程中新写入的数据
  4. 官方主推,ES6+ 标准深度分页方案
  5. 内存友好,只读取需要的数据,不做大规模排序缓存
  6. 基于唯一排序键时天然不重不漏,实时新增不会造成偏移重复;天然支持断点续传(记住最后一条 id 即可)。

缺点

  1. 不支持跳页,只能上一页、下一页顺序翻页,无法直接跳转到第 100 页
  2. 前端不能展示总页数、总条数(统计 total 开销大),只能展示 “下一页” 按钮
  3. 排序规则一旦变更,游标直接失效;必须设计唯一排序键(例:create_time desc, _id asc
  4. 如果中间数据被删除,会出现游标断层漏一条

使用PHPAPi构建和查询

创建表和测试数据,索引相当于MySQL中的表,文档相当于 MySQL 中的行记录。程序在新增/修改/删除记录的时候调用ES的接口对ES文档进行同步的新增/修改/删除操作。同步构建有很大缺陷。比如:ES宕机或者ES处理文档消耗了很长的时间影响用户的体验。可以使用KaFka中间件来进行异步构建文档。

在 composer.json 文件中引入 elasticsearch-php

{
    "require": {
        "elasticsearch/elasticsearch": "~6.0"
    }
}
CREATE TABLE articles ( id INT NOT NULL PRIMARY KEY auto_increment, title VARCHAR ( 200 ) NOT NULL COMMENT '标题', content text COMMENT '内容' );
INSERT INTO articles ( title, content )
VALUES
( 'Laravel 测试1', 'Laravel 测试文章内容1' ),
( 'Laravel 测试2', 'Laravel 测试文章内容2' ),
( 'Laravel 测试3', 'Laravel 测试文章内容3' );

实例化

require './vendor/autoload.php';
use Elasticsearch\ClientBuilder;
$client = ClientBuilder::create()->build();

elasticsearch的动态性质,在添加第一个文档的时候自动创建了索引和一些默认设置。

创建索引

$params['index'] = 'articles_index';
$params['body']['settings']['number_of_shards'] = 2;
$params['body']['settings']['number_of_replicas'] = 0;
$client->indices()->create($params);

将文档加入索引

foreach ($lists as $row) {
    $params = [
        'body' =>
            [
                'id' => $row['id'],
                'title' => $row['title'],
                'content' => $row['content']
            ],
        'id' => 'article_' . $row['id'], //maping db id
        'index' => 'articles_index',
        'type' => 'articles_type'
    ];
    $client->index($params);
}

 从索引中获取文档

$params = ['index' => 'articles_index', 'type' => 'articles_type', 'id' => 'articles_1'];
$res = $client->get($params);
print_r($res);

删除索引

$params = [
    'index' => 'articles_index'
];
$res = $client->indices()->delete($params);
print_r($res);

搜索文档,如果需要获取更多明细信息可以根据mysql表ID获取

<?php
$params = ['index' => 'articles_index', 'type' => 'articles_type'];
$params['body']['query']['match']['content'] = 'Laravel';
$params['body']['size'] = 10;
$params['body']['from'] = 200;
$params['body']['sort'] = ['content' => ['order' => 'desc']];
$res = $client->search($params);
print_r($res);
//相当于sql语句:select * from articles where content='Laravel' limit 200,10 order by content desc; 

从索引中删除文档

$params = ['index' => 'articles_index', 'type' => 'articles_type', 'id' => 'articles_1'];
$res = $client->delete($params);
print_r($res);

Elasticsearch更新和删除文档的过程。

  • 删除和更新也都是写操作,但是Elasticsearch中的文档是不可变的,因此不能被删除或者改动以展示其变更;
  • 磁盘上的每个段都有一个相应的.del文件。当删除请求发送后,文档并没有真的被删除,而是在.del文件中被标记为删除。该文档依然能匹配查询,但是会在结果中被过滤掉。当段合并时,在.del文件中被标记为删除的文档将不会被写入新段。
  • 在新的文档被创建时,Elasticsearch会为该文档指定一个版本号,当执行更新时,旧版本的文档在.del文件中被标记为删除,新版本的文档被索引到一个新段。旧版本的文档依然能匹配查询,但是会在结果中被过滤掉。

ElasticSearch搜索term和terms的区别

在查询的字段只有一个值的时候,应该使用term而不是terms,在查询字段包含多个的时候才使用terms(类似于sql中的in、or),使用terms语法,JSON中必须包含数组。

第一种(单个值,term):

{
    "query":{
        "bool":{
            "must":[
                {
                    "range":{
                        "update_time":{
                            "gt":"1488556800000"
                        }
                    }
                },
                {
                    "term":{
                        "lang":1
                    }
                },
                {
                    "terms":{
                        "domain":[
                            "dailymasala.co",
                            "goldenmob.com"
                        ]
                    }
                },
                {
                    "prefix":{
                        "user_id":"errVideo_setInterval_"
                    }
                }
            ]
        }
    },
    "from":0,
    "size":10
}

 第二种(数组形式,terms):

{
  "query": {
    "bool": {
      "must": [
        {
          "range": {
            "update_time": {
              "gt": "1488556800000"
            }
          }
        },
        {
          "terms": {
            "lang": [1]
          }
        },
        {
          "terms": {
            "domain": [
              "dailymasala.co",
              "goldenmob.com"
            ]
          }
        },
        {
          "prefix": {
            "user_id": "errVideo_setInterval_"
          }
        }
      ]
    }
  },
  "from": 0,
  "size": 10
}

elasticsearch 中term与match区别

term是代表完全匹配,也就是精确查询,搜索前不会再对搜索词进行分词,所以我们的搜索词必须是文档分词集合中的一个。比如说我们要找标题为北京奥运的所有文档

match查询会先对搜索词进行分词,分词完毕后再逐个对分词结果进行匹配,因此相比于term的精确搜索,match是分词匹配搜索。要搜索词的分词集合中的一个或多个存在于文档中即可,例如,当我们搜索中国杭州,搜索词会先分词为中国杭州,只要文档中包含搜索杭州任意一个词,都会被搜索到。对于match搜索,可以按照分词后的分词集合的or或者and进行匹配,默认为or

curl -XGET http://localhost:9200/index/doc/_search?pretty -d 
'{
    "query": {
        "match": {
            "content": {
                "query": "中国世界",
                "operator": "and"
            }
        }
    }
}'

ES性能优化

{
    "settings":{
        "number_of_shards": 3,
        "number_of_replicas": 1,
        "index.store.type": "niofs",
        "index.query.default_field": "content",
        "index.unassigned.node_left.delayed_timeout": "5m",
    },
    "mappings":{
        "man":{
            "dynamic":true,
            "_all":{"enabled":false},
            "properties":{
            }
        }
    }
}

安全控制可以使用apache/nginx的http认证

在 Elasticsearch 中,写入和打开一个新段的轻量的过程叫做 refresh 。 默认情况下每个分片会每秒自动刷新一次。这就是为什么我们说 Elasticsearch 是  实时搜索: 文档的变化并不是立即对搜索可见,但会在一秒之内变为可见.

更新延迟问题:搜索时加上?refresh=wait_for,表示如果1秒内有请求立即更新并可见

更多推荐