kibana
下载kibana
kibana安装
#配置配置文件
[es@Centos100 config]$ cat kibana.yml
#kibina服务端口
server.port: 5601
#kibina服务IP,不能使用127或者localhost,否则其他电脑访问不了
server.host: "192.168.113.100"
#配置所有es实例的url
elasticsearch.hosts: ["http://192.168.113.100:9200","http://192.168.113.101:9200"]
#索引名称
kibana.index: ".kibana"
#支持中文
i18n.locale: "zh-CN"
web访问:192.168.113.101:5601
Restful API
注意: 学习restFul API语法是非常重要的,它相当于是学习关系数据库的sql语句,在真正的环境开发下需要使用它来完成业务的。
kibana的使用
Restful API语法
集群基本操作
//1. 查询集群健康状态
// 方法一:结果以json形式显示(number_of_nodes和number_of_data_nodes分别代表节点数量和数据节点数量)
GET _cluster/health
// 方法二:结果以列表形式显示,参数v表示显示表头
GET _cat/health?v=true
//2. 查询集群的主机
GET _cat/nodes?v
//3. 查询集群中的所有索引,结果以列表形式显示,参数v表示显示表头
GET /_cat/indices?v=true
//4. 查询索引trips下字段以route_开头的数据类型
GET trips/_field_caps?fields=route_*,transit_mode
//5. 查看索引my-index-000001的映射结构
GET /my-index-000001/_mappings
//6. 设置集群的分片,默认分片和副本数都是1
PUT /my-index-000001
{
"settings":{
//分片数
"number_of_shards": 3,
//副本数
"number_of_replicas": 1
}
}
创建索引
注意: es索引名不能有大写
// 方法一:
// 如果customer索引不存在,该操作会创建customer索引并向索引中添加或修改一个文档。
// 如果id为1的文档不存在,该操作会向customer索引中添加一个id为1的文档;
// 如果id为1的文档存在,该操作会向customer索引中修改id为1的文档的数据,并更新_version数据版本号(该修改是先删除再修改的操作)
PUT /customer/_doc/1
{
"name":"John Doe"
}
//方法二:使用映射创建
PUT /my-index-000001
{
"mappings": {
"properties": {
"foo": {
"type": "keyword"
}
}
}
}
查询索引
简单查询
// 查询索引为customer,文档id为1的数据
GET /customer/_doc/1
// 全文检索 match_all
// 全文检索索引bank,并按照account_number字段正排,再按照age倒排,
GET /bank/_search
{
"query":{
"match_all": {}
},
"sort":[
{
"account_number":"asc"
},
{
"age":"desc"
}
],
"size":100, //设置页面显示个数,默认显示前10条记录
"from":0 //设置从第几条记录开始,默认从0开始
}
//带参数(参数多位有空格的话用""包含)筛选查询(全文检索,会对查询关键词进行拆解匹配,有一个匹配上就匹配上)和排序查询(一个查询条件)
GET /person/_doc/_search?q=school:"xiao ming"&sort=age:desc
GET /person/_doc/_search?size=2&from=0
// 参数使用分词查询 match
// 查询索引bank,查询address字段中包含mill或lane的文本,这里面对查询参数address进行了分词,英文默认以空格进行分词,查询不区分大小写
GET /bank/_search
{
"query":{
"match":{
"address": "mill lane"
}
}
}
// 参数不使用分词查询 match_phrase
// 查询索引bank,查询address字段中包含"mill lane"的文本,这里不会对参数address进行分词,查询不区分大小写
GET /bank/_search
{
"query":{
"match_phrase": {
"address": "mill lane"
}
}
}
//通配符检索
//select * from my-index-000001 where my_wildcard like '%quite%lengthy'(sql里面没有这种操作,这里只是为简单明了的表达其意)
GET my-index-000001/_search
{
"query": {
"wildcard": {
"my_wildcard": {
"value": "*quite*lengthy"
}
}
}
}
//多字段检索
//select * from person where name = 'xiao' or school = 'xiao'
GET /person/_doc/_search
{
"query":{
"multi_match":{
"query":"xiao", //指定需要匹配的内容
"fields":["name","school"] //指定字段匹配上面的内容,他们之间是should的关系
}
}
}
//将在1ms内能够查询到的数据返回,其他单位还有ms(毫秒),s(秒),m(分钟)
GET /person/_doc/_search?timeout=1ms
//指定查询字段
GET /person/_doc/_search
{
"query":{
"match_all": {}
},
"_source": ["name","age"] //指定字段
}
GET /person/_doc/1?_source=name,age
//不分词查询
GET /person/students/_search
{
"query": {
"term": { //对下面的查询参数不分词查询,注意:只是参数不分词,但是查询对象还是会分词
"school": {
"value": "cheng zhong xiao xue"
}
}
}
}
组合查询
// must:必须匹配
// should:应该匹配
// must_not:禁止匹配
// 查询索引bank的 age = 40 && state != "ID"
GET /bank/_search
{
"query":{
"bool":{
"must":[
{
"match":{
"age":40
}
}
],
"must_not":[
{
"match":{
"state":"ID"
}
}
]
}
}
}
// 查询索引bank的 age = 40 && state = "ID"
GET /bank/_search
{
"query":{
"bool":{
"must":[
{
"match":{
"age":40
}
},
{
"match":{
"state":"ID"
}
}
]
}
}
}
// 查询索引bank的 age = 40 && (state = "UT" || state = "ID")
GET /bank/_search
{
"query":{
"bool":{
"must":[
{
"match":{
"age":40
}
},
{
"bool": {
"should":[
{
"match":{
"state":"UT"
}
},
{
"match":{
"state":"ID"
}
}
]
}
}
]
}
}
}
每个must和should条款中的标准的程度会影响文档的相关性得分。分数越高,文档就越符合您的搜索条件。默认情况下,es会根据相关性分数排序返回结果,如果在外面包裹一层filter,那么es就不会去计算相关性得分
// 查询索引bank的 age = 40 && (state = "UT" || state = "ID"),使用filter,不计算相关度排名
GET /bank/_search
{
"query": {
"bool":{
"filter": [
{
"bool":{
"must":[
{
"match":{
"age":40
}
},
{
"bool": {
"should":[
{
"match":{
"state":"UT"
}
},
{
"match":{
"state":"ID"
}
}
]
}
}
]
}
}
]
}
}
}
// 一般情况下,范围查询不需要进行相关度排名
// 查询索引bank的 balance >= 20000 && balance <= 30000
//gt 大于;lt 小于;gte 大于等于;lte 小于等于
GET /bank/_search
{
"query":{
"bool": {
"must": [
{
"match_all": {}
}
],
"filter": [
{
"range": {
"balance": {
"gte": 20000,
"lte": 30000
}
}
}
]
}
}
}
聚合查询
//如果分组字段不是keyword类型需要先对其设置fielddata为true
PUT /person/_mapping
{
"properties":{
"nj":{
"type":"text",
"fielddata":true
}
}
}
//聚合查询
//select state,count(0) group_by_state from bank group by state order by count(0) desc
GET /bank/_search
{
"size":0, //聚合时设置size为0可以使查询的结果只包含聚合结果
"aggs": {
"group_by_state": {
"terms": {
"field": "state.keyword", //加上keyword,这样在聚合的过程中state就不会被分词
"size": 100, //es默认显示10条记录,使用size可以显示更多的聚合记录
"order": {
"_count": "desc"
}
}
}
}
}
//select avg(balance) avg_balance from bank group by state order by avg(balance) desc
GET /bank/_search
{
"size":0,
"aggs": {
"group_by_state": {
"terms": {
"field": "state.keyword",
"order":{
"avg_balance":"desc"
}
},
"aggs": {
"avg_balance": {
"avg": {
"field": "balance"
}
}
}
}
}
}
滚动检索
#第一次查询参数scroll=1m表示开启了一个滚动检索,检索限定时间为1分钟
GET /person/_doc/_search?scroll=1m
{
"size":"2",
"query":{
"match_all": {}
},
"sort": ["_doc"]
}
#后面的查询将前一个查询的scoll_id带过来查询,不需要再带前面的条件即可实现滚动查询
GET /_search/scroll
{
"scroll":"1m",
"scroll_id":"FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFmZ2TG9ER254U0RLSlZMb195WEJvcUEAAAAAAAAAvhZYNzVDVl9MbFJNZUdfUWhLb19pSFB3"
}
滚动查询与分页查询的区别是它不是用来展示给用户看的,而是用于系统处理大量数据,一批一批拉数据的,是一个非常实用的工具。
特殊查询
//1. 同时搜索多个index下的数据,多个index用逗号隔开
GET /person,ecommerce/_search
//2. 使用*通配符查询以per打头的索引下的数据
GET /per*/_search
//3. 搜索全部索引的数据
GET /_all/_search
//4. 这两个查询是有区别,第一个查询使用了分词检索,查的是包含xiao xue这个单词,但是第二个查询也使用了分词检索,但是是查询包含以xiao开头的单词,即是在第一个查询的基础上做模糊检索
GET /person/_doc/_search?q=school:"xiao xue"
GET /person/_doc/_search?q=name:xiao*
//5. + 和 -
// +表示必须的意思,加与不加没有多大区别,因为默认就是必须
GET /person/_doc/_search?q=+name:xiao*
// -表示非的意思,即取+的反
GET /person/_doc/_search?q=-name:xiao*
//6. 全字段匹配,即全文检索,他会先把所有的字段拼成一个大串,然后对这个大串进行分词,再匹配,也就是说包括可以分词不能分词的date类型
GET /person/_doc/_search?q=xiao
索引映射
PUT my-index-000001
{
"mappings": {
"properties": {
"number_one": {
"type": "integer",
"index": true //es默认该属性为true,表示可以查询检索的,如果设置为false,则表示该字段不能用于检索查询
},
"number_two": {
"type": "integer",
"coerce": false //false:强制设置该字段只能是interger类型,es不会为其自动转换类型,如果类型不匹配就不允许存储;true(默认值):该字段可以自动转换数据类型存储
}
}
}
}
PUT my-index-000001
{
"mappings": {
"properties": {
"first_name": {
"type": "text",
"copy_to": "full_name" //将first_name字段的值复制到字段full_name中
},
"last_name": {
"type": "text",
"copy_to": "full_name" //将last_name字段的值复制到字段full_name中
},
"full_name": {
"type": "text"
}
}
}
}
GET my-index-000001/_search
{
"query": {
"match": {
"full_name": {
"query": "John zhang",
"operator": "and" //查询full_name,and表示要求每个复制字段都能匹配"John zhang"
}
}
}
}
PUT my-index-000001
{
"mappings": {
"properties": {
"status_code": {
"type": "keyword"
},
"session_id": {
"type": "keyword",
"doc_values": false //false:将session_id不建立倒排索引,节省空间;true(默认值):建立倒排索引
}
}
}
}
常见的es数据类型
别名(alias)
PUT trips
{
"mappings": {
"properties": {
"distance": {
"type": "long"
},
"route_length_miles": {
"type": "alias", //alias表示route_length_miles属于别名字段
"path": "distance" //别名目标字段distance
}
}
}
}
GET _search
{
"query": {
"range" : {
"route_length_miles" : { //查询的时候可以使用别名进行查询
"gte" : 3
}
}
}
}
数组
PUT my-index-000001/_doc/1
{
"message": "some arrays in this document...",
"tags": [ "elasticsearch", "wow" ], //tags字段是一个数组
"lists": [ //list字段是一个json数组
{
"name": "prog_list",
"description": "programming list"
},
{
"name": "cool_list",
"description": "cool stuff list"
}
]
}
PUT my-index-000001/_doc/2
{
"message": "no arrays in this document...",
"tags": "elasticsearch", //数组字段也可以单个添加
"lists": {
"name": "prog_list",
"description": "programming list"
}
}
GET my-index-000001/_search
{
"query": {
"match": {
"tags": "elasticsearch" //数组字段检索,它会匹配数组中的每一个元素,只要有一个元素符合查询条件就展示
}
}
}
二进制base64编码(binary)
PUT my-index-000001
{
"mappings": {
"properties": {
"name": {
"type": "text"
},
"blob": {
"type": "binary" //设置blob字段为blob类型,则默认情况下该字段不建立倒排索引
}
}
}
}
PUT my-index-000001/_doc/1
{
"name": "Some binary blob",
"blob": "U29tZSBiaW5hcnkgYmxvYg=="
}
boolean
PUT my-index-000001
{
"mappings": {
"properties": {
"is_published": {
"type": "boolean" //设置为boolean类型的字段只能存放true和false,或者"true","false",尽管如此,但是他们查询和聚合中所代表的意义是一样的
}
}
}
}
POST my-index-000001/_doc/1
{
"is_published": "true"
}
日期类型(date)
PUT my-index-000001
{
"mappings": {
"properties": {
"date": {
"type": "date", //设置字段date为日期类型
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis" //日期类型的格式,epoch_millis表示时间戳
}
}
}
}
字符串类型(text)
text类型在检索时默认会被分词,如果不指定mappings映射,直接put数据,es对推断出类型为text的字段,都会再存一个keyword类型
PUT my-index-000001
{
"mappings": {
"properties": {
"full_name": {
"type": "text",
"fields": { //如果指定了mappings映射,如果你同时还想要不分词检索,那你必须使用fields以keyword数据类型存储字段
"raw":{
"type":"keyword"
}
}
}
}
}
}
GET /my-index-000001/_search
{
"query":{
"match": {
"full_name": "hello"
}
}
}
GET /my-index-000001/_search
{
"query":{
"match": {
"full_name.raw": "Hello world!" //注意:keyword是完全匹配,并且区分大小写
}
}
}
字符串类型(keyword、constant_keyword)
keyword、constant_keyword类型在检索时默认不会被分词
PUT my-index-000001
{
"mappings": {
"properties": {
"tags": {
"type": "keyword" //keyword表示这个字段是字符串类型,他不会建立分词索引
}
}
}
}
PUT my-index-000001
{
"mappings": {
"properties": {
"tags": {
"type": "constant_keyword" //constant_keyword表示这个字段是常量关键字,第一次赋值后他不能被修改
}
}
}
}
Object
在es中,json对象就是Object类型,es把Object类型以扁平化的方式存储
PUT my-index-000001
{
"mappings": {
"properties": {
"region": {
"type": "keyword"
},
"manager": { //manager就是一个Object类型,它的类型不是以type来定义的,而是一个小型的properties结构
"properties": {
"age": { "type": "integer" },
"name": { //name也是一个Object类型
"properties": {
"first": { "type": "text" },
"last": { "type": "text" }
}
}
}
}
}
}
}
PUT my-index-000001/_doc/1
{
"region": "US",
"manager": {
"age": 30,
"name": [
{
"first": "John",
"last": "Smith"
},
{
"first" : "Alice",
"last" : "White"
}
]
}
}
//该数据在es中以下面这种扁平化的方式存储
{
"region":"US",
"manager.age":30,
"manager.name.first": ["John","Alice"]
"manager.name.last": ["Smith","White"]
}
对象数组(nested)
所谓对象数组,区分于Object,对象数组是把数组里的每一个成员作为一个独立的对象,而Object则是把数据扁平化
PUT my-index-000001
{
"mappings": {
"properties": {
"user": {
"type": "nested" //设置user为对象数组
}
}
}
}
//nested类型中,user1与user2相互独立
PUT my-index-000001/_doc/1
{
"group" : "fans",
"user" : [
{ //这是对象user1
"first" : "John",
"last" : "Smith"
},
{ //这是对象user2
"first" : "Alice",
"last" : "White"
}
]
}
//nested查询user.first=Alice&&user.last=Smith是查不到任何数据的,因为这两个数据不在同一对象中,但是如果是Object,就可以查到
GET my-index-000001/_search
{
"query": {
"nested": {
"path": "user",
"query": {
"bool": {
"must": [
{ "match": { "user.first": "Alice" }},
{ "match": { "user.last": "Smith" }}
]
}
}
}
}
}
数字类型
数字类型没有啥好说,常见的就是:long、integer、short、byte、double、float
删除索引
DELETE /ecommerce
添加文档数据
// 方法一:
// 如果customer索引不存在,该操作会创建customer索引并向索引中添加或修改一个文档。
// 如果id为1的文档不存在,该操作会向customer索引中添加一个id为1的文档;
// 如果id为1的文档存在,该操作会向customer索引中修改id为1的文档的数据,并更新_version数据版本号(该修改是先删除再修改的操作)
PUT /customer/_doc/1
{
"name":"John Doe"
}
修改文档数据
// 方法一:
// 如果customer索引不存在,该操作会创建customer索引并向索引中添加或修改一个文档。
// 如果id为1的文档不存在,该操作会向customer索引中添加一个id为1的文档;
// 如果id为1的文档存在,该操作会向customer索引中修改id为1的文档的数据,并更新_version数据版本号(该修改是先删除再修改的操作)
PUT /customer/_doc/1
{
"name":"John Doe"
}
// 方法二:只针对选中字段进行修改,原字段保持不变,如果没有该字段,就新增
POST /my-index-000001/_update/2
{
"doc":{
"foo":"qux"
}
}
删除文档数据
//删除索引为ecommerce,id为1的数据,数据就算全部删除,索引依然会保留
DELETE /ecommerce/_doc/1
批处理操作
//bulk批量更新
//1. bulk批量更新delete操作1行,create操作2行,index操作2行,update操作2行
//2. bulk下所有的操作都是按顺序执行的,不是并发执行的
//3. bulk下其中一个操作失败,不影响其他的操作
//4. delete删除,create创建,index(没有创建,有就先删除再创建),update更新
//5. bulk里面每个操作的单独json不能换行,必须在同一行
//6. bulk里面的操作会先加载到内存中,里面的操作也不是越多越好,太多性能会下降,一般差不多1000~5000个操
// 针对单个index
// 批量添加文档数据,批量处理大约可以处理1000到5000个文档,总负载在5MB到15MB之间
作左右即可
POST /students/_doc/_bulk
{"index":{"_id":1}}
{"name":"xiaohong","age":18,"nj":"五年级","bj":"一班","xuehao":"10","school":"cheng zhong xiao xue"}
{"index":{"_id":2}}
{"name":"xiaoLong","age":19,"nj":"一年级","bj":"一班","xuehao":"2","school":"cheng zhong xiao xue"}
// 针对多个index
POST _bulk
{ "index" : { "_index" : "my-index-000001", "_id" : "3" } }
{ "foo" : "baz" }
{ "index" : { "_index" : "my-index-000001", "_id" : "4" } }
{ "foo" : "qux" }
//批量查询
//批量查询不同index,不同type下的数据
GET /_mget
{
"docs":[
{
"_index":"person",
"_type":"_doc",
"_id":1
},
{
"_index":"person",
"_type":"_doc",
"_id":2
}
]
}
//批量查询同一index,同一type下的数据
GET /person/_doc/_mget
{
"ids":[1,2]
}
SQL API
es支持mysql类的sql语法
//查询索引library当release_date<2000年时的数据
//format:结果显示格式
// txt 表格形式
// csv 按照指定分隔符分隔每一列数据,参数delimiter配合cvs用来指定分隔符,值是%加上ASCII码的十六进制后两位,默认是逗号。
// json json格式
// tsv 按照tab键分隔符分隔每一列数据
// yaml 按照列表层级结构展示
POST /_sql?format=txt
{
"query": "SELECT * FROM library WHERE release_date < '2000-01-01'",
"fetch_size":1 //限制返回结果个数,如果format类型是json则会在结果中返回cursor,使用cursor可以完成下一页的跳转
}
//下一页
POST /_sql?format=json
{
"cursor": "sDXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAAEWYUpOYklQMHhRUEtld3RsNnFtYU1hQQ==:BAFmBGRhdGUBZgVsaWtlcwFzB21lc3NhZ2UBZgR1c2Vy9f///w8="
}
//当最后没有返回的时候我们要关闭游标
POST /_sql/close
{
"cursor" : "i6+xAwFaAXN4RkdsdVkyeDFaR1ZmWTI5dWRHVjRkRjkxZFdsa0RYRjFaWEo1UVc1a1JtVjBZMmdCRm5FeloyVXRlQzFMVkMwdGFIRmpUSEV3Y1dod1dHY0FBQUFBQUFBaFZoWllOelZEVmw5TWJGSk5aVWRmVVdoTGIxOXBTRkIz/w8EAWYGYXV0aG9yAQZhdXRob3IBBHRleHQAAAABZgRuYW1lAQRuYW1lAQR0ZXh0AAAAAWYKcGFnZV9jb3VudAEKcGFnZV9jb3VudAEEbG9uZwAAAAFmDHJlbGVhc2VfZGF0ZQEMcmVsZWFzZV9kYXRlAQhkYXRldGltZQEAAAEP"
}
es安全技术
准备步骤
//1. 配置elasticsearch.yml,添加下列设置
xpack.security.enabled: true
//2. 启动es
//3. 在es的bin目录下输入以下命令,设置es的内置用户的密码
E:\elasticsearch-7.10.2-windows\bin> .\elasticsearch-setup-passwords interactive
future versions of Elasticsearch will require Java 11; your Java version from [D:\Java\jdk1.8.0_144\jre] does not meet this requirement
Initiating the setup of passwords for reserved users elastic,apm_system,kibana,kibana_system,logstash_system,beats_system,remote_monitoring_user.
You will be prompted to enter passwords as the process progresses.
Please confirm that you would like to continue [y/N]y
Enter password for [ ]: //超级用户
Reenter password for [elastic]:
Enter password for [apm_system]: //APM 服务器在 Elasticsearch 中存储监控信息时使用的用户
Reenter password for [apm_system]:
Enter password for [kibana_system]: //Kibana 用于连接 Elasticsearch 并与之通信的用户
Reenter password for [kibana_system]:
Enter password for [logstash_system]: //Logstash 在 Elasticsearch 中存储监控信息时使用的用户
Reenter password for [logstash_system]:
Enter password for [beats_system]: //Beats 在 Elasticsearch 中存储监控信息时使用的用户
Reenter password for [beats_system]:
Enter password for [remote_monitoring_user]: //在 Elasticsearch 中收集和存储监控信息时使用的用户 Metricbeat
Reenter password for [remote_monitoring_user]:
Changed password for user [apm_system]
Changed password for user [kibana_system]
Changed password for user [kibana]
Changed password for user [logstash_system]
Changed password for user [beats_system]
Changed password for user [remote_monitoring_user]
Changed password for user [elastic]
//4. 配置kibana.yml,设置kibana的用户密码
elasticsearch.username: "kibana_system"
elasticsearch.password: "123456"
//5. 启动kibana,使用elastic用户登录
使用kibana创建用户和赋予角色权限
## es的扩容方案
方法一:垂直扩容,给服务器加硬盘
方法二:水平扩容,增加服务器,增加es节点
每次扩容,es都会自动实现负载均衡,尽量保证每个服务器上的primary shard和replica shard上的数量相同
```shell
#注意primary shard在集群搭建完成时数量就已经定好了,不容更改,我们可以调整replica shard的数量尽量保证每个集群还是那个的分片数相同
PUT /blogs/_settings
{
"number_of_replicas" : 2
}
例如:原本集群有3台主机,3个primary shard,每个primary shard有1个replica shard,即共有6个shard,均匀分布在3台主机上。现在把集群扩充到5台,为了保证数据的完整性,在集群挂掉2个主机,数据依然完整,我们需要保证每个数据都有3个shard,也就是说每个primary shard都要扩充到至少2个replica shard,即共9个shard均匀分布在5台主机上
primary shard和replica shard机制
- index 包含多个 shard
- 每个 shard 都是一个最小的工作单元,承载部分的数据,Lucene 实例,完整的简历索引和处理请求的能力
- 增减节点时,shard 会自动在 nodes 中负载均衡
- primary shard 和 replica shard,每一个 doc 只会存在某一个 primary shard 以及其对应的 replica shard 中,不可能存在于多个primary shard中
- replica shard 是 primary shard 的副本,负责容错,以及承担读请求负载
- primary shard 的数量在创建索引的时候就固定了,replica shard 的数量可以随时修改,因此当超出系统扩容瓶颈时,我们可以增加 replica shard 的数量来保证系统的稳定性
- primary shard 的默认数量是5,replica shard 默认是1,默认有10个 shard ,其中5个 primary shard 以及5个 replica shard
- primary shard 和 replica shard 不能和自己的 replica shard 放在一个节点中(这样规定是为避免节点宕机的时候,primary shard和 replica shard 数据都都丢失,起不到容错的作用),但是可以和其他的 primary shard 的 replica shard 放在同一个节点中
提升服务器的容错性
比如3个 primary shard 和3台服务器,的情况下:
3个primary shard和1个replica shard可以容忍一台服务器宕机,而保证数据不丢失
3个primary shard 和2个replica shard可以容忍两台服务器宕机,而保证数据不丢失
也就是说,最好设置replica shard数量提升到更加靠近(服务器节点数 - 1),这样可以将服务器的容错性提升到极致。
es纠错机制
- master如果宕机,es剩余的节点会自动选举新的master,来承担master的责任,如果宕机的节点中包含primary shard,那此时集群的健康状态会变成red
- master将丢失的primary shard的某个replica shard提升为primary shard,此时集群的健康状态会yellow,因为所有的primary shard都变成active了。但是因为少了一个replica shard,所以是yellow
- 重启故障节点主机,master会将缺失的副本copy一份送到重启后的故障主机上,并且该主机会使用之前已有的shard,只是同步一下新的数据而已,此时集群健康状态为green
document的核心元数据
_index
- 代表一个document存放在哪个index中
- 类似的数据放一个索引,非类似的数据放另外一个索引
- index中包含了很多类似的document
- 索引名称必须是小写,不能用下划线开头,不能包含逗号
_id
- 代表document的唯一标识,与index和type一起连用可以定位一个document
- 我们可以手动指定docment的id,也可以不指定,右es自动为我们创建,一般来说从其他系统导入数据到es会采用手动指定的方式,一开始做系统就指定es作为数据库的话,我们可以使用es自动创建索引的方式
es的并发控制方案
悲观锁和乐观锁
- 悲观锁的并发控制方案,就是在各种情况下,都上锁,上锁之后只有一个线程可以操作这一条数据,当然不同场景下,上的锁级别也是不同的,有行级锁,表级锁,读锁,写锁。
- 乐观锁引入一个version版本的概念,乐观锁是不加锁的,线程在读取数据然后写入数据的时候会判断当前读取的数据版本与数据库中的数据版本是否相同,如果不同,线程会重新去数据库拉取数据再写入数据,如果相同,就写入数据,es默认使用的是乐观锁。
- 悲观锁方便,直接加锁,对应用程序来说,透明,不需要做额外的操作,缺点就是并发能力很低,同一时间只能有一条线程操作数据
- 乐观锁并发能力高,缺点就是麻烦,每次更新都要先比对版本号,并且可能要重新加载数据再修改,这个过程还有可能会重复好几次。
乐观锁的并发版本控制实践
//携带版本号修改数据,只有当es库中原数据的版本号跟我们携带的版本号一致,才能够修改成功,否则会被乐观锁阻止数据的更新操作
POST /person/students/1/_update?if_seq_no=17&if_primary_term=5
{
"script":"ctx._source.age+=1"
}
//设置尝试次数,es会自动匹配当前的版本号,版本号不一致会继续重新拉取数据更新,尝试直到超过5次,会显示更新失败
POST /person/students/1/_update?retry_on_conflict=5
{
"script":"ctx._source.age+=1"
}
es的脚本
- 直接执行脚本命令
//修改数据+1 POST /ecommerce/product/1/_update { "script":"ctx._source.price+=1" }
- 创建永久脚本,执行脚本
//创建脚本 POST _scripts/脚本ID POST _scripts/test-add-ages { "script":{ "lang":"painless", //脚本语言 "source": "ctx._source.age+=params.new_age" //脚本内容 } } //执行脚本 POST /person/_doc/1/_update { "script":{ "id":"test-add-ages", //需要执行的脚本ID "params":{ "new_age":1 //参数 } } } POST _scripts/test-del-student { "script":{ "lang":"painless", "source":"ctx.op = ctx._source.age == params.new_age ? 'delete' : 'none'" //ctx.op是es的内置变量,用来记录操作对象的 } } POST /person/_doc/1/_update { "script":{ "id":"test-del-student", "params":{ "new_age":28 } } }
mapping
#1. 查看索引下每个字段的数据类型;es的date类型和keyword类型都不会被分词
GET /person/_mapping
#2. 类型推断:
#true or false --> boolean
#123 --> long
#123.45 --> double
#2019-01-01 --> date
#"hello world" --> text
#{
# "name":"zhangsan", --> object
# "age":23
#}
#3. 建立fruit索引的mapping,一旦建立就不能更改,但是可以添加字段
PUT /fruit
{
"mappings":{
"properties":{
#对name字段进行处理,确保text类型的name字段可以排序
"name:":{
"type":"text",
#设置二级字段
"fields": {
#设置字段raw,类型为keyword,使他不会被分词
"raw": {
"type": "keyword"
}
},
#设置排序
"fielddata": true
},
"cd":{
"type":"text"
},
"scrq":{
"type":"date"
},
"bzq":{
"type":"long"
},
"des":{
"type":"text",
"analyzer": "english"
}
}
}
}
#4. 给已经建立mapping的索引添加字段
PUT /fruit/_mapping
{
"properties":{
"address:":{
"type":"text"
}
}
}
#5. 对text类型的name进行排序
GET /website/_doc/_search
{
"query":{
"match_all":{}
},
"sort":[
{
"name.raw":{ #使用raw,这样就不会导致分词排序,使排序更加精确
"order":"desc"
}
}
]
}
定制dynamic策略
dynamic的值
- true:遇到陌生字段,进行dynamic mapping,即自动在mapping中增加陌生字段的映射关系
- false:遇到陌生字段就忽略,即依然会存储该字段的值,但不会建立mapping映射关系,所以对该字段进行查询是查询不到任何信息的
- strict:遇到陌生字段就报错,不允许添加陌生字段
demo:
PUT /my_index
{
"mappings": {
"dynamic":"strict", #
"properties":{
"title":{
"type":"text"
},
"address":{
"type":"object",
"properties": {
"province:":{
"type":"text"
},
"city":{
"type":"text"
}
},
"dynamic":"strict"
}
}
}
}
关闭dynamic自动推断类型的功能
PUT /my_index/
{
"mappings": {
"date_detection":"false"
}
}
分词器
//测试分词器
GET /_analyze
{
//指定测试的分词器
"analyzer": "standard",
//指定分词文本内容
"text":"Text to analyze"
}
//定制属于自己的分词器
PUT /my_index
{
"settings": {
"analysis": {
//设置自定义转换器
"char_filter": {
"&_to_and":{ //自定义转换器名称
"type":"mapping",
"mappings":["&=>and"]
}
},
//设置自定义过滤器
"filter": {
"my_stopwords":{ //自定义过滤器名称
"type":"stop",
"stopwords":["the","a"]
}
},
//自定义分词器
"analyzer": {
"my_analyzer":{ //自定义分词器的名字
"type":"custom", //类型自定义
"char_filter":["html_strip","&_to_and"], //转换器 html_strip(过滤html标签)
"tokenizer":"standard", //指定分词器
"filter":["lowercase","my_stopwords"], //过滤器,lowercase(大写转小写)
"stopwords":"_english_" //启用系统english停用词
}
}
}
}
}
查询总结
//from + size的大小的限制
PUT /index/_settings
{
"index.max_result_window":10000
}
//直接查询某个id下的数据
GET /index/_doc/id
//全部查询
GET /index/_doc/_search
//轻量级查询
//q=fieldName:(+/-)keyword(*) 指定查询条件(分词检索)
//q=keyword(*)全文检索
//sort=fieldName:(asc/desc) 指定排序字段和排序方式
//timout=1ms 指定查询限定时间,有ms(毫秒),s(秒),m(分钟)
//from=0&size=10 分页参数
//_source=name,age 指定查询字段
//scroll=1m 滚动查询
GET /index/_doc/_search?q=fieldName:(+/-)keyword(*)&sort=fieldName:(asc/desc)&timout=1ms&from=0&size=10&_source=name,age&scroll=1m
//查询表达式
GET /index/_doc/_search
{
//放开查询,默认只能查询10000条,比如count聚合统计,只有设置了这个才能保证数据的准确性
"track_total_hits":true
//1. 查询条件
"query":{
//1. 查询所有
"match_all":{},
//2. 按照指定条件查询(分词检索)
"match":{
"field_name":"search_word" //指定字段名称和查询内容,相当于轻量级查询q=fieldName:keyword
},
//3. 多条件查询(分词检索)
"bool":{
//1. 必须
"must":[
{
"match":{
"field_name":"search_word" //指定字段名称和查询内容,相当于轻量级查询q=fieldName:+keyword
}
},
{
"wildcard":{
"field_name.keyword":"*search_word*" //模糊检索
}
},
{
"range":{
"field_name":{ //需要查询范围的字段名称
"gt":25 // (gt:大于;gte:大于等于;lt:小于;lte:小于等于)
"lt":30
}
}
}
],
//2. 应当
"should":[
"match":{
"field_name":"search_word" //指定字段名称和查询内容,相当于轻量级查询q=fieldName:keyword
}
],
//3. 非
"must_not":[
"match":{
"field_name":"search_word" //指定字段名称和查询内容,相当于轻量级查询q=fieldName:-keyword
}
],
//4. 与query差不多,query可以放的,filter也可以放,只是它查询不参与相关度分数的计算排序
"filter":{
}
},
//4. 范围查询,它与from...to的范围查询不一样,那个是用在分组里面的
"range":{
"field_name":{ //需要查询范围的字段名称
"gt":25 // (gt:大于;gte:大于等于;lt:小于;lte:小于等于)
"lt":30
}
},
//5. 短语匹配,必须全部匹配上(精确搜索,即keyword不分词,但是查询的字段可以分词)
"match_phrase":{
"field_name":"search_word" //指定字段名称和查询内容
},
//6. 多条件匹配,相当于一个小型的全文检索
"multi_match":{
"query":"search_word", //指定需要匹配的内容
"fields":["field_name1","field_name2"] //指定字段匹配上面的内容,他们之间是should的关系
},
//7. 不分词查询,适用于查询不分词的字段,对于分词字段使用会出问题的,它是更加精确的搜索(keyword不分词,查询的字段也不能分词,如果查询的字段可以分词,那他有可能会匹配不到)
"term":{
"field_name": { //查询字段
"value": "search_word" //指定查询内容
}
}
},
//2. 排序字段
"sort":[
{
"field_name":"desc" //指定排序字段和排序方式
}
],
//3. 展示字段
"_source":["field_name1","field_name2"], //查询展示的字段
//4. 分页
"from":0, //数据从0开始计算
"size":2, //查询的数量,一般分组用它设置0来屏蔽源数据,如果不指定es默认查询前10条
//5. 关键词高亮显示
"highlight":{
"fields": {
"field_name":{} //设置需要高亮显示的字段
}
},
//6. 聚合查询,分组(注意需要先设置分组字段的fielddata为true,怎么设置可以看上面的例子)
"aggs":{
//1. 分组名称自定义(group_name(自定义分组名称);aggs(二次分组);order(排序);)
"group_name":{
//1. 设置分组字段
"terms":{ //分组字段(terms(分组字段);avg(求平均值);value_count(求总数))
//1. 指定字段名称
"field": "field_name"
//2. 指定分组规则
"order":{
"group_name2":"desc" //指定排序分组和排序方式
},
},
//2. 按照范围分组
"range": { //范围分组
"field": "field_name", //分组字段
"ranges": [ //指定分组范围
{
"from": 20,
"to": 25
},
{
"from": 25,
"to": 30
}
]
},
//3. 求平均值
"avg": {
"field":"field_name" //字段名称
}
},
//2. 分组2
"aggs": { //二次分组,三次分组就跟二次分组一样往下排
}
}
}
es相关度的计算
- Term frequency,搜索文本中的各词条在field文本中出现的次数,出现次数越多越相关
- Inverse documnet frequency,搜索文本中的各个词条在整个索引的所有文档中出现了多少次,出现次数越多越不相关
- Field-length norm,field长度越长相关度越弱,field长度越短,相关度越强
索引
倒排索引和正排索引
搜索时要依靠倒排索引,排序时要依靠正排索引
倒排索引的结构
- 包含这个关键词的document list
- 包含这个关键词的所有document的数量
- 这个关键词在每个document中出现的次数
- 这个关键词在这个document中出现的次序
- 每个document的长度
- 包含这个关键词所在document的平均长度
倒排索引不可变的好处与坏处
- 不需要锁,提升并发能力,避免锁的问题
- 数据不变,一直保存在os cache中,只要cache内存足够
- filter cache一直驻留在内存,因为数据不变
- 可以压缩,节省cpu和io开销
- 每次都要重新构建整个索引
重建索引
//1. 给已经创建的索引(my_index)起别名(goods_index),java应用使用索引别名(goods_index)来查询数据
PUT /my_index/_alias/goods_index
//2. 创建重建的索引(my_index_new),指定mapping映射
PUT /my_index_new
{
"mappings": {
"properties": {
"date":{
"type": "text"
}
}
}
}
//3. 使用滚动查询循环将数据插入新索引
GET /my_index/_doc/_search?scroll=1m
{
"size":"10000",
"query":{
"match_all": {}
},
"sort": ["_doc"]
}
//4. 修改别名指向到新索引
POST /_aliases
{
"actions": [
{
"remove": {
"index": "my_index",
"alias": "goods_index"
}
},
{
"add": {
"index": "my_index_new",
"alias": "goods_index"
}
}
]
}
java API
标准API
依赖添加:
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.10.2</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
<version>7.10.2</version>
</dependency>
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>7.10.2</version>
</dependency>
获取客户端连接
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.apache.http.impl.nio.reactor.IOReactorConfig;
/**
* 获取
* @return
*/
private static RestHighLevelClient getClient() {
RestHighLevelClient client = null;
try {
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials("elastic", "cqrjxk39"));
RestClientBuilder builder = RestClient.builder(
new HttpHost("localhost", 9200, "http"))
.setHttpClientConfigCallback(new HttpClientConfigCallback() {
@Override
public HttpAsyncClientBuilder customizeHttpClient(
HttpAsyncClientBuilder httpClientBuilder) {
return httpClientBuilder
.setDefaultCredentialsProvider(credentialsProvider).setDefaultIOReactorConfig(IOReactorConfig.custom()
.setIoThreadCount(1)
.build());
}
}).setRequestConfigCallback(new RestClientBuilder.RequestConfigCallback() {
@Override
public RequestConfig.Builder customizeRequestConfig(
RequestConfig.Builder requestConfigBuilder) {
return requestConfigBuilder
.setConnectTimeout(5000)
.setSocketTimeout(60000);
}
});
client = new RestHighLevelClient(builder);
} catch (Exception e) {
e.printStackTrace();
}
return client;
}
创建索引
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
public static void createIndex() throws Exception {
RestHighLevelClient client = RestClientUtils.getClient();
//创建索引user_test
CreateIndexRequest request = new CreateIndexRequest("user_test");
CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
//响应状态
boolean acknowledged = response.isAcknowledged();
System.out.println("响应状态:" + acknowledged);
client.close();
}
查询索引
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.client.indices.GetIndexResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
public static void getIndex() throws Exception {
RestHighLevelClient client = RestClientUtils.getClient();
GetIndexRequest request = new GetIndexRequest("user_test");
GetIndexResponse response = client.indices().get(request, RequestOptions.DEFAULT);
//获取别名
response.getAliases();
//获取映射
response.getMappings();
//索引信息
response.getSettings();
client.close();
}
删除索引
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
public static void deleteIndex() throws Exception {
RestHighLevelClient client = RestClientUtils.getClient();
DeleteIndexRequest request = new DeleteIndexRequest("user_test");
AcknowledgedResponse response = client.indices().delete(request, RequestOptions.DEFAULT);
//响应状态
boolean flag = response.isAcknowledged();
System.out.println(flag);
client.close();
}
添加数据
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
/**
* 添加json数据
* @throws Exception
*/
public static void addJSONData() throws Exception {
RestHighLevelClient client = RestClientUtils.getClient();
IndexRequest request = new IndexRequest();
request.index("user_test").id("1002");
JSONObject data = new JSONObject();
data.put("name", "lisi");
data.put("age", 24);
data.put("sex", "男");
request.source(data.toString(), XContentType.JSON);
IndexResponse response = client.index(request, RequestOptions.DEFAULT);
String result = response.getResult().toString();
System.out.println(result);
client.close();
}
/**
* 添加map数据
* @throws Exception
*/
public static void addMapData() throws Exception {
RestHighLevelClient client = RestClientUtils.getClient();
IndexRequest request = new IndexRequest();
request.index("user_test").id("1001");
Map<String,Object> map = new HashMap<String,Object>();
map.put("name", "zhangsan");
map.put("age", 23);
map.put("sex", "男");
request.source(map);
IndexResponse response = client.index(request, RequestOptions.DEFAULT);
String result = response.getResult().toString();
System.out.println(result);
client.close();
}
/**
* 添加数组数据
* @throws Exception
*/
public static void addARRData() throws Exception {
RestHighLevelClient client = RestClientUtils.getClient();
IndexRequest request = new IndexRequest();
request.index("user_test").id("1003");
Object[] arr = {"name","wangwu","age",25,"sex","男"};
request.source(arr);
IndexResponse response = client.index(request, RequestOptions.DEFAULT);
String result = response.getResult().toString();
System.out.println(result);
client.close();
}
删除数据
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
public static void deleteData() throws Exception {
RestHighLevelClient client = RestClientUtils.getClient();
DeleteRequest request = new DeleteRequest();
request.index("user_test").id("1001");
DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);
String result = response.getResult().toString();
System.out.println(result);
client.close();
}
修改数据
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
public static void updateData() throws Exception {
RestHighLevelClient client = RestClientUtils.getClient();
UpdateRequest request = new UpdateRequest();
request.index("user_test").id("1001");
request.doc(XContentType.JSON, "age",22,"sex","女");
UpdateResponse response = client.update(request, RequestOptions.DEFAULT);
String result = response.getResult().toString();
System.out.println(result);
client.close();
}
修改数据跟添加数据一样,还支持其他多种数据类型,原理差不多相同在,这里就不举例了
根据主键查询数据
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
public static void getData() throws Exception {
RestHighLevelClient client = RestClientUtils.getClient();
GetRequest request = new GetRequest();
request.index("user_test").id("1001");
GetResponse response = client.get(request, RequestOptions.DEFAULT);
String id = response.getId();
String data = response.getSourceAsString();
System.out.println(id);
System.out.println(data);
client.close();
}
批处理
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentType;
public static void bulkData() throws Exception {
RestHighLevelClient client = RestClientUtils.getClient();
BulkRequest request = new BulkRequest();
//IndexRequest对象必须在add方法里面创建并且连贯写,否则会报错
request.add(new IndexRequest().index("user_test").id("1004").source(XContentType.JSON,"name","zhaoliu","age",26,"sex","男"));
request.add(new IndexRequest().index("user_test").id("1005").source(XContentType.JSON,"name","tangqi","age",27,"sex","男"));
BulkResponse response = client.bulk(request, RequestOptions.DEFAULT);
//响应时间
TimeValue took = response.getTook();
System.out.println(took);
//响应结果
BulkItemResponse[] responses = response.getItems();
client.close();
}
复杂查询
import org.apache.lucene.search.TotalHits;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.metrics.MaxAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.ParsedMax;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortOrder;
/**
* 分组查询
* @throws Exception
*/
public static void searchAgg() throws Exception {
RestHighLevelClient client = RestClientUtils.getClient();
SearchRequest request = new SearchRequest();
request.indices("user_test");
SearchSourceBuilder builder = new SearchSourceBuilder();
MaxAggregationBuilder field = AggregationBuilders.max("maxAge").field("age");
builder.aggregation(field);
builder.size(0);
request.source(builder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//获取分组结果
Aggregations aggs = response.getAggregations();
ParsedMax p = aggs.get("maxAge");
System.out.println(p.getName() + "===========" + p.getValue());
client.close();
}
/**
* 组合查询
* @throws Exception
*/
public static void searchBool() throws Exception {
RestHighLevelClient client = RestClientUtils.getClient();
SearchRequest request = new SearchRequest();
request.indices("user_test");
SearchSourceBuilder builder = new SearchSourceBuilder();
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
boolQuery.must(QueryBuilders.matchQuery("sex", "男"));
//范围查询
boolQuery.must(QueryBuilders.rangeQuery("age").gte(20).lte(25));
builder.query(boolQuery);
request.source(builder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
//获取查询条数
TotalHits total = hits.getTotalHits();
System.out.println(total);
for (SearchHit hit : hits) {
String data = hit.getSourceAsString();
System.out.println(data);
}
client.close();
}
/**
* 查询全部
* @throws Exception
*/
public static void searchAll() throws Exception {
RestHighLevelClient client = RestClientUtils.getClient();
SearchRequest request = new SearchRequest();
request.indices("user_test");
SearchSourceBuilder builder = new SearchSourceBuilder().query(QueryBuilders.matchAllQuery());
//分页参数
builder.from(0);
builder.size(2);
//排序
builder.sort("age", SortOrder.DESC);
//过滤字段(包含和不包含字段数组只要有一个不为空就行)
//不包含的字段
String[] excludes = {};
//包含的字段
String[] includes = {"name"};
builder.fetchSource(includes, excludes);
request.source(builder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
//获取查询条数
TotalHits total = hits.getTotalHits();
System.out.println(total);
for (SearchHit hit : hits) {
String data = hit.getSourceAsString();
System.out.println(data);
}
client.close();
}
标准API总结
涉及的几个重要的类和方法:
|--创建连接:
|----连接: RestHighLevelClient
|------连接关闭:close()
|------操作索引:indices().create,indices().delete
|------操作数据(添加):index()
|------操作数据(删除):delete()
|------操作数据(修改):update()
|--创建索引:
|----请求: CreateIndexRequest
|----响应: CreateIndexResponse
|------响应状态:isAcknowledged()
|--删除索引:
|----请求: DeleteIndexRequest
|----响应: AcknowledgedResponse
|------响应状态:isAcknowledged()
|--添加数据:
|----请求: IndexRequest
|------数据包裹:source(),支持json串,map,数组
|----响应: IndexResponse
|--删除数据:
|----请求: DeleteRequest
|----响应:DeleteResponse
|--修改数据:
|----请求: UpdateRequest
|------数据包裹:doc(),支持json串,map,数组
|----响应: UpdateResponse
|--根据主键查询数据
|----请求: GetRequest
|----响应: GetResponse
|------数据包裹:getSourceAsString()
|--批处理:
|----请求: BulkRequest
|----响应: BulkResponse
Spring API
Spring Data API
注意: Spring Data与elastic之间有着极为严格的兼容性关系,我们在选择Spring框架时需要谨慎考虑
Spring与es之间的兼容性
这里我们选择SpringBoot框架下面的版本来连接elastic7.10:
在这里插入代码片
SQL API
Spring Jdbc
依赖添加:
<dependency>
<groupId>org.elasticsearch.plugin</groupId>
<artifactId>x-pack-sql-jdbc</artifactId>
<version>7.10.2</version>
</dependency>
<!--配置setting.xml的repositories-->
<repositories>
<repository>
<id>elastic.co</id>
<url>https://artifacts.elastic.co/maven</url>
</repository>
</repositories>
配置application-context.xml:
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="org.elasticsearch.xpack.sql.jdbc.EsDriver"></property>
<property name="jdbcUrl" value="jdbc:es://http://127.0.0.1:9200"></property>
<property name="user" value="elastic"></property>
<property name="password" value="1"></property>
<property name="maxPoolSize" value="4"></property>
<property name="minPoolSize" value="2"></property>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>