Lanyon 记录下日常工作与学习哈~,还有技术分享哦。。🎉

Elasticsearch核心技术与实战

在极客时间上学习elasticsearch课程,主要关注点在queryDSL语句以及集群的管理,在本地基于es 7.1来构建集群服务,启动脚本如下,同时在conf/elasticsearch.yml中添加xpack.ml.enabled: falsehttp.host: 0.0.0.0的配置(禁用ml及启用host):

bash> bin/elasticsearch -E node.name=node0 -E cluster.name=geektime -E path.data=node0_data -d
bash> bin/elasticsearch -E node.name=node1 -E cluster.name=geektime -E path.data=node1_data -d
bash> bin/elasticsearch -E node.name=node2 -E cluster.name=geektime -E path.data=node2_data -d
bash> bin/elasticsearch -E node.name=node3 -E cluster.name=geektime -E path.data=node3_data -d

docker容器中启动cerebro服务,用于监控elasticsearch集群的状态,docker启动命令如下:

bash> docker run -d --name cerebro -p 9100:9000 lmenezes/cerebro:latest

文档index基础操作

1) elasticsearch中创建新文档,用post请求方式,url内容为index/_doc/id。当未指定{id}时,会自动生成随机的idput方式用于更新文档,当PUT users/_doc/1?op_type=createPUT users/_create/1指定文档id存在时,就会报错。

POST users/_doc
{
  "user": "mike",
  "post_date": "2019-04-15T14:12:12",
  "message": "trying out kibana"
}

2) elasticsearch的分词器analysis,分词是指把全文本转换为一些列的单词(term/token)的过程,其通常由Character FiltersTokenizerToken Filters这三部分组成。具体url示例如下,analyzer的类型可以有:standardstopsimple等。

GET _analyze
{
  "analyzer": "stop",
  "text": "2 running Quick brown-foxes leap over lazy dogs in the summer evening."
}

3) urlquery string的语法,指定字段v.s.泛查询,其中df为默认字段,当不指定df只按q查询时,则是泛查询,从_doc的所有字段检索:

GET /movies/_search?q=2012&df=title&sort=year:desc&from=0&size=10&timeout=1s

URl Search、Request Body查询及文档Mapping

1)在elasticsearch中查询可以分为url searchrequest body查询,其中url searchGET方式,相关参数放在url中。df指定默认查询字段,q为查询字符串。当未指定df时,称为泛查询,会拿数值与doc中所有字段进行匹配:

# es中查询的dsl,df指定默认字段,q为查询数值,TermQuery
GET kibana_sample_data_ecommerce/_search?q=Eddie&df=customer_first_name
{
  "profile": "true"
}
# 若不用df的话,可以用q=field:value来进行替换
GET kibana_sample_data_ecommerce/_search?q=customer_first_name:Eddie
{
  "profile": "true"
}

2)Phrase queryTerm query的区别,PhraseQuery会按整个字符串进行匹配,而TermQuery则会对字符串进行分词。对于term来说,只要Field value中包含任意一个单词就可以。

# phrase query,相当于不会做分词,匹配完整字符串(1条)
GET kibana_sample_data_ecommerce/_search?q=customer_full_name:"Eddie Underwood"
{
  "profile": "true"
}
# term query,对字符串进行了分词,好像也有keyword概念,任意匹配Eddie或Underwood就可以
GET kibana_sample_data_ecommerce/_search?q=customer_full_name:Eddie Underwood
{
  "profile": "true"
}

此外,在url query中还支持分组的概念,也就是Bool Query。当查询条件为customer_full_name:(Eddie Underwood)时,会分别按EddieUnderwood进行匹配,其是任意的满足关系。若想在字段中同时满足要求,则可在分组中添加AND操作符。此外,url query还支持range查询及通配符查询。

# bool query,full_name中包括Eddie或Underwood才可以,实现同时包含,则需添加AND关键字
GET kibana_sample_data_ecommerce/_search?q=customer_full_name:(Eddie AND Underwood)
{
  "profile": "true"
}
# 数值范围查询,(订单总额)taxful_total_price大于50
GET kibana_sample_data_ecommerce/_search?q=taxful_total_price:>=50
{
  "profile": "true"
}
# 通配符查询,只要email字段中含"gwen"就会被匹配
GET kibana_sample_data_ecommerce/_search?q=email:gwen*
{
  "profile": "true"
}

3)Request body查询的详细解释,这其实是一种更通用的写法,使用POST请求方式。在body中使用_source指定要获取的字段列表,同时sort可指定按哪个字段进行排序。query部分指定了具体的查询条件,operatorand最终效果类似于phrase queryelasticsearchpainless脚本用于特定计算,返回计算后的新字段(如金额转换等)。

# es request body的写法,按订单总金额排序desc,_source过滤doc中的字段
POST kibana_sample_data_ecommerce/_search
{
  "_source": ["taxful_total_price", "total_quantity", "customer_full_name", "manufacturer"],
  "sort": [{"taxful_total_price": "desc"}],
  "query": {
    "match": {
      "customer_full_name": {
        "query": "Eddie Lambert",
        "operator": "and"
      }
    }
  },
  "script_fields": {
    "addtional_field": {
      "script": {
        "lang": "painless",
        "source": "doc['taxful_total_price'].value + '_hello'"
      }
    }
  }
}

此外,对于match_phrase则不会进行分词,对_doc会直接进行查询。body中的slop参数可用于近似度查询,提升数据检索的容错性。

# match_phrase查询,不会进行分词,直接匹配total字符串,slop指定term结果
POST kibana_sample_data_ecommerce/_search
{
  "query": {
    "match_phrase": {
      "customer_full_name": {
        "query": "Eddie Lambert",
        "slop": 1
      }
    }
  }
}

4)query_stringsimple_query_string的区别,query_stringurl query类似,也需指定default_field。同时,其也支持多字段fields及多分组query的查询,simple_query_string#query也需指定查询条件。

# query_string和url query比较类似,也支持分组,如下的query_string#fields
POST /users/_search
{
  "query": {
    "query_string": {
      "default_field": "name",
      "query": "Ruan AND YiMing"
    }
  }
}
POST /users/_search
{
  "query": {
    "query_string": {
      "fields": ["name", "about"],
      "query": "(Ruan And YiMing) OR (Java AND Elasticsearch)"
    }
  }
}
POST /users/_search
{
  "query": {
    "simple_query_string": {
      "query": "Ruan AND YiMing",
      "fields": ["name"]
    }
  }
}

5)对于文档mapping这一部分,类似比喻的话,相当于是数据表的schema,规定了字段的约束信息。对于dynamic mappingelasticsearch支持三种模式:truefalsestrict。其默认值为true,当设置mappingfalse时,新添加的字段不能检索,但会在_source部分展示,当为strict时,索引文档新增字段时,会进行报错。

GET mapping_test/_mapping
# 修改dynamic为false,新加的字段不能被索引
PUT dynamic_mapping_test/_mapping
{
  "dynamic": false
}
PUT dynamic_mapping_test/_doc/10
{
  "anotherField": "otherValue"
}
# dynamic为false时,新增的字段无法被检索,strict模式下,新添加字段会报错
POST dynamic_mapping_test/_search
{
  "query": {
    "match": {
      "anotherField": "otherValue"
    }
  }
}

深入ElasticSearch搜索机制

1)深入理解分词的逻辑,在使用_bulk api批量写入一批文档后,查询文档时,通过原有的字段是检索不到的,必须将其转换为小些。向products索引写入3条数据,分别为Apple的产品。

# _bulk api批量写入数据,一次写入3条数据
POST /products/_bulk
{"index": {"_id": 1}}
{"productID": "XHDK-1902-#fj3", "desc": "iPhone", "price": 30}
{"index": {"_id": 2}}
{"productID": "XHDK-1003-#446", "desc": "iPad", "price": 35}
{"index": {"_id": 3}}
{"productID": "XHDK-6902-#521", "desc": "MBP", "price": 40}

通过term queryiPhone进行检索时,是查不到数据的。原因是在存储文档时,elasticsearch对字段值进行了分词,数据字段按小写形式进行存储,当用iphone检索时是可以的。此外,elasticsearch中每个字段都有keyword属性,在用field.keyword查询时则可以进行完整的匹配。

