一篇文章带你入门并使用Elastic Stack
一篇文章带你入门并使用Elastic Stack
为什么需要ElasticSearch?
根本:用like查询 不能满足需求。
比如一个短语叫:蔡徐坤喜欢打篮球。如果我们搜索"蔡徐坤篮球",那么利用like是查询不到的。
且在大量数据的前提下,like查询效率低下,速度慢。
ElasticSearch概念
核心:类比数据库
可以简单理解成一个数据库,例如MySQL.
学习es的时候可以对照着学。例如elasticSearch的索引就可以类比成mysql中的表。
安装
es迭代超级快!建议读官方文档,本文章只是初步讲解,可以跟着这个思路去读es的官方文档。
首先明确几个概念:
- estack 是一套技术栈,包含了数据的整合,抽取,存储 ,使用
- beats 从不同类型的文件/应用来获取,采集数据(数据采集器)
- logstash:从多个采集器或者数据源中抽取,转换数据(转成我们需要的)。并向es输送。
- es:存储查询数据
- kibana:可视化es数据
安装ES,kibana
在该页面中www.elastic.co/guide/index…
找到
把两个页面打开到新的标签页。
注意不要用8的版本!
很不适合新手!会有https以及安全校验,初次使用问题很多。
我这里选择7.17版本
注意,这一套技术栈的版本一定要一致!!
ES相比于MySQL,能自动做分词,能高效灵活查询内容。
Quick start
kibana启动后:http://localhost:5601/app/dev_tools#/console
以下只是笔者写的demo,更多实操需要同学们自己去官网阅读。
这是es的DSL语法,仅仅供es使用
基本使用
增加文档
POST article/_doc
{
"title":"文章",
"desc":"好文章"
}
查询文档
后面的索引是增加文档后返回的
GET article/_doc/_-cifocBWgoYUa01_dPj
查询结果
{
"_index" : "article",
"_type" : "_doc",
"_id" : "_-cifocBWgoYUa01_dPj",
"_version" : 1,
"_seq_no" : 1,
"_primary_term" : 1,
"found" : true,
"_source" : {
"title" : "文章",
"desc" : "好文章"
}
}
compound queries
POST _search
{
"query": {
"bool" : {
//must:必须要包含
"must" : {
//term必须完全符合,不允许模糊
"term" : { "user.id" : "kimchy" }
},
//过滤器,不影响评分
"filter": {
"term" : { "tags" : "production" }
},
//must_not:必须不包含
"must_not" : {
"range" : {
"age" : { "gte" : 10, "lte" : 20 }
}
},
//should 和 minimum_should_match连起来用,这里minimum_should_match为1,说明should中的条件至少需要满足一个
"should" : [
{ "term" : { "tags" : "env1" } },
{ "term" : { "tags" : "deployed" } }
],
"minimum_should_match" : 1,
"boost" : 1.0
}
}
}
更新文档
POST article/_doc/_-cifocBWgoYUa01_dPj
{
"title":"文章",
"desc":"坏文章"
}
更新结果
{
"_index" : "article",
"_type" : "_doc",
"_id" : "_-cifocBWgoYUa01_dPj",
"_version" : 2,
"_seq_no" : 2,
"_primary_term" : 1,
"found" : true,
"_source" : {
"title" : "文章",
"desc" : "坏文章"
}
}
删除文档
DELETE article/_doc/_-cifocBWgoYUa01_dPj
索引的概念
正向索引:理解为书籍的目录,可以高速找到对应的内容。(怎么根据页码找到文章)
倒排索引:
根据内容找到文章。本质:构建倒排索引。
比如两句句话:
文章A:nika是南信大的学生
文章B:南信大是nika的母校
构建倒排索引
分词 | 内容id |
---|---|
nika | 文章A,文章B |
南信大 | 文章A,文章B |
学生 | 文章A |
母校 | 文章B,文章B |
用户搜 nika学生
es先切词。nika,学生
所以根据nika,找到了文章A,B,学生找到了文章A。
Mapping的概念
官网也有。
理解为数据库的表结构,有哪些字段,有哪些类型
查询表结构
Dynamic Mapping
GET article/_mapping
表结构结果
{
"article" : {
"mappings" : {
"properties" : {
"desc" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"title" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
}
}
}
这里的mapping虽然说可以类比成mysql中的表结构,但是比它更加灵活。你在添加数据的时候,甚至可以添加结构中没有的字段,比如:
POST article/_doc/1
{
"title":"文章",
"desc":"好文章",
"extra":"我是表结构中没有的字段"
}
结果也是成功的
{
"_index" : "article",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 4,
"_primary_term" : 1
}
Explicit mapping
当然,es也支持显式的表结构创建
创建一个user表,定义好年龄是整数,email是keyword(不可分词的字符串,它不会把这个词拆分),name是text
PUT /user
{
"mappings": {
"properties": {
"age": { "type": "integer" },
"email": { "type": "keyword" },
"name": { "type": "text" }
}
}
}
再查询一下user的mapping
{
"user" : {
"mappings" : {
"properties" : {
"age" : {
"type" : "integer"
},
"email" : {
"type" : "keyword"
},
"name" : {
"type" : "text"
}
}
}
}
}
小总结
至此,es的增删改查你应该已经学会了,我们需要类比着去学。mapping可以类比成MySQL的表结构(schema),索引就是MySQL中的表
EQL
ESC简介
在了解EQL之前,我们需要知道什么是ECS(Elastic Common Schema)。是一种用于定义各种类型日志和指标的公共数据模型规范。
ECS提供了一组字段名称和数据类型,包括一组预定义字段,涵盖常见的数据点,如时间戳,用户代理,ip地址,URL,Http状态码,除了这些预定义字段,es也能允许自定义字段,使用户将数据点添加到公共数据模型中。
所以,本质上,用人话说就是,ESC就是一个别人给你提供好的表结构,它很规范,你可以在它的基础上加表结构字段,就这么简单
ESC简介
EQL其实就是 专门查询 ESC 的语法。
使用EQL
首先,我们先创建一个ESC的表结构,其实在官网上的Quick Start 的demo中,创建的就是ESC的表结构。
我们粘一下官网的demo,改以下表名,就叫他my_ecs吧
POST my_ecs/_doc
{
"@timestamp": "2099-05-06T16:21:15.000Z",
"event": {
"original": "192.0.2.42 - - [06/May/2099:16:21:15 +0000] "GET /images/bg.jpg HTTP/1.0" 200 24736"
}
}
创建完了以后,我们就可以使用EQL语法了,打开官方文档。www.elastic.co/guide/en/el…
这个文档里面的东西别去记忆,用的时候找就好了。
SQL
es不仅支持以上语法,如果你学过sql的话,那你甚至可以直接用sql进行查询!
POST /_sql?format=txt
{
"query": "SELECT * FROM article WHERE title like '%文章%'"
}
desc | extra | title
---------------+---------------+---------------
好文章 |null |文章
好文章 |我是表结构中没有的字段 |文章
这是学习成本最低的方式
小总结
DSL的语法,类似HTTP请求,以json的格式操作es,简单,灵活,拓展性好。但是可读性差
EQL的语法,声明式,易于读写,支持多个索引,多个类型的查询,结果更加直观,但是只适用于特定场景。
SQL语法,学习成本低,但需要额外插件或者工具,性能差
DSL是应用最广,最推荐的语言
分词器
分词的一种规则
比如官方的示例:使用了whitespace(空格)的分析器,分析这个text
POST _analyze
{
"analyzer": "whitespace",
"text": "The quick brown fox."
}
结果
{
"tokens" : [
{
"token" : "The",
"start_offset" : 0,
"end_offset" : 3,
"type" : "word",
"position" : 0
},
{
"token" : "quick",
"start_offset" : 4,
"end_offset" : 9,
"type" : "word",
"position" : 1
},
{
"token" : "brown",
"start_offset" : 10,
"end_offset" : 15,
"type" : "word",
"position" : 2
},
{
"token" : "fox.",
"start_offset" : 16,
"end_offset" : 20,
"type" : "word",
"position" : 3
}
]
}
标准分词规则,但是一般不用
POST _analyze
{
"tokenizer": "standard",
"filter": [ "lowercase", "asciifolding" ],
"text": "Is this déja vu?"
}
结果:我们发现,词被分成了四个,且全变成了小写,韵母也没了,这就是filter的作用
{
"tokens" : [
{
"token" : "is",
"start_offset" : 0,
"end_offset" : 2,
"type" : "<ALPHANUM>",
"position" : 0
},
{
"token" : "this",
"start_offset" : 3,
"end_offset" : 7,
"type" : "<ALPHANUM>",
"position" : 1
},
{
"token" : "deja",
"start_offset" : 8,
"end_offset" : 12,
"type" : "<ALPHANUM>",
"position" : 2
},
{
"token" : "vu",
"start_offset" : 13,
"end_offset" : 15,
"type" : "<ALPHANUM>",
"position" : 3
}
]
}
IK分词器(常用,国内友好,ES插件)
这是个github 15k Star 的开源项目。这是对es的一个扩展。
由于官网上默认的分词器都是针对英文的。比如,英文都是空格分割的,而中文一般不这样分词,因此我们需要插件帮我们去给中文分词。
由于我们用的是7.17版本,因此我们需要下7.17版本的依赖
我们这里下7.17.7。下zip压缩包。
在你的es文件夹里面新建一个plugins目录,将这个压缩包解压进去。
然后最关键一点来了。
由于我们的es是7.17.9,然而我们的插件是7.17.7,因为下不到7.17.9,只能退而求其次。我们这个时候要修改一个文件
打开prperties文件,把里面所有7.17.7改为7.17.9即可。
再次去bin目录,启动es。
我们用再到kibana的控制台,去试试ik的分词器。
用一下他的分词器ik_smart
POST _analyze
{
"analyzer": "ik_max_word",
"text": "nika是南信大的学生"
}
结果
{
"tokens" : [
{
"token" : "nika",
"start_offset" : 0,
"end_offset" : 4,
"type" : "ENGLISH",
"position" : 0
},
{
"token" : "是",
"start_offset" : 4,
"end_offset" : 5,
"type" : "CN_CHAR",
"position" : 1
},
{
"token" : "南",
"start_offset" : 5,
"end_offset" : 6,
"type" : "CN_CHAR",
"position" : 2
},
{
"token" : "信",
"start_offset" : 6,
"end_offset" : 7,
"type" : "CN_CHAR",
"position" : 3
},
{
"token" : "大",
"start_offset" : 7,
"end_offset" : 8,
"type" : "CN_CHAR",
"position" : 4
},
{
"token" : "的",
"start_offset" : 8,
"end_offset" : 9,
"type" : "CN_CHAR",
"position" : 5
},
{
"token" : "学生",
"start_offset" : 9,
"end_offset" : 11,
"type" : "CN_WORD",
"position" : 6
}
]
}
我们发现,他确实生效了,把学生归为一个词了,但是仍然不能按照我们的想法去分词,比如我要分成:我是。南信大的。学生。
其实这个也很简单,可以自己自定义一个词库。可以自己配置。感兴趣同学再搜索搜索。
ik_max_word
再试试ik_max_word,他其实做的是尽可能多的去分词。比如
POST _analyze
{
"analyzer": "ik_max_word",
"text": "大学生"
}
他的结果是
{
"tokens" : [
{
"token" : "大学生",
"start_offset" : 0,
"end_offset" : 3,
"type" : "CN_WORD",
"position" : 0
},
{
"token" : "大学",
"start_offset" : 0,
"end_offset" : 2,
"type" : "CN_WORD",
"position" : 1
},
{
"token" : "学生",
"start_offset" : 1,
"end_offset" : 3,
"type" : "CN_WORD",
"position" : 2
}
]
}
ik_smart
而ik_smart的结果则是
{
"tokens" : [
{
"token" : "大学生",
"start_offset" : 0,
"end_offset" : 3,
"type" : "CN_WORD",
"position" : 0
}
]
}
看上去更加智能些!
ES打分机制
es有自己独特的打分机制。
举个例子,四句话
- nika是南信大的大二学生
- nika是大二学生
- nika是学生
这里用户搜nika,如果只要求返回一条数据,那么就会返回第三条。
因为es默默地去给每句话打分,哪个高,哪个排在前面,因为第三条不仅匹配,且占这句话的比例高。
其实背后也很复杂,有自己的算法,感兴趣的同学深入了解。
Spring Data Elasticsearch
spring-data 系列:是spring提供的操作数据的框架。
比如spring-data-redis,spring-data-mongodb之类的
我们这次要用的是 spring-data-elasticsearch
这里我们去看spring的官方文档
这里要注意的是,我们安装的es的版本也必须要和依赖版本强一致!!
这里进行查看版本依赖关系
我们用的是7.17.7,因此,选择spring-data-elasticsearch 4.4.x版本的,回到spring.io/projects/sp…页面,选择对应版本的GA版本。
点进去之后,我们先阅读核心概念
这个文章里有相关增删改查的示例。不得不说,写的相当差。但没办法。
Java代码实操
创建ES Mapping(表结构)
tip:如果你声明一个字段的type是text,那么你可以传一个text,也可以传一个text的数组,这是es的一个特性。
es中尽量存放需要用户筛选,并且搜索的数据,且频繁变化的数据也不建议放入es中。
PUT article_v1
{
"aliases": {
"article": {}
},
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"content": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"tags": {
"type": "keyword"
},
"thumbNum": {
"type": "long"
},
"favourNum": {
"type": "long"
},
"userId": {
"type": "keyword"
},
"createTime": {
"type": "date"
},
"updateTime": {
"type": "date"
},
"isDelete": {
"type": "keyword"
}
}
}
}
-
alias:别名,你用article也能找到这个索引(表)article_v1,方便数据迁移
-
如果你的字段类型是text,这个字段就是可以被分词,可以模糊查询的;如果是keyword,就只能完全匹配,精确查询。
-
analyser(存储时生效的分词器),存储时候生效的分词器,用ik_max_word,拆解的多,尽可能被搜出。
-
search_analyser(查询时生效的分词器):用ik_smart,更偏向于用户想搜的分词
-
如果想让text的分词字段也支持精确查询
"fields": { "keyword": { "type": "keyword", "ignore_above": 256 //超过字符数忽略查询 } }
这些结构的选择,都是实践状态下的最佳实践,以后创建表结构参考这个案例就好了。
增删改查
第一种方式:通过ElasticsearchRepository.可以根据方法名,生成查询语句
public interface PostEsDao extends ElasticsearchRepository<PostEsDTO, Long> {
List<PostEsDTO> findByUserId(Long userId);
}
ArticleEsDTO articleEsDTO = new ArticleEsDTO();
articleEsDTO.setTitle("nika");
articleEsDTO.setContent("nika是南信大的学生");
articleEsDTO.setUserId(0L);
articleEsDTO.setCreateTime(new Date());
articleEsDTO.setUpdateTime(new Date());
articleEsDTO.setIsDelete(0);
articleEsDao.save(articleEsDTO);
System.out.println(articleEsDTO);
其它的api自己追一追源码即可,非常简单,和mybatis和mongoTemplate之类的东西非常类似,这里不一一列举。
第二种方式:更灵活,传入参数更多。
@Resource
private ElasticsearchRestTemplate elasticsearchRestTemplate;
同理,不一一列举
小总结
在写es的过程中,你会发现,只要你能在kibana的devtools里面把DSL写出来,就能映射到Java的api中。
因此我们在写复杂的业务的时候,尽量先在kibana的devtools中把这个DSL能跑通之后,再把这段逻辑映射到代码实现里。
尽量不要用sql,性能低下
ES数据同步
es的运用场景有很多,比如熟知的日志系统,搜索系统等。
他的主要优势点是查。而这个数据是从哪里来呢?没错就是从其它数据源来,当其他数据源的数据增加的时候,我们需要对es的数据做相应的同步。
同步策略无非以下几种:
- 定时任务同步。如每隔一段时间,根据上一分钟updatetime字段,从 MySQL中查询相应数据,同步到es。这个适用于,MySQL写的不多,查询需求不紧急的情况。
- 双写。在写MySQL的同时,写es。这个比较危险,需要事务进行保证双写要么成功要么失败。
- logStash组件
LogStash
这个比较简单,同学们按照本文讲的方式自己探索即可
转载自:https://juejin.cn/post/7225862001187651645