java 使用ElasticSearch完成百万级数据查询附近的人功能

上一篇文章介绍了ElasticSearch使用Repository和ElasticSearchTemplate完成构建复杂查询条件,简单介绍了ElasticSearch使用地理位置的功能。

这一篇我们来看一下使用ElasticSearch完成大数据量查询附近的人功能,搜索N米范围的内的数据。

准备环境

本机测试使用了ElasticSearch最新版5.5.1,SpringBoot1.5.4,spring-data-ElasticSearch2.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,等待百万数据插入,大概几十秒。

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

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

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

后记,在后来的使用中,Elasticsearch2.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);
    }

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • elasticsearch head的安装及使用过程解析

    ealsticsearch只是后端提供各种api,那么怎么直观的使用它呢?elasticsearch-head将是一款专门针对于elasticsearch的客户端工具 elasticsearch-head配置包,下载地址:https://github.com/mobz/elasticsearch-head elasticsearch-head是一个基于node.js的前端工程,启动elasticsearch-head的步骤如下(这里针对的是elasticsearch 5.x以上的版本): 1.进

  • SpringBoot整合Elasticsearch7.2.0的实现方法

    Spring boot 2.1.X整合Elasticsearch最新版的一处问题 新版本的Spring boot 2的spring-boot-starter-data-elasticsearch中支持的Elasticsearch版本是2.X,但Elasticsearch实际上已经发展到7.2.X版本了,为了更好的使用Elasticsearch的新特性,所以弃用了spring-boot-starter-data-elasticsearch依赖,而改为直接使用Spring-data-elastics

  • JAVA使用ElasticSearch查询in和not in的实现方式

    ElasticSearch Elasticsearch是一个基于Lucene的搜索服务器.它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口.Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎.设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便. 最近用到ES查询,因用的是Java写的,需要实现一个需求:过滤一部分id,查询时不需要查出来. 既然需要不包含,那么首先需要实现包含的方式(精确完

  • 基于Lucene的Java搜索服务器Elasticsearch安装使用教程

    一.安装Elasticsearch Elasticsearch下载地址:http://www.elasticsearch.org/download/ ·下载后直接解压,进入目录下的bin,在cmd下运行elasticsearch.bat 即可启动Elasticsearch ·用浏览器访问: http://localhost:9200/   ,如果出现类似如下结果则说明安装成功: { "name" : "Benedict Kine", "cluster_na

  • java操作elasticsearch的案例解析

    这篇文章主要介绍了java操作elasticsearch的案例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 到目前为止,我们一直都是使用RESTful风格的 API操作elasticsearch服务,但是通过我们之前的学习知道,elasticsearch提供了很多语言的客户端用于操作elasticsearch服务,例如:java.python..net.JavaScript.PHP等.而我们此次就学习如何使用java语言来操作elasti

  • 使用java操作elasticsearch的具体方法

    系统环境: vm12 下的centos 7.2 当前安装版本: elasticsearch-2.4.0.tar.gz Java操作es集群步骤1:配置集群对象信息:2:创建客户端:3:查看集群信息 1:集群名称 默认集群名为elasticsearch,如果集群名称和指定的不一致则在使用节点资源时会报错. 2:嗅探功能 通过client.transport.sniff启动嗅探功能,这样只需要指定集群中的某一个节点(不一定是主节点),然后会加载集群中的其他节点,这样只要程序不停即使此节点宕机仍然可以

  • SpringBoot整合Spring Data Elasticsearch的过程详解

    Spring Data Elasticsearch提供了ElasticsearchTemplate工具类,实现了POJO与elasticsearch文档之间的映射 elasticsearch本质也是存储数据,它不支持事物,但是它的速度远比数据库快得多, 可以这样来对比elasticsearch和数据库 索引(indices)--------数据库(databases) 类型(type)------------数据表(table) 文档(Document)---------------- 行(ro

  • Elasticsearch实现复合查询高亮结果功能

    一.Es的配置 实现es的全文检索功能的第一步,首先从与es进行连接开始,这里我使用的是es的5.x java api语法. public TransportClient esClient() throws UnknownHostException{ Settings settings = Settings.builder() .put("cluster.name", "my-application") //节点的名字 .put("client.trans

  • 利用Java多线程技术导入数据到Elasticsearch的方法步骤

    前言 近期接到一个任务,需要改造现有从mysql往Elasticsearch导入数据MTE(mysqlToEs)小工具,由于之前采用单线程导入,千亿数据需要两周左右的时间才能导入完成,导入效率非常低.所以楼主花了3天的时间,利用java线程池框架Executors中的FixedThreadPool线程池重写了MTE导入工具,单台服务器导入效率提高十几倍(合理调整线程数据,效率更高). 关键技术栈 Elasticsearch jdbc ExecutorService\Thread sql 工具说明

  • java 使用ElasticSearch完成百万级数据查询附近的人功能

    上一篇文章介绍了ElasticSearch使用Repository和ElasticSearchTemplate完成构建复杂查询条件,简单介绍了ElasticSearch使用地理位置的功能. 这一篇我们来看一下使用ElasticSearch完成大数据量查询附近的人功能,搜索N米范围的内的数据. 准备环境 本机测试使用了ElasticSearch最新版5.5.1,SpringBoot1.5.4,spring-data-ElasticSearch2.1.4. 新建Springboot项目,勾选Elas

  • java使用elasticsearch分组进行聚合查询过程解析

    这篇文章主要介绍了java使用elasticsearch分组进行聚合查询过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 java连接elasticsearch 进行聚合查询进行相应操作 一:对单个字段进行分组求和 1.表结构图片: 根据任务id分组,分别统计出每个任务id下有多少个文字标题 1.SQL:select id, count(*) as sum from task group by taskid; java ES连接工具类 p

  • MySQL百万级数据量分页查询方法及其优化建议

    数据库SQL优化是老生常谈的问题,在面对百万级数据量的分页查询,又有什么好的优化建议呢?下面将列举了一些常用的方法,供大家参考学习! 方法1: 直接使用数据库提供的SQL语句 语句样式: MySQL中,可用如下方法: SELECT * FROM 表名称 LIMIT M,N 适应场景: 适用于数据量较少的情况(元组百/千级) 原因/缺点: 全表扫描,速度会很慢 且 有的数据库结果集返回不稳定(如某次返回1,2,3,另外的一次返回2,1,3). Limit限制的是从结果集的M位置处取出N条输出,其余

  • SpringBoot 整合 Elasticsearch 实现海量级数据搜索功能

    目录 一.简介 二.代码实践 2.1.导入依赖 2.2.配置环境变量 2.3.创建 elasticsearch 的 config 类 2.4.索引管理 2.5.文档管理 三.小结 今天给大家讲讲 SpringBoot 框架 整合 Elasticsearch 实现海量级数据搜索. 一.简介 在上篇ElasticSearch 文章中,我们详细的介绍了 ElasticSearch 的各种 api 使用. 实际的项目开发过程中,我们通常基于某些主流框架平台进行技术开发,比如 SpringBoot,今天我

  • 详解MySQL数据库千万级数据查询和存储

    百万级数据处理方案 数据存储结构设计 表字段设计 表字段 not null,因为 null 值很难查询优化且占用额外的索引空间,推荐默认数字 0. 数据状态类型的字段,比如 status, type 等等,尽量不要定义负数,如 -1.因为这样可以加上 UNSIGNED,数值容量就会扩大一倍. 可以的话用 TINYINT.SMALLINT 等代替 INT,尽量不使用 BIGINT,因为占的空间更小. 字符串类型的字段会比数字类型占的空间更大,所以尽量用整型代替字符串,很多场景是可以通过编码逻辑来实

  • Java使用elasticsearch基础API使用案例讲解

    1.依赖 我用的是 springboot 2.2.5.RELEASE 版本,这里只贴出主要依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency> 2.配置+测试源码 这里根据elasticsearch服务端的地址进行

  • MySQL百万级数据大分页查询优化的实现

    目录 一.MySQL分页起点越大查询速度越慢 二. limit大分页问题的性能优化方法 (1)利用表的覆盖索引来加速分页查询 (2)用上次分页的最大id优化 三.MySQL百万数据快速生成 3.1.创建内存表及普通表 3.2.创建函数 3.3.创建插入内存表数据的存储过程 3.4.创建内存表数据插入普通表的存储过程 3.5.运行存储过程插入数据 参考链接: 前言:在数据库开发过程中我们经常会使用分页,核心技术是使用用limit start, count分页语句进行数据的读取. 一.MySQL分页

  • Java中Elasticsearch 实现分页方式(三种方式)

    目录 ES 简介 ES 的特点: 一.from + size 浅分页 二.scroll 深分页 scroll删除 三.search_after 深分页 ES 简介 Elasticsearch 是一个基于 Lucene 实现的搜索服务器.它提供了一个分布式多用户能力的全文搜索引擎,基于 RESTful web 接口.Elasticsearch是用Java语言开发的,并作为Apache许可条款下的开放源码发布,是一种流行的企业级搜索引擎.Elasticsearch用于云计算中,能够达到实时搜索,稳定

  • java操作elasticsearch详细方法总结

    目录 一.前言 二.java操作es的常用模式 三.rest-api 操作 1.前置准备 2.索引相关操作api的使用 2.1  创建索引 2.2  获取索引 2.3  删除索引 3.文档常用操作api的使用 3.1  索引添加文档 3.2  修改文档 3.3  删除文档 3.4  批量添加文档 3.5  批量删除 4.文档搜索相关api的使用 4.1  查询某个索引下的所有数据 4.2  批量查询多条数据 4.3  根据条件精准查询 4.4  分页查询 4.5  查询结果按照某个字段进行排序

随机推荐