基本概念
ES是基于Lucene的搜索服务器,是一个分布式多用户的全文搜索引擎。
ES | |
---|---|
cluster | 整个ES默认就是集群状态,整个集群是完整、互备的状态 |
node | 一个节点 |
shard | 分片。默认分5片,7.x版本默认分1片 |
index | 5.x的版本对应关系型数据库的database,6.x和7.x的版本对应关系型数据库中的表 |
type | 5.x的版本对应关系型数据库的表,6.x的版本只允许有一个,7.x的版本被废弃。 |
document | 对应关系型数据库的行 |
field | 对应关系型数据库的列 |
特点
天然分片、天然集群
ES 6.x的版本,分片shard默认为5片;ES 7.x的版本,shard默认为1片。
一般一个分片的数据不要超过30G,10G以下的数据建议分1片。
天然索引
ES使用倒排索引,根据内容分词去匹配主键。
倒排索引
ES使用倒排索引的数据结构,有倒排索引就有正向索引。
正向索引使用的是文档的id去匹配文档,如果我们的数据量很大,我们要在海量数据中查找想要的数据,就要先进行全表扫描找出id对应的文档,再去文档中查找指定内容。
倒排索引是先对每个文档进行分词,提取关键字,建立关键字与文档间的关系。每个关键字都会对应多个文档,文档对应的越多,关键字和文档的相关程度(评分)越高。当我们查找时,会根据我们的关键字进行分词,将文档相关程度高的排在前面。
Restful API
PUT插入数据|document
格式:PUT index/type/id (这里的id非字段中的id,可以省略,如果省略会自动生成)
注意:
- PUT是幂等性插入,即多次业务操作的结果是一致的。
- PUT更新数据时,会覆盖原有的document。
PUT movie0508/movie/1
{ "id":1,
"name":"operation red sea",
"doubanScore":8.5,
"actorList":[
{"id":1,"name":"zhang yi"},
{"id":2,"name":"hai qing"},
{"id":3,"name":"zhang han yu"}
]
}
PUT movie0508/movie/2
{
"id":2,
"name":"operation meigong river",
"doubanScore":8.0,
"actorList":[
{"id":3,"name":"zhang han yu"}
]
}
PUT movie0508/movie/3
{
"id":3,
"name":"incident red sea",
"doubanScore":5.0,
"actorList":[
{"id":4,"name":"zhang chen"}
]
}
POST插入数据|document
格式:POST index/type/id (这里的id非字段中的id,可以省略,如果省略会自动生成)
注意:
- POST是非幂等性插入。
- POST更新数据时,可以只更新指定字段的内容。
POST movie0508/movie
{
"id":"3",
"name":"Monkey King Reborn",
"score":5.5,
"actorList":
[
{"id":1,"name":"bian jiang"},
{"id":2,"name":"zhang lei"}
]
}
DELETE删除单个文档
格式:DELETE index/type/id(此id非字段中的id值,是用来分片的id)
注意:
- ES中删除文档并不会真正的删除,而是将文档的数据标记为“已过期”,也会占用磁盘空间。
DELETE movie0508/movie/3
DELETE删除index
格式:DELETE index
注意:
- ES中删除索引是真正的删除,不会进行标记。
DELETE movie0508
POST修改数据
格式:POST index/type/id/_update{ “doc”:{ “字段名”:值 } }
PUT index/type/id{数据}
注意:
- PUT修改数据时会覆盖原来的document。
- POST可以修改指定字段的数据,其他的数据不变。
POST movie0508/movie/2/_update
{
"doc":{
"score":4.6
}
}
GET查询
查询指定index的数据
格式:GET index/type/_search(type可以省略)
GET movie0508/movie/_search
查询指定id的数据
格式:GET index/type/id
GET movie0508/movie/3
按条件查询(全部)
格式:
GET index/type/_search
{
“query”:{
“match_all”:{}
}
}
GET movie0508/movie/_search
{
"query": {
"match_all": {}
}
}
按分词查询
按照分词查询会对匹配字段的值进行分词,按照分词匹配相关度,按照相关度从大到小排序。
格式:
GET index/type/_search
{
"query": {
"match": {
"字段": "值"
}
}
}
GET movie0508/movie/_search
{
"query": {
"match": {
"name": "sea"
}
}
}
按分词属性查询
GET movie0508/movie/_search
{
"query": {
"match": {
"actorList.name": "han"
}
}
}
查询指定词(等值查询)
格式:
GET index/type/_search
{
"query": {
"term": {
"keyword类型的字段": {
"value": "要查询的值"
}
}
}
}
GET movie0508/movie/_search
{
"query": {
"term": {
"name.keyword": {
"value": "operation red sea"
}
}
}
}
按短语(一组词)查询
GET index/type/_search
{
"query": {
"match_phrase": {
"字段": "值"
}
}
}
GET movie0508/movie/_search
{
"query": {
"match_phrase": {
"name": "red sea"
}
}
}
模糊|容错查询
当要查询的单词和词库中一个单词都无法匹配,可以返回相差不多的单词和一定的评分。
GET index/type/_search
{
"query": {
"fuzzy": {
"字段": "值"
}
}
}
GET movie0508/movie/_search
{
"query": {
"fuzzy": {
"name": "rad"
}
}
}
查询后过滤
GET index/type/_search
{
“query”: {
“match”: { “字段”: “值” }
},
“post_filter”: {
“term”: {
“字段”: [“值1”, “值2”]
}
}
}
GET movie0508/movie/_search
{
"query": {
"match": {
"name": "red"
}
},
"post_filter": {
"term": {
"id": 3
}
}
}
边查询边过滤(推荐使用)
GET index/type/_search
{
“query”:{
“bool”: {
“filter”: {
“term”: {
“字段”:"值"
}
},
“must”: [
“match”:{
“字段”:"值"
}
]
}
}
}
GET movie0508/movie/_search
{
"query": {
"bool": {
"filter": {
"term": {
"name": "red"
}
},
"must": [
{
"match": {
"actorList.name": "zhang han yu"
}
}
]
}
}
}
按范围过滤
GET index/type/_search
{
“query”: {
“bool”: {
“filter”: {
“range”: {
“字段”:{
# gte:大于等于 gt:大于
“gte|gt”: 值,
“lte|lt”: 值
}
}
}
}
}
}
GET movie0508/movie/_search
{
"query": {
"bool": {
"filter": {
"range": {
"doubanScore": {
"gte": 5,
"lte": 6
}
}
}
}
}
}
排序
GET index/type/_search
{
"query": {
"match": {
"字段": "值"
}
},
"sort": [
{
"字段": {
"order": “desc”|"asc"
}
}
]
}
GET movie0508/movie/_search
{
"query": {
"match": {
"name": "sea"
}
},
"sort": [
{
"doubanScore": {
"order": "desc"
}
}
]
}
分页查询
GET index/type/_search
{
"query": {
"match_all": {}
},
"from": 起始条数,
"size": 每页条数
}
GET movie0508/movie/_search
{
"query": {
"match_all": {}
},
"from": 0,
"size": 2
}
指定查询字段
GET index/type/_search
{
“query”: {
“match_all”: {}
},
“_source”: [“字段1”,“字段2”]
}
GET movie0508/movie/_search
{
"query": {
"match_all": {}
},
"_source": ["actorList.name","name"]
}
高亮
GET index/type/_search
{
“query”: {
“match”: {
“字段”: “值”
}
},
“highlight”: {
“fields”: {“字段”:{}}
}
}
GET movie0508/movie/_search
{
"query": {
"match": {
"name": "sea"
}
},
"highlight": {
"fields": {"name":{}}
}
}
分组
GET index/type/_search
{
"aggs": {
"分组后的别名": {
"terms": {
"field": “分组字段.keyword”,
"size": 显示的条数
}
}
}
}
注意:
分组字段必须是keyword类型,即必须是等值,不能分词。默认按照count聚合。
GET movie0508/movie/_search
{
"aggs": {
"group_by_act": {
"terms": {
"field": "actorList.name.keyword",
"size": 10
}
}
}
}
聚合
GET index/type/_search
{
"aggs": {
"分组别名1": {
"terms": {
"field": “分组字段.keyword”,
"order": {
"分组别名2": "desc|asc"
}
},
"aggs": {
"分组别名2": {
"聚合类型": {
"field": "聚合字段"
}
}
}
}
}
}
GET movie0508/movie/_search
{
"aggs": {
"group_act": {
"terms": {
"field": "actorList.name.keyword",
"order": {
"avg_score": "desc"
}
},
"aggs": {
"avg_score": {
"avg": {
"field": "doubanScore"
}
}
}
}
}
}
定义mapping
如果没有mapping,ES默认会根据数据自动推断字段的类型。
- true|false => bool
- 1 => long
- 1.1 => float
- "a" => text + keyword
- "2021-05-11" => date
对于有些数据,我们只需要等值检索,不需要分词,那么只存储为keyword类型即可。
注意:
mapping只能对不包含数据的字段进行定义。
基于中文分词创建mapping
PUT index
{
“mappings”: {
“type名称”:{
“properties”: {
“字段”:{
“type(固定)”: “类型”,
“analyzer”: “分词器类型”
}
}
}
}
}
PUT movie0511_chi
{
"mappings": {
"movie": {
"properties": {
"id": {
"type": "long"
},
"name": {
"type": "text",
"analyzer": "ik_smart"
},
"doubanScore": {
"type": "double"
},
"actorList": {
"properties": {
"id": {
"type": "long"
},
"name": {
"type": "keyword"
}
}
}
}
}
}
}
索引别名 _aliases
- 一个索引可以有多个别名,一个别名可以对应多个索引。
- 别名可以灵活的对索引进行分组,根据分组进行统计。
- 创建索引的子集,筛选符合某条件的数据。
- 索引别名的更换是原子性的,可以在运行的集群中无缝切换。
创建索引同时指定别名
PUT index
{
“aliases”: {
“别名”:{}
},
“mappings”: {
…
}
}
PUT movie0511_chi
{
"aliases": {
"movie05-query": {}
},
"mappings": {
"movie": {
"properties": {
"id":{
"type": "long"
},
"name":{
"analyzer":"ik_smart",
"type": "text"
},
"doubanScore":{
"type":"double"
},
"actorList":{
"properties":{
"id":{
"type":"long"
},
"name":{
"type":"keyword"
}
}
}
}
}
}
}
已存在索引添加别名
POST _aliases
{
"actions": [
{
"add": {
"index": “索引名”,
"alias": "别名"
}
}
]
}
POST _aliases
{
"actions": [
{
"add": {
"index": "movie0511_chi",
"alias": "movie_chi-query"
}
}
]
}
过滤,创建子集
POST _aliases
{
"actions": [
{
"add": {
"index": "movie0508",
"alias": "movie-",
"filter": {
"term": {
"actorList.name.keyword": "zhang han yu"
}
}
}
}
]
}
按别名查询
GET 别名/_search
GET movie_chi-query/_search
删除别名
POST _aliases
{
"actions": [
{
"remove": {
"index": “索引名”,
"alias": "要删除的别名"
}
}
]
}
POST _aliases
{
"actions": [
{
"remove": {
"index": "movie0511_chi",
"alias": "movie_chi-query"
}
}
]
}
别名无缝切换
POST _aliases
{
“actions”: [
{
“remove”: {
“index”: “索引1”,
“alias”: “别名”
}
},
{
“add”: {
“index”: “索引2”,
“alias”: “别名”
}
}
]
}
POST _aliases
{
"actions": [
{
"remove": {
"index": "movie0511_chi",
"alias": "movie_chi-query"
}
},
{
"add": {
"index": "movie0508",
"alias": "movie-query"
}
}
]
}
查询别名列表
GET _cat/aliases
索引模板
作用
根据业务需求,创建一系列符合某些mappings和settings的模板,通过使用索引模板,可以使得索引具备可知的一致性。
如果ES中shard特别多,使用模板创建索引可能会有延迟。如果延迟不能接受,那么使用脚本创建索引。
使用场景
索引分割
- 根据时间间隔将一个业务索引分割为多个索引。比如:按照每天日期存储每日的数据。
- 结构变化的灵活性。ES不允许对数据的结构进行修改,如果数据的结构发生变化,那么我们可以在下一个周期对结构进行改变。
- 查询范围优化。查询时不会对全部时间周期的表进行扫描,只查询某个时间范围内的数据。
创建模板
- index_patterns表示index的匹配规则。
- {index}-query是索引别名,{index}就是匹配到的index名称。
PUT _template/template_movie
{
"index_patterns": ["movie_test*"],
"settings": {
"number_of_shards": 1
},
"aliases" : {
"{index}-query": {},
"movie_test-query":{}
},
"mappings": {
"_doc": {
"properties": {
"id": {
"type": "keyword"
},
"movie_name": {
"type": "text",
"analyzer": "ik_smart"
}
}
}
}
}
Scala使用Java API操作ES
导入jest依赖
<dependency>
<groupId>io.searchbox</groupId>
<artifactId>jest</artifactId>
<version>5.3.3</version>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>4.5.2</version>
</dependency>
<dependency>
<groupId>org.codehaus.janino</groupId>
<artifactId>commons-compiler</artifactId>
<version>2.7.8</version>
</dependency>
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>2.4.6</version>
</dependency>
测试插入和查询
import io.searchbox.client.config.HttpClientConfig
import io.searchbox.client.{JestClient, JestClientFactory}
import io.searchbox.core.{Index, Search, SearchResult}
import java.util
import org.apache.lucene.queryparser.xml.builders.BooleanQueryBuilder
import org.elasticsearch.index.query.{BoolQueryBuilder, MatchQueryBuilder, QueryBuilders, TermQueryBuilder}
import org.elasticsearch.search.builder.SearchSourceBuilder
import org.elasticsearch.search.sort.SortOrder
object EsUtils {
private var factor: JestClientFactory = null
def getConn(): JestClient = {
if (factor == null) {
builder()
}
factor.getObject
}
//创建连接
def builder() = {
factor = new JestClientFactory
factor.setHttpClientConfig(
//ES连接地址
new HttpClientConfig.Builder("http://hadoop102:9200")
//是否多线程
.multiThreaded(true)
//最大连接数量
.maxTotalConnection(20)
//连接超时时间
.connTimeout(10000)
//读取超时时间
.readTimeout(10000).build()
)
}
//插入数据
def insertIndex(): Unit = {
val jestClient: JestClient = getConn()
//Builder设计模式
//参数必须是可以转换为Json格式的对象
val index = new Index.Builder(movieTest(2, "蜘蛛侠"))
.index("movie_test01")
.`type`("_doc")
.id("1001")
.build()
jestClient.execute(index)
jestClient.close()
}
//查询数据
def searchResult() = {
val jestClient: JestClient = getConn()
//直接查询
val query = "{\n \"query\": {\n \"bool\": {\n \"filter\": {\n \"term\": {\n \"actorList.name.keyword\": \"zhang han yu\"\n }\n },\n \"must\": [\n {\n \"match\": {\n \"name\": \"operation\"\n }\n }\n ]\n }\n },\n \"from\": 0,\n \"size\": 20,\n \"sort\": [\n {\n \"doubanScore\": {\n \"order\": \"desc\"\n }\n }\n ]\n}"
//使用ES自带的API
val searchBuilder = new SearchSourceBuilder()
//将ES查询转换为API
val booleanBuilder: BoolQueryBuilder = QueryBuilders.boolQuery()
booleanBuilder.filter(new TermQueryBuilder("actorList.name.keyword","zhang han yu"))
booleanBuilder.must(new MatchQueryBuilder("name","operation"))
searchBuilder.from(0)
searchBuilder.size(20)
searchBuilder.sort("doubanScore",SortOrder.DESC)
//API查询转换为字符串
val query2: String = searchBuilder.toString
val queryBuilder = new Search.Builder(query2)
.addIndex("movie0508")
.addType("movie")
.build()
val result = jestClient.execute(queryBuilder)
//将结果封装为可以转化为json的对象,比如Map、样例类
//Map必须是Java中的Map,因为使用的是java的API
val hits: util.List[SearchResult#Hit[util.Map[String, Any], Void]] = result.getHits(classOf[util.Map[String,Any]])
import scala.collection.JavaConversions._
val res: List[util.Map[String, Any]] = hits.map(_.source).toList
println(res.mkString("\n"))
jestClient.close()
}
def main(args: Array[String]): Unit = {
searchResult()
}
}
case class movieTest(id: Int, name: String)