概括:基于Restful标准的高扩展高可用的实时数据分析的全文搜索工具
Index
类似于mysql数据库中的database
Type
类似于mysql数据库中的table表,es中可以在Index中建立type(table),通过mapping进行映射。
Document
由于es存储的数据是文档型的,一条数据对应一篇文档即相当于mysql数据库中的一行数据row,一个文档中可以有多个字段也就是mysql数据库一行可以有多列。
Field
es中一个文档中对应的多个列与mysql数据库中每一列对应
Mapping
可以理解为mysql或者solr中对应的schema,只不过有些时候es中的mapping增加了动态识别功能,感觉很强大的样子,其实实际生产环境上不建议使用,最好还是开始制定好了对应的schema为主。
indexed
就是名义上的建立索引。mysql中一般会对经常使用的列增加相应的索引用于提高查询速度,而在es中默认都是会加上索引的,除非你特殊制定不建立索引只是进行存储用于展示,这个需要看你具体的需求和业务进行设定了。
Query DSL
类似于mysql的sql语句,只不过在es中是使用的json格式的查询语句,专业术语就叫:QueryDSL
GET/PUT/POST/DELETE
分别类似与mysql中的select/update/delete…
Gateway层
es用来存储索引文件的一个文件系统且它支持很多类型,例如:本地磁盘、共享存储(做snapshot的时候需要用到)、hadoop的hdfs分布式存储、亚马逊的S3。它的主要职责是用来对数据进行长持久化以及整个集群重启之后可以通过gateway重新恢复数据。
Distributed Lucene Directory
Gateway上层就是一个lucene的分布式框架,lucene是做检索的,但是它是一个单机的搜索引擎,像这种es分布式搜索引擎系统,虽然底层用lucene,但是需要在每个节点上都运行lucene进行相应的索引、查询以及更新,所以需要做成一个分布式的运行框架来满足业务的需要。
四大模块组件
districted lucene directory之上就是一些es的模块,Index Module是索引模块,就是对数据建立索引也就是通常所说的建立一些倒排索引等;Search Module是搜索模块,就是对数据进行查询搜索;Mapping模块是数据映射与解析模块,就是你的数据的每个字段可以根据你建立的表结构通过mapping进行映射解析,如果你没有建立表结构,es就会根据你的数据类型推测你的数据结构之后自己生成一个mapping,然后都是根据这个mapping进行解析你的数据;River模块在es2.0之后应该是被取消了,它的意思表示是第三方插件,例如可以通过一些自定义的脚本将传统的数据库(mysql)等数据源通过格式化转换后直接同步到es集群里,这个river大部分是自己写的,写出来的东西质量参差不齐,将这些东西集成到es中会引发很多内部bug,严重影响了es的正常应用,所以在es2.0之后考虑将其去掉。
Discovery、Script
es4大模块组件之上有 Discovery模块:es是一个集群包含很多节点,很多节点需要互相发现对方,然后组成一个集群包括选主的,这些es都是用的discovery模块,默认使用的是 Zen,也可是使用EC2;es查询还可以支撑多种script即脚本语言,包括mvel、js、python等等。
Transport协议层
再上一层就是es的通讯接口Transport,支持的也比较多:Thrift、Memcached以及Http,默认的是http,JMX就是java的一个远程监控管理框架,因为es是通过java实现的。
RESTful接口层
最上层就是es暴露给我们的访问接口,官方推荐的方案就是这种Restful接口,直接发送http请求,方便后续使用nginx做代理、分发包括可能后续会做权限的管理,通过http很容易做这方面的管理。如果使用java客户端它是直接调用api,在做负载均衡以及权限管理还是不太好做。
一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。在目前主流的三种Web服务交互方案中,REST相比于SOAP(Simple Object Access protocol,简单对象访问协议)以及XML-RPC更加简单明了
(Representational State Transfer
意思是:表述性状态传递)
它使用典型的HTTP方法,诸如GET,POST.DELETE,PUT来实现资源的获取,添加,修改,删除等操作。即通过HTTP动词来实现资源的状态扭转
复制代码
GET 用来获取资源
POST 用来新建资源(也可以用于更新资源)
PUT 用来更新资源
DELETE 用来删除资源
以命令的方式执行HTTP协议的请求
GET/POST/PUT/DELETE
示例:
访问一个网页
curl www.baidu.com
curl -o tt.html www.baidu.com
显示响应的头信息
curl -i www.baidu.com
显示一次HTTP请求的通信过程
curl -v www.baidu.com
执行GET/POST/PUT/DELETE操作
curl -X GET/POST/PUT/DELETE url
(1)配置JDK环境
配置环境变量
export JAVA_HOME="/opt/jdk1.8.0_144"
export PATH="$JAVA_HOME/bin:$PATH"
export CLASSPATH=".:$JAVA_HOME/lib"
(2)安装ElasticSearch6.2.4
下载地址:https://www.elastic.co/cn/downloads/elasticsearch
启动报错:不允许root用户
解决方式:
bin/elasticsearch -Des.insecure.allow.root=true
或者修改bin/elasticsearch,加上ES_JAVA_OPTS属性:
ES_JAVA_OPTS="-Des.insecure.allow.root=true"
再次启动:权限问题
这是出于系统安全考虑设置的条件。由于ElasticSearch可以接收用户输入的脚本并且执行,为了系统安全考 虑,建议创建一个单独的用户用来运行ElasticSearch。
创建用户组和用户:
groupadd esgroup
useradd esuser -g esgroup -p espassword
更改elasticsearch文件夹及内部文件的所属用户及组:
cd /opt
chown -R esuser:esgroup elasticsearch-6.2.4
切换用户并运行:
su esuser
./bin/elasticsearch
再次启动显示已杀死:内存问题
需要调整JVM的内存大小:
vi bin/elasticsearch
ES_JAVA_OPTS="-Xms512m -Xmx512m"
再次启动:启动成功
如果显示如下类似信息:
[INFO ][o.e.c.r.a.DiskThresholdMonitor] [ZAds5FP] low disk watermark [85%] exceeded on [ZAds5FPeTY-ZUKjXd7HJKA][ZAds5FP][/opt/elasticsearch-6.2.4/data/nodes/0] free: 1.2gb[14.2%], replicas will not be assigned to this node
需要清理磁盘空间。
后台运行:./bin/elasticsearch -d
测试连接:curl 127.0.0.1:9200
会看到一下JSON数据:
[root@localhost ~]# curl 127.0.0.1:9200
{
"name" : "rBrMTNx",
"cluster_name" : "elasticsearch",
"cluster_uuid" : "-noR5DxFRsyvAFvAzxl07g",
"version" : {
"number" : "5.1.1",
"build_hash" : "5395e21",
"build_date" : "2016-12-06T12:36:15.409Z",
"build_snapshot" : false,
"lucene_version" : "6.3.0"
},
"tagline" : "You Know, for Search"
}
实现远程访问:
需要对config/elasticsearch.yml进行 配置:
network.host: 192.168.9.222
再次启动报错:系统设置相关报错
处理第一个错误:
vim /etc/security/limits.conf //文件最后加入
esuser soft nofile 65536
esuser hard nofile 65536
esuser soft nproc 4096
esuser hard nproc 4096
处理第二个错误:
进入limits.d目录下修改配置文件。
vim /etc/security/limits.d/20-nproc.conf
修改为 esuser soft nproc 4096
处理第三个错误:
vim /etc/sysctl.conf
vm.max_map_count=655360
执行以下命令生效:
sysctl -p
关闭防火墙:systemctl stop firewalld.service
再次启动成功!
Head是elasticsearch的集群管理工具,可以用于数据的浏览和查询
(1)elasticsearch-head是一款开源软件,被托管在github上面,所以如果我们要使用它,必须先安装git,通过git获取elasticsearch-head
(2)运行elasticsearch-head会用到grunt,而grunt需要npm包管理器,所以nodejs是必须要安装的
(3)elasticsearch5.0之后,elasticsearch-head不做为插件放在其plugins目录下了。
使用git拷贝elasticsearch-head到本地
cd /usr/local/
git clone git://github.com/mobz/elasticsearch-head.git
(4)安装elasticsearch-head依赖包
[root@localhost local]# npm install -g grunt-cli
[root@localhost _site]# cd /usr/local/elasticsearch-head/
[root@localhost elasticsearch-head]# cnpm install
(5)修改Gruntfile.js
[root@localhost _site]# cd /usr/local/elasticsearch-head/
[root@localhost elasticsearch-head]# vi Gruntfile.js
在connect-->server-->options下面添加:hostname:’*’,允许所有IP可以访问
(6)修改elasticsearch-head默认连接地址
[root@localhost elasticsearch-head]# cd /usr/local/elasticsearch-head/_site/
[root@localhost _site]# vi app.js
将this.base_uri = this.config.base_uri || this.prefs.get("app-base_uri") || "http://localhost:9200";中的localhost修改成你es的服务器地址
(7)配置elasticsearch允许跨域访问
打开elasticsearch的配置文件elasticsearch.yml,在文件末尾追加下面两行代码即可:
http.cors.enabled: true
http.cors.allow-origin: "*"
(8)打开9100端口
[root@localhost elasticsearch-head]# firewall-cmd --zone=public --add-port=9100/tcp --permanent
重启防火墙
[root@localhost elasticsearch-head]# firewall-cmd --reload
(9)启动elasticsearch
(10)启动elasticsearch-head
[root@localhost _site]# cd /usr/local/elasticsearch-head/
[root@localhost elasticsearch-head]# node_modules/grunt/bin/grunt server
(11)访问elasticsearch-head
关闭防火墙:systemctl stop firewalld.service
浏览器输入网址:http://192.168.9.222:9100/
Kibana是一个针对Elasticsearch的开源分析及可视化平台,使用Kibana可以查询、查看并与存储在ES索引的数据进行交互操作,使用Kibana能执行高级的数据分析,并能以图表、表格和地图的形式查看数据
(1)下载Kibana
https://www.elastic.co/downloads/kibana
(2)把下载好的压缩包拷贝到/soft目录下
(3)解压缩,并把解压后的目录移动到/user/local/kibana
(4)编辑kibana配置文件
[root@localhost /]# vi /usr/local/kibana/config/kibana.yml
server.host: "192.168.9.222"
elasticsearch.url: "http://192.168.9.222:9200"
将server.host,elasticsearch.url修改成所在服务器的ip地址
(5)开启5601端口
Kibana的默认端口是5601
开启防火墙:systemctl start firewalld.service
开启5601端口:firewall-cmd --permanent --zone=public --add-port=5601/tcp
重启防火墙:firewall-cmd –reload
(6)启动Kibana
[root@localhost /]# /usr/local/kibana/bin/kibana
浏览器访问:http://192.168.9.222:5601
(1)下载中文分词器
https://github.com/medcl/elasticsearch-analysis-ik
下载elasticsearch-analysis-ik-master.zip 版本对应
(2)解压elasticsearch-analysis-ik-master.zip
unzip elasticsearch-analysis-ik-master.zip
(3)进入elasticsearch-analysis-ik-master,编译源码
mvn clean install -Dmaven.test.skip=true
(4)在es的plugins文件夹下创建目录ik
(5)将编译后生成的elasticsearch-analysis-ik-版本.zip移动到ik下,并解压
(6)解压后的内容移动到ik目录下
(7)重启elasticsearch,日志出现如下标识成功:
[2018-12-10T16:18:21,207][INFO ][o.e.p.PluginsService ][kZ_ZS3W] loaded plugin [analysis-ik]
查阅其他资料。
分词器:从一串文本中切分出一个一个的词条,并对每个词条进行标准化
包括三部分:
character filter:分词之前的预处理,过滤掉HTML标签,特殊符号转换等
tokenizer:分词
token filter:标准化
内置分词器:
standard 分词器:(默认的)他会将词汇单元转换成小写形式,并去除停用词和标点符号,支持中文采用的方法为单字切分
simple 分词器:首先会通过非字母字符来分割文本信息,然后将词汇单元统一为小写形式。该分析器会去掉数字类型的字符。
Whitespace 分词器:仅仅是去除空格,对字符没有lowcase化,不支持中文;
并且不对生成的词汇单元进行其他的标准化处理。
language 分词器:特定语言的分词器,不支持中文
添加索引:
#带自定义配置的添加:
PUT /lib/
{
"settings":{
"index":{
"number_of_shards": 5, # 默认5个,不能修改
"number_of_replicas": 1 # 随时修改,默认1个
}
}
}
# 使用默认配置的添加: lib前后加 / 和不加没区别,后面加了/ 反而在kibana中没提示
PUT lib
查看索引信息:
GET /lib/_settings # 查询单个
GET _all/_settings # 查询所有
添加文档:
# 指定id为1,如果不指定,需要用post提交,elasticsearch自动生成
PUT /lib/user/1
{
"first_name" : "Jane",
"last_name" : "Smith",
"age" : 32,
"about" : "I like to collect rock albums",
"interests": [ "music" ]
}
# 这里没有id,自动生成。
POST /lib/user/
{
"first_name" : "Douglas",
"last_name" : "Fir",
"age" : 23,
"about": "I like to build cabinets",
"interests": [ "forestry" ]
}
查看文档:
GET /lib/user/1
GET /lib/user/
GET /lib/user/1?_source=age,interests # _source:指定查询id为1的age和interests两个字段
更新文档:
# 覆盖更新,相当于重新插入,这里没覆盖到的字段,更新后就没了,可能会引起丢失字段
PUT /lib/user/1
{
"first_name" : "Jane",
"last_name" : "Smith",
"age" : 36,
"about" : "I like to collect rock albums",
"interests": [ "music" ]
}
# 正确更新
POST /lib/user/1/_update
{
"doc":{
"age":33
}
}
删除一个文档
DELETE /lib/user/1
删除一个索引
DELETE /lib
使用es提供的Multi Get API:
使用Multi Get API可以通过索引名、类型名、文档id一次得到一个文档集合,文档可以来自同一个索引库,也可以来自不同索引库
使用curl命令:
curl 'http://192.168.9.222:9200/_mget' -d '{
"docs":[
{
"_index": "lib",
"_type": "user",
"_id": 1
},
{
"_index": "lib",
"_type": "user",
"_id": 2
}
]
}'
在客户端工具中:
GET /_mget
{
"docs":[
{
"_index": "lib",
"_type": "user",
"_id": 1
},
{
"_index": "lib",
"_type": "user",
"_id": 2
},
{
"_index": "lib",
"_type": "user",
"_id": 3
}
]
}
可以指定具体的字段:
GET /_mget
{
"docs":[
{
"_index": "lib",
"_type": "user",
"_id": 1,
"_source": "interests"
},
{
"_index": "lib",
"_type": "user",
"_id": 2,
"_source": ["age","interests"]
}
]
}
获取同索引同类型下的不同文档:
GET /lib/user/_mget
{
"docs":[
{
"_id": 1
},
{
"_type": "user",
"_id": 2
}
]
}
GET /lib/user/_mget
{
"ids": ["1","2"]
}
bulk的格式:(_mget只能获取,bulk能增删改查)
{action:{metadata}}\n # 除非有请求体,否则命令必须在一行,如delete命令
{requstbody}\n
action:(行为)
create:文档不存在时创建
update:更新文档
index:创建新文档或替换已有文档
delete:删除一个文档
metadata:
_index,_type,_id
create 和index的区别
如果数据存在,使用create操作失败,会提示文档已经存在,使用index则可以成功执行。
示例:
POST lib/user/_bulk
{"delete":{"_index":"lib","_type":"user","_id":"1"}}
批量添加:
POST /lib2/books/_bulk
{"index":{"_id":1}} # 插入_id为1,内容为java、55的
{"title":"Java","price":55}
{"index":{"_id":2}} # 插入_id为2...
{"title":"Html5","price":45}
{"index":{"_id":3}}
{"title":"Php","price":35}
{"index":{"_id":4}}
{"title":"Python","price":50}
批量获取验证下是否成功:
GET /lib2/books/_mget
{
"ids": ["1","2","3","4"]
}
删除:没有请求体
POST /lib2/books/_bulk
{"delete":{"_index":"lib2","_type":"books","_id":4}}
{"create":{"_index":"tt","_type":"ttt","_id":"100"}}
{"name":"lisi"}
{"index":{"_index":"tt","_type":"ttt"}}
{"name":"zhaosi"}
{"update":{"_index":"lib2","_type":"books","_id":"4"}}
{"doc":{"price":58}}
bulk一次最大处理多少数据量:
bulk会把将要处理的数据载入内存中,所以数据量是有限制的,最佳的数据量不是一个确定的数值,它取决于你的硬件,你的文档大小以及复杂性,你的索引以及搜索的负载。
一般建议是1000-5000个文档,大小建议是5-15MB,默认不能超过100M,可以在es的配置文件(即$ES_HOME下的config下的elasticsearch.yml)中。
ElasticSearch采用了乐观锁来保证数据的一致性,也就是说,当用户对document进行操作时,并不需要对该document作加锁和解锁的操作,只需要指定要操作的版本即可。当版本号一致时,ElasticSearch会允许该操作顺利执行,而当版本号存在冲突时,ElasticSearch会提示冲突并抛出异常(VersionConflictEngineException异常)。
ElasticSearch的版本号的取值范围为1到2^63-1。
如执行如下命令5次:第一次是created,其他4次都是updated,并且"_version": 递增
PUT /lib/user/1
{
"first_name": "ljh",
"last_name": "jianhui",
"age": "99",
"interests": ["music","computer"]
}
返回结果:
"_index": "lib",
"_type": "user",
"_id": "1",
"_version": 5, # 版本号递增
"result": "updated",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 7,
"_primary_term": 1
}
并发修改时,带上: ?version=期望的版本号
PUT /lib/user/1?version=7 # 如果当前版本号对不上,则报错
{
"first_name": "ljh",
"last_name": "jianhui",
"age": "99",
"interests": ["music","computer"]
}
elasticsearch在处理外部版本号时会与对内部版本号的处理有些不同。它不再是检查_version是否与请求中指定的数值_相同_,而是检查当前的_version是否比指定的数值小。如果请求成功,那么外部的版本号就会被存储到文档中的_version中。
为了保持_version与外部版本控制的数据一致
使用version_type=external
如上案例,如果此时/lib/user/1 的_version=6,那么使用外部版本控制,给定的version必须大于6,等于也不行.
# 使用&符号,并且修后version等于给定值999
PUT /lib/user/1?version=999&version_type=external
{
"first_name": "ljh",
"last_name": "jianhui",
"age": "99",
"interests": ["music","computer"]
}
PUT /myindex/article/1
{
"post_date": "2018-05-10",
"title": "Java",
"content": "java is the best language",
"author_id": 119
}
PUT /myindex/article/2
{
"post_date": "2018-05-12",
"title": "html",
"content": "I like html",
"author_id": 120
}
PUT /myindex/article/3
{
"post_date": "2018-05-16",
"title": "es",
"content": "Es is distributed document store",
"author_id": 110
}
# 查不出来,mapping后成了日期格式,不会被分词
GET /myindex/article/_search?q=2018-05
GET /myindex/article/_search?q=2018-05-10
GET /myindex/article/_search?q=html
GET /myindex/article/_search?q=java
# 可以限制字段来查询
GET /myindex/article/_search?q=post_date:2018-05
GET /myindex/article/_search?q=post_date:2018-05-10
GET /myindex/article/_search?q=content:html
GET /myindex/article/_search?q=content:java
# 查询所有
GET /myindex/article/_search
#查看es自动创建的mapping
GET /myindex/article/_mapping
es自动创建了index,type,以及type对应的mapping(dynamic mapping)
什么是映射:mapping定义了type中的每个字段的数据类型以及这些字段如何分词等相关属性
{
"myindex": {
"mappings": {
"article": {
"properties": {
"author_id": {
"type": "long"
},
"content": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"post_date": {
"type": "date"
},
"title": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}
}
}
创建索引的时候,可以预先定义字段的类型以及相关属性,这样就能够把日期字段处理成日期,把数字字段处理成数字,把字符串字段处理字符串值等
(1)核心数据类型(Core datatypes)
字符型:string,string类型包括text 和 keyword
最主要区别:text会被分词,keyword不会被分词
text类型被用来索引长文本,在建立索引前会将这些文本进行分词,转化为词的组合,建立索引。允许es来检索这些词语。text类型不能用来排序和聚合。
Keyword类型不需要进行分词,可以被用来检索过滤、排序和聚合。keyword 类型字段只能用本身来进行检索
数字型:long, integer, short, byte, double, float
日期型:date
布尔型:boolean
二进制型:binary
(2)复杂数据类型(Complex datatypes)
数组类型(Array datatype):数组类型不需要专门指定数组元素的type,例如:
字符型数组: [ “one”, “two” ]
整型数组:[ 1, 2 ]
数组型数组:[ 1, [ 2, 3 ]] 等价于[ 1, 2, 3 ]
对象数组:[ { “name”: “Mary”, “age”: 12 }, { “name”: “John”, “age”: 10 }]
对象类型(Object datatype):_ object _ 用于单个JSON对象;
嵌套类型(Nested datatype):_ nested _ 用于JSON数组;
(3)地理位置类型(Geo datatypes)
地理坐标类型(Geo-point datatype):_ geo_point _ 用于经纬度坐标;
地理形状类型(Geo-Shape datatype):_ geo_shape _ 用于类似于多边形的复杂形状;
(4)特定类型(Specialised datatypes)
IPv4 类型(IPv4 datatype):_ ip _ 用于IPv4 地址;
Completion 类型(Completion datatype):_ completion 提供自动补全建议;
Token count 类型(Token count datatype): token_count _ 用于统计做了标记的字段的index数目,该值会一直增加,不会因为过滤条件而减少。
mapper-murmur3
类型:通过插件,可以通过 _ murmur3 _ 来计算 index 的 hash 值;
附加类型(Attachment datatype):采用 mapper-attachments
插件,可支持_ attachments _ 索引,例如 Microsoft Office 格式,Open Document 格式,ePub, HTML 等。
"store":false //是否单独设置此字段的是否存储而从_source字段中分离,默认是false,只能搜索,不能获取值
"index": true // 分词,不分词是:false,设置成false,字段将不会被索引,默认每个都会创建索引
"analyzer":"ik" // 指定分词器,默认分词器为standard analyzer
"boost":1.23 // 字段级别的分数加权,默认值是1.0
"doc_values":false // 对not_analyzed字段,默认都是开启,分词字段不能使用,对排序和聚合能提升较大性能,节约内存
"fielddata":{"format":"disabled"} // 针对分词字段,参与排序或聚合时能提高性能,不分词字段统一建议使用doc_value
"fields":{"raw":{"type":"string","index":"not_analyzed"}} //可以对一个字段提供多种索引模式,同一个字段的值,一个分词,一个不分词
"ignore_above":100 //超过100个字符的文本,将会被忽略,不被索引
"include_in_all":ture//设置是否此字段包含在_all字段中,默认是true,除非index设置成no选项
"index_options":"docs"//4个可选参数docs(索引文档号) ,freqs(文档号+词频),positions(文档号+词频+位置,通常用来距离查询),offsets(文档号+词频+位置+偏移量,通常被使用在高亮字段)分词字段默认是position,其他的默认是docs
"norms":{"enable":true,"loading":"lazy"}//分词字段默认配置,不分词字段:默认{"enable":false},存储长度因子和索引时boost,建议对需要参与评分字段使用 ,会额外增加内存消耗量
"null_value":"NULL"//设置一些缺失字段的初始化值,只有string可以使用,分词字段的null值也会被分词
"position_increament_gap":0//影响距离查询或近似查询,可以设置在多值字段的数据上火分词字段上,查询时可指定slop间隔,默认值是100
"search_analyzer":"ik"//设置搜索时的分词器,默认跟ananlyzer是一致的,比如index时用standard+ngram,搜索时用standard用来完成自动提示功能
"similarity":"BM25"//默认是TF/IDF算法,指定一个字段评分策略,仅仅对字符串型和分词类型有效
"term_vector":"no"//默认不存储向量信息,支持参数yes(term存储),with_positions(term+位置),with_offsets(term+偏移量),with_positions_offsets(term+位置+偏移量) 对快速高亮fast vector highlighter能提升性能,但开启又会加大索引体积,不适合大数据量用
(1)动态映射:
当ES在文档中碰到一个以前没见过的字段时,它会利用动态映射来决定该字段的类型,并自动地对该字段添加映射。
可以通过dynamic设置来控制这一行为,它能够接受以下的选项:
true:默认值。动态添加字段
false:忽略新字段
strict:如果碰到陌生字段,抛出异常
dynamic设置可以适用在根对象上或者object类型的任意字段上。
给索引lib2创建映射类型
POST /lib2
{
"settings":{
"number_of_shards" : 3,
"number_of_replicas" : 0
},
"mappings":{
"books":{
"properties":{
"title":{"type":"text"},
"name":{"type":"text","index":false},
"publish_date":{"type":"date","index":false},
"price":{"type":"double"},
"number":{"type":"integer"}
}
}
}
}
给索引lib2创建映射类型
POST /lib2
{
"settings":{
"number_of_shards" : 3,
"number_of_replicas" : 0
},
"mappings":{
"books":{
"properties":{
"title":{"type":"text"},
"name":{"type":"text","index":false},
"publish_date":{"type":"date","index":false},
"price":{"type":"double"},
"number":{
"type":"object",
"dynamic":true
}
}
}
}
}
PUT /lib3
{
"settings":{
"number_of_shards" : 3,
"number_of_replicas" : 0
},
"mappings":{
"user":{
"properties":{
"name": {"type":"text"},
"address": {"type":"text"},
"age": {"type":"integer"},
"interests": {"type":"text"},
"birthday": {"type":"date"}
}
}
}
}
PUT lib3/user/1
{
"name":"zhang san",
"address": "shatian",
"age":18,
"interests": "drink,dance",
"birthday": "2018-05-21"
}
PUT lib3/user/2
{
"name":"li si",
"address": "tongfan",
"age":28,
"interests": "shopping",
"birthday": "2000-01-21"
}
PUT lib3/user/3
{
"name":"wang wu",
"address": "guangfeng",
"age":20,
"interests": "reading,drink,dance",
"birthday": "2015-05-21"
}
GET /lib3/user/_search?q=name:lisi
GET /lib3/user/_search?q=name:zhaoliu&sort=age:desc # 排序字段是long或者integer等数值
term query会去倒排索引中寻找确切的term,它并不知道分词器的存在。这种查询适合keyword 、numeric、date没有分词的。【直接查name没有结果,因为name是text,term中输入的词不会被分词,match会被分词】
term:查询某个字段里含有某个关键词的文档
精确查找,term中的field不能有空格等,因为倒排索引后都是一堆的词,而不是短语,(短语使用match_phrase),所以如果字段内容是:a,b,c ,使用term去查b,c 肯定是查不到的。因为term中field不会被分词,那么就是说需要匹配b,c整体,但是a,b,c被拆分了a b c 所以匹配不到。
GET /lib3/user/_search/
{
"query": {
"term": {"interests": "changge"}
}
}
terms:查询某个字段里含有多个关键词的文档
GET /lib3/user/_search
{
"query":{
"terms":{
"interests": ["hejiu","changge"]
}
}
}
from:从哪一个文档开始
size:需要的个数
GET /lib3/user/_search
{
"from":0, # 从哪个开始
"size":2, # 取几个
"query":{
"terms":{
"interests": ["hejiu","changge"]
}
}
}
GET /lib3/user/_search
{
"version":true, # 默认没有版本号
"query":{
"terms":{
"interests": ["hejiu","changge"]
}
}
}
match query知道分词器的存在,会对filed进行分词操作,然后再查询
GET /lib3/user/_search
{
"query":{
"match":{
"name": "zhaoliu"
}
}
}
GET /lib3/user/_search
{
"query":{
"match":{
"age": 20
}
}
}
match_all:查询所有文档
GET /lib3/user/_search
{
"query": {
"match_all": {}
}
}
multi_match:可以指定多个字段
GET /lib3/user/_search
{
"query":{
"multi_match": {
"query": "lvyou",
"fields": ["interests","name"]
}
}
}
match_phrase:短语匹配查询
ElasticSearch引擎首先分析(analyze)查询字符串,从分析后的文本中构建短语查询,这意味着必须匹配短语中的所有分词,并且保证各个分词的相对位置不变:
GET lib3/user/_search
{
"query":{
"match_phrase":{
"interests": "duanlian,shuoxiangsheng"
}
}
}
GET /lib3/user/_search
{
"_source": ["address","name"],
"query": {
"match": {
"interests": "changge"
}
}
}
GET /lib3/user/_search
{
"query": {
"match_all": {}
},
"_source": {
"includes": ["name","address"], # 包含的字段
"excludes": ["age","birthday"] # 排除的字段
}
}
使用通配符 *
GET /lib3/user/_search
{
"_source": {
"includes": "addr*",
"excludes": ["name","bir*"]
},
"query": {
"match_all": {}
}
}
使用sort实现排序:
desc:降序,asc升序
GET /lib3/user/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"age": {
"order":"asc"
}
}
]
}
GET /lib3/user/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"age": {
"order":"desc"
}
}
]
}
GET /lib3/user/_search
{
"query": {
"match_phrase_prefix": {
"name": {
"query": "zhao" # 匹配一个词的前缀
}
}
}
}
range:实现范围查询
参数:
from,to:这2个默认包含边界
gte:大于等于,gt大于;
lte :小于等于,lt小于;
include_lower:是否包含范围的左边界,默认是true
include_upper:是否包含范围的右边界,默认是true
GET /lib3/user/_search
{
"query": {
"range": {
"birthday": {
"from": "1990-10-10",
"to": "2018-05-01"
}
}
}
}
GET /lib3/user/_search
{
"query": {
"range": {
"age": {
"from": 20,
"to": 25,
"include_lower": true,
"include_upper": false
}
}
}
}
允许使用通配符* 和 ?来进行查询
*代表0个或多个字符
?代表任意一个字符
GET /lib3/user/_search
{
"query": {
"wildcard": {
"name": "zhao*"
}
}
}
GET /lib3/user/_search
{
"query": {
"wildcard": {
"name": "li?i"
}
}
}
value:查询的关键字
boost:查询的权值,默认值是1.0
fuzziness :参考,默认0.5,填写“auto”,或者>5表示能编辑2次,老实说,没太明白……,估计也不常用吧
prefix_length:指明区分词项的共同前缀长度,默认是0,前缀必须匹配串的长度
max_expansions:查询中的词项可以扩展的数目,默认可以无限大
GET /lib3/user/_search
{
"query": {
"fuzzy": {
"interests": "chagge"
}
}
}
GET /lib3/user/_search
{
"query": {
"fuzzy": {
"interests": {
"value": "chagge"
}
}
}
}
GET /lib3/user/_search
{
"query":{
"match":{
"interests": "changge"
}
},
"highlight": {
"fields": {
"interests": {}
}
}
}
创建mapping时,将title的权重设置为普通的2倍,那么最终匹配的得分会受影响,以达到获取用户想要的排名结果。
#设置mapping时设定。
PUT my_index
{
"mappings": {
"_doc": {
"properties": {
"title": {
"type": "text",
"boost": 2 # 将title的值匹配的权重设置为普通的2倍
},
"content": {
"type": "text"
}
}
}
}
}
#查询时设定:
GET lib3/user/_search
{
"query": {
"match": {
"name": {
"query": "zhan si san",
"boost":"2" # 查询提升匹配的分值_score
}
}
}
}
filter是不计算相关性的,同时可以cache。因此,filter速度要快于query。
POST /lib4/items/_bulk
{"index": {"_id": 1}}
{"price": 40,"itemID": "ID100123"}
{"index": {"_id": 2}}
{"price": 50,"itemID": "ID100124"}
{"index": {"_id": 3}}
{"price": 25,"itemID": "ID100124"}
{"index": {"_id": 4}}
{"price": 30,"itemID": "ID100125"}
{"index": {"_id": 5}}
{"price": null,"itemID": "ID100127"}
####2.8.1 简单的过滤查询
GET /lib4/items/_search
{
"post_filter": {
"term": {
"price": 40
}
}
}
GET /lib4/items/_search
{
"post_filter": {
"terms": {
"price": [25,40]
}
}
}
GET /lib4/items/_search
{
"post_filter": {
"term": {
"itemID": "ID100123" # 这里无法查询出来,因为itemId是text类型会被分词,分词后存储为小写,所以这里有2种处理办法,第一种,把id改为小写:id100123,第二种设置字段index:false
}
}
}
查看分词器分析的结果:
GET /lib4/_mapping
不希望商品id字段被分词,则重新创建映射
DELETE lib4
PUT /lib4
{
"mappings": {
"items": {
"properties": {
"itemID": {
"type": "text",
"index": false
}
}
}
}
}
可以实现组合过滤查询
格式:
{
"bool": {
"must": [],
"should": [],
"must_not": []
}
}
must:必须满足的条件—and
filter 不must不同,filter分值被忽略,过滤器字句在过滤器上下文执行,
should:可以满足也可以不满足的条件–or
must_not:不需要满足的条件–not
GET /lib4/items/_search
{
"post_filter": {
"bool": {
"should": [
{"term": {"price":25}},
{"term": {"itemID": "id100123"}}
],
"must_not": {
"term":{"price": 30}
}
}
}
}
嵌套使用bool:
GET /lib4/items/_search
{
"post_filter": {
"bool": {
"should": [
{"term": {"itemID": "id100123"}},
{
"bool": {
"must": [
{"term": {"itemID": "id100124"}},
{"term": {"price": 40}}
]
}
}
]
}
}
}
gt: >
lt: <
gte: >=
lte: <=
GET /lib4/items/_search
{
"post_filter": {
"range": {
"price": {
"gt": 25,
"lt": 50
}
}
}
}
GET /lib4/items/_search
{
"query": {
"bool": {
"filter": {
"exists":{
"field":"price"
}
}
}
}
}
GET /lib4/items/_search
{
"query" : {
"constant_score" : {
"filter": {
"exists" : { "field" : "price" }
}
}
}
}
ElasticSearch提供了一种特殊的缓存,即过滤器缓存(filter cache),用来存储过滤器的结果,被缓存的过滤器并不需要消耗过多的内存(因为它们只存储了哪些文档能与过滤器相匹配的相关信息),而且可供后续所有与之相关的查询重复使用,从而极大地提高了查询性能。
注意:ElasticSearch并不是默认缓存所有过滤器,
以下过滤器默认不缓存:
numeric_range
script
geo_bbox
geo_distance
geo_distance_range
geo_polygon
geo_shape
and
or
not
以下默认是开启缓存:
exists,missing,range,term,terms
开启方式:在filter查询语句后边加上
"_catch":true
(1)sum
GET /lib4/items/_search
{
"size":0,
"aggs": {
"price_of_sum": {
"sum": {
"field": "price"
}
}
}
}
(2)min
GET /lib4/items/_search
{
"size": 0,
"aggs": {
"price_of_min": {
"min": {
"field": "price"
}
}
}
}
(3)max
GET /lib4/items/_search
{
"size": 0,
"aggs": {
"price_of_max": {
"max": {
"field": "price"
}
}
}
}
(4)avg
GET /lib4/items/_search
{
"size":0,
"aggs": {
"price_of_avg": {
"avg": {
"field": "price"
}
}
}
}
(5)cardinality:求基数,互不相同的数,类似distinct,不包含null。(null,1,2,3,基数为3)
GET /lib4/items/_search
{
"size":0,
"aggs": {
"price_of_cardi": {
"cardinality": {
"field": "price"
}
}
}
}
(6)terms:分组
GET /lib4/items/_search
{
"size":0,
"aggs": {
"price_group_by": {
"terms": {
"field": "price"
}
}
}
}
对那些有唱歌兴趣的用户按年龄分组
GET /lib3/user/_search
{
"query": {
"match": {
"interests": "changge"
}
},
"size": 0,
"aggs":{
"age_group_by":{
"terms": {
"field": "age",
"order": {
"avg_of_age": "desc"
}
},
"aggs": {
"avg_of_age": {
"avg": {
"field": "age"
}
}
}
}
}
}
将多个基本查询组合成单一查询的查询
接收以下参数:
must:
文档 必须匹配这些条件才能被包含进来。
must_not:
文档 必须不匹配这些条件才能被包含进来。
should:
如果满足这些语句中的任意语句,将增加 _score,否则,无任何影响。它们主要用于修正每个文档的相关性得分。
filter:
必须 匹配,但它以不评分、过滤模式来进行。这些语句对评分没有贡献,只是根据过滤标准来排除或包含文档。
相关性得分是如何组合的。每一个子查询都独自地计算文档的相关性得分。一旦他们的得分被计算出来, bool 查询就将这些得分进行合并并且返回一个代表整个布尔操作的得分。
下面的查询用于查找 title 字段匹配 how to make millions 并且不被标识为 spam 的文档。那些被标识为 starred 或在2014之后的文档,将比另外那些文档拥有更高的排名。如果 两者 都满足,那么它排名将更高:
{
"bool": {
"must": { "match": { "title": "how to make millions" }},
"must_not": { "match": { "tag": "spam" }},
"should": [
{ "match": { "tag": "starred" }},
{ "range": { "date": { "gte": "2014-01-01" }}}
]
}
}
如果没有 must 语句,那么至少需要能够匹配其中的一条 should 语句。但,如果存在至少一条 must 语句,则对 should 语句的匹配没有要求。
如果我们不想因为文档的时间而影响得分,可以用 filter 语句来重写前面的例子:
{
"bool": {
"must": { "match": { "title": "how to make millions" }},
"must_not": { "match": { "tag": "spam" }},
"should": [
{ "match": { "tag": "starred" }}
],
"filter": {
"range": { "date": { "gte": "2014-01-01" }}
}
}
}
通过将 range 查询移到 filter 语句中,我们将它转成不评分的查询,将不再影响文档的相关性排名。由于它现在是一个不评分的查询,可以使用各种对 filter 查询有效的优化手段来提升性能。
bool 查询本身也可以被用做不评分的查询。简单地将它放置到 filter 语句中并在内部构建布尔逻辑:
{
"bool": {
"must": { "match": { "title": "how to make millions" }},
"must_not": { "match": { "tag": "spam" }},
"should": [
{ "match": { "tag": "starred" }}
],
"filter": {
"bool": {
"must": [
{ "range": { "date": { "gte": "2014-01-01" }}},
{ "range": { "price": { "lte": 29.99 }}}
],
"must_not": [
{ "term": { "category": "ebooks" }}
]
}
}
}
}
它将一个不变的常量评分应用于所有匹配的文档。它被经常用于你只需要执行一个 filter 而没有其它查询(例如,评分查询)的情况下。
{
"constant_score": {
"filter": {
"term": { "category": "ebooks" }
}
}
}
term 查询被放置在 constant_score 中,转成不评分的filter。这种方式可以用来取代只有 filter 语句的 bool 查询。
ElasticSearch是一个分布式系统,隐藏了复杂的处理机制
分片机制:我们不用关心数据是按照什么机制分片的、最后放入到哪个分片中
分片的副本:
集群发现机制(cluster discovery):比如当前我们启动了一个es进程,当启动了第二个es进程时,这个进程作为一个node自动就发现了集群,并且加入了进去
shard负载均衡:比如现在有10shard,集群中有3个节点,es会进行均衡的进行分配,以保持每个节点均衡的负载请求
请求路由
垂直扩容:购置新的机器,替换已有的机器
水平扩容:直接增加机器
增加或减少节点时会自动均衡
主节点的主要职责是和集群操作相关的内容,如创建或删除索引,跟踪哪些节点是群集的一部分,并决定哪些分片分配给相关的节点。稳定的主节点对集群的健康是非常重要的。
每个节点都能接收请求
每个节点接收到请求后都能把该请求路由到有相关数据的其它节点上
接收原始请求的节点负责采集数据并返回给客户端
1.index包含多个shard
2.每个shard都是一个最小工作单元,承载部分数据;每个shard都是一个lucene实例,有完整的建立索引和处理请求的能力
3.增减节点时,shard会自动在nodes中负载均衡
4.primary shard和replica shard,每个document肯定只存在于某一个primary shard以及其对应的replica shard中,不可能存在于多个primary shard
5.replica shard是primary shard的副本,负责容错,以及承担读请求负载
6.primary shard的数量在创建索引的时候就固定了,replica shard的数量可以随时修改
7.primary shard的默认数量是5,replica默认是1,默认有10个shard,5个primary shard,5个replica shard
8.primary shard不能和自己的replica shard放在同一个节点上(否则节点宕机,primary shard和副本都丢失,起不到容错的作用),但是可以和其他primary shard的replica shard放在同一个节点上
PUT /myindex
{
"settings" : {
"number_of_shards" : 3,
"number_of_replicas" : 1
}
}
这个时候,只会将3个primary shard分配到仅有的一个node上去,另外3个replica shard是无法分配的(一个shard的副本replica,他们两个是不能在同一个节点的)。集群可以正常工作,但是一旦出现节点宕机,数据全部丢失,而且集群不可用,无法接收任何请求。
将3个primary shard分配到一个node上去,另外3个replica shard分配到另一个节点上
primary shard 和replica shard 保持同步
primary shard 和replica shard 都可以处理客户端的读请求
1.扩容后primary shard和replica shard会自动的负载均衡
2.扩容后每个节点上的shard会减少,那么分配给每个shard的CPU,内存,IO资源会更多,性能提高
3.扩容的极限,如果有6个shard,扩容的极限就是6个节点,每个节点上一个shard,如果想超出扩容的极限,比如说扩容到9个节点,那么可以增加replica shard的个数
4.6个shard,3个节点,最多能承受几个节点所在的服务器宕机?(容错性)
任何一台服务器宕机都会丢失部分数据
为了提高容错性,增加shard的个数:
9个shard,(3个primary shard,6个replicashard),这样就能容忍最多两台服务器宕机了
总结:扩容是为了提高系统的吞吐量,同时也要考虑容错性,也就是让尽可能多的服务器宕机还能保证数据不丢失
以9个shard,3个节点为例:
1.如果master node 宕机,此时不是所有的primary shard都是Active status,所以此时的集群状态是red。
容错处理的第一步:是选举一台服务器作为master
容错处理的第二步:新选举出的master会把挂掉的primary shard的某个replica shard 提升为primary shard,此时集群的状态为yellow,因为少了一个replica shard,并不是所有的replica shard都是active status
容错处理的第三步:重启故障机,新master会把所有的副本都复制一份到该节点上,(同步一下宕机后发生的修改),此时集群的状态为green,因为所有的primary shard和replica shard都是Active status
1._index:
说明了一个文档存储在哪个索引中
同一个索引下存放的是相似的文档(文档的field多数是相同的)
索引名必须是小写的,不能以下划线开头,不能包括逗号
2._type:
表示文档属于索引中的哪个类型
一个索引下只能有一个type
类型名可以是大写也可以是小写的,不能以下划线开头,不能包括逗号
3._id:
文档的唯一标识,和索引,类型组合在一起唯一标识了一个文档
可以手动指定值,也可以由es来生成这个值
1.手动指定
put /index/type/66
通常是把其它系统的已有数据导入到es时
2.由es生成id值
post /index/type
es生成的id长度为20个字符,使用的是base64编码,URL安全,使用的是GUID算法,分布式下并发生成id值时不会冲突
其实就是我们在添加文档时request body中的内容
指定返回的结果中含有哪些字段:
get /index/type/1?_source=name
替换方式:
PUT /lib/user/4
{
"first_name" : "Jane",
"last_name" : "Lucy",
"age" : 24,
"about" : "I like to collect rock albums",
"interests": [ "music" ]
}
修改方式(partial update部分修改):
POST /lib/user/2/_update
{
"doc":{
"age":26
}
}
删除文档:标记为deleted,随着数据量的增加,es会选择合适的时间删除掉
es有内置的脚本支持,可以基于groovy脚本实现复杂的操作
1.修改年龄
POST /lib/user/4/_update
{
"script": "ctx._source.age+=1"
}
2.修改名字
POST /lib/user/4/_update
{
"script": "ctx._source.last_name+='hehe'"
}
3.添加爱好
POST /lib/user/4/_update
{
"script": {
"source": "ctx._source.interests.add(params.tag)",
"params": {
"tag":"picture"
}
}
}
4.删除爱好
POST /lib/user/4/_update
{
"script": {
"source": "ctx._source.interests.remove(ctx._source.interests.indexOf(params.tag))",
"params": {
"tag":"picture"
}
}
}
5.删除文档
POST /lib/user/4/_update
{
"script": {
"source": "ctx.op=ctx._source.age==params.count?'delete':'none'",
"params": {
"count":29 # 如果这里是数值类型,不要加引号
}
}
}
6.upsert (如果存在执行script,如果不存在则,插入更新)
POST /lib/user/4/_update
{
"script": "ctx._source.age += 1",
"upsert": {
"first_name" : "Jane",
"last_name" : "Lucy",
"age" : 20,
"about" : "I like to collect rock albums",
"interests": [ "music" ]
}
}
使用的是乐观锁:_version
retry_on_conflict:
POST /lib/user/4/_update?retry_on_conflict=3
重新获取文档数据和版本信息进行更新,不断的操作,最多操作的次数就是retry_on_conflict的值
1.文档路由到分片上:
一个索引由多个分片构成,当添加(删除,修改)一个文档时,es就需要决定这个文档存储在哪个分片上,这个过程就称为数据路由(routing)
2.路由算法:
shard=hash(routing) % number_of_pirmary_shards
示例:一个索引,3个primary shard
(1)每次增删改查时,都有一个routing值,默认是文档的_id的值
(2)对这个routing值使用哈希函数进行计算
(3)计算出的值再和主分片个数取余数
余数肯定在0—(number_of_pirmary_shards-1)之间,文档就在对应的shard上
routing值默认是文档的_id的值,也可以手动指定一个值,手动指定对于负载均衡以及提高批量读取的性能都有帮助
3.primary shard个数一旦确定就不能修改了(修改后,文档路由会出错)
这里有点类似redis cluster模式下,测试可以redis-cli -c
1:发送增删改请求时,可以选择任意一个节点,该节点就成了协调节点(coordinating node)
2.协调节点使用路由算法进行路由,然后将请求转到primary shard所在节点,该节点处理请求,并把数据同步到它的replica shard
3.协调节点对客户端做出响应
1.任何一个增删改操作都可以跟上一个参数:consistency
PUT /lib/user/4?consistency=all
{
“script”: “ctx._source.last_name +=‘heheh’”
}
可以给该参数指定的值:
all: (all shard)所有的primary shard和replica shard都是活跃的才能执行
one: (primary shard)只要有一个primary shard是活跃的就可以执行
quorum: (default) 默认值,大部分shard是活跃的才能执行 (例如共有6个shard,至少有3个shard是活跃的才能执行写操作)
2.quorum机制:多数shard都是可用的,
int((primary+number_of_replica)/2)+1
例如:3个primary shard,1个replica
int((3+1)/2)+1=3
至少3个shard是活跃的
注意:可能出现shard不能分配齐全的情况
比如:1个primary shard,1个replica
int((1+1)/2)+1=2
但是如果只有一个节点,因为primary shard和replica shard不能在同一个节点上,所以仍然不能执行写操作
再举例:1个primary shard,3个replica,2个节点
int((1+3)/2)+1=3
因为相同的replica shard在同一节点上只能有一个,所以这里会出现分配不齐全齐全。最后:当活跃的shard的个数没有达到要求时,
es默认会等待一分钟,如果在等待的期间活跃的shard的个数没有增加,则显示timeout
# 自定义等待时间
PUT /index/type/id?timeout=60s
第一步:查询请求发给任意一个节点,该节点就成了coordinating node,该节点使用路由算法算出文档所在的primary shard
第二步:协调节点把请求转发给primary shard也可以转发给replica shard(使用轮询调度算法(Round-Robin Scheduling,把请求平均分配至primary shard 和replica shard)
第三步:处理请求的节点把结果返回给协调节点,协调节点再返回给应用程序
特殊情况:请求的文档还在建立索引的过程中,primary shard上存在,但replica shar上不存在,但是请求被转发到了replica shard上,这时就会提示找不到文档,所有es是近实时
bulk的格式:
{action:{metadata}}\n # 行为
{requstbody}\n # 请求体
为什么不使用如下格式:
[{
"action": {
},
"data": {
}
}]
这种方式可读性好,但是内部处理就麻烦了:
1.将json数组解析为JSONArray对象,在内存中就需要有一份json文本的拷贝,另外还有一个JSONArray对象。
2.解析json数组里的每个json,对每个请求中的document进行路由
3.为路由到同一个shard上的多个请求,创建一个请求数组
4.将这个请求数组序列化
5.将序列化后的请求数组发送到对应的节点上去
耗费更多内存,增加java虚拟机开销
1.不用将其转换为json对象,直接按照换行符切割json,内存中不需要json文本的拷贝
2.对每两个一组的json,读取meta,进行document路由
3.直接将对应的json发送到node上去
{
"took": 419,
"timed_out": false,
"_shards": {
"total": 3,
"successful": 3,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 3,
"max_score": 0.6931472,
"hits": [
{
"_index": "lib3",
"_type": "user",
"_id": "3",
"_score": 0.6931472,
"_source": {
"address": "bei jing hai dian qu qing he zhen",
"name": "lisi"
}
},
{
"_index": "lib3",
"_type": "user",
"_id": "2",
"_score": 0.47000363,
"_source": {
"address": "bei jing hai dian qu qing he zhen",
"name": "zhaoming"
}
}
}
}
took:查询耗费的时间,单位是毫秒
_shards:共请求了多少个shard
total:查询出的文档总个数
max_score: 本次查询中,相关度分数的最大值,文档和此次查询的匹配度越高,_score的值越大,排位越靠前
hits:默认查询前10个文档
timed_out:
GET /lib3/user/_search?timeout=10ms #只返回10ms查询到的数据
{
"_source": ["address","name"],
"query": {
"match": {
"interests": "changge"
}
}
}
GET _search
GET /lib/_search
GET /lib,lib3/_search
GET /*3,*4/_search
GET /lib/user/_search
GET /lib,lib4/user,items/_search
GET /_all/_search
GET /_all/user,items/_search
GET /lib3/user/_search
{
"from":0,
"size":2,
"query":{
"terms":{
"interests": ["hejiu","changge"]
}
}
}
GET /_search?from=0&size=3
deep paging:查询的很深,比如一个索引有三个primary shard,分别存储了6000条数据,我们要得到第100页的数据(每页10条),类似这种情况就叫deep paging
如何得到第100页的10条数据?
在每个shard中搜索990到999这10条数据,然后用这30条数据排序,排序之后取10条数据就是要搜索的数据,这种做法是错的,因为3个shard中的数据的_score分数不一样,可能这某一个shard中第一条数据的_score分数比另一个shard中第1000条都要高,所以在每个shard中搜索990到999这10条数据然后排序的做法是不正确的。
正确的做法是每个shard把0到999条数据全部搜索出来(按排序顺序),然后全部返回给coordinate node,由coordinate node按_score分数排序后,取出第100页的10条数据,然后返回给客户端。
deep paging性能问题
1.耗费网络带宽,因为搜索过深的话,各shard要把数据传送给coordinate node,这个过程是有大量数据传递的,消耗网络,
2.消耗内存,各shard要把数据传送给coordinate node,这个传递回来的数据,是被coordinate node保存在内存中的,这样会大量消耗内存。
3.消耗cpu coordinate node要把传回来的数据进行排序,这个排序过程很消耗cpu.
鉴于deep paging的性能问题,所以应尽量减少使用。
PUT /myindex/article/1
{
"post_date": "2018-05-10",
"title": "Java",
"content": "java is the best language",
"author_id": 119
}
PUT /myindex/article/2
{
"post_date": "2018-05-12",
"title": "htitle",
"content": "I like html",
"author_id": 120
}
PUT /myindex/article/3
{
"post_date": "2018-05-16",
"title": "es",
"content": "Es is distributed document store",
"author_id": 110
}
PUT /myindex/article/4
{
"post_date": "2018-05-16",
"title": "es",
"content": "Es is distributed document store",
"author_id": 110,
"address":"shatianzhen"
}
# 查询:
GET /myindex/_search
# 时间字段不分词,必须精确匹配
GET /myindex/article/_search?q=post_date:2018-05-10
# 如果会在所有字段进行匹配,效率低,指定字段,或者使用copy_to优化
GET /myindex/article/_search?q=html,document
# 指定字段
GET /myindex/article/_search?q=content:html,document
copy_to:
copy_to 需要在自定义mapping中设定,将字段合并在一起。如下:
# 将address、content 使用copy_to fullcontent
PUT /myindex/article/_mapping
{
"properties": {
"address": {
"type": "text",
"copy_to": "fullcontent", # copy_to
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"author_id": {
"type": "long"
},
"content": {
"type": "text",
"copy_to": "fullcontent", # copy_to
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"ids": {
"type": "long"
},
"post_date": {
"type": "date"
},
"title": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
# 查询
# 可以查询出htitle,依旧所有匹配
GET /myindex/article/_search?q=htitle,document
# 指定fullcontent,htitle无法查询出来。
GET /myindex/article/_search?q=fullcontent:htitle,document
copy_to字段是把其它字段中的值,以空格为分隔符组成一个大字符串,然后被分析和索引,但是不存储,也就是说它能被查询,但不能被取回显示。
注意:copy_to指向的字段字段类型要为:text
对一个字符串类型的字段进行排序通常不准确,因为已经被分词成多个词条了
解决方式:对字段索引两次,一次索引分词(用于搜索),一次索引不分词(用于排序)
GET /lib3/_search
GET /lib3/user/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"interests": {
"order": "desc" # 因为被分词了,所以报错
}
}
]
}
# 修改_mapping
DELETE lib3
PUT /lib3
{
"settings":{
"number_of_shards" : 3,
"number_of_replicas" : 0
},
"mappings":{
"user":{
"properties":{
"name": {"type":"text"},
"address": {"type":"text"},
"age": {"type":"integer"},
"birthday": {"type":"date"},
"interests": {
"type":"text",
"fields": { # 设置不分词,同时设置fielddata:true
"raw":{
"type": "keyword"
}
},
"fielddata": true # 这个不能漏掉
}
}
}
}
}
# 现在可以排序了
GET /lib3/user/_search?sort=interests:desc # 但是这里是根据分词后的第一个词排序
# 根据完整的一行排序:
GET /lib3/user/_search?sort=interests.raw:desc
# 或
GET /lib3/user/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"interests.raw": {
"order": "desc"
}
}
]
}
3.23 如何计算相关度分数
使用的是TF/IDF算法(Term Frequency&Inverse Document Frequency)
1.Term Frequency:我们查询的文本中的词条在document本中出现了多少次,出现次数越多,相关度越高
搜索内容: hello world
Hello,I love china.
Hello world,how are you!
2.Inverse Document Frequency:我们查询的文本中的词条在索引的所有文档中出现了多少次,出现的次数越多,相关度越低
搜索内容:hello world
hello,what are you doing?
I like the world.
hello 在索引的所有文档中出现了500次,world出现了100次
3.Field-length(字段长度归约) norm:field越长,相关度越低
搜索内容:hello world
{“title”:“hello,what’s your name?”,“content”:{“owieurowieuolsdjflk”}}
{“title”:“hi,good morning”,“content”:{“lkjkljkj…world”}}
查看分数是如何计算的:
GET /lib3/user/_search?explain=true
{
"query":{
"match":{
"interests": "duanlian,changge"
}
}
}
查看一个文档能否匹配上某个查询:
GET /lib3/user/2/_explain
{
"query":{
"match":{
"interests": "duanlian,changge"
}
}
}
DocValues其实是Lucene在构建倒排索引时,会额外建立一个有序的正排索引(基于document => field value的映射列表)
非字符串类型,如数字,日期等,在创建倒排索引的时候,还会创建正排索引。字符串可通过配置mapping设定。
正排索引:
{“birthday”:“1985-11-11”,age:23}
{“birthday”:“1989-11-11”,age:29}
document age birthday
doc1 23 1985-11-11
doc2 29 1989-11-11
存储在磁盘上,节省内存
对排序,分组和一些聚合操作能够大大提升性能
注意:默认对不分词的字段是开启的,对分词字段无效(需要把fielddata设置为true)
PUT /lib3
{
"settings":{
"number_of_shards" : 3,
"number_of_replicas" : 0
},
"mappings":{
"user":{
"properties":{
"name": {"type":"text"},
"address": {"type":"text"},
"age": {
"type":"integer",
"doc_values":false # 可以关闭默认正排索引,就不能排序了
},
"interests": {"type":"text"},
"birthday": {"type":"date"}
}
}
}
}
如果一次性要查出来比如10万条数据,那么性能会很差,此时一般会采取用scoll滚动查询,一批一批的查,直到所有数据都查询完为止。
1.scoll搜索会在第一次搜索的时候,保存一个当时的视图快照,之后只会基于该旧的视图快照提供数据搜索,如果这个期间数据变更,是不会让用户看到的
2.采用基于_doc(不使用_score)进行排序的方式,性能较高
3.每次发送scroll请求,我们还需要指定一个scoll参数,指定一个时间窗口,每次搜索请求只要在这个时间窗口内能完成就可以了
# 第一次查询,会返回一个scroll_id
GET /lib3/user/_search?scroll=1m
{
"query": {
"match_all": {}
},
"sort":["_doc"],
"size":3
}
# 以后每次查询,使用前面的scroll_id
GET /_search/scroll
{
"scroll": "1m",
"scroll_id": "DnF1ZXJ5VGhlbkZldGNoAwAAAAAAAAAdFkEwRENOVTdnUUJPWVZUd1p2WE5hV2cAAAAAAAAAHhZBMERDTlU3Z1FCT1lWVHdadlhOYVdnAAAAAAAAAB8WQTBEQ05VN2dRQk9ZVlR3WnZYTmFXZw==" # 使用前面返回的id
}
当添加的doc出现了没有定义的字段,通过dynamic来设置处理策略:
1.true:遇到陌生字段就 dynamic mapping
2.false:遇到陌生字段就忽略
3.strict:约到陌生字段就报错
PUT /lib5
{
"settings": {
"number_of_shards": 5,
"number_of_replicas": 0
},
"mappings": {
"user":{
"dynamic": "strict", # 对整个type生效
"properties": {
"name": {
"type": "text"
},
"address":{
"type": "object",
"dynamic": true # 对address这个字段处理
}
}
}
}
}
#会报错
PUT /lib5/user/1
{
"name":"lisi",
"age":20,
"address":{
"province":"beijing",
"city":"beijing"
}
}
date_detection:默认会按照一定格式识别date,比如yyyy-MM-dd,默认true
可以手动关闭某个type的date_detection
PUT /lib8
{
"settings":{
"number_of_shards" : 3,
"number_of_replicas" : 0
},
"mappings":{
"user":{
"date_detection": false # 不自动识别为日期
}
}
}
定制 dynamic mapping template(type)
PUT /my_index
{
"mappings": {
"my_type": {
"dynamic_templates": [
{
"en": {
"match": "*_en", # 如果字段匹配到这个,则使用下面mapping的模版
"match_mapping_type": "string",
"mapping": { # 这里模版就定义了一个字段,使用english作为分词器,该分词器忽略is、a、an等
"type": "text",
"analyzer": "english"
}
}
}
]
}
}
}
#使用了模板
PUT /my_index/my_type/3
{
"title_en": "this is my dog"
}
#没有使用模板
PUT /my_index/my_type/5
{
"title": "this is my cat"
}
GET my_index/my_type/_search
{
"query": {
"match": {
"title": "is" # 如果是english分词器会忽略is
}
}
}
一个field的设置是不能修改的,如果要修改一个field,那么应该重新按照新的mapping,建立一个index,然后将数据批量查询出来,重新用bulk api写入到index中。
批量查询的时候,建议采用scroll api,并且采用多线程并发的方式来reindex数据,每次scroll就查询指定日期的一段数据,交给一个线程即可。
PUT /index1/type1/4
{
"content":"1990-12-12"
}
GET /index1/type1/_search
GET /index1/type1/_mapping
#报错
PUT /index1/type1/4
{
"content":"I am very happy."
}
如果要添加,那么需要修改content的类型为string类型,
执行以下报错,不允许修改(字段类型一旦确定无法修改)
PUT /index1/_mapping/type1
{
"properties": {
"content":{
"type": "text"
}
}
}
修改字段,需要重建索引!
#创建一个新的索引,把index1(旧索引)索引中的数据查询出来导入到新的索引中
#但是之前的Java应用程序使用的是之前的索引,为了不用重启Java应用程序,给index1(旧索引)这个索引起个#别名
# 给旧的索引起一个别名:index2,第三方类似Java应用程序使用别名,而不是使用真实索引名称
PUT /index1/_alias/index2
#创建新的索引,把content的类型改为字符串
PUT /newindex
{
"mappings": {
"type1":{
"properties": {
"content":{
"type": "text"
}
}
}
}
}
#如果数据量很大,使用scroll批量查询,再用bulk批量插入
GET /index1/type1/_search?scroll=1m
{
"query": {
"match_all": {}
},
"sort": ["_doc"],
"size": 2
}
#使用bulk批量写入新的索引
POST /_bulk
{"index":{"_index":"newindex","_type":"type1","_id":1}}
{"content":"1982-12-12"}
#将别名index2和新的索引关联,应用程序不用重启
POST /_aliases
{
"actions": [
{"remove": {"index":"index1","alias":"index2"}},
{"add": {"index": "newindex","alias": "index2"}}
]
}
GET index2/type1/_search
倒排索引包括:
文档的列表,文档的数量,词条在每个文档中出现的次数,出现的位置,每个文档的长度,所有文档的平均长度
索引不变的原因:
不需要锁,提升了并发性能
可以一直保存在缓存中(filter)
节省cpu和io开销
数据准备
PUT /index1
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 0
},
"mappings": {
"blog":{
"properties": {
"id":{
"type": "long"
},
"title":{
"type": "text",
"analyzer": "ik_max_word"
},
"content":{
"type": "text",
"analyzer": "ik_max_word"
},
"postdate":{
"type": "date"
},
"url":{
"type": "text"
}
}
}
}
}
# 添加数据
PUT /index1/blog/1
{
"id":"1",
"title":"Java设计模式之装饰模式",
"content":"在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。",
"postdate":"2018-12-13",
"url":"csdn.net/79239072"
}
pom中加入ElasticSearch6.2.4的依赖:
<repositories>
<repository>
<id>maven-aliid>
<url>http://maven.aliyun.com/nexus/content/groups/public/url>
<releases>
<enabled>trueenabled>
releases>
<snapshots>
<enabled>trueenabled>
<updatePolicy>alwaysupdatePolicy>
<checksumPolicy>failchecksumPolicy>
snapshots>
repository>
repositories>
<dependencies>
<dependency>
<groupId>org.elasticsearch.clientgroupId>
<artifactId>transportartifactId>
<version>6.2.4version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-coreartifactId>
<version>2.9.0version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.2version>
<configuration>
<source>1.8source>
<target>1.8target>
<encoding>UTF-8encoding>
configuration>
plugin>
plugins>
build>
public class EsDemo {
private static Settings settings;
private static TransportClient client;
@Before
public void before() {
settings = Settings.builder().put("cluster.name", "elasticsearch").build();
try {
client = new PreBuiltTransportClient(settings)
.addTransportAddress(new TransportAddress(InetAddress.getByName("192.168.9.222"), 9300));
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
查询指定id
@Test
public void test1() throws Exception {
GetResponse response = client.prepareGet("lib3", "user", "1").execute().actionGet();
System.out.println(response.getSourceAsString());
client.close();
}
mget查询:
@Test
public void mget() {
MultiGetResponse multiGetItemResponses = client.prepareMultiGet()
.add("index1", "blog", "1", "2"
).add("lib3", "user", "1", "2", "3").get();
for (MultiGetItemResponse item : multiGetItemResponses) {
GetResponse response = item.getResponse();
if (response != null && response.isExists()) {
System.out.println(response.getSourceAsString());
}
}
}
查询:
match query
multi match query
term/terms
@Test
public void testMatch() {
/*MatchAllQueryBuilder builder = QueryBuilders.matchAllQuery();*/
/* MatchQueryBuilder builder = QueryBuilders.matchQuery("interests", "dance");*/
/* // 显示查询的词内容,然后才是字段
MultiMatchQueryBuilder builder = QueryBuilders.multiMatchQuery("shopping guangfeng", "interests","address");*/
/*TermQueryBuilder builder = QueryBuilders.termQuery("interests", "shopping");*/
/*TermsQueryBuilder builder = QueryBuilders.termsQuery("interests", "shopping","drink");*/
/*RangeQueryBuilder builder = QueryBuilders.rangeQuery("birthday").from("2000-01-01").to("2016-01-21");*/
/*PrefixQueryBuilder builder = QueryBuilders.prefixQuery("interests", "dri");*/
/*WildcardQueryBuilder builder = QueryBuilders.wildcardQuery("interests", "*");*/
/*FuzzyQueryBuilder builder = QueryBuilders.fuzzyQuery("interests", "shapping");*/
/*// 查询type下所有
TypeQueryBuilder builder = QueryBuilders.typeQuery("user");*/
IdsQueryBuilder builder = QueryBuilders.idsQuery("user").addIds("1", "2");
SearchResponse searchResponse = client.prepareSearch("lib3").setQuery(builder).get();
SearchHits hits = searchResponse.getHits();
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
}
client.close();
}
/**
* 插入数据
*/
@Test
public void testInsert() {
try {
XContentBuilder doc = XContentFactory.jsonBuilder().startObject()
.field("id", "1")
.field("title", "Java设计模式之装饰模式")
.field("content", "在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能")
.field("postdate", new SimpleDateFormat("yyyy-MM-dd").format(new Date()))
.field("url", "csdn.net/79239072").endObject();
System.out.println(doc.string());
// 添加文档
IndexResponse response = client.prepareIndex("index1", "blog", "1").setSource(doc).get();
System.out.println(response.status());
} catch (IOException e) {
e.printStackTrace();
}
client.close();
}
/**
* 删除文档
*/
@Test
public void testDelete() {
DeleteResponse deleteResponse = client.prepareDelete("index1", "blog", "11").get();
System.out.println(deleteResponse.status());
client.close();
}
查询删除:
/**
* 查询删除DeleteByQuery
*/
@Test
public void testDeleteByQuery() {
BulkByScrollResponse response = DeleteByQueryAction.INSTANCE.newRequestBuilder(client)
.filter(QueryBuilders.matchQuery("title", "工厂"))
.source("index1")
.get();
long deleted = response.getDeleted();
System.out.println(deleted);
}
/**
* client.update 以及client.prepareUpdate
* 直接调用client.prepareUpdate
*/
@Test
public void testPostUpdate() {
try {
UpdateResponse updateResponse = client.prepareUpdate("index1", "blog", "1").setDoc(XContentFactory.jsonBuilder().startObject()
.field("title", "单例设计模式").endObject()).get();
System.out.println(updateResponse.status());
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 调用client.update,两者区别??
*/
@Test
public void testPostUpdate2() {
try {
UpdateRequest updateRequest = new UpdateRequest().index("index1").type("blog").id("1").doc(XContentFactory.jsonBuilder().startObject().field("content", "单例设计模式").endObject());
UpdateResponse response = client.update(updateRequest).get();
System.out.println(response.status());
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
/**
* 如果存在则更新,否则插入indexRequest内容。
* update or insert 如果存在则update,不存在则insert
*/
@Test
public void testUpsert() {
try {
IndexRequest indexRequest = new IndexRequest("index1", "blog", "2").source(XContentFactory.jsonBuilder().startObject()
.field("id", "1")
.field("title", "工厂模式")
.field("content", "静态工厂,实例工厂,不存在则isnert")
.field("postdate", new SimpleDateFormat("yyyy-MM-dd").format(new Date()))
.field("url", "csdn.net/marljh").endObject());
UpdateRequest updateRequest = new UpdateRequest("index1", "blog", "2").doc(XContentFactory.jsonBuilder().startObject()
.field("content", "这是工厂模式,设计模式,这是已经存在则update")
.endObject()).upsert(indexRequest);
UpdateResponse updateResponse = client.update(updateRequest).get();
System.out.println(updateResponse.status());
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
@Test
public void testBulk() {
XContentBuilder baidu = null;
XContentBuilder google = null;
try {
baidu = XContentFactory.jsonBuilder().startObject()
.field("id", "3")
.field("title", "baidu")
.field("content", "baidu")
.field("postdate", new SimpleDateFormat("yyyy-MM-dd").format(new Date()))
.field("url", "www.baidu.com").endObject();
google = XContentFactory.jsonBuilder().startObject()
.field("id", "4")
.field("title", "google")
.field("content", "googlel")
.field("postdate", new SimpleDateFormat("yyyy-MM-dd").format(new Date()))
.field("url", "www.google").endObject();
} catch (IOException e) {
e.printStackTrace();
}
BulkRequestBuilder bulkRequestBuilder = client.prepareBulk();
// 不设定id,则自动创建
bulkRequestBuilder.add(client.prepareIndex("index1", "blog", "3").setSource(baidu));
bulkRequestBuilder.add(client.prepareIndex("index1", "blog", "4").setSource(google));
BulkResponse bulkItemResponses = null;
bulkItemResponses = bulkRequestBuilder.get();
// 下面这个代码需要抛出2个异常,实际使用没啥区别!
// bulkItemResponses = bulkRequestBuilder.execute().get();
System.out.println(bulkItemResponses.status());
if (bulkItemResponses.hasFailures()) {
System.out.println("bulk has failures!");
String failureMessage = bulkItemResponses.buildFailureMessage();
System.out.println(failureMessage);
}
}
/**
* 聚合函数 max min avg cardinality
*/
@Test
public void testAggs() {
/* AggregationBuilder agg = AggregationBuilders.max("aggMax").field("age");
SearchResponse searchResponse = client.prepareSearch("lib3").addAggregation(agg).get();
Max max = searchResponse.getAggregations().get("aggMax");
System.out.println(max.getValue());*/
/*AggregationBuilder agg = AggregationBuilders.min("aggMin").field("age");
SearchResponse searchResponse = client.prepareSearch("lib3").addAggregation(agg).get();
Min min = searchResponse.getAggregations().get("aggMin");
System.out.println(min.getValue());*/
AvgAggregationBuilder avgAggregationBuilder = AggregationBuilders.avg("avgAge").field("age");
SearchResponse searchResponse = client.prepareSearch("lib3").addAggregation(avgAggregationBuilder).get();
Avg avgAge = searchResponse.getAggregations().get("avgAge");
System.out.println(avgAge.getValue());
/*AggregationBuilder card = AggregationBuilders.cardinality("aggCardinality").field("age");
SearchResponse searchResponse = client.prepareSearch("lib3").addAggregation(card).get();
Cardinality cardinality = searchResponse.getAggregations().get("aggCardinality");
System.out.println(cardinality.getValue());*/
}
/**
* 类似查询:GET /myindex/article/_search?q=fullcontent:htitle,document
*/
@Test
public void testQ() {
/*//类似指定字段查询:GET /myindex/article/_search?q=fullcontent:htitle,document
CommonTermsQueryBuilder builder = QueryBuilders.commonTermsQuery("name", "li si");*/
/*//类似查询:GET /myindex/article/_search?q=htitle,document
QueryStringQueryBuilder builder = QueryBuilders.queryStringQuery("shopping");*/
// 类似
SimpleQueryStringBuilder builder = QueryBuilders.simpleQueryStringQuery("dance tongfan");
SearchResponse searchResponse = client.prepareSearch("lib3").setQuery(builder).get();
SearchHits hits = searchResponse.getHits();
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
}
}
/**
* 组合查询bool
*/
@Test
public void testBool() {
/* BoolQueryBuilder builder = QueryBuilders.boolQuery().must(QueryBuilders.matchQuery("interests", "dance shopping"))
.should(QueryBuilders.matchQuery("name","ljh"))
.mustNot(QueryBuilders.matchQuery("interests","reading"))
.filter(QueryBuilders.rangeQuery("birthday").gte("2010-10-10").format("yyyy-MM-dd"));*/
// 常量分数
// ConstantScoreQueryBuilder builder = QueryBuilders.constantScoreQuery(QueryBuilders.termQuery("name", "ljh"));
QueryStringQueryBuilder builder = QueryBuilders.queryStringQuery("ljh");
SearchResponse searchResponse = client.prepareSearch("lib3").setQuery(builder).get();
System.out.println(searchResponse.getHits().getMaxScore());
SearchHits hits = searchResponse.getHits();
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
}
}
/**
* 分组聚合
* select age,count(1) from dual group by age;
*/
@Test
public void testBucket() {
// 分组聚合
TermsAggregationBuilder agg = AggregationBuilders.terms("terms").field("age");
SearchResponse searchResponse = client.prepareSearch("lib3").addAggregation(agg).execute().actionGet();
Terms terms = searchResponse.getAggregations().get("terms");
for (Terms.Bucket entry : terms.getBuckets()) {
System.out.println(entry.getKey() + ":" + entry.getDocCount());
}
}
/**
* 过滤filter
*/
@Test
public void testFilter() {
TermsQueryBuilder termsQueryBuilder = QueryBuilders.termsQuery("age", "20");
FilterAggregationBuilder filterAggregationBuilder = AggregationBuilders.filter("filter", termsQueryBuilder);
SearchResponse searchResponse = client.prepareSearch("lib3").addAggregation(filterAggregationBuilder).execute().actionGet();
Filter filter = searchResponse.getAggregations().get("filter");
System.out.println(filter.getDocCount());
}
/**
* filter聚合,打印结果可以根据指定key来输出:
* dance_count:4
* drink_count:4
*/
@Test
public void testAGGFilter() {
FiltersAggregationBuilder filtersAggregationBuilder = AggregationBuilders.filters("filters",
new FiltersAggregator.KeyedFilter("dance_count", QueryBuilders.termsQuery("interests", "dance"))
, new FiltersAggregator.KeyedFilter("drink_count", QueryBuilders.termsQuery("interests", "drink")));
SearchResponse searchResponse = client.prepareSearch("lib3").addAggregation(filtersAggregationBuilder).execute().actionGet();
Filters filters = searchResponse.getAggregations().get("filters");
for (Filters.Bucket entry : filters.getBuckets()) {
System.out.println(entry.getKey() + ":" + entry.getDocCount());
}
}
/**
* 打印结果:
* all:6
* middle:2
* from:2
*/
@Test
public void testAGGRange() {
RangeAggregationBuilder rangeAggregationBuilder = AggregationBuilders.range("my_range").field("age")
.addUnboundedTo("all", 50) // (*,to)
.addRange("middle", 25, 50) // [25,50)
.addUnboundedFrom("from", 25);// [from,*)
SearchResponse searchResponse = client.prepareSearch("lib3").addAggregation(rangeAggregationBuilder).execute().actionGet();
Range range = searchResponse.getAggregations().get("my_range");
for (Range.Bucket entry : range.getBuckets()) {
System.out.println(entry.getKey() + ":" + entry.getDocCount());
}
}
/**
* 字段没有值,值为null时,会统计出来,如果字段是不存在的,则个数就是索引下的全部
* {"value_missinig":{"doc_count":0}}
*/
@Test
public void testMissing() {
MissingAggregationBuilder missingAggregationBuilder = AggregationBuilders.missing("value_missinig").field("age");
SearchResponse searchResponse = client.prepareSearch("demo").addAggregation(missingAggregationBuilder).execute().actionGet();
Missing missing = searchResponse.getAggregations().get("value_missinig");
System.out.println(missing.toString());
}
/**
* 集群信息查询
*/
@Test
public void testManagerCluster() {
ClusterHealthResponse healths = client.admin().cluster().prepareHealth().get();
String clusterName = healths.getClusterName();
System.out.println("clusterName : " + clusterName);
int numberOfDataNodes = healths.getNumberOfDataNodes();
System.out.println("numberOfDataNodes : "+numberOfDataNodes);
for(ClusterIndexHealth health:healths.getIndices().values()){
String index = health.getIndex();
int numberOfShards = health.getNumberOfShards();
int numberOfReplicas = health.getNumberOfReplicas();
ClusterHealthStatus status = health.getStatus();
System.out.println(status.toString());
System.out.printf("index= %s,numberOfShards=%s,numberOfReplicas=%s\n",index,numberOfShards,numberOfReplicas);
}
}
import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
import org.elasticsearch.action.bulk.BulkRequestBuilder;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.get.MultiGetItemResponse;
import org.elasticsearch.action.get.MultiGetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.cluster.health.ClusterHealthStatus;
import org.elasticsearch.cluster.health.ClusterIndexHealth;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.index.query.*;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.DeleteByQueryAction;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.filter.*;
import org.elasticsearch.search.aggregations.bucket.missing.Missing;
import org.elasticsearch.search.aggregations.bucket.missing.MissingAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.range.Range;
import org.elasticsearch.search.aggregations.bucket.range.RangeAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.avg.Avg;
import org.elasticsearch.search.aggregations.metrics.avg.AvgAggregationBuilder;
import org.elasticsearch.transport.client.PreBuiltTransportClient;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.net.InetAddress;
import java.text.SimpleDateFormat;
import java.util.Date;
public class EsDemo {
private static Settings settings;
private static TransportClient client;
@Before
public void before() throws Exception {
settings = Settings.builder().put("cluster.name", "elasticsearch").build();
client = new PreBuiltTransportClient(settings)
.addTransportAddress(new TransportAddress(InetAddress.getByName("192.168.9.222"), 9300));
}
/**
* 查询api
*/
@Test
public void test1() throws Exception {
GetResponse response = client.prepareGet("lib3", "user", "1").execute().actionGet();
System.out.println(response.getSourceAsString());
client.close();
}
/**
* 查询API
* match query
* multi match query
* term/terms
*/
@Test
public void testMatch() {
/*MatchAllQueryBuilder builder = QueryBuilders.matchAllQuery();*/
/* MatchQueryBuilder builder = QueryBuilders.matchQuery("interests", "dance");*/
/* // 显示查询的词内容,然后才是字段
MultiMatchQueryBuilder builder = QueryBuilders.multiMatchQuery("shopping guangfeng", "interests","address");*/
/*TermQueryBuilder builder = QueryBuilders.termQuery("interests", "shopping");*/
/*TermsQueryBuilder builder = QueryBuilders.termsQuery("interests", "shopping","drink");*/
/*RangeQueryBuilder builder = QueryBuilders.rangeQuery("birthday").from("2000-01-01").to("2016-01-21");*/
/*PrefixQueryBuilder builder = QueryBuilders.prefixQuery("interests", "dri");*/
/*WildcardQueryBuilder builder = QueryBuilders.wildcardQuery("interests", "*");*/
/*FuzzyQueryBuilder builder = QueryBuilders.fuzzyQuery("interests", "shapping");*/
/*// 查询type下所有
TypeQueryBuilder builder = QueryBuilders.typeQuery("user");*/
IdsQueryBuilder builder = QueryBuilders.idsQuery("user").addIds("1", "2");
SearchResponse searchResponse = client.prepareSearch("lib3").setQuery(builder).get();
SearchHits hits = searchResponse.getHits();
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
}
client.close();
}
/**
* 插入数据
*/
@Test
public void testInsert() throws Exception {
XContentBuilder doc = XContentFactory.jsonBuilder().startObject()
.field("id", "1")
.field("title", "Java设计模式之装饰模式")
.field("content", "在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。")
.field("postdate", new SimpleDateFormat("yyyy-MM-dd").format(new Date()))
.field("url", "csdn.net/79239072").endObject();
System.out.println(doc.string());
// 添加文档
IndexResponse response = client.prepareIndex("index1", "blog", "1").setSource(doc).get();
System.out.println(response.status());
client.close();
}
/**
* 删除文档
*/
@Test
public void testDelete() {
DeleteResponse deleteResponse = client.prepareDelete("index1", "blog", "11").get();
System.out.println(deleteResponse.status());
client.close();
}
/**
* client.update 以及client.prepareUpdate
* 直接调用client.prepareUpdate
*/
@Test
public void testPostUpdate() throws Exception {
UpdateResponse updateResponse = client.prepareUpdate("index1", "blog", "1").setDoc(XContentFactory.jsonBuilder().startObject()
.field("title", "单例设计模式").endObject()).get();
System.out.println(updateResponse.status());
}
/**
* 调用client.update,两者区别??
*/
@Test
public void testPostUpdate2() throws Exception {
UpdateRequest updateRequest = new UpdateRequest().index("index1").type("blog").id("1").doc(XContentFactory.jsonBuilder().startObject().field("content", "单例设计模式").endObject());
UpdateResponse response = client.update(updateRequest).get();
System.out.println(response.status());
}
/**
* 如果存在则更新,否则插入indexRequest内容。
* update or insert 如果存在则update,不存在则insert
*/
@Test
public void testUpsert() throws Exception {
IndexRequest indexRequest = new IndexRequest("index1", "blog", "2").source(XContentFactory.jsonBuilder().startObject()
.field("id", "1")
.field("title", "工厂模式")
.field("content", "静态工厂,实例工厂,不存在则isnert")
.field("postdate", new SimpleDateFormat("yyyy-MM-dd").format(new Date()))
.field("url", "csdn.net/marljh").endObject());
UpdateRequest updateRequest = new UpdateRequest("index1", "blog", "2").doc(XContentFactory.jsonBuilder().startObject()
.field("content", "这是工厂模式,设计模式,这是已经存在则update")
.endObject()).upsert(indexRequest);
UpdateResponse updateResponse = client.update(updateRequest).get();
System.out.println(updateResponse.status());
}
@Test
public void mget() {
MultiGetResponse multiGetItemResponses = client.prepareMultiGet()
.add("index1", "blog", "1", "2"
).add("lib3", "user", "1", "2", "3").get();
for (MultiGetItemResponse item : multiGetItemResponses) {
GetResponse response = item.getResponse();
if (response != null && response.isExists()) {
System.out.println(response.getSourceAsString());
}
}
}
@Test
public void testBulk() {
XContentBuilder baidu = null;
XContentBuilder google = null;
try {
baidu = XContentFactory.jsonBuilder().startObject()
.field("id", "3")
.field("title", "baidu")
.field("content", "baidu")
.field("postdate", new SimpleDateFormat("yyyy-MM-dd").format(new Date()))
.field("url", "www.baidu.com").endObject();
google = XContentFactory.jsonBuilder().startObject()
.field("id", "4")
.field("title", "google")
.field("content", "googlel")
.field("postdate", new SimpleDateFormat("yyyy-MM-dd").format(new Date()))
.field("url", "www.google").endObject();
} catch (IOException e) {
e.printStackTrace();
}
BulkRequestBuilder bulkRequestBuilder = client.prepareBulk();
// 不设定id,则自动创建
bulkRequestBuilder.add(client.prepareIndex("index1", "blog", "3").setSource(baidu));
bulkRequestBuilder.add(client.prepareIndex("index1", "blog", "4").setSource(google));
BulkResponse bulkItemResponses = null;
bulkItemResponses = bulkRequestBuilder.get();
// 下面这个代码需要抛出2个异常,实际使用没啥区别!
// bulkItemResponses = bulkRequestBuilder.execute().get();
System.out.println(bulkItemResponses.status());
if (bulkItemResponses.hasFailures()) {
System.out.println("bulk has failures!");
String failureMessage = bulkItemResponses.buildFailureMessage();
System.out.println(failureMessage);
}
}
/**
* 查询删除DeleteByQuery
*/
@Test
public void testDeleteByQuery() {
BulkByScrollResponse response = DeleteByQueryAction.INSTANCE.newRequestBuilder(client)
.filter(QueryBuilders.matchQuery("title", "工厂"))
.source("index1")
.get();
long deleted = response.getDeleted();
System.out.println(deleted);
}
/**
* 聚合函数 max min avg cardinality
*/
@Test
public void testAggs() {
/* AggregationBuilder agg = AggregationBuilders.max("aggMax").field("age");
SearchResponse searchResponse = client.prepareSearch("lib3").addAggregation(agg).get();
Max max = searchResponse.getAggregations().get("aggMax");
System.out.println(max.getValue());*/
/*AggregationBuilder agg = AggregationBuilders.min("aggMin").field("age");
SearchResponse searchResponse = client.prepareSearch("lib3").addAggregation(agg).get();
Min min = searchResponse.getAggregations().get("aggMin");
System.out.println(min.getValue());*/
AvgAggregationBuilder avgAggregationBuilder = AggregationBuilders.avg("avgAge").field("age");
SearchResponse searchResponse = client.prepareSearch("lib3").addAggregation(avgAggregationBuilder).get();
Avg avgAge = searchResponse.getAggregations().get("avgAge");
System.out.println(avgAge.getValue());
/*AggregationBuilder card = AggregationBuilders.cardinality("aggCardinality").field("age");
SearchResponse searchResponse = client.prepareSearch("lib3").addAggregation(card).get();
Cardinality cardinality = searchResponse.getAggregations().get("aggCardinality");
System.out.println(cardinality.getValue());*/
}
/**
* 类似查询:GET /myindex/article/_search?q=fullcontent:htitle,document
*/
@Test
public void testQ() {
/*//类似指定字段查询:GET /myindex/article/_search?q=fullcontent:htitle,document
CommonTermsQueryBuilder builder = QueryBuilders.commonTermsQuery("name", "li si");*/
/*//类似查询:GET /myindex/article/_search?q=htitle,document
QueryStringQueryBuilder builder = QueryBuilders.queryStringQuery("shopping");*/
// 类似
SimpleQueryStringBuilder builder = QueryBuilders.simpleQueryStringQuery("dance tongfan");
SearchResponse searchResponse = client.prepareSearch("lib3").setQuery(builder).get();
SearchHits hits = searchResponse.getHits();
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
}
}
/**
* 组合查询bool
*/
@Test
public void testBool() {
/* BoolQueryBuilder builder = QueryBuilders.boolQuery().must(QueryBuilders.matchQuery("interests", "dance shopping"))
.should(QueryBuilders.matchQuery("name","ljh"))
.mustNot(QueryBuilders.matchQuery("interests","reading"))
.filter(QueryBuilders.rangeQuery("birthday").gte("2010-10-10").format("yyyy-MM-dd"));*/
// 常量分数
// ConstantScoreQueryBuilder builder = QueryBuilders.constantScoreQuery(QueryBuilders.termQuery("name", "ljh"));
QueryStringQueryBuilder builder = QueryBuilders.queryStringQuery("ljh");
SearchResponse searchResponse = client.prepareSearch("lib3").setQuery(builder).get();
System.out.println(searchResponse.getHits().getMaxScore());
SearchHits hits = searchResponse.getHits();
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
}
}
/**
* 分组聚合
* select age,count(1) from dual group by age;
*/
@Test
public void testBucket() {
// 分组聚合
TermsAggregationBuilder agg = AggregationBuilders.terms("terms").field("age");
SearchResponse searchResponse = client.prepareSearch("lib3").addAggregation(agg).execute().actionGet();
Terms terms = searchResponse.getAggregations().get("terms");
for (Terms.Bucket entry : terms.getBuckets()) {
System.out.println(entry.getKey() + ":" + entry.getDocCount());
}
}
/**
* 过滤filter
*/
@Test
public void testFilter() {
TermsQueryBuilder termsQueryBuilder = QueryBuilders.termsQuery("age", "20");
FilterAggregationBuilder filterAggregationBuilder = AggregationBuilders.filter("filter", termsQueryBuilder);
SearchResponse searchResponse = client.prepareSearch("lib3").addAggregation(filterAggregationBuilder).execute().actionGet();
Filter filter = searchResponse.getAggregations().get("filter");
System.out.println(filter.getDocCount());
}
/**
* filter聚合,打印结果可以根据指定key来输出:
* dance_count:4
* drink_count:4
*/
@Test
public void testAGGFilter() {
FiltersAggregationBuilder filtersAggregationBuilder = AggregationBuilders.filters("filters",
new FiltersAggregator.KeyedFilter("dance_count", QueryBuilders.termsQuery("interests", "dance"))
, new FiltersAggregator.KeyedFilter("drink_count", QueryBuilders.termsQuery("interests", "drink")));
SearchResponse searchResponse = client.prepareSearch("lib3").addAggregation(filtersAggregationBuilder).execute().actionGet();
Filters filters = searchResponse.getAggregations().get("filters");
for (Filters.Bucket entry : filters.getBuckets()) {
System.out.println(entry.getKey() + ":" + entry.getDocCount());
}
}
/**
* 打印结果:
* all:6
* middle:2
* from:2
*/
@Test
public void testAGGRange() {
RangeAggregationBuilder rangeAggregationBuilder = AggregationBuilders.range("my_range").field("age")
.addUnboundedTo("all", 50) // (*,to)
.addRange("middle", 25, 50) // [25,50)
.addUnboundedFrom("from", 25);// [from,*)
SearchResponse searchResponse = client.prepareSearch("lib3").addAggregation(rangeAggregationBuilder).execute().actionGet();
Range range = searchResponse.getAggregations().get("my_range");
for (Range.Bucket entry : range.getBuckets()) {
System.out.println(entry.getKey() + ":" + entry.getDocCount());
}
}
/**
* 字段没有值,值为null时,会统计出来,如果字段是不存在的,则个数就是索引下的全部
* {"value_missinig":{"doc_count":0}}
*/
@Test
public void testMissing() {
MissingAggregationBuilder missingAggregationBuilder = AggregationBuilders.missing("value_missinig").field("age");
SearchResponse searchResponse = client.prepareSearch("demo").addAggregation(missingAggregationBuilder).execute().actionGet();
Missing missing = searchResponse.getAggregations().get("value_missinig");
System.out.println(missing.toString());
}
/**
* 集群信息查询
*/
@Test
public void testManagerCluster() {
ClusterHealthResponse healths = client.admin().cluster().prepareHealth().get();
String clusterName = healths.getClusterName();
System.out.println("clusterName : " + clusterName);
int numberOfDataNodes = healths.getNumberOfDataNodes();
System.out.println("numberOfDataNodes : " + numberOfDataNodes);
for (ClusterIndexHealth health : healths.getIndices().values()) {
String index = health.getIndex();
int numberOfShards = health.getNumberOfShards();
int numberOfReplicas = health.getNumberOfReplicas();
ClusterHealthStatus status = health.getStatus();
System.out.println(status.toString());
System.out.printf("index= %s,numberOfShards=%s,numberOfReplicas=%s\n", index, numberOfShards, numberOfReplicas);
}
}
}