上一篇文章介紹了elasticsearch使用repository和elasticsearchtemplate完成構建復雜查詢條件,簡單介紹了elasticsearch使用地理位置的功能。
這一篇我們來看一下使用elasticsearch完成大數據量查詢附近的人功能,搜索n米范圍的內的數據。
準備環境
本機測試使用了elasticsearch最新版5.5.1,springboot1.5.4,spring-data-elasticsearch2.1.4.
新建springboot項目,勾選elasticsearch和web。
pom文件如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
<?xml version= "1.0" encoding= "utf-8" ?> <project xmlns= "http://maven.apache.org/pom/4.0.0" xmlns:xsi= "http://www.w3.org/2001/xmlschema-instance" xsi:schemalocation= "http://maven.apache.org/pom/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelversion> 4.0 . 0 </modelversion> <groupid>com.tianyalei</groupid> <artifactid>elasticsearch</artifactid> <version> 0.0 . 1 -snapshot</version> <packaging>jar</packaging> <name>elasticsearch</name> <description>demo project for spring boot</description> <parent> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-parent</artifactid> <version> 1.5 . 4 .release</version> <relativepath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceencoding>utf- 8 </project.build.sourceencoding> <project.reporting.outputencoding>utf- 8 </project.reporting.outputencoding> <java.version> 1.8 </java.version> </properties> <dependencies> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-data-elasticsearch</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-web</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-test</artifactid> <scope>test</scope> </dependency> <dependency> <groupid>com.sun.jna</groupid> <artifactid>jna</artifactid> <version> 3.0 . 9 </version> </dependency> </dependencies> <build> <plugins> <plugin> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-maven-plugin</artifactid> </plugin> </plugins> </build> </project> |
新建model類person
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
package com.tianyalei.elasticsearch.model; import org.springframework.data.annotation.id; import org.springframework.data.elasticsearch.annotations.document; import org.springframework.data.elasticsearch.annotations.geopointfield; import java.io.serializable; /** * model類 */ @document (indexname= "elastic_search_project" ,type= "person" ,indexstoretype= "fs" ,shards= 5 ,replicas= 1 ,refreshinterval= "-1" ) public class person implements serializable { @id private int id; private string name; private string phone; /** * 地理位置經緯度 * lat緯度,lon經度 "40.715,-74.011" * 如果用數組則相反[-73.983, 40.719] */ @geopointfield private string address; public int getid() { return id; } public void setid( int id) { this .id = id; } public string getname() { return name; } public void setname(string name) { this .name = name; } public string getphone() { return phone; } public void setphone(string phone) { this .phone = phone; } public string getaddress() { return address; } public void setaddress(string address) { this .address = address; } } |
我用address字段表示經緯度位置。注意,使用string[]和string分別來表示經緯度時是不同的,見注釋。
1
2
3
4
5
|
import com.tianyalei.elasticsearch.model.person; import org.springframework.data.elasticsearch.repository.elasticsearchrepository; public interface personrepository extends elasticsearchrepository<person, integer> { } |
看一下service類,完成插入測試數據的功能,查詢的功能我放在controller里了,為了方便查看,正常是應該放在service里
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
package com.tianyalei.elasticsearch.service; import com.tianyalei.elasticsearch.model.person; import com.tianyalei.elasticsearch.repository.personrepository; import org.springframework.beans.factory.annotation.autowired; import org.springframework.data.elasticsearch.core.elasticsearchtemplate; import org.springframework.data.elasticsearch.core.query.indexquery; import org.springframework.stereotype.service; import java.util.arraylist; import java.util.list; @service public class personservice { @autowired personrepository personrepository; @autowired elasticsearchtemplate elasticsearchtemplate; private static final string person_index_name = "elastic_search_project" ; private static final string person_index_type = "person" ; public person add(person person) { return personrepository.save(person); } public void bulkindex(list<person> personlist) { int counter = 0 ; try { if (!elasticsearchtemplate.indexexists(person_index_name)) { elasticsearchtemplate.createindex(person_index_type); } list<indexquery> queries = new arraylist<>(); for (person person : personlist) { indexquery indexquery = new indexquery(); indexquery.setid(person.getid() + "" ); indexquery.setobject(person); indexquery.setindexname(person_index_name); indexquery.settype(person_index_type); //上面的那幾步也可以使用indexquerybuilder來構建 //indexquery index = new indexquerybuilder().withid(person.getid() + "").withobject(person).build(); queries.add(indexquery); if (counter % 500 == 0 ) { elasticsearchtemplate.bulkindex(queries); queries.clear(); system.out.println( "bulkindex counter : " + counter); } counter++; } if (queries.size() > 0 ) { elasticsearchtemplate.bulkindex(queries); } system.out.println( "bulkindex completed." ); } catch (exception e) { system.out.println( "indexerservice.bulkindex e;" + e.getmessage()); throw e; } } } |
注意看bulkindex方法,這個是批量插入數據用的,bulk也是es官方推薦使用的批量插入數據的方法。這里是每逢500的整數倍就bulk插入一次。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
|
package com.tianyalei.elasticsearch.controller; import com.tianyalei.elasticsearch.model.person; import com.tianyalei.elasticsearch.service.personservice; import org.elasticsearch.common.unit.distanceunit; import org.elasticsearch.index.query.geodistancequerybuilder; import org.elasticsearch.index.query.querybuilders; import org.elasticsearch.search.sort.geodistancesortbuilder; import org.elasticsearch.search.sort.sortbuilders; import org.elasticsearch.search.sort.sortorder; import org.springframework.beans.factory.annotation.autowired; import org.springframework.data.domain.pagerequest; import org.springframework.data.domain.pageable; import org.springframework.data.elasticsearch.core.elasticsearchtemplate; import org.springframework.data.elasticsearch.core.query.nativesearchquerybuilder; import org.springframework.data.elasticsearch.core.query.searchquery; import org.springframework.web.bind.annotation.getmapping; import org.springframework.web.bind.annotation.restcontroller; import java.text.decimalformat; import java.util.arraylist; import java.util.list; import java.util.random; @restcontroller public class personcontroller { @autowired personservice personservice; @autowired elasticsearchtemplate elasticsearchtemplate; @getmapping ( "/add" ) public object add() { double lat = 39.929986 ; double lon = 116.395645 ; list<person> personlist = new arraylist<>( 900000 ); for ( int i = 100000 ; i < 1000000 ; i++) { double max = 0.00001 ; double min = 0.000001 ; random random = new random(); double s = random.nextdouble() % (max - min + 1 ) + max; decimalformat df = new decimalformat( "######0.000000" ); // system.out.println(s); string lons = df.format(s + lon); string lats = df.format(s + lat); double dlon = double .valueof(lons); double dlat = double .valueof(lats); person person = new person(); person.setid(i); person.setname( "名字" + i); person.setphone( "電話" + i); person.setaddress(dlat + "," + dlon); personlist.add(person); } personservice.bulkindex(personlist); // searchquery searchquery = new nativesearchquerybuilder().withquery(querybuilders.querystringquery("spring boot or 書籍")).build(); // list<article> articles = elas、ticsearchtemplate.queryforlist(se、archquery, article.class); // for (article article : articles) { // system.out.println(article.tostring()); // } return "添加數據" ; } /** * geo_distance: 查找距離某個中心點距離在一定范圍內的位置 geo_bounding_box: 查找某個長方形區域內的位置 geo_distance_range: 查找距離某個中心的距離在min和max之間的位置 geo_polygon: 查找位于多邊形內的地點。 sort可以用來排序 */ @getmapping ( "/query" ) public object query() { double lat = 39.929986 ; double lon = 116.395645 ; long nowtime = system.currenttimemillis(); //查詢某經緯度100米范圍內 geodistancequerybuilder builder = querybuilders.geodistancequery( "address" ).point(lat, lon) .distance( 100 , distanceunit.meters); geodistancesortbuilder sortbuilder = sortbuilders.geodistancesort( "address" ) .point(lat, lon) .unit(distanceunit.meters) .order(sortorder.asc); pageable pageable = new pagerequest( 0 , 50 ); nativesearchquerybuilder builder1 = new nativesearchquerybuilder().withfilter(builder).withsort(sortbuilder).withpageable(pageable); searchquery searchquery = builder1.build(); //queryforlist默認是分頁,走的是queryforpage,默認10個 list<person> personlist = elasticsearchtemplate.queryforlist(searchquery, person. class ); system.out.println( "耗時:" + (system.currenttimemillis() - nowtime)); return personlist; } } |
看controller類,在add方法中,我們插入90萬條測試數據,隨機產生不同的經緯度地址。
在查詢方法中,我們構建了一個查詢100米范圍內、按照距離遠近排序,分頁每頁50條的查詢條件。如果不指明pageable的話,estemplate的queryforlist默認是10條,通過源碼可以看到。
啟動項目,先執行add,等待百萬數據插入,大概幾十秒。
然后執行查詢,看一下結果。
第一次查詢花費300多ms,再次查詢后時間就大幅下降,到30ms左右,因為es已經自動緩存到內存了。
可見,es完成地理位置的查詢還是非常快的。適用于查詢附近的人、范圍查詢之類的功能。
后記,在后來的使用中,elasticsearch2.3版本時,按上面的寫法出現了geo類型無法索引的情況,進入es的為string,而不是標注的geofiled。在此記錄一下解決方法,將string類型修改為geopoint,且是org.springframework.data.elasticsearch.core.geo.geopoint包下的。然后需要在創建index時,顯式調用一下mapping方法,才能正確的映射為geofield。
如下
1
2
3
4
|
if (!elasticsearchtemplate.indexexists( "abc" )) { elasticsearchtemplate.createindex( "abc" ); elasticsearchtemplate.putmapping(person. class ); } |
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:http://blog.csdn.net/tianyaleixiaowu/article/details/76177583