ElasticSearch
1.简介
ES是基于Lucene的高扩展的分布式搜索服务器,支持开箱即用。
优势:
- 扩展性好,可部署上百台服务器集群,处理PB级数据。(1PB = 1024TB)
- 索引数据、搜索数据速度非常快,接近实时。
- 支持全文检索、结构化搜索、数据分析、复杂语言处理等。
- 更适合查多改少的情景。
2.与Lucene关系
- Luncene是一个库。使用它需要使用Java语言集成在应用中,Lucene非常复杂,需要深入了解检索相关知识来理解它的工作。
- ES使用Java开发,以Lucene为核心来实现所有的索引和搜索功能。用简单的Restful接口隐藏Lucene的复杂度。
3.工作原理
ES使用了有限状态转换器实现了全文检索的倒排索引,实现了用于存储数值数据和地理位置数据的BDK树,以及用于分析的列存储。
ES不支持事物,默认将所有的字段全部建立索引。
向 Elasticsearch 中存储数据,其实就是向 es 中的 index 下面的 type 中存储 json 类型的数据。
当ES节点启动后,会利用多播寻找集群的其他节点,并与之建立连接。
ES中,存储数据的基本单位就是索引。
案例:ES中存储订单系统的销售数据,就在ES中创建了一个索引(order-index),所有销售数据都在该索引中,一个索引就像数据库,type(类型)相当于一张表,一个index会有多个type;mapping相当于表的结构定义,定义了什么字段类型,往index中的一个type加一行数据就叫做一个documnet,每个document有多个filed,每个filed代表一个字段的值。
4.核心概念
集群(cluster)
在ES中,集群是由一个或多个ES节点组成的,每个集群都有一个唯一的名称/标识符,用作节点加入集群的依据。
每一个集群中有一个Master节点,若是这个Master节点挂了,集群可以用其他的节点代替。 在ES中还支持跨集群复制、跨集群检索等等功能
实例和节点(Instances and Nodes)
节点也就是运行的ES实例,它隶属于某一个集群。假设当我们启动一个节点想加入已经存在的一个集群中时,可以在配置文件中配置该集群的名称,以及通信的ip + port,ES会自动通过“单点传送”的方式来自动发现集群并尝试加入这个集群。
节点分为下面几种类型:
- Master-eligible node:负责管理和配置集群,例如增加、删除节点相关动作。
- Data node:文档实际上就存储在数据节点,负责执行相关的操作, 例如CRUD、搜索、聚合等等操作
- Coordinating node:用于处理请求的路由、查询结果集的汇总、智能负载均衡..
- Ingest node:用于文档在indexing之前进行预处理
- Machine learning node:主要用于机器学习的任务,但是需要Basic License。
分片
如果有大量文档,由于内存限制、磁盘能力不足,无法快速响应请求,一个节点可能不够,这时数据可以分为较小的分片放在不同的服务器上。当查询的索引在不同的分片上,ES会把查询发送给每个相关的分片,并将结果组合在一起。
副本
为了提高吞吐量或实现高可用性,可以使用分片副本。
副本是一个分片的精确复制,一个分片有零个或多个副本。
ES中可以有许多相同的分片,其中一个可以修改索引结构,该分片为主分片。当主分片丢失时,集群将副分片提升为主分片。
端口号
9300端口: ES节点之间通讯使用
9200端口: ES节点 和 外部 通讯使用
9300是TCP协议端口号,ES集群之间通讯端口号
9200端口号,暴露ES RESTful接口端口号
5.逻辑结构
数据在ES中是一个倒排索引表:
- 将要搜索的文档内容分词,所有不重复的词组成分词列表。
- 将搜索的文档最终以Document方式存储。
- 每个词都与Document关联。
- 生成一个“关键词-文档”形式的映射结构。
案例:
- 文档1:中国古代的精美散文(长文章已关键词进行排序)
- 文档2:古代精美散文作者
- 文档3:如何写出精美散文
词典集合:
单词ID | 单词 | 倒排列表 |
---|---|---|
1 | 中国 | 1 |
2 | 古代 | 1,2 |
3 | 精美 | 1,2,3 |
4 | 散文 | 1,2,3 |
5 | 作者 | 2 |
6 | 如何 | 3 |
7 | 写出 | 2 |
分词器
standard分词器,默认分词器,对中文不友好。
IKAnalyzer: 免费开源的java分词器,目前比较流行的中文分词器之一。
需要注意ik插件的版本最好跟elasticsearch一致,不然要更改ik的配置。
更改方式:https://blog.csdn.net/Dongguabai/article/details/119932969
分词器设置
es支持创建索引和查询时分别使用不同的分词器
- analyzer,设置插入数据时的分词器
- search_analyzer,设置查询数据时的分词器,如果不指定,默认使用analyzer的分词器。
分词策略
ik_smart:做最粗力度的拆分,用于搜索,精确搜索。
共和国国歌会分成:共和国,国歌。
ik_max_word:最细粒度的拆分,最大化将文章内容分词。
共和国国歌会分成:共和国,共和,国,国歌。
字符串结构
text:会分词,不支持聚合、排序,支持模糊,精确查询。
keyword:不分词,支持聚合、排序,支持模糊,精确查询。
查询方式
term: 精确查询,对查询的值不分词,直接进倒排索引去匹配。 match:模糊查询,对查询的值分词,对分词的结果一一进入倒排索引去匹配
join查询
ES的连接查询有两种方式
nested
PUT index_test/type_info/1000 { "userId": 1000, "mobile": "13301020202", "nick": "梅西", "vipType": 1, "vipPoints": 1200, "regTime": "2018-06-18 12:00:31", "order": [ { "status": 1, "payMethod": 2, "amount": 100, "productCount": 3 }, { "status": 2, "payMethod": 2, "amount": 230, "productCount": 1 } ] }
parent/child关联查询
- 创建索引时,父文档和子文档的字段都是在一个索引中。
- 一个父文档可以对应多个子文档。
- 父/子文档是完全独立的。父文档更新不会影响子文档,反之同理。
- 查询时:子查父: has_child,父查子:has_parent
- ES 中不支持类似的 JOIN 查询。即便 child aggregation 也不能做到像 SQL 那样的 JOIN 操作!
- 如果需求父文档和子文档的数据同时获取,需要先查询父文档数据,再查询子文档数据。
- 子文档的数据需要放在父文档同一个分片下
- children aggregation:对关联的 child 文档进行聚合操作
PUT test_info { "mappings": { "properties": { "contentType": { "type": "integer" }, "title": { "type": "text" }, "description": { "type": "text" }, "content": { "type": "text" }, "article_join": { //关联字段名称,自定义 "type": "join", //定义该字段为关联字段 "relations": { //定义父子关联字段匹配关系 "article": [ //父文档关联字段名 "category", //子文档关联字段名,可以一个父文档对应多个子文档,以数组排列 "author" ] } }, "categoryId": { //子文档字段 "type": "keyword" }, "categoryName": { //子文档字段 "type": "text" }, "authorId": { //子文档字段 "type": "keyword" }, "authorName": { //子文档字段 "type": "text" }, "authorDescription": {//子文档字段 "type": "text" }, "job": { //子文档字段 "type": "text" }, "topImage": { //子文档字段 "type": "keyword" } } } }
父文档插入数据:
PUT julang_article_info/_doc/1
{
"contentType": 3,
"title": "杭盖乐队:个性造就独一无二",
"description": "杭盖乐队:个性造就独一无二",
"content": "<p>禹国刚:追忆深交所创立缘起|致敬中国股市三十年</p>",
"publishTime": 1664091487,
"duration": "185.452",
"shareUrl": "images/qrcode/209_qrcode.png",
"haibao": "images/haibao/209_haibao.png",
"qrcode": "images/qrcode/209_qrcode.png",
"code":"分享code",
"article_join": {
"name": "article"
}
}
子文档插入数据
PUT julang_article_info/_doc/category-1?routing=1 //需要放在同一个分片下
{
"categoryId": 111,
"categoryName": "测试视频标签1",
"article_join": {
"name":"category",
"parent":1 //父文档 _id
}
}
PUT julang_article_info/_doc/author-1?routing=1
{
"authorId": 111,
"authorName": "测试作者姓名1",
"authorDescription":"测试作者描述1",
"job":"测试作者工作1",
"topImage":"测试作者头像1",
"article_join": {
"name": "author",
"parent": 1
}
}
查询子文档
GET julang_article_info/_search
{
"query": {
"has_parent": {
"parent_type": "article",
"query": {
"term": {
"_id": "1"
}
}
}
}
}
主要区别:
- nested所有实体存储在一个type的文档中,parent-child模式,子type和父type存储在不同的文档里。
- 查询效率nested要高,但是在更新的时候,es会删除整个文档再创建,而parent-child只会删除更新的文档再创建,不影响其他文档。所以更新效率上parent-child要高。
使用场景:
- nested:少量子文档,并且不会经常改变的情况下使用。(用户消费流水)
- parent-child:大量子文档,频繁更新的情况下使用。(订单状态,物流信息)
使用建议:
慎用,不管是嵌套还是父子文档,以子数据为参数对嵌套或者父子文档数据聚合,都非常复杂,且性能低下。适用于以父数据为参数直接查询,不聚合操作。
6.部署
步骤:
//这里使用5.6.9版本较为合适
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.6.9.zip
unzip elasticsearch-5.6.9.zip
cd elasticsearch-5.6.9/
//ES在版本5以后不允许root用户启动,需手动创建用户
adduser elasticsearch
passwd elasticsearch
//将文件夹权限赋值给elasticsearch用户
chown -R elasticsearch /usr/local/elasticsearch-5.6.9
//切换用户
su elasticsearch
//启动es
./bin/elasticsearch -d
//检测是否启动
curl localhost:9200
修改config/elasticsearch.yml文件,network.host值改为远程主机的IP
安装分词插件:
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v5.6.9/elasticsearch-analysis-ik-5.6.9.zip
重新启动Elastic便会加载这个插件
//该命令显示已安装的插件
curl localhost:9200/_cat/plugins
IK分词器的版本号,要与ES的版本一致,如不一致则无法启动。
如果不引入中文分词器,那么ES会默认将每一个中文都会进行分词,不会智能组词。
7.SpringBoot整合ES
在 elasticsearch 的官网上提供了两种 java 语言的 API ,一种是 Java Transport Client,一种是 Java REST Client。
**Java Transport Client是基于 TCP 协议交互的,**在 elasticsearch 7.0+ 版本后官方不再赞成使用,在Elasticsearch 8.0的版本中完全移除 TransportClient
**Java REST Client 是基于 HTTP 协议交互,**而 Java REST Client 又分为 Java Low Level REST Client 和 Java High Level REST Client
Java High Level REST Client 是在 Java Low Level REST Client 的基础上做了封装,使其以更加面向对象和操作更加便利的方式调用 elasticsearch 服务。
es 8.x 中废弃了
RestHighLevelClient
,使用新版的java api client
,但是spring data elasticsearch还未更新到该版本.所以需要兼容es 8.x。 初始化需引入new RestHighLevelClientBuilder(httpClient).setApiCompatibilityMode(true).build();
QueryBuilder (Java High Level REST Cilent)
QueryBuilder 主要用来构建查询条件、过滤条件,SortBuilder主要是构建排序。
要构建QueryBuilder ,我们可以使用工具类QueryBuilders,主要是它的实现类BoolQueryBuilder 。里面有大量的方法用来完成各种各样的QueryBuilder的构建,字符串的、Boolean型的、match的、地理范围的等等。
要构建SortBuilder,可以使用SortBuilders来完成各种排序。
然后就可以通过NativeSearchQueryBuilder来组合这些QueryBuilder和SortBuilder,再组合分页的参数等等,最终就能得到一个SearchQuery了。
8.可视化工具(Kibana)
需要与es版本一致,否则报错。
修改kibana.yml中远程地址,连接远程ip的ElasticSearch。
elasticsearch.url: "http://81.70.31.48:8085"
数据操作
创建索引
shell# 语法: PUT /<index> # 示例: PUT /laowang
创建数据
shell# 语法 PUT /<index>/_doc/<_id> POST /<index>/_doc/ PUT /<index>/_create/<_id> POST /<index>/_create/<_id> index:索引名称,如果索引不存在,会自动创建 _doc:类型 <_id>:唯一识别符,创建一个数据时,可以自定义ID,也可以让他自动生成 # ES 存储数据三个必要构成条件,每一条数据必须有以上的数据结构 # 示例: PUT /student/user/4 { "name":"congtianqi", "sex":"male" }
简单查询
shell# 查看所有索引信息 GET /_all GET _all # 查看所有索引的数据 GET /_all/_search # 查看指定索引信息 GET /student # 查看指定索引的数据 GET /student/_search # 查看指定数据 GET /student/user/1
条件查询
shell# 可以省略 value 行,与 Key 合并到一行 GET /news/_search { "query": { "term": { <-------- 使用 term 匹配,适用于精确查找 "title":"北京烤鸭" <------- 简写,并为一行 } } } # 方法二: GET /news/_search { "query": { "match": { <-------- 使用 match 匹配,适用于模糊查找 "title": "北京烤鸭" } } }
修改数据
shell# 注意,修改数据时,除了要修改的值,其他字段的值也要带上,否则原有的其他字段会丢失 PUT /student/user/2 { "name":"wqh", "gender":"male", "age":"19" }