温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

java如何使用ElasticSearch实现百万级数据查询附近的人功能

发布时间:2021-07-10 11:40:36 来源:亿速云 阅读:393 作者:小新 栏目:编程语言

这篇文章给大家分享的是有关java如何使用ElasticSearch实现百万级数据查询附近的人功能的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。

使用ElasticSearch完成大数据量查询附近的人功能,搜索N米范围的内的数据。

准备环境

本机测试使用了ElasticSearch最新版5.5.1,SpringBoot1.5.4,spring-data-ElasticSearch3.1.4.

新建Springboot项目,勾选ElasticSearch和web。

pom文件如下

<?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

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分别来表示经纬度时是不同的,见注释。

import com.tianyalei.elasticsearch.model.Person;  import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;   public interface PersonRepository extends ElasticsearchRepository<Person, Integer> {    }

看一下Service类,完成插入测试数据的功能,查询的功能我放在Controller里了,为了方便查看,正常是应该放在Service里

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插入一次。

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,等待百万数据插入,大概几十秒。

然后执行查询,看一下结果。

java如何使用ElasticSearch实现百万级数据查询附近的人功能

第一次查询花费300多ms,再次查询后时间就大幅下降,到30ms左右,因为ES已经自动缓存到内存了。

可见,ES完成地理位置的查询还是非常快的。适用于查询附近的人、范围查询之类的功能。

后记,在后来的使用中,Elasticsearch3.3版本时,按上面的写法出现了geo类型无法索引的情况,进入es的为String,而不是标注的geofiled。在此记录一下解决方法,将String类型修改为GeoPoint,且是org.springframework.data.elasticsearch.core.geo.GeoPoint包下的。然后需要在创建index时,显式调用一下mapping方法,才能正确的映射为geofield。

如下

if (!elasticsearchTemplate.indexExists("abc")) {        elasticsearchTemplate.createIndex("abc");        elasticsearchTemplate.putMapping(Person.class);      }

感谢各位的阅读!关于“java如何使用ElasticSearch实现百万级数据查询附近的人功能”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,让大家可以学到更多知识,如果觉得文章不错,可以把它分享出去让更多的人看到吧!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI