前言:
最近感觉有好多事情要做,时间真是越来越珍贵啦
正文:
首先Elasticsearch(以下简称es)是一个基于Lucene的搜索服务器,顾名思义是查询方面的一个能手,但是他也是有一套自己的增删改的、相对而言用处较少,也比较简单所以简单略过啦:
增:指定的 /Index/Type/id 发送 PUT 请求
可不指定id,POST
删:curl -X DELETE 'localhost:9200/accounts/person/1'
更新:PUT 请求,重新发送一次数据
查:/Index/Type/Id发出 GET
搜索/Index/Type/_search 返回值:
total:返回记录数,本例是2条。
max_score:最高的匹配程度,本例是1.0。
hits:返回的记录组成的数组。
用于操作的curl格式如下:
curl -X<VERB> '<PROTOCOL>://<HOST>:<PORT>/<PATH>?<QUERY_STRING>' -d '<BODY>'
1、VERB:http方法,get、post、put、head或delete(我是大写的)
2、PROTOCOL:http或https
3、HOST:es集群中任意节点的主机名,可以用localhost代本机节点
4、PATH:API的终端路径
5、QUERY_STRING:任意可选的查询字符串参数 (如 ?pretty 将输出 JSON 返回值)
6、BODY:一个 JSON 格式的请求体 (不是必须)
curl -XGET 'http://localhost:9200/_count?pretty' -d '
{
"query": {
"match_all": {}
}
}
'
为了撑得起搜索服务器的名号,es为查询提供了丰富的支持:
常用的term查询:
不分词地查询某字段里有(一个或多个)关键词的文档
#查询preview字段有elasticsearch或book关键词的文档(从1开始返回2个结果)
#minimum_match:最小匹配集,1最少有一个匹配,只要有一个出现在preview中文档将被查询出来,如为2 则priview中两个词都要有才能被选中
GET /Index/type/_search
{
"from":1,
"size":2,
"query":{
"term":{
"preview":["elasticsearch","book"],
"minimum_match":1
}
}
}
简体:
GET /index/type/_search?q=preview:elasticsearch
match查询:
会根据自指定的字段提供合适的分词器(安装分词器或用自带的)
GET /Index/type/_search{
"query":{
"match":{
"preview":"elasticsearch"
}
}
}
还有match_phrase,其slop指定关键词之间要相隔多少个词,这个就不写了,占地;
常用的MySQL中有group by、order by、and 、or、not等,es中有没有相似的语法呐?既然我问了那应该是有的
{
"bool":{
"must":[],//and
"should":[],//or
"must_not:[]//not
}
}
#官网上贴过来个看起来高大上的例子
POST _search
{
"query": {
"bool" : {
"must" : {
"term" : { "user" : "kimchy" }
},
"filter": {//filter通:过滤,符合规则 则添加到桶中,只影响聚合
"term" : { "tag" : "tech" }
},
"must_not" : {//这个在bool里面,就是嵌套查了
"range" : {//范围过滤,gte是大于等于、lte小于等于、gt 大于 lt:小于
"age" : { "gte" : 10, "lte" : 20 }
}
},//bool结束
"should" : [
{ "term" : { "tag" : "wow" } },
{ "term" : { "tag" : "elasticsearch" } }
],
"minimum_should_match" : 1,//最少应该匹配1个
"boost" : 1.0//类似权重,给符合添加的结果设定权重
}
}
}
另一种使用and、or、not查询:
{
"filtered" : {
"query" : { ... },//默认match_all
"filter" : { ... },
"strategy": "leap_frog"
}
}
#实例如下
#The filtered query is passed as the value of the query parameter in the search request.
#在搜索请求中过滤后的查询作为查询参数
GET /index/type/_search
{
"query":{
"filtered":{
"filter":{
"bool":{##or
"should":[
{"term":{"productID":"124566"}},
{
"bool":{
"must":[##and
{"term":{"productID":"24345"}},
{"term":{"price":20}}
]
}
}
]
}
}
}
}
}
#翻译成SQL就是:
select document from products where productID = "124566" or (productID="24345" and price=20)
post_filter:后置过滤器,查询完毕过滤搜索结果;只和聚合一起使用且使用了不同的过滤条件时使用
分组聚合aggs:
对一份数据执行分组聚合,这个aggs里面可以进一步操作:count、avg、max、min等;
GET /tvs/sales/_search
{
"size" : 0,##只获取聚合结果,而不要执行聚合的原始数据
"aggs" : {
"popular_colors" : { ##给每个aggs起的名字,随便取什么都ok
"histogram": {#按照给定值进行分组,price以2000为间隔分组
"field": "price",
"interval": 2000 #2000一个区域,数据对应放入
},
"terms" : {
"field" : "color"##根据指定的字段color的值进行分组
},
"aggs": {//嵌套、专业点叫做下钻分析,多个维度、多次分组
"avg_price": {
"avg": {//取price的平均数avg,可以min、max、sum或count
"field": "price"
},
"cardinality": {#去重,具体:对brand去重
"field": "brand"
}
}
}
}
}
}
#相当于:Select avg(price) from tvs.sales group by color
结果有一个默认的排序规则:
按照doc_count降序排序,doc_count是es中bucket操作默认执行的一个内置metric(聚合统计操作:max、min、count、avg、sum)
此外还支持地理位置的查询,这个挺简单的,上网查吧;
增的话也挺简单的,用PUT和POST,可以插入一条也可以多条
PUT /website/blogs/1
{
"title": "我的第一篇博客",
"content": "这是我的第一篇博客,开通啦!!!",
"userId": 1
}
#多条
POST /company/rd_center/_bulk
{ "index": { "_id": "1" }}
{ "name": "北京研发总部", "city": "北京", "country": "中国" }
{ "index": { "_id": "2" }}
{ "name": "上海研发中心", "city": "上海", "country": "中国" }
{ "index": { "_id": "3" }}
{ "name": "硅谷人工智能实验室", "city": "硅谷", "country": "美国" }
JAVA API
首先连上es:这个连接方式开始的代码一直报红线,弄了好长时间,刚开始接触es、又有任务在身,我真是要崩溃了,后来在社区里面shiyuan给了我下面的连接方式,当然es不只这一种连接方式,当时给我的代码同一个类import了两个包,所以悲剧了很长时间;在es中文社区得到了很多帮助,很谢谢大佬们的帮助,特别是laoyang360,现在看看自己问了很多不该问的问题,只怪那是太年轻
public TransportClient Client() throws UnknownHostException {
Settings settings = Settings.builder()
.put("cluster.name", "elasticsearch")
.put("client.transport.sniff", true)
.build();
TransportClient client = new PreBuiltTransportClient(settings)
.addTransportAddress(new TransportAddress(InetAddress.getByName("192.168.22.95"), 9300));
return client;
}
上面获得了集群或者es服务器的连接,接下来该index:
//如果index存在则插入 不存在则创建,这只是一种形式,兼顾建与增所以写了出来
IndexRequest indexRequest = new IndexRequest("car_shop", "cars", "1")
.source(XContentFactory.jsonBuilder()
.startObject()
.field("brand", "宝马")
.field("name", "宝马320")
.field("price", 310000)
.field("produce_date", "2018-04-01")
.endObject()
);
UpdateRequest updateRequest = new UpdateRequest("car_shop", "cars", "1")
.doc(XContentFactory.jsonBuilder()
.startObject()
.field("price", 310000)
.endObject())
.upsert(indexRequest);
UpdateResponse updateResponse = client.update(updateRequest).get();
System.out.println(updateResponse.getVersion());//锁机制也用到了这个版本控制
client.close();
Elasticsearch的jar包为我们提供了很丰富的增删改特别是查的方法,如图
可以点出来很多方法,根据需要使用
目前使用的是:boolQuery
QueryBuilder queryBuilder = QueryBuilders.boolQuery()
.must(QueryBuilders.matchQuery("brand","宝马"))//must
.mustNot(QueryBuilders.termQuery("name.raw","宝马313"))//must_not
.should(QueryBuilders.rangeQuery("produce_date").gte("2017-02-04").lte("2017-09-09"))//should
.should(QueryBuilders.rangeQuery("price").gte(28000).lte(35000));//should
SearchResponse searchResponse = client.prepareSearch("car_shop")//index
.setTypes("cars")//type
.setQuery(queryBuilder)//类似参数
.get();//执行
//response,这个应该能猜出来是什么吧,hits击中
for (SearchHit searchHit : searchResponse.getHits().getHits()) {
System.out.println(searchHit.getSourceAsString());
}
还有disMaxQuery和curl格式、DSL查询差不多、灵活运用吧,学习中……:
QueryBuilder disMaxQueryBuilder = QueryBuilders.disMaxQuery()
.add(QueryBuilders.matchQuery("title","java solution"))
.add(QueryBuilders.matchQuery("content","java solution"));
SearchResponse searchResponse = client.prepareSearch("index")
.setTypes("type")
.setQuery(disMaxQueryBuilder)
.get();
client.close();
多索引查询:
MultiGetResponse multiGetesponses = client.prepareMultiGet()
.add("car_shop", "cats", "1")
.add("car_shop", "cars", "2")
.get();
for (MultiGetItemResponse multiGetItemResponses : multiGetesponses) {
GetResponse getResponse = multiGetItemResponses.getResponse();
if (getResponse.isExists()) {
System.out.println(getResponse.getSourceAsString());
}
}
client.close();
client这个也是可以查的:
SearchResponse searchResponse = client.prepareSearch("car_shop")
.setTypes("cars")
.setQuery(QueryBuilders.matchQuery("brand","宝马"))
.setSize(1)//每个primary分片返回的文档数
.get();
searchResponse = client.prepareSearch("car_shop")
.setTypes("cars")
.setQuery(QueryBuilders.multiMatchQuery("宝马","brand","name"))//brand和name中有 宝马 这个值的
.get();
searchResponse = client.prepareSearch("car_shop")
.setTypes("cars")
.setQuery(QueryBuilders.termQuery("name.raw", "宝马318"))
.setScroll(new TimeValue(6000))//滚动的有效时长:保持搜索的上下文环境多长时间
.get();
searchResponse = client.prepareSearch("car_shop")
.setTypes("cars")
.setQuery(QueryBuilders.prefixQuery("name", "宝"))//name以宝字开头的
.get();
搜索请求一次请求最大量为[10000],但是为了请求大量数据应该怎么办呐?在Scroll帮助下我们可以做一个初始阶段搜索并且持续批量地从es中获取结果直到没有结果剩下,这有点像传统数据库里的cursors(游标)。
——————————————————————————————————————————
我们可以看出Scroll API的创建并不是为了实时的用户响应,而是为了处理大量的数据。从 scroll 请求返回的结果只是反映了 search 发生那一时刻的索引状态,就像一个快照。
我们通过SearchResponse对象的getScrollId()方法获取滚动ID;滚动ID将在下一次请求中使用,这个 ID 可以传递给 scroll API 来检索 下一个批次的结果,每次返回下一个批次结果 直到没有结果返回时停止
do {
for (SearchHit searchHit : searchResponse.getHits().getHits()) {
System.out.println("batch" + ++batchCount);
System.out.println(searchHit.getSourceAsString());
}
searchResponse = client.prepareSearchScroll(searchResponse.getScrollId())
.setScroll(new TimeValue(6000))
.execute()
.actionGet();
} while (searchResponse.getHits().getHits().length != 0);
client.close();
es命名很不错,看到名字大概就知道了他的作用,这样挺好,也就不做过多的解释了;
参考 参考 参考
滚动搜索(Scroll API)
如果获取位置,这个要怎么办呐?
//搜索两个坐标点组成的一个区域
SearchResponse searchResponse = client.prepareSearch("car_shop")
.setTypes("shops")
.setQuery(QueryBuilders.geoBoundingBoxQuery("pin.location")
.setCorners(40.74, -72.1, 30.43, -72.44))
.get();
//指定一个区域,由三个坐标点,组成,比如上海大厦,东方明珠塔,上海火车站
List<GeoPoint> points = new ArrayList<GeoPoint>();
points.add(new GeoPoint(40.73, -74.1));
points.add(new GeoPoint(40.01, -71.12));
points.add(new GeoPoint(50.56, -90.58));
searchResponse = client.prepareSearch("car_shop")
.setTypes("shops")
.setQuery(QueryBuilders.geoPolygonQuery("pin.location", points))
.get();
//搜索距离当前位置在200公里内的4s店
searchResponse = client.prepareSearch("car_shop")
.setTypes("shops")
.setQuery(QueryBuilders.geoDistanceQuery("pin.location")
.point(40, -70)
.distance(200, DistanceUnit.KILOMETERS))
.get();
client.close();
利用模板查询也是es的一大特色(新建文件,保存到config/script目录下的后缀名是.mustache文件),有一个复用的思想:
Map<String,Object> scriptParam = new HashMap<String,Object>();
scriptParam.put("from",0);//模板所需参数
scriptParam.put("size",1);
scriptParam.put("brand","宝马");
//读取 模板文件 上面是参数 第一个是名字、类型、参数、索引……
SearchResponse searchResponse = new SearchTemplateRequestBuilder(client)
.setScript("page_query_by_brand")//模板名称
// .setScriptType(ScriptType.FILE)
// .setScriptParams(scriptParams)
.setRequest(new SearchRequest("car_shop").types("sales"))
.get()
.getResponse();
模板大概长这个样子:
{
"query": {
"bool": {
"must": {
"match": {
"line": "{{text}}"
}
},
"filter": {
{{#line_no}}
"range": {
"line_no": {
{{#start}}
"gte": "{{start}}"
{{#end}},{{/end}}
{{/start}}
{{#end}}
"lte": "{{end}}"
{{/end}}
}
}
{{/line_no}}
}
}
}
}
小结:
es虽然有增删改查,不过单一职责嘛建议大家还是用他来查询,其他的事情可以让数据库或缓存来做,要学习的地方还要很多,感觉自己还没有入门,这些知识虽然写了博客,但是很尴尬。
[死磕Elasitcsearch]知识星球地址:http://t.cn/RmwM3N9;微信公众号:铭毅天下; 博客:https://blog.csdn.net/laoyang360