# 直接用iPhone在desc#value查询,搜不到记录。但用desc.keyword可以,因为在保存文档时,iPhone在索引中已进行了小写
POST /products/_search
{
  "query": {
    "term": {
      "desc.keyword": {
        "value": "iPhone"
      }
    }
  }
}
# 将query改为filter的方式,忽略TF-IDF算分问题,避免相关性算分的开销,提升查询性能
POST /products/_search
{
  "explain": true,
  "query": {
    "constant_score": {
      "filter": {
        "term": {
          "productID.keyword": "XHDK-1902-#fj3"
        }
      }
    }
  }
}

为了提升查询效率,可以用constant_score#filter来替换term query,因为其不进行算分,所以效率能高一些。同时,其也支持range queryexists操作符。

# 用range方式进行范围查询,通过doc.price进行过滤
GET /products/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "range": {
          "price": {
            "gte": 20, "lte": 30
          }
        }
      }
    }
  }
}
# 用exists来查找一些field值非空的文档,并将其进行返回
POST /products/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "exists": {
          "field": "desc"
        }
      }
    }
  }
}

2)query contextfilter context影响算分的问题,默认情况下elasticsearch会按照匹配度问题给文档进行打分,在文档每部分可使用boost来影响其分数,当文档中两个字段都含关键词时,可通过boost设置权重,进而影响文档的排名。

# query context与filter context影响算分问题
POST /blogs/_bulk
{"index": {"_id": 1}}
{"title": "Apple iPad", "content": "Apple iPad,Apple iPad"}
{"index": {"_id": 2}}
{"title": "Apple iPad,Apple iPad", "content": "Apple iPad"}
# 通过boost指定每部分字段的权重,进而影响文档的算分排序
POST blogs/_search
{
  "query": {
    "bool": {
      "should": [
        {"match": {
          "title": {
            "query": "apple,ipad",
            "boost": 1
          }
          }
        },
        {"match": {
          "content": {
            "query": "apple,ipad",
            "boost": 2
          }
        }}
      ]
    }
  }
}

bool查询中,mustshould是算分的,而must_not则不计入算分,在检索示例中可通过mustmust_not来过滤文档。默认情况下,用term query查询时,只要doc中包含关键字的频率高,则其相应的算分也会高。在具有相同数量关键词的字段中,doc长度越小的文档相关性越高。

# 批量写入关于apple的新闻数据,批量写入文档记录
POST news/_bulk
{"index": {"_id": 1}}
{"content": "Apple Mac"}
{"index": {"_id": 2}}
{"content": "Apple iPad"}
{"index": {"_id": 3}}
{"content": "Apple employee like Apple Pie and Apple Juice"}
# 然而并不是所期望的,返回了apple食品记录
POST news/_search
{
  "query": {
    "bool": {
      "must": {
        "match": {"content": "apple"}
      }
    }
  }
}

可通过must_not对不符合条件的文档进行剔除,若只是想将不相关的文档分数减小,则可以通过boosting#positiveboosting#negative使得对文档进行重新的计分,这样不相关的文档也会进行展示,但其排名比较靠后。

# 用must_not排除pie字符串,只剩余电子产品
POST news/_search
{
  "query": {
    "bool": {
      "must": {"match": {"content": "apple"}},
      "must_not": {"match": {"content": "pie"}}
    }
  }
}
# 当不想删除时,可使用boosting#positive、negative方式排序
POST news/_search
{
  "query": {
    "boosting": {
      "positive": {
        "match": {"content": "apple"}
      },
      "negative": {
        "match": {"content": "pie"}
      },
      "negative_boost": 0.5
    }
  }
}

3)disjunction query也是关于文档相关性的,若文档中有两部分都匹配,若想按文档匹配度高的那一部分排序的话(不按累加求和),则应使用此查询。同时,还可按tie_breaker对文档分数进行扰乱,进而影响文档的排名。

PUT /blogs/_bulk
{"index": {"_id": 1}}
{"title": "Quick brown rabbits", "body": "Brown rabbits are commonly seen"}
{"index": {"_id": 2}}
{"title": "Keeping pets happy", "body": "My quick brown fox eats rabbits on a regular basis."}
# 用dis_max#queries找两部分,各自评分最高的内容,此外还可通过tie_breaker进行调整
POST /blogs/_search
{
  "query": {
    "dis_max": {
      "queries": [
        {"match": {"title": "Brown fox"}},
        {"match": {"body": "Brown fox"}}
      ],
      "tie_breaker": 0.2
    }
  }
}

