一、es 介绍
1、背景
在订单管理系统中,订单查询的调用量都非常大,如果直接查询数据库,那数据库的压力可想而知,而且有时需要执行一些复杂的查询,sql 并不能够友好的支持,需要查询很多张表。再比如用户手误输入的关键词错了或存在错别字,那使用 sql 是无法搜索到。所以打算使用 Elasticsearch 来承载订单查询的主要压力。
总的来说,使用 es 的几个原因如下
关系型数据库在进行模糊(%关键字%)搜索的时候,会全表扫描,查询非常慢
关系型数据库在关键字搜索时,并不支持全文分词搜索,比如用户本打算搜索:公众号-臻大虾,却手误:公号-臻大虾,es 可以根据分词的结果搜索出想要的结果。
在数据分析、日志分析上用到 es
2、es 基本概念
Elasticsearch 是一个分布式、RESTful 风格的搜索和数据分析引擎,适用于包括文本、数字、地理空间、结构化和非结构化数据等在内的所有类型的数据。Elasticsearch 在 Apache Lucene 的基础上开发而成,由 Elasticsearch N.V.(即现在的 Elastic)于 2010 年首次发布。
Elasticsearch 是文件存储,Elasticsearch 是面向文档型数据库,一条数据在这里就是一个文档,用 JSON 作为文档序列化的格式,比如下面这条用户数据:
{
"name":"臻大虾",
"sex":0,
"age":24
}
3、es 优势
- 分布式:横向可扩展性,增加服务器可直接配置在集群中
- 高可用:提供了复制功能,具有容错机制,能自动发现新的或失败的节点,重组和重新平衡节点数据
- 实时性:数据进入 es,可达到近实时搜索
- Restful api:json 格式的 RESTful 风格
- 全文检索:基于 lucene 的强大的全文检索能力
4、使用场景
全文检索
当我们使用百度搜索、谷歌搜索时,输入关键字,就能搜索到最相关的文章,这就是利用了 es 强大的全文检索的能力。
用户行为
平时淘宝买东西时,你是否发现推荐的商品跟你最近搜索的关键词很享受,这就是通过收集用户的行为日志,分析并建立用户模型,保存在 es 中,并利用 es 强大的深入搜索和聚合的能力,可以更好的分析和展示用户的行为数据。例如推荐系统,就是利用用户模型的用户数据,对用户数据交叉查询,分析出用户细粒度的喜好。
监控系统
利用 es 高性能查询的特性,收集系统的监控数据,近实时展现监控数据,同时也方便用户对监控数据进行关键字排查。
日志系统
常用的方案是 ELK(elasticsearch+logstash+kibana),利用 logstash 去收集 logback 的日志信息,再通过 es 做存储,最后可以再 kibana 去利用 es api 查看和分析日志的相关信息。
5、es 的核心概念
索引(index)
索引是 es 最大的数据单元,类似于关系型数据库中的库,是多个相似文档的集合。每个索引有一个或多个分片,每个分片有多个副片。
文档(document)
一条数据就是一个文档,类似于数据库表中的一条记录,比如:
{
"name":"臻大虾",
"sex":0,
"age":24
}
字段(field)
文档的属性,类似表中的字段,比如如下 json 的健:name
{
"name":"臻大虾"
}
映射(mapping)
映射是对文档中每个字段类型进行定义,类似表结构,包含数据类型,长度之类的,比如:
"mappings": {
"properties": {
"name": {
"type": "text"
},
"age": {
"type": "long"
}
}
}
分片(Shards)
在创建索引时,可以设置主分片个数和副本个数,类似数据库的分表,将单个索引文件分成多份存储,当请求过来时,通过路由计算找到主分片(hash(字段,比如 id)%分主片数量)。
好处:
- 如果一个索引数据量很大,会造成硬盘和搜索速度的瓶颈,分片能分担压力
- 分片允许我们进行水平切分和扩展容量
- 可以在多个分片上进行分布式的、并行的操作,提高系统吞吐量
注意:主分片在创建之后是无法修改的,而副本可以随时修改。那想修改主分片的数量怎么办呢,删除重新建。
"settings": {
"number_of_shards": 2,//主分片
"number_of_replicas": 1//副本
}
副本(Replicas)
由主分片复制来的,提供高可用
好处:
- 高可用,当一个主分片挂了,副本可以代替工作
- 副本也可以执行搜索操作,分摊了主分片的压力
集群(Cluster)
一个集群就是由一个或多个节点组织在一起,具有相同集群名的节点才能组成一个集群。它们共同持有整个的数据,并一起提供索引和搜索功能。
注意:主分片和副本处于不同节点,这样当主分片的机器挂了,副本由于在不同机器上,不会受到影响,副本变为主分片继续工作。所以 es 最小的高可用配置为两台服务器
节点(node)
单个 es 实例称为一个节点(node),一个节点是集群中的一个服务器,作为集群的一部分,存储数据。
类型(type)
7.x 移除了 type,8.x 将彻底移出
二、索引原理
es 使用的是倒排索引也叫反向索引,既然有倒排索引,那是不是有正排索引,有的,我们先介绍下正排索引。
1、正排索引
正排索引是以文档的 ID 为关键字,文档中每个字段的值为 value,主要场景是通过 id 获取文档信息,平时用的 msyql 关系型数据库就是以这种方式查询的。
举个例子
id | 内容 |
---|---|
1 | my name is zhendaxia |
2 | my name is jack |
通过 id 可以很快查询到内容,但是当查询比如 name 的时候,需要使用 like,再加上数据量大的时候,查询的时间是很久的,无法满足查询快速的要求。
2、倒排索引
倒排索引是以字或词为关键字进行索引,记录出现这个关键词的文档的 ID
比如上面的例子使用倒排索引如下:
content | docid |
---|---|
my | 1,2 |
name | 1,2 |
is | 1,2 |
zhendaxia | 1 |
jack | 2 |
倒排索引,通过字或词快速的找到所有文档的 id,在根据文档 id 能快速找到内容。由于人类的词汇数量是相对有限且固定的,所以效率并不会由于日后关键词的增长而受到很大的影响。
三、集群扩容
1、集群健康
集群的健康状态有三种:绿色 green、黄色 yellow、红色 red
绿色(健康):所有的主分片和副分片都正常运行
黄色(亚健康):所有主分片正常运行,但有副分片没正常运行
红色(不健康):有主分片没正常运行
2、扩容
扩容一般分为两种,垂直和水平
1)、垂直扩容
升级服务器,买性能更好的服务器替换原有的服务器,不过这种扩容不推荐,毕竟单台机器的性能总是有瓶颈的
2)、水平扩容
水平扩容也叫横向扩容,就是增加服务器数量,多台普通的服务器组织在一起形成强大的计算能力。俗话说:团结就是力量。
四、浏览器插件
head 插件是 ES 的一个可视化插件,类似于 navicat 和 mysql 的关系。head 插件是一个用来浏览、与 ES 数据进行交互的 web 前端展示插件,是一个用来监视 ES 状态的客户端插件。
以下是插件的一些简单介绍
五、常用 api
1、创建索引
PUT /index
{
"settings": {
"number_of_shards": 2,
"number_of_replicas": 1
},
"mappings": {
"properties": {
"text_name": {
"type": "text"
},
"keyword_name": {
"type": "keyword"
},
"english_name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword"
}
}
},
"age": {
"type": "long"
},
"classId": {
"type": "long"
},
"score": {
"type": "long"
},
"createTime": {
"type": "long"
}
}
}
}
当看到请求体时,细心的你可能会发现 text_name、keyword_name、english_name 这三个字段都是字符串,但类型好像有些不同,区别是什么呢?是的,这几个类型往往是刚接触 es 的新手经常弄错的地方。
首先,看下 text 和 keyword 的区别
text:可以分词,用户全文搜索,可以模糊匹配搜索
keyword:不能分词,关键词搜索,只能对某个值进行整体搜索
type 是 text,但有 fields-keyword:这种类型,一种是自己加入的,另一种是在往 es 插入数据的时候,字段 english_name 还没有创建。
这时 es 会根据数据类型,自动帮你创建一个字段,如果是字符串类型,由于无法判断你的这个字符串你是用来精确查询还是模糊查询,所以 es 会创建类型是 text,支持模糊查询,同时会创建 fields,type 是 keyword,支持精确查询,所以当你要精确查询的时候,字段名就不是原来的 english_name,而是要使用 english_name.keyword
举个例子来说明下,首先插入了以下数据,关键字 zhen
{
"text_name": "zhen daxia",
"keyword_name": "zhen daxia",
"english_name": "zhen daxia",
"age": 18,
"classId": 2,
"score": 90,
"createTime": 1629353892784
}
- 查询 text_name,由于 text_name 类型是 text,会讲 zhen daxia 分词为 zhen、daxia,所以当使用 zhen 查询时,能匹配到 zhen,所以会有结果返回.
如何查看 zhen daxia 被分为哪些词语,可以使用 GET 你的索引/_doc/数据 id/_termvectors?fields=字段名,比如我的索引是 test-user,那语句就是:GET test-user/_doc/1/_termvectors?fields=text_name
GET test-user/_search
{
"query": {
"term": {
"text_name": {
"value": "zhen"
}
}
}
}
- 查询 keyword_name,由于 keyword_name 类型是 keyword,不会分词,所以 zhen 无法搜索到数据
- 查询 english_name,同 text_name,可以搜到
- 查询 english_name.keyword,同 keyword_name,无法搜索到结果
2、增加映射字段
PUT /index/_mapping
{
"properties":{
"keyword-name":{
"type":"keyword"
}
}
}
3、查询
GET test-user/_search
3.1 match(全文检索)
全文检索,会分词,模糊查询,比如关键字 zhen daxia,会被拆为 zhen、daxia
{
"query": {
"match": {
"text_name": "zhen daxia"
}
}
}
spring boot 方法
boolQueryBuilder.filter(QueryBuilders.matchQuery("text_name", "zhen daxia"));
3.2 term(精确查询)
精确查询,不会拆词,比如关键字 zhen daxia,会直接使用 zhen daxia 搜索
{
"query": {
"term": {
"keyword_name": {
"value": "zhen daxia"
}
}
}
}
spring boot 方法
QueryBuilders.termQuery("keyword_name", "zhen daxia");
3.3 terms(多值匹配)
和 term 查询一样,但它允许你指定多值进行匹配,如果这个字段包含了指定值中的任何一个值,那么这个文档就算是满足条件。类似 mysql 的 in
{
"query": {
"terms": {
"keyword_name": [
"zhen",
"daxia"
]
}
}
}
spring boot 方法
QueryBuilders.termsQuery("keyword_name", Lists.newArrayList("zhen","daxia"));
3.4 range(范围查询)
范围查询,比如搜索大于等于 20 且小于等于 30 的数据
{
"query": {
"range": {
"age": {
"gte": 20, # 大于等于 大于用 gt
"lte": 30 # 小于等于 小于用 lt
}
}
}
}
spring boot 方法
RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("age");
rangeQueryBuilder.gte(20);
rangeQueryBuilder.lte(30);
3.5 prefix(前缀查询)
前缀查询,比如搜索 zhen,则前缀是 zhen 的都会被搜索出来
{
"query": {
"prefix": {
"keyword_name": {
"value": "zhen"
}
}
}
}
spring boot 方法
QueryBuilders.prefixQuery("keyword_name","zhen");
3.6 wildcard(通配符模糊查询)
通配符模糊查询,类似 mysql 的 like,?匹配一个字符,*匹配 0~n 个字符
{
"query": {
"wildcard": {
"keyword_name": {
"value": "*大虾"
}
}
}
}
spring boot 方法
QueryBuilders.wildcardQuery("keyword_name","*大虾")
3.7 fuzzy(模糊查询,不精确查询)
不同于 mysql 的 like,它可以错误一些字,比如搜索 mock,可以搜索出 mick
{
"query": {
"fuzzy": {
"keyword_name": "mock"
}
}
}
spring boot 方法
QueryBuilders.fuzzyQuery("keyword_name","mock");
3.8 must、must not、should
//must:必须
boolQueryBuilder.must(QueryBuilders.termQuery("keyword_name","mick"));
//must not:非
boolQueryBuilder.mustNot(QueryBuilders.termQuery("keyword_name","mick"));
//should:类似mysql的或
boolQueryBuilder.should(QueryBuilders.termQuery("keyword_name","jack"));
boolQueryBuilder.should(QueryBuilders.termQuery("keyword_name","mick"));
3.9 match all(查询全部)
查询全部,默认 10 条
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
sourceBuilder.query(matchAllQueryBuilder);
sourceBuilder.size(10);
3.10 match_phrase
分词后,待查询的字段同时匹配分词后的所有关键词
顺序也是一样
比如有以下数据:
1. keyword_name:zhen daxia
2. keyword_name:daxia zhen
3. keyword_name:I am zhen daxia
4. keyword_name:daxia haha
查询 zhen daxia,则返回 1 和 3,2:顺序不对,4:没有匹配到全部分词
可通过 slp 调节因子,比如 1,少匹配一个也满足
{
"query": {
"match_phrase": {
"keyword_name": {
"query": "zhen daxia",
"slop": 1
}
}
}
}
3.11 multi_match(多字段匹配)
多字段匹配,有一个字段匹配,就满足,keyword_name=jack,或 english_name=jack,就算满足
{
"query": {
"multi_match": {
"query": "jack",
"fields": ["keyword_name","english_name"]
}
}
}j
3.12 filter 和 must(过滤)
filter 与 must 是属于同一个级别的查询方式,都可以作为 query->bool 的属性
filter: 不计算评分, 查询效率高;有缓存(推荐)
must: 要计算评分,查询效率低;无缓存
3.13 聚合查询(聚合)
根据名字分组
builder.aggregation(AggregationBuilders.terms("agg").field("keyword_name").size(10));
我是臻大虾,分享更多java后端干货,咱们下期见