Elasticsearch实现搜附近功能
1、搭建一个es集群,步骤如下
# 基于docker搭建
docker pull elasticsearch:6.5.4
# 单个进程中的最大线程数
vim /etc/sysctl.conf
# 在配置文件sysctl.conf加上下面的内容,wq保存
vm.max_map_count=262144
# 在虚拟机中输入如下指令,使其配置生效
/sbin/sysctl -p
# 创建文件夹
mkdir /root/es-cluster/node01 -p
mkdir /root/es-cluster/node02 -p
mkdir /root/es-cluster/node03 -p
# 分别在文件夹node01,node02,node03下面创建jvm.options
-Xms128m
-Xmx128m
-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=75
-XX:+UseCMSInitiatingOccupancyOnly
-XX:+AlwaysPreTouch
-Xss1m
-Djava.awt.headless=true
-Dfile.encoding=UTF-8
-Djna.nosys=true
-XX:-OmitStackTraceInFastThrow
-Dio.netty.noUnsafe=true
-Dio.netty.noKeySetOptimization=true
-Dio.netty.recycler.maxCapacityPerThread=0
-Dlog4j.shutdownHookEnabled=false
-Dlog4j2.disable.jmx=true
-Djava.io.tmpdir=${ES_TMPDIR}
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=data
-XX:ErrorFile=logs/hs_err_pid%p.log
8:-XX:+PrintGCDetails
8:-XX:+PrintGCDateStamps
8:-XX:+PrintTenuringDistribution
8:-XX:+PrintGCApplicationStoppedTime
8:-Xloggc:logs/gc.log
8:-XX:+UseGCLogFileRotation
8:-XX:NumberOfGCLogFiles=32
8:-XX:GCLogFileSize=64m
9-:-Xlog:gc*,gc+age=trace,safepoint:file=logs/gc.log:utctime,pid,tags:filecount=32,filesize=64m
9-:-Djava.locale.providers=COMPAT
# 分别在node01,node02,node03目录下,创建elasticsearch.yml文件,并输入如下内容(其他目录只需要改一下端口号和节点名称即可):
cluster.name: es-tanhua-cluster
node.name: node01
node.master: true
node.data: true
network.host: 192.168.241.100
http.port: 9200
discovery.zen.ping.unicast.hosts: ["192.168.241.100"]
discovery.zen.minimum_master_nodes: 2
http.cors.enabled: true
http.cors.allow-origin: "*"
# node.master: true 具有被选举成master的权利
# discovery.zen.minimum_master_nodes: 2 最先获得两票的就是master
# node.data: true 用来存储数据
# 分别创建3个容器(其余的两个只需要更改名称和挂载目录名称即可)
docker create --restart=always --name es-node01 --net host -v /root/es-cluster/node01/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml -v /root/es-cluster/node01/jvm.options:/usr/share/elasticsearch/config/jvm.options -v es-cluster-node01-data:/usr/share/elasticsearch/data elasticsearch:6.5.4
# 启动容器
docker start es-node01 es-node02 es-node03
# 查看日志
docker logs -f es-node01
# 单个启动并查看日志
docker start es-node01 && docker logs -f es-node01
2、搭建工程
导入依赖
<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>
编写application.properties
spring.application.name = tanhua-es
server.port = 18086
spring.data.elasticsearch.cluster-name=es-tanhua-cluster
spring.data.elasticsearch.cluster-nodes=192.168.241.100:9300,192.168.241.100:9301,192.168.241.100:9302
编写用户位置实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(indexName = "tanhua", type = "user_location", shards = 6, replicas = 2)
public class UserLocationEs{
@Id
private Long userId;
/**
* x:经度 y:纬度
*/
@GeoPointField
private GeoPoint location;
@Field(type = FieldType.Keyword)
private String address;
@Field(type = FieldType.Long)
private Long created;
@Field(type = FieldType.Long)
private Long updated;
@Field(type = FieldType.Long)
private Long lastUpdated;
}
// @Document(indexName = "tanhua", type = "user_location", shards = 6, replicas = 2)
// 索引的名字是tanhua(库) ;类型是user_location(表); shards = 6 分成六片 ; replicas = 2 副本为两个
控制层 UserLocationController
@RestController
@RequestMapping("es/user/location")
public class UserLocationController {
@Autowired
private UserLocationService userLocationService;
/**
* 查询用户的地理位置
*/
@GetMapping("{userId}")
public ResponseEntity<UserLocationEs> queryUserLocation(@PathVariable("userId") Long userId) {
try {
UserLocationEs userLocationEs = this.userLocationService.queryByUserId(userId);
if (null == userLocationEs) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
return ResponseEntity.ok(userLocationEs);
} catch (Exception e) {
e.printStackTrace();
}
return ResponseEntity.status(HttpStatus.INSUFFICIENT_STORAGE).build();
}
/**
* 更新用户的地理位置
*/
@PostMapping
public ResponseEntity<UserLocationEs> updateUserLocation(@RequestBody Map<String, Object> param) {
try {
Long userId = Long.valueOf(param.get("userId").toString());
Double longitude = Double.valueOf(param.get("longitude").toString());
Double latitude = Double.valueOf(param.get("latitude").toString());
String address = param.get("address").toString();
boolean result = this.userLocationService.updateUserLocation(userId, longitude, latitude, address);
if (result) {
return ResponseEntity.ok(null);
}
} catch (NumberFormatException e) {
e.printStackTrace();
}
return ResponseEntity.status(HttpStatus.INSUFFICIENT_STORAGE).build();
}
/**
* 搜索附近的人
*/
@PostMapping("list")
public ResponseEntity<List<UserLocationEs>> queryUserFromLocation(@RequestBody Map<String, Object> param) {
try {
Double longitude = Double.valueOf(param.get("longitude").toString());
Double latitude = Double.valueOf(param.get("latitude").toString());
Double distance = Double.valueOf(param.get("distance").toString());
Integer page = param.get("page") == null ? 1 : Integer.valueOf(param.get("page").toString());
Integer pageSize = param.get("pageSize") == null ? 100 : Integer.valueOf(param.get("pageSize").toString());
Page<UserLocationEs> userLocationEs = this.userLocationService.queryUserFromLocation(longitude, latitude, distance, page, pageSize);
return ResponseEntity.ok(userLocationEs.getContent());
} catch (NumberFormatException e) {
e.printStackTrace();
}
return ResponseEntity.status(HttpStatus.INSUFFICIENT_STORAGE).build();
}
}
业务层
@Service
public class UserLocationService {
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
/**
* 更新用户的地理位置
*/
public boolean updateUserLocation(Long userId, Double longitude, Double latitude, String address) {
try {
if (!this.elasticsearchTemplate.indexExists("tanhua")) {
// (如果不存在tanhua数据库的话)创建索引
this.elasticsearchTemplate.createIndex(UserLocationEs.class);
}
if (!this.elasticsearchTemplate.typeExists("tanhua", "user_location")) {
// 创建type
this.elasticsearchTemplate.putMapping(UserLocationEs.class);
}
GetQuery getQuery = new GetQuery();
getQuery.setId(userId.toString());
UserLocationEs ul = this.elasticsearchTemplate.queryForObject(getQuery, UserLocationEs.class);
if (null == ul) {
// 没有的话,创建一个
UserLocationEs userLocationEs = new UserLocationEs();
userLocationEs.setLocation(new GeoPoint(latitude, longitude));
userLocationEs.setAddress(address);
userLocationEs.setUserId(userId);
userLocationEs.setCreated(System.currentTimeMillis());
userLocationEs.setUpdated(userLocationEs.getCreated());
userLocationEs.setLastUpdated(userLocationEs.getCreated());
IndexQuery indexQuery = new IndexQueryBuilder().withObject(userLocationEs).build();
this.elasticsearchTemplate.index(indexQuery);
} else {
// 有的话,更新
Map<String, Object> map = new HashMap<>();
map.put("lastUpdated", ul.getUpdated());
map.put("updated", System.currentTimeMillis());
map.put("address", address);
map.put("location", new GeoPoint(latitude, longitude));
UpdateRequest updateRequest = new UpdateRequest();
updateRequest.doc(map);
UpdateQuery updateQuery = new UpdateQueryBuilder()
.withId(userId.toString())
.withClass(UserLocationEs.class)
.withUpdateRequest(updateRequest).build();
this.elasticsearchTemplate.update(updateQuery);
}
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 查询用户的位置信息
*/
public UserLocationEs queryByUserId(Long userId) {
GetQuery getQuery = new GetQuery();
getQuery.setId(userId.toString());
return this.elasticsearchTemplate.queryForObject(getQuery, UserLocationEs.class);
}
/**
* 根据位置搜索
*
* @param longitude 经度
* @param latitude 纬度
* @param distance 距离(米)
* @param page 页数
* @param pageSize 页面大小
*/
public Page<UserLocationEs> queryUserFromLocation(Double longitude, Double latitude, Double distance, Integer page, Integer pageSize) {
String fieldName = "location";
// 实现了SearchQuery接口,用于组装QueryBuilder和SortBuilder以及Pageable等
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
// 分页
PageRequest pageRequest = PageRequest.of(page - 1, pageSize);
nativeSearchQueryBuilder.withPageable(pageRequest);
// 定义bool查询
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
// 以某点为中心,搜索指定范围
GeoDistanceQueryBuilder distanceQueryBuilder = new GeoDistanceQueryBuilder(fieldName);
distanceQueryBuilder.point(latitude, longitude);
// 定义查询单位:公里
distanceQueryBuilder.distance(distance / 1000, DistanceUnit.KILOMETERS);
boolQueryBuilder.must(distanceQueryBuilder);
nativeSearchQueryBuilder.withQuery(boolQueryBuilder);
// 按距离升序
GeoDistanceSortBuilder distanceSortBuilder =
new GeoDistanceSortBuilder(fieldName, latitude, longitude);
distanceSortBuilder.unit(DistanceUnit.KILOMETERS);
distanceSortBuilder.order(SortOrder.ASC);
nativeSearchQueryBuilder.withSort(distanceSortBuilder);
return this.elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), UserLocationEs.class);
}
}
测试类
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class TestUserLocationService {
@Autowired
@SuppressWarnings("all")
private UserLocationService userLocationService;
@Test
public void testUpdateUserLocation() {
this.userLocationService.updateUserLocation(1L, 121.512253, 31.24094, "金茂大厦");
this.userLocationService.updateUserLocation(2L, 121.506377, 31.245105, "东方明珠广播电视塔");
this.userLocationService.updateUserLocation(10L, 121.508815, 31.243844, "陆家嘴地铁站");
this.userLocationService.updateUserLocation(12L, 121.511999, 31.239185, "上海中心大厦");
this.userLocationService.updateUserLocation(25L, 121.493444, 31.240513, "上海市公安局");
this.userLocationService.updateUserLocation(27L, 121.494108, 31.247011, "上海外滩美术馆");
this.userLocationService.updateUserLocation(30L, 121.462452, 31.253463, "上海火车站");
this.userLocationService.updateUserLocation(32L, 121.81509, 31.157478, "上海浦东国际机场");
this.userLocationService.updateUserLocation(34L, 121.327908, 31.20033, "虹桥火车站");
this.userLocationService.updateUserLocation(38L, 121.490155, 31.277476, "鲁迅公园");
this.userLocationService.updateUserLocation(40L, 121.425511, 31.227831, "中山公园");
this.userLocationService.updateUserLocation(43L, 121.594194, 31.207786, "张江高科");
}
@Test
public void testQuery() {
Page<UserLocationEs> userLocationPage = this.userLocationService.queryUserFromLocation(121.512253, 31.24094, 1000d, 1, 100);
userLocationPage.forEach(userLocationES -> {
System.out.println(userLocationES);
double distance = GeoDistance.ARC.calculate(31.24094, 121.512253, userLocationES.getLocation().getLat(), userLocationES.getLocation().getLon(), DistanceUnit.METERS);
System.out.println("距离我 : " + distance + "米");
});
}
}