多字段查询的搜索语法,most_fields会累计多个字段的分数之和,cross_fields也就是当query在多个字段中存在时,就会返回结果,也就是所谓的跨字段查询。

PUT address/_doc/1
{
  "street": "5 Poland Street",
  "city": "London",
  "country": "United Kingdom",
  "postcode": "W1V 3DG"
}
# 使用most_fields是可以的,但增加operator:and就不可以了。可将type改为cross_fields,表示将query string在多个字段中进行检索
POST address/_search
{
  "query": {
    "multi_match": {
      "query": "Poland Street W1V",
      "fields": ["street", "city", "country", "postcode"],
      "type": "cross_fields",
      "operator": "and"
    }
  }
}

可以使用alias语法对索引进行重命名,应用场景多为elasticsearch索引数据备份,为避免应用服务端开发时修改配置,可做到无感数据源切换。

# index的alias操作,用于对address进行重命名
POST _aliases
{
  "actions": [
    {
      "add": {
        "index": "address",
        "alias": "address_latest"
      }
    }
  ]
}

深入ElasticSearch聚合分析

elasticsearch聚合分metricbucket两类,metric类似于一些指标(countavgsum等),而bucket相当于sql语句中的group by操作。

select count(brand)=>[metric] from cars group by brand=>[bucket];

一个简单的例子,通过elasticsearch请求分别统计maxminavg的平均工资,size设置为0表示不返回原始文档。aggs表示聚合语法开始,其中maxmin为聚合类型,里面的fieldsalary表示要聚合的字段。其实,简化语法可直接用stats替换max,其在一次执行中会统计出相关指标。

# Metrics聚合,找最低、最高及平均工资
POST employees/_search
{
  "size": 0,
  "aggs": {
    "max_salary": {
      "max": {
        "field": "salary"
      }
    },
    "min_salary": {
      "min": {
        "field": "salary"
      }
    },
    "avg_salary": {
      "avg": {
        "field": "salary"
      }
    }
  }
}

elasticsearch通过jobs#terms进行分桶操作,首先一点elasticsearch不能对text类型字段进行分桶(keyword是可以的),需打开fielddata的配置。aggs还可以嵌套,如下是对员工按age进行排序,并取前2位进行展示。

# 对keyword进行聚合,必须要用.keyword,避免分词,直接用job会报错,还可指定terms#size参数
POST employees/_search
{
  "size": 0,
  "aggs": {
    "jobs": {
      "terms": {
        "field": "job.keyword"
      },
      "aggs": {
        "old_employee": {
          "top_hits": {
            "size": 2,
            "sort": [
              {
                "age": {
                  "order": "desc"
                }
              }
            ]
          }
        }
      }
    }
  }
}
# 对text字段打开fielddata,支持terms aggregation
PUT employees/_mapping
{
  "properties": {
    "job": {
      "type": "text",
      "fielddata": "true"
    }
  }
}

cardinate操作相当于sql中的distinct count操作,可用于去重后的计数。salary还支持按range进行数量查询,其中key的值可以进行自定义。

# 对job.keyword进行聚合分析,cardinate操作,相当于做distinct count操作
POST employees/_search
{
  "size": 0,
  "aggs": {
    "cardinate": {
      "cardinality": {
        "field": "job.keyword"
      }
    }
  }
}
# salary range分桶,可以自定义桶#key,并按range进行查询
POST employees/_search
{
  "size": 0,
  "aggs": {
    "salary_range": {
      "range": {
        "field": "salary",
        "ranges": [
          {
            "to": 10000
          },
          {
            "from": 10000,
            "to": 20000
          },
          {
            "key": ">20000",
            "from": 20000
          }
        ]
      }
    }
  }
}

histogram用于展示员工薪资的直方图,field表示按哪个字段展示,interval为直方图每格的间隔大小。此外,elasticsearch还支持pipeline操作,其会将aggs后的结果再进行分析,常见的有min_bucketmax_bucketavg_bucket等操作。

