正排索引(正常索引)
静夜思
鹅
倒排索引
es
-----------
关系型数据库 es
database index
table type(6.x之前一个index有多个type, 6.x开始一个index只能有一个type,
7.x开始把type去掉)
row document
column field
Elasticsearch是一个高度可伸缩的开源全文搜索引擎。Elasticsearch让你可以快速、实时地存储、搜索和分析大量数据,它通常作为互联网应用的内部搜索引擎,为需要复杂搜索功能的应用提供支持。
ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。
redis | mysql | elasticsearch | hbase | hadoop/hive | |
---|---|---|---|---|---|
容量/容量扩展 | 低 | 中【单表500G接近极限】 | 较大【T级别】 | 海量 | 海量 |
查询时效性 | 极高 | 中等 | 较高 | 中等 | 低 |
查询灵活性 | 较差 k-v模式 | 非常好,支持sql | 较好,关联查询较弱,但是可以全文检索,DSL语言可以处理过滤、匹配、排序、聚合等各种操作 | 较差,主要靠rowkey,scan的话性能不行,或者建立二级索引 | 非常好,支持sql |
写入速度 | 极快 | 中等 | 较快 | 较快 | 慢 |
一致性、事务 | 弱 | 强 | 弱(不支持join) | 弱 | 弱 |
天然分片,天然集群
天然索引
ES 所有数据都是默认进行索引的,这点和MySQL正好相反,MySQL是默认不加索引,要加索引必须特别说明,ES只有不加索引才需要说明。
--而ES使用的是倒排索引和MySQL的B+Tree索引不同。
'传统关系性数据库弊端'
1)对于传统的关系性数据库对于关键词的查询,只能逐字逐行的匹配,性能非常差。
2)匹配方式不合理,比如搜索“小密手机” ,如果用like进行匹配, 根本匹配不到。但是考虑使用者的用户体验的话,除了完全匹配的记录,还应该显示一部分近似匹配的记录,至少应该匹配到“手机”。
'倒排索引是怎么处理的'
全文搜索引擎目前主流的索引技术就是倒排索引的方式。
传统的保存数据的方式都是 --记录→单词
而倒排索引的保存数据的方式是 --单词→记录
基于分词技术构建倒排索引:
1.首先每个记录保存数据时,都不会直接存入数据库。系统先会对数据进行分词,然后以倒排索引结构保存。
2.然后等到用户搜索的时候,会把搜索的关键词也进行分词,会把“红海行动”分词分成:红海和行动两个词。
3.这样的话,先用红海进行匹配,得到id=1和id=2的记录编号,再用行动匹配可以迅速定位id为1,3的记录。
4.那么全文索引通常,还会根据匹配程度进行打分,显然1号记录能匹配的次数更多。所以显示的时候以评分进行排序的话,1号记录会排到最前面。而2、3号记录也可以匹配到。
'索引结构对比'
传统关系性数据库:B+Tree
ES:Lucene 倒排索引结构
1.Lucene是第一个提供全文文本搜索的函数库,提供了一个简单而强大的应用程序接口;
2.Nutch则是Doug在Lucene基础上将开源思想继续深化的成果,是一个真正的应用程序;
3.ElasticSearch ,简称为ES , ES是一个开源的高扩展的分布式全文检索引擎,它可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上百台服务器,处理PB级别的数据。ES也使用Java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单。
1.近实时(Near Realtime /NRT)
ES是一个近实时的搜索平台,从生成文档索引到文档成为可搜索,有一个轻微的延迟(通常是一秒钟)
2.集群(Cluster)
ES 默认就是集群状态,整个集群是一份完整、互备的数据。
集群是一个或多个节点(服务器)的集合。集群中的节点一起存储数据,对外提供搜索功能。集群由一个唯一的名称标识,该名称默认是“elasticsearch”。集群名称很重要,节点都是通过集群名称加入集群。
集群不要重名,取名一般要有明确意义,否则会引起混乱。例如,开发、测试和生产集群的名称可以使用logging-dev、logging-test和logging-prod。
集群节点数不受限制,可以只有一个节点。
3.节点(Node)
节点是一个服务器,属于某个集群。节点存储数据,参与集群的索引和搜索功能。与集群一样,节点也是通过名称来标识的。默认情况下,启动时会分配给节点一个UUID(全局惟一标识符)作为名称。如有需要,可以给节点取名,通常取名时应考虑能方便识别和管理。
默认情况下,节点加入名为elasticsearch的集群,通过设置节点的集群名,可加入指定集群。
/**/
4.索引(Index) --类似rdbms的database(5.x)
索引是具有某种特征的文档集合,相当于一本书的目录。例如,可以为客户数据建立索引,为订单数据建立另一个索引。索引由名称标识(必须全部为小写),可以使用该名称,对索引中的文档进行建立索引、搜索、更新和删除等操作。一个集群中,索引数量不受限制。
类似于rdbms的database(5.x), 对于用户来说是一个逻辑数据库,虽然物理上会被分多个shard存放,也可能存放在多个node中。 6.x 7.x index相当于table
5.类型(Type) --类似于rdbms的table
类似于rdbms的table,但是与其说像table,其实更像面向对象中的class , 同一Json的格式的数据集合。(6.x只允许建一个,7.0被废弃,造成index实际相当于table级
6.文档(Document) --类似于rdbms的 row、面向对象里的object
文档是可以建立索引的基本信息单元,相当于书的具体章节。
例如,可以为单个客户创建一个文档,为单个订单创建另一个文档。文档用JSON (JavaScript对象表示法)表示。在索引中,理论上可以存储任意数量的文档。
/**/
7.字段|属性(Filed) --相当于字段、属性
8.分片与副本(Shards & Replicas)
索引可能存储大量数据,数据量可能超过单个节点的硬件限制。
'分片重要的原因:'
允许水平切分内容,以便内容可以存储到普通的服务器中
允许跨分片操作(如查询时,查询多个分片),提高性能/吞吐量
分片如何部署、如何跨片搜索完全由Elasticsearch管理,对外是透明的。
网络环境随时可能出现故障,如果某个分片/节点由于某种原因离线或消失,那么使用故障转移机制是非常有用的,强烈建议使用这种机制。为此,Elasticsearch允许为分片创建副本。
'副本重要的原因'
在分片/节点失败时提供高可用性。因此,原分片与副本不应放在同一个节点上。
扩展吞吐量,因为可以在所有副本上并行执行搜索。
总而言之,索引可以分片,索引分片可以创建副本。复制后,每个索引将具有主分片与副本分片。
创建索引时,可以为每个索引定义分片和副本的数量。之后,还可以随时动态更改副本数量。您可以使用_shrink和_split api更改现有索引的分片数量,但动态修改副本数量相当麻烦,最好还是预先计划好分片数量。
默认情况下,Elasticsearch中的每个索引分配一个主分片和一个副本(7.X之前,默认是5片,副本是0。7.X默认改为1片,副本为1)。如果集群中有两个节点,就可以将索引主分片部署在一个节点,副本分片放在另一个节点,提高可用性。
MySQL | ES5.X | ES6.X | ES7.X |
---|---|---|---|
Database | Index | ||
Table | Type | Index(Type成了摆设) | Index(Type被移除掉) |
Row | Document | Document | |
Column | Field | Field |
假设有如下实体
public class Movie {
String id;
String name;
Double doubanScore;
List<Actor> actorList;
}
public class Actor{
String id;
String name;
}
这两个对象如果放在关系型数据库保存,会被拆成2张表,但是ElasticSearch是用一个 json来表示一个document。
保存到ES中应该是
{
"id":"1",
"name":"operation red sea",
"doubanScore":"8.5",
"actorList":[
{"id":"1","name":"zhangyi"},
{"id":"2","name":"haiqing"},
{"id":"3","name":"zhanghanyu"}
]
}
[atguigu@hadoop162 software]$ tar -zxvf elasticsearch-6.6.0.tar.gz -C /opt/module/
[atguigu@hadoop162 module]$ mv elasticsearch-6.6.0/ elasticsearch
修改yml配置的注意事项:
每行必须顶格,不能有空格
“:”后面必须有一个空格
[atguigu@hadoop162 elasticsearch]$ cd config/
[atguigu@hadoop162 config]$ vim elasticsearch.yml
#集群名称,同一集群名称必须相同
cluster.name: atguigu
#单个节点名称
node.name: hadoop162
#把bootstrap自检程序关掉
bootstrap.memory_lock: false
bootstrap.system_call_filter: false
#网络部分 改为当前的ip地址 ,端口号保持默认9200就行
network.host: hadoop162
http.port: 9200
#自发现配置:新节点向集群报到的主机名
discovery.zen.ping.unicast.hosts: ["hadoop162","hadoop163","hadoop164"]
vim /opt/module/elasticsearch/config/jvm.options
[atguigu@hadoop162 module]$ my_rsync elasticsearch
hadoop163修改为:node.name: node-2 network.host: hadoop163
[atguigu@hadoop163 ~]$ cd /opt/module/elasticsearch/config/
[atguigu@hadoop163 config]$ vim elasticsearch.yml
hadoop164修改为:node.name: node-3 network.host: hadoop164
[atguigu@hadoop164 ~]$ cd /opt/module/elasticsearch/config/
[atguigu@hadoop164 config]$ vim elasticsearch.yml
这时直接在hadoop162上单独启动ES,会报如下异常:
[atguigu@hadoop162 bin]$ ./elasticsearch
因为默认elasticsearch是单机访问模式,就是只能自己访问自己。但是上面我们已经设置成允许应用服务器通过网络方式访问,而且生产环境也是这种方式。这时,Elasticsearch就会因为嫌弃单机版的低端默认配置而报错,甚至无法启动。所以我们在这里就要把服务器的一些限制打开,能支持更多并发。
问题1max file descriptors [4096] for elasticsearch process likely too low, increase to at least [65536] elasticsearch
原因:
系统允许 Elasticsearch 打开的最大文件数需要修改成65536
解决:
sudo vim /etc/security/limits.conf
添加内容
* soft nofile 65536
* hard nofile 131072
* soft nproc 2048
* hard nproc 65536
注意:*不要省略
分发文件
sudo /home/atguigu/bin/xsync /etc/security/limits.conf
问题2:max virtual memory areas vm.max_map_count [65530] likely too low, increase to at least [262144]
原因:
一个进程可以拥有的虚拟内存区域的数量。
解决:
sudo vim /etc/sysctl.conf
在文件最后添加一行
vm.max_map_count=262144
即可永久修改
分发文件:
sudo /home/atguigu/bin/xsync /etc/sysctl.conf
重启linux使配置生效
再次单独启动hadoop162上的ES
[atguigu@hadoop162 bin]$ ./elasticsearch
测试方式1 :curl http://hadoop162:9200/_cat/nodes?v
测试方式2 :在浏览器中,输入http://hadoop162:9200/查看效果
[atguigu@hadoop162 software]$ tar -zxvf kibana-6.6.0-linux-x86_64.tar.gz -C /opt/module/
[atguigu@hadoop162 module]$ mv kibana-6.6.0-linux-x86_64/ kibana
[atguigu@hadoop162 kibana]$ cd config/
[atguigu@hadoop162 config]$ vim kibana.yml
#授权远程访问
server.host: "hadoop162"
server.port: 5601
#指定Es地址(可以指定多个,多个之间用逗号分隔)
server.name: "atguigu-es"
elasticsearch.hosts: ["http://hadoop162:9200"]
启动Kinana
[atguigu@hadoop162 kibana]$ bin/kibana
成功后,提示如下
查看进程
[atguigu@hadoop162 ~]$ ps -ef |grep node
浏览器访问http://hadoop162:5601/
查询集群健康情况
GET /_cat/health?v ?v表示显示头信息
查询各个节点状态
GET /_cat/nodes?v
GET /-cat/indices?v
ES中会默认存在一些索引
health | green(集群完整) yellow(单点正常、集群不完整) red(单点不正常) |
---|---|
status | 是否能使用 |
index | 索引名 |
uuid | 索引统一编号 |
pri | 主节点几个分片 |
rep | 从节点几个(副本数) |
docs.count | 文档数 |
docs.deleted | 文档被删了多少 |
store.size | 整体占空间大小 |
pri.store.size | 主节点占空间大小 |
--API:PUT 索引名?pretty
PUT movie_index
PUT movie_index?pretty
使用PUT创建名为“movie_index”的索引。末尾追加pretty,可以漂亮地打印JSON响应(如果有的话)。红色警告说在7.x分片数会由默认的5改为1,我们忽略即可
索引名命名要求:
Ø 仅可以为小写字母,不能下划线开头
Ø 不能包括 , /, *, ?, ", <, >, |, 空格, 逗号, #
Ø 7.0版本之前可以使用冒号:,但不建议使用并在7.0版本之后不再支持
Ø 不能以这些字符 -, _, + 开头
Ø 不能包括 . 或 …
Ø 长度不能超过 255 个字符
--API:GET /_cat/shards/索引名
GET /_cat/shards/movie_index
GET /_cat/shards/movie_index?v
--API:DELETE /索引名
DELETE /movie_index
get :查询
put :添加、创建
post :
delete : 删除
--格式
PUT /索引/类型/id{ --这个id是es的唯一身份
json语句
}
--案例实操
PUT /user/_doc/1
{
"id": 1, --这个id只是内容里普普通通的字段
"name": "lisi",
"age": 20,
"girls": [
{"name": "zhiling", "age": 50},
{"name": "fengjie", "age": 40}
]
}
GET /_cat/shards/user?v --查看user索引的状况
--GET /索引名/类型/id
GET /user/_doc/1
--输出格式解析
{
"_index" : "user", --索引
"_type" : "_doc", --类型
"_id" : "1", --id
"_version" : 4, --版本
"_seq_no" : 3,
"_primary_term" : 3,--主条目
"found" : true,
"_source" : { --document的具体内容
"id" : 1,
"name" : "lisi",
"age" : 20,
"girls" : [
{
"name" : "zhiling",
"age" : 50
},
{
"name" : "fengjie",
"age" : 40
}
]
}
}
--查询所有
GET /user/_doc/_search
一般写成
GET /user/_search --该索引下所有类型的所有数据【相当于全局搜索】
默认显示10条,可以通过size控制,只是控制显示几条,该查出几条还是几条
GET /user/_search
{
"size":10
}
--GET /user/_search搜索出的数据解析,加上上一条解析
{
"took" : 21, --花费的时间
"timed_out" : false, --有没超时
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : { --是否命中
"total" : 2, --两条记录
"max_score" : 1.0, --最大的分数,分数大的在前面
"hits" : [
] --具体命中的document的数据
DELETE /user/_doc/1 --很少执行删除操作
--整体替换,将之前的换成现在的 【和新增没多大区别】
PUT /user/_doc/2
{
"id":3
}
--部分替换,只想把名字换掉,其它的不变
POST /user/_doc/1/_update
{
"doc":{
"name": "zs",
--更改中有新增的也可加进去
"a":7
}
}
--同一个文档就是同一个id,所以同一个id插入多次就会覆盖
--插入一次跟多次,都是这一个id。幂等性
--幂等性:同一个操作,操作多次对系统的影响是一样的
--更新【幂等性】
--部分替换,只想把名字换掉,其它的不变
POST /user/_doc/1/_update
{
"doc":{
"name": "zs",
--更改中有新增的也可加进去
"a":7
}
}
--【非幂等性】,执行一次多一条
--可加id也可不加,
--不加的话,加入到数据是幂等的
--不加ID的时候只能用POST
POST /user/_doc/1
{
"id": 1,
"name": "lisi",
"age": 20,
"girls": [
{"name": "zhiling", "age": 50},
{"name": "fengjie", "age": 40}
]
}
POST /索引名/类型名/_bulk?pretty
"_bulk表示批量操作"
注意:Kibana要求批量操作的json内容写在同一行
--需求一:添加数据的批处理
POST /movie_index/movie/_bulk
{"index":{"_id":66}}
{"id":300,"name":"incident red sea","doubanScore":5.0,"actorList":[{"id":4,"name":"zhang cuishan"}]}
{"index":{"_id":88}}
{"id":300,"name":"incident red sea","doubanScore":5.0,"actorList":[{"id":4,"name":"zhang cuishan"}]}
--需求二:对操作的批次处理
POST /movie_index/movie/_bulk
{"update":{"_id":"66"}}
{"doc": { "name": "wudangshanshang" } }
{"delete":{"_id":"88"}}
GET /索引名/_search?q=* &pretty
GET /movie_index/_search的完整体:
GET /movie_index/_search
{
"query": {
"match_all": {}
}
}
GET movie_index/movie/_search
{
"query":{
①结果最多
"match": {
"name":"operation red"
--搜索出来的是包含operation或者包含red,或者全包含的
--如果要让这两变成完整的不可分割的整体如下操作
②最少 类似于sql的where a =''
"name.keyword": "operation red sea"
--这个必须完全匹配,如果只要搜包含operation red的,见操作二
--词如果搜索到匹配到越多,分数越高
}
--操作二
③次之 类似于sql中的like "%abc%"
"match_phrase": {
"name":"operation red"
--能搜索包含operation red这个整体的,operation red不糊拆分搜索
}
}
}
--一般代双引号的会分词,不带的数字等不分词
ES中,name属性会进行分词,底层以倒排索引的形式进行存储,对查询的内容也会进行分词,然后和文档的name属性内容进行匹配,所以命中3次,不过命中的分值不同。
注意:ES底层在保存字符串数据的时候,会有两种类型text和keyword
text:分词
keyword:不分词
GET movie_index/movie/_search
{
"query":{
"match": {"actorList.name":"zhang han yu"}
}
}
按短语查询,不再利用分词技术,直接用短语在原始数据中匹配
GET movie_index/movie/_search
{
"query":{
"match_phrase": {"actorList.name":"zhang han yu"}
}
}
GET movie_index/movie/_search
{
"query":{
"term":{
"doubanScore":5.0
}
}
}
GET movie_index/movie/_search
{
"query":{
"match":{
"actorList.name.":"zhang han yu"
--这是会查找包含zhang的han的yu的,包含其中一个或者两个或者全包含的
}
}
}
----------------------------------------------------------------
--terms匹配一个值
GET movie_index/movie/_search
{
"query":{
"term":{
"actorList.name.keyword":"zhang han yu"
--只会查找zhang han yu
}
}
}
----------------------------------------------------------------
--terms匹配多个值
GET movie_index/movie/_search
{
"query":{
"term":{
"doubanScore":[
"5.0",
"8.0"
]
}
}
}
校正匹配分词,当一个单词都无法准确匹配,ES通过一种算法对非常接近的单词也给与一定的评分,能够查询出来,但是消耗更多的性能,对中文来讲,实现不是特别好。
GET movie_index/movie/_search
{
"query":{
"fuzzy": {"name":"rad"}
}
}
PUT /bank
POST /bank/_doc/_bulk?pretty
--加入银行文档的数据
GET /bank/_search
--查询出性别为女的,再过滤出地址为171的
GET /bank/_search
{
"query":{
"match":{
"gender":"F"
--大小写搜都行,只是索引会变成小写去搜索
}
},
"post_filter":{
"term":{
"address":"171"
}
}
}
--这个相当于与操作,用的不多,一般先过滤后查询
--must 与 【女的跟171的】
GET /bank/_search
{
"query":{
"bool":{ --组合查询必须用bool
"must":[ --与
{ "match":{
"gender":"F"
} },
{ "term":{
"value": "171"
} }
]
}
}
}
--should 或 【要么是女的,要么是171的】
GET /bank/_search
{
"query":{
"bool":{ --组合查询必须用bool
"should":[ --或
{ "match":{
"gender":"F"
} },
{ "term":{
"value": "171"
} }
]
}
}
}
--must_not 非 [不是女的,不是171的]
GET /bank/_search
{
"query":{
"bool":{ --组合查询必须用bool
"must_not":[ --或
{ "match":{
"gender":"F"
} },
{ "term":{
"value": "171"
} }
]
}
}
}
--must 和should同时出现 should不影响hit命中,影响Score分数
GET /bank/_search
{
"query":{
"bool":{ --组合查询必须用bool
"must":[ --必须是女的
{ "match":{
"gender":"F"
} }
],
"shoule":[ --如果这个女的address是171,则分数更高
{"match":{
"address":"171"
}}
]
}
}
}
GET /bank/_search
{
"query":{
"range":{
"age":{
"gte":30
"ite":40
}
}
}
}
关于范围操作符:
gt | 大于 |
---|---|
lt | 小于 |
gte | 大于等于 great than or equals |
lte | 小于等于 less than or equals |
--对结果默认使用分数降序排序
GET /bank/_search
{
"size":20
"query":{
"match":{
"gender":"F"
}
},
"sort":[
"age":{
"order":"asc" --按年龄升序排
},
{
"balance":{
"order":"desc" --按钱降序排
}
}
]
}
--查询:每页10条,返回第1页
GET /bank/_search
{
"size":10,
"from":(页数-1)*size --es不负责计算,需要算好传给它
}
GET /bank/_search
{
"query": { "match_all": {} },
"_source": ["gender", "balance"]
}
只显示name和doubanScore字段
--给171和F加高亮
GET /bank/_search
{
"query":{
"bool":{
"must":[
{ "match":{
"address":"171"
} },
{ "match":{
"gender":"F"
}}
]
}
"match":{
"address":"171"
}
},
"hightlight":{
"fields":[
{"address":{}},
{"gender":{}}
]
}
}
按性别分组,求每组总人数
select count(*) from bank where ... group by gender
--TEXT类型不能直接分组,必须加keyword
GET /bank/_search
{
"size":1
"query":{ "match_all":{} },
"aggs":{
"group_by_gender":{
"term":{
"field":"gender.keyword", --按那个分组,分组字段
"size":10
}
}
}
}
{
"took" : 13,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 1000,
"max_score" : 1.0,
"hits" : [
{
"_index" : "bank",
"_type" : "_doc",
"_id" : "25",
"_score" : 1.0,
"_source" : {
"account_number" : 25,
"balance" : 40540,
"firstname" : "Virginia",
"lastname" : "Ayala",
"age" : 39,
"gender" : "F",
"address" : "171 Putnam Avenue",
"employer" : "Filodyne",
"email" : "[email protected]",
"city" : "Nicholson",
"state" : "PA"
}
}
]
},
"aggregations" : {
"group_by_balance" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "M",
"doc_count" : 507
},
{
"key" : "F",
"doc_count" : 493
}
]
}
}
}
选性别分组,求每组的总钱数
select sum(balance) from bank where ... group by gender
--TEXT类型不能直接分组,必须加keyword
GET /bank/_search
{
"size":1
"query":{ "match_all":{} },
"aggs":{
"group_by_gender":{
"term":{
"field":"gender.keyword", --按谁分组,分组字段
"size":10 --默认按每组总人数降序排,取前10
},
"aggs":{ --分组后做啥操作,可以有多个
"sun_balance":{ --这个名字随便取
"sum":{ --做什么操作,函数,聚合类型
"field":"balance" --对谁做聚合
}
}
}
}
}
}
"aggregations" : {
"group_by_gender" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [ --几个组
{
"key" : "M",
"doc_count" : 507,
"sum_balance" : {
"value" : 1.3082527E7
}
},
{
"key" : "F",
"doc_count" : 493,
"sum_balance" : {
"value" : 1.263231E7
}
}
]
}
}
选性别分组,求每组的总钱数,并求钱数最多的
select sum(balance),max(balance) from bank where ... group by gender
--TEXT类型不能直接分组,必须加keyword
GET /bank/_search
{
"size":1
"query":{ "match_all":{} },
"aggs":{
"group_by_gender":{
"term":{
"field":"gender.keyword", --按谁分组,分组字段
"size":10 --默认按每组总人数降序排,取前10
},
"aggs":{ --分组后做啥操作,可以有多个
"sun_balance":{ --这个名字随便取
"sum":{ --做什么操作,函数,聚合类型
"field":"balance" --对谁做聚合
}
},
"max_balance":{
"max":{
"field":"balance"
}
}
}
}
}
}
"aggregations" : {
"group_by_gender" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "M",
"doc_count" : 507,
"sum_balance" : {
"value" : 1.3082527E7
},
"max_balance" : {
"value" : 49989.0
}
},
{
"key" : "F",
"doc_count" : 493,
"sum_balance" : {
"value" : 1.263231E7
},
"max_balance" : {
"value" : 49795.0
}
}
]
}
}
选性别和年龄分组,求性别组的总钱数,并求钱数最多的,求年龄组,总钱数,和平均钱数
select sum(balance),max(balance) from bank where ... group by gender
select sum(balance),max(balance) from bank where ... group by age
--TEXT类型不能直接分组,必须加keyword
GET /bank/_search
{
"size":1
"query":{ "match_all":{} },
"aggs":{
"group_by_gender":{
"term":{
"field":"gender.keyword", --按谁分组,分组字段
"size":10
--几个就代表只显示几个,默认是按照doc_count降序排
},
"aggs":{ --分组后做啥操作,可以有多个
"sun_balance":{ --这个名字随便取
"sum":{ --做什么操作,函数,聚合类型
"field":"balance" --对谁做聚合
}
},
"max_balance":{
"max":{
"field":"balance"
}
}
}
}
"group_by_age":{
"term":{
"field":"age", --按谁分组,分组字段
"size":10 --默认按每组总人数降序排,取前10
},
"aggs":{ --分组后做啥操作,可以有多个
"sum_balance":{ --这个名字随便取
"sum":{ --做什么操作,函数,聚合类型
"field":"balance" --对谁做聚合
}
},
"avg_balance":{
"avg":{
"field":"balance"
}
}
}
}
}
}
选性别和年龄分组,求性别组的总钱数,并求钱数最多的,求年龄组,总钱数,和平均钱数,并按总钱数降序排序
--TEXT类型不能直接分组,必须加keyword
GET /bank/_search
{
"size":1
"query":{ "match_all":{} },
"aggs":{
"group_by_gender":{
"term":{
"field":"gender.keyword", --按谁分组,分组字段
"size":10 --默认按每组总人数降序排,取前10
},
"aggs":{ --分组后做啥操作,可以有多个
"sun_balance":{ --这个名字随便取
"sum":{ --做什么操作,函数,聚合类型
"field":"balance" --对谁做聚合
}
},
"max_balance":{
"max":{
"field":"balance"
}
}
}
}
"group_by_age":{
"term":{
"field":"age", --按谁分组,分组字段
"size":10 --默认按每组总人数降序排,取前10
"order":{
"sum_balance":"desc"
}
},
"aggs":{ --分组后做啥操作,可以有多个
"sum_balance":{ --这个名字随便取
"sum":{ --做什么操作,函数,聚合类型
"field":"balance" --对谁做聚合
}
},
"avg_balance":{
"avg":{
"field":"balance"
}
}
}
}
}
}
可见总钱数降序排序
GET _analyze
{
"text":"hello world"
}
--按照空格对单词进行切分
GET _analyze
{
"text":"小米手机"
}
--按照每个汉子进行切分
通过上面的查询,我们可以看到ES本身自带的中文分词,就是单纯把中文一个字一个字的分开,根本没有词汇的概念。但是实际应用中,用户都是以词汇为条件,进行查询匹配的,如果能够把文章以词汇为单位切分开,那么与用户的查询条件能够更贴切的匹配上,查询速度也更加快速。
常见的一些开源分词器对比,我们使用IK分词器
分词器 | 优势 | 劣势 |
---|---|---|
Smart Chinese Analysis | 官方插件 | 中文分词效果惨不忍睹 |
IKAnalyzer | 简单易用,支持自定义词典和远程词典 | 词库需要自行维护,不支持词性识别 |
结巴分词 | 新词识别功能 | 不支持词性识别 |
Ansj中文分词 | 分词精准度不错,支持词性识别 | 对标hanlp词库略少,学习成本高 |
Hanlp | 目前词库最完善,支持的特性非常多 | 需要更优的分词效果,学习成本高 |
①解压
[atguigu@hadoop162 software]$ unzip elasticsearch-analysis-ik-6.6.0.zip -d /opt/module/elasticsearch/plugins/ik
注意
使用unzip进行解压
-d指定解压后的目录
必须放到ES的plugins目录下,并在plugins目录下创建单独的目录
安装unzip
sudo yum install unzip
②分发
my_rsync /opt/module/elasticsearch/plugins/ik
③重启
[atguigu@hadoop162 elasticsearch]$ es.sh stop
[atguigu@hadoop162 elasticsearch]$ es.sh start
④测试
GET movie_index/_analyze
{
"text": "我是中国人"
}
GET movie_index/_analyze
{
"analyzer": "ik_smart",
"text": "我是中国人"
}
GET /movie_index/_analyze
{
"analyze":"ik_max_word",
"text":"我是中国人"
}
之前说Type可以理解为关系型数据库的Table,那每个字段的数据类型是如何定义的呢?
实际上每个Type中的字段是什么数据类型,由mapping定义,如果我们在创建Index的时候,没有设定mapping,系统会自动根据一条数据的格式来推断出该数据对应的字段类型,具体推断类型如下:
Ø true/false → boolean
Ø 1020 → long
Ø 20.1 → float
Ø “2018-02-01” → date
Ø “hello world” → text +keyword
默认只有text会进行分词,keyword是不会分词的字符串。mapping除了自动定义,还可以手动定义,但是只能对新加的、没有数据的字段进行定义,一旦有了数据就无法再做修改了。
mapping就是给field指定类型
PUT /movie_index_cdh
{
"mapping":{
"properties":{
"id":{
"type":"long"
}
}
}
}
GET /movie_index_cdh/_search
PUT /movie_indx_cdh/movie/1
{
"id":123
}
PUT /movie_indx_cdh/movie/1
{
"id":"123" --这会报错,因为上面mapping指定了type是long类型
}
那么以后想建index,一定会先建mapping
--查看自定义mapping
GET /movie_index/_mapping
直接创建Document
这个时候index不存在,建立文档的时候自动创建index,同时mapping会自动定义
①定义Index,指定mapping
PUT movie_chn_2
{
"mappings": {
"movie":{
"properties": {
"id":{
"type": "long"
},
"name":{
"type": "text",
"analyzer": "ik_smart"
},
"doubanScore":{
"type": "double"
},
"actorList":{
"properties": {
"id":{
"type":"long"
},
"name":{
"type":"keyword"
}
}
}
}
}
}
}
② 向Index中放入Document
PUT /movie_chn_2/movie/1
{ "id":1,
"name":"红海行动",
"doubanScore":8.5,
"actorList":[
{"id":1,"name":"张译"},
{"id":2,"name":"海清"},
{"id":3,"name":"张涵予"}
]
}
PUT /movie_chn_1/movie/2
{
"id":2,
"name":"湄公河行动",
"doubanScore":8.0,
"actorList":[
{"id":3,"name":"张涵予"}
]
}
PUT /movie_chn_1/movie/3
{
"id":3,
"name":"红海事件",
"doubanScore":5.0,
"actorList":[
{"id":4,"name":"张三丰"}
]
}
③查看手动定义的mapping
GET /movie_chn_2/_mapping
④查询测试
GET /movie_chn_2/_mapping
{
"query":{
"match":{
"name":"海行"
}
}
}
⑤分析结论
上面查询没有命中任何记录,是因为我们在创建Index的时候,指定使用ik分词器进行分词
索引模板。顾名思义就是创建索引的模具,其中可以定义一系列规则来帮助我们构建符合特定业务需求的索引的mapping和settings,通过使用索引模板可以让我们的索引具备可预知的一致性。
索引、模板名
PUT _template/template_movie2020
{
"index_patterns":["movie_test * "], --如果索引是以movie_test开头,就启用下面模板
"setting":{
"number_of_shards":1
},
"aliases":{ --给模板起别名
"{index}-query":{},#可以设置为日期,用于查看某一天的数据 --每个独一无二的别名
"movie_test-query":{} #查询这个索引能查到的所有数据 --只要以movie_test开头的模板都有这样一个共同订单的别名
},
"mapping":{
"_doc":{
"properties":{
"id":{
"type":"keyword"
},
"movie_name":{
"type":"text",
"analyzer":"ik_smart"
}
}
}
}
}
--index模板:当向一个不存在的index插入数据的时候,会自动根据模板创建index
PUT /movie_test_2/_doc/1
{
"id":"hello word"
"movie_name":"你好啊"
}
GET /_cat/aliases?v --查看所有的别名,会发现每个索引模板有两个别名
GET /movie_test-query/_search --查看指定的别名
GET /_cat/templates
GET /_template/template_movie2020
类似hive中的分区表
分割索引就是根据时间间隔把一个业务索引切分成多个索引。
比如: 把order_info 变成 order_info_20200101,order_info_20200102 …
这样好处有两个:
结构变化灵活
因为ES不允许对数据结构进行修改,但是实际使用中索引的结构和配置难免变化,那么只要对下一个间隔的索引进行修改,原来的索引维持原状,这样就有了一定灵活性。
要想实现这个效果,我们只需要在需要变化大索引那天将模板重新建立即可
查询范围优化
因为一般情况并不会查询全部时间周期的数据,那么通过切分索引,物理上减少了扫描数据的范围,也是对性能的优化。
使用索引模板,一般在向索引中插入第一条数据创建索引,如果ES中的shard(分片)特别多,有可能创建索引会变慢,如果延迟不能接受,可以不使用模板,使用定时脚本在头一天提前建立第二天1索引。