# salary Histogram,工资分布的直方图
POST /employees/_search
{
  "size": 0,
  "aggs": {
    "salary_histogram": {
      "histogram": {
        "field": "salary",
        "interval": 20000,
        "extended_bounds": {
          "min": 0,
          "max": 100000
        }
      }
    }
  }
}
# elasticsearch pipeline操作, min_bucket最终选出最低平均工资,max_bucket则求最大的工作类型,avg_bucket只是所有类型工作的平均值,percentiles_bucket为百分位数的统计
POST /employees/_search
{
  "size": 0,
  "aggs": {
    "jobs": {
      "terms": {
        "field": "job.keyword",
        "size": 10
      },
      "aggs": {
        "avg_salary": {
          "avg": {
            "field": "salary"
          }
        }
      }
    },
    "min_salary_by_jobs": {
      "percentiles_bucket": {
        "buckets_path": "jobs>avg_salary"
      }
    }
  }
}

Aggs Query聚合的filter这块,共分为FilterPost_Filterglobal3种类型,第一个在aggs#old_person#filter中,其行为属于前置filter(也即先过滤再agg)。第二个属于post_aggs,先进行aggs然后只展示Dev Managerbucket桶。而all#global{}会排除query#filter的作用,而对所有doc进行计算。

# Filter,先按age#from 从35岁开始filter
POST employees/_search
{
  "size": 0,
  "aggs": {
    "old_person": {
      "filter": {
        "range": {
          "age": {
            "from": 35
          }
        }
      },
      "aggs": {
        "jobs": {
          "terms": {
            "field": "job.keyword"
          }
        }
      }
    }
  }
}

#post filter,相当于先做bucket分桶操作,然后再进行filter过滤
POST /employees/_search
{
  "aggs": {
    "jobs": {
      "terms": {
        "field": "job.keyword"
      }
    }
  },
  "post_filter": {
    "match": {
      "job.keyword": "Dev Manager"
    }
  }
}

ElasticSearch数据建模

数据建模-对象及Nested对象,例如blog文档中含User对象,结构类似于json。在用Rest接口进行查询时,可通过user.username进行嵌套式查询。

# 插入一条blog信息, user为嵌套的对象,包含3个字段
PUT nested_blog/_doc/1
{
  "content": "I like elasticsearch",
  "time": "2022-11-06T00:00:00",
  "user": {
    "userid": 1,
    "username": "Jack",
    "city": "ShangHai"
  }
}
# 查询blog的信息,对text做了分词,不区分大小写了
POST nested_blog/_search
{
  "query": {
    "bool": {
      "must": [
        {"match": {"content": "elasticsearch"}},
        {"match": {"user.username": "Jack"}}
      ]
    }
  }
}

当嵌套字段类型为数组时,通过bool查询其返回的结果会存在异常。此时,indexmapping和查询的dsl也必须改为nested query

# 电影的mapping信息,对于数组类型字段,需将`type`改为`nested`
PUT my_movies
{
  "mappings": {
    "properties": {
      "actors": {
        "type": "nested",
        "properties": {"first_name": {"type": "keyword"},
          "last_name": {"type": "keyword"}}
      },
      "title": {
        "type": "text",
        "fields": {"keyword": {"type": "keyword", "ignore_above": 256}}
      }
    }
  }
}
# 写入一条电影信息, actors部分为一个数组
PUT my_movies/_doc/1
{
  "title": "Speed",
  "actors": [{"first_name": "Keanu", "last_name": "Reeves"},
  {"first_name": "Dennis", "last_name": "Hopper"}]
}

在进行数据检索时,bool类型的query,在json结构中也需指明nested.path,这样检索数据时,才会按同一个对象的first_namelast_name一起检索。此外,对于普通嵌套对象,Agg操作是不生效的。

# 查询电影信息,但是检索到了结果,需调整为Nested Query, 再根据条件筛选就正确
POST my_movies/_search
{
  "query": {
    "bool": {
      "must": [
        {"match": {"title": "Speed"}},
        {"nested": {
          "path": "actors",
          "query": {
            "bool": {
              "must": [
                {"match": {"actors.first_name": "Keanu"}},
                {"match": {"actors.last_name": "Reeves"}}
              ]
            }
          }
        }}
      ]
    }
  }
}
# 嵌套对象的Agg聚合操作,也需指定类型为Nested Query,普通Agg是不生效的
POST my_movies/_search
{
  "size": 0,
  "aggs": {
    "actors": {
      "nested": {
        "path": "actors"
      },
      "aggs": {
        "actor_name": {
          "terms": {
            "field": "actors.first_name",
            "size": 10
          }
        }
      }
    }
  }
}

elasticsearch中的父子文档,索引的mapping如下所示,blog_comments_relation#typejoin,在relations中定义了blogcomment的对应关系。在写入blog文档时,blog_comments_relation#name的值为blog

# Es中的父/子文档,blog_comments_relation#此part未看懂
PUT my_blogs
{
  "settings": {
    "number_of_shards": 2
  },
  "mappings": {
    "properties": {
      "blog_comments_relation": {
        "type": "join",
        "relations": {
          "blog": "comment"
        }
      },
      "content": {
        "type": "text"
      },
      "title": {
        "type": "keyword"
      }
    }
  }
}
# 索引父文档,分别写入两个文档
PUT my_blogs/_doc/blog1
{
  "title": "Learning Elasticsearch",
  "content": "Learning ELK @ geektime",
  "blog_comments_relation": {
    "name": "blog"
  }
}
PUT my_blogs/_doc/blog2
{
  "title": "Learning Hadoop",
  "content": "Learning Hadoop",
  "blog_comments_relation": {
    "name": "blog"
  }
}

索引comment子文档,需在json结构中指定idcomment1routing信息,其中index name值为comment,对应的parent值为blog1。通过my_blogs/_search可以查到所有文档列表:

# 索引子文档,需指定routing路由字段值
PUT my_blogs/_doc/comment1?routing=blog1
{
  "comment": "I am learning ELk",
  "username": "Jack",
  "blog_comments_relation": {
    "name": "comment",
    "parent": "blog1"
  }
}
PUT my_blogs/_doc/comment2?routing=blog2
{
  "comment": "I like Hadoop !!!",
  "username": "Jack",
  "blog_comments_relation": {
    "name": "comment",
    "parent": "blog2"
  }
}
# 查询所有文档,包含blog和comment两种类型
POST my_blogs/_search
{}

父子文档间的查询,通过父文档id查询,若查看blog#comment,则可以通过parent_id来查询,其中type值为comment。若想根据comment查询对应的blog,则可使用has_child注解。此外,可通过comment2routing查看blog2下所有的评论数据。

# 根据父文档id查询
GET my_blogs/_doc/blog2
# parentId查询,依据blog2查到其下所有comment
POST my_blogs/_search
{
  "query": {
    "parent_id": {
      "type": "comment",
      "id": "blog2"
    }
  }
}
# has child查询返回父文档, has parent查询会返回子文档
POST my_blogs/_search
{
  "query": {
    "has_child": {
      "type": "comment",
      "query": {
        "match": {
          "username": "Jack"
        }
      }
    }
  }
}
# 通过id和routing来访问子文档
GET my_blogs/_doc/comment2?routing=blog2

对于elasticsearch中已有的index,要修改其某个字段类型时,只能对当前索引进行reindex操作。直接更新索引mapping文件,会抛出remote_transport_exception的异常。

# reindex api,类似于导数据
POST _reindex
{
  "source": {
    "index": "reindex_blogs"
  },
  "dest": {
    "index": "blogs_fix"
  }
}

elasticsearchpipelinepainless脚本,可通过PUT请求直接注册一个blog_pipelineprocessors可以有多种类型,像split会对指定字段进行切分,并且指定切分字符串为,。在索引文档时,可以指定blog_pipeline,这样存入文档的字段会被切分开。

# 为ES增加一个pipeline, 对index的文档进行计算
PUT _ingest/pipeline/blog_pipeline
{
  "description": "a blog pipeline",
    "processors": [
    {
      "split": {
        "field": "tags",
        "separator": ","
      }
    },
    {
      "set": {
        "field": "views",
        "value": 0
      }
    }
  ]
}
# 测试pipeline,确实tags字段被切分了,同时增加了views字段
POST _ingest/pipeline/blog_pipeline/_simulate
{
  "docs": [
    {
      "_source": {
        "title": "Introducing big data....",
        "tags": "openstask,k8s",
        "content": "you known, for cloud"
      }
    }
  ]
}
PUT tech_blogs/_doc/2?pipeline=blog_pipeline
{
    "title": "Introducing big data....",
    "tags": "openstask,k8s",
    "content": "you known, for cloud"
}

painless脚本内容如下,在script语法中指定执行脚本,其中ctx可取上下文中定义的对象。

POST tech_blogs/_update/1
{
  "script": {
    "source": "ctx._source.views += params.views",
    "params": {
      "views": 100
    }
  }
}