Spring Boot和Redis Geo实现附近的人【redis实战 三】

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: Spring Boot和Redis Geo实现附近的人【redis实战 三】

欢迎来到我的博客,代码的世界里,每一行都是一个故事


前言

在数字化世界中,了解“你我之间的距离”不仅仅是一句浪漫的话语,也是现代应用不可或缺的功能之一。无论是社交应用中的“附近的人”,还是物流追踪中的“包裹距离”,对距离的精准计算和周围环境的了解都显得至关重要。本文将带你进入Spring Boot和Redis Geo的强大世界,探索如何轻松实现这些看似复杂的功能。

Redis Geo回顾

Redis Geo是Redis数据库提供的一个功能强大的地理空间位置处理模块。它允许你存储地理位置信息并执行各种地理空间查询。下面是一些关键点:

  • 基于经纬度:Redis Geo使用经度和纬度来存储地点的位置。
  • Geohash编码:内部通过Geohash实现,这种编码将二维的经纬度转换成一维的字符串,便于快速计算和检索。
  • 快速查询:提供了一系列命令用于快速计算两地距离,查找某个范围内的地点等,如GEODIST, GEORADIUSGEOPOS
  • 应用场景广泛:从社交应用中的“附近的人”功能,到物流行业的配送路线优化,再到城市规划和旅游推荐等,Redis Geo都有广泛的应用。

redis中GEO的优势

Redis Geo与其他数据库系统(如关系型数据库、NoSQL数据库或专用的地理空间数据库)相比,在处理地理空间数据方面具有多项优势。这些优势主要源于Redis的性能、效率以及与地理空间数据处理紧密结合的特定命令。以下是Redis Geo相比其他数据库的主要优势:

1. 性能

  • 快速响应:作为内存数据库,Redis提供极快的读写速度,这对于需要快速响应地理查询的实时应用来说非常重要。
  • 高效的地理空间索引:通过Geohash和排序集合,Redis Geo能够快速定位和检索地理空间数据,其性能通常优于传统的数据库系统。

2. 存储效率

  • 紧凑的数据结构:Redis利用紧凑的数据结构存储地理信息,相比于传统数据库需要更多的存储空间来维护地理空间索引,Redis在空间上更加高效。
  • 内存优化:作为内存数据库,Redis对内存的使用高度优化,即使处理大量的地理空间对象也能保持较低的内存占用。

3. 简化的操作和集成

  • 简单直观的地理空间命令:Redis提供了一系列直观的命令用于地理空间数据处理,如GEOADD, GEODIST, GEORADIUS等,无需复杂的SQL查询或专用的地理空间函数。
  • 易于集成:Redis广泛应用于各种环境,与多种编程语言和框架(如Spring Boot)的集成也非常简单。

4. 灵活性和可扩展性

  • 水平扩展:Redis提供主从复制、持久化和分片等特性,使得它可以轻松扩展以支持更大的数据集和更高的并发。
  • 支持多种数据结构:除了地理空间数据,你还可以利用Redis的其他数据结构(如字符串、列表、集合等)来构建复杂的应用逻辑。

5. 成本效益

  • 降低资源需求:由于其高效的性能和存储方式,Redis可以帮助你减少对硬件资源的需求,从而降低成本。
  • 开源:Redis是开源软件,你可以免费使用,社区支持也非常丰富。

注意事项

虽然Redis Geo有许多优势,但它也有自己的局限性,例如它不支持像PostGIS那样复杂的地理空间查询和分析功能。此外,作为内存数据库,如果没有适当的持久化策略,可能会有数据丢失的风险。因此,选择合适的数据库还需要根据你的具体需求、资源和预期的使用场景来决定。在一些情况下,将Redis Geo与其他数据库系统结合使用,可能会得到更好的效果。

实战

本实战我们主要是实现周围的人这个功能,也可以理解为滴滴打车中根据距离派车这样的逻辑

maven依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
</dependency>

controller实现

在实际应用中应该分为三层,业务代码还是要写入到service层中

package fun.bo.controller;
import lombok.Data;
import org.springframework.data.geo.*;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
 * @author xiaobo
 */
@RestController
@RequestMapping("/api")
public class GeoController {
  // 真实业务场景中不建议将业务代码放到controller层,且下面的实体放到dto或者vo中
  @Resource
  private RedisTemplate<String, String> redisTemplate;
  private static final String GEO_KEY = "locations";
  // 新增坐标点
  @PostMapping("/addLocation")
  public ResponseEntity<?> addLocation(@RequestBody GeoLocation location) {
    // 增加坐标点
    redisTemplate.opsForGeo().add(GEO_KEY, new Point(location.getLongitude(), location.getLatitude()), location.getName());
    return ResponseEntity.ok("坐标添加成功");
  }
  // 查询附近的点
  @PostMapping("/searchNearby")
  public ResponseEntity<?> searchNearby(@RequestBody SearchRequest request) {
    Circle circle = new Circle(new Point(request.getLongitude(), request.getLatitude()), new Distance(request.getRadius(), Metrics.KILOMETERS));
    // 获取范围坐标点
    GeoResults<RedisGeoCommands.GeoLocation<String>> results = redisTemplate.opsForGeo()
      .radius(GEO_KEY, circle, RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeCoordinates().limit(10));
    return ResponseEntity.ok(results);
  }
  // 辅助类定义
  @Data
  public static class GeoLocation {
    private String name;
    private double latitude;
    private double longitude;
    // 省略getter和setter方法
  }
  @Data
  public static class SearchRequest {
    private double latitude;
    private double longitude;
    private double radius;
    // 省略getter和setter方法
  }
}

前端页面实现

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>地图坐标交互</title>
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"/>
    <style>
        #map { height: 400px; }
        .input-group { margin: 10px 0; }
    </style>
</head>
<body>
<div id="map"></div>
<div class="input-group">
    <input type="text" id="locationName" placeholder="位置名称">
    <input type="text" id="coords" placeholder="点击地图获取坐标">
    <button onclick="addLocation()">新增</button>
</div>
<div class="input-group">
    <input type="text" id="searchCoords" placeholder="点击地图设置查询中心">
    <input type="number" id="radius" placeholder="半径(km)">
    <button onclick="searchNearby()">查询附近的点</button>
</div>
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
<script>
    var map = L.map('map').setView([34.3416, 108.9398], 5);
    var searchCircle;  // 用于引用查询圈
    L.tileLayer('http://webrd02.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=7&x={x}&y={y}&z={z}', {
        attribution: 'Map data &copy; <a href="https://www.amap.com/">高德地图</a>'
    }).addTo(map);
    map.on('click', function(e) {
        document.getElementById('coords').value = e.latlng.lat + ", " + e.latlng.lng;
        document.getElementById('searchCoords').value = e.latlng.lat + ", " + e.latlng.lng;
        // 模拟反向地理编码获取位置名称
    });
    function addLocation() {
        var name = document.getElementById('locationName').value || 'Unnamed Location';
        var coords = document.getElementById('coords').value.split(", ");
        fetch('/api/addLocation', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({name: name, latitude: coords[0], longitude: coords[1]}),
        })
            .then(response => response.text())
            .then(data => alert(data))
            .catch(error => console.error('Error:', error));
    }
    function searchNearby() {
        var searchCoords = document.getElementById('searchCoords').value.split(", ");
        var radius = document.getElementById('radius').value;
        // 绘制查询圈
        if (searchCircle) {
            map.removeLayer(searchCircle);
        }
        searchCircle = L.circle([searchCoords[0], searchCoords[1]], {
            color: 'blue',
            fillColor: '#f03',
            fillOpacity: 0.1,
            radius: radius * 1000  // Leaflet的半径单位是米
        }).addTo(map);
        fetch('/api/searchNearby', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({latitude: searchCoords[0], longitude: searchCoords[1], radius: radius}),
        })
            .then(response => response.json())
            .then(data => {
                // console.log(data)
                data.content.forEach(result => {
                    var point = result.content.point;
                    var name = result.content.name;
                    var marker = L.marker([point.y, point.x]).addTo(map);
                    marker.bindPopup(name).openPopup();
                });
            })
            .catch(error => console.error('Error:', error));
    }
</script>
</body>
</html>

效果图(实现)

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
1月前
|
NoSQL 安全 测试技术
Redis游戏积分排行榜项目中通义灵码的应用实战
Redis游戏积分排行榜项目中通义灵码的应用实战
54 4
|
21天前
|
存储 NoSQL Java
使用lock4j-redis-template-spring-boot-starter实现redis分布式锁
通过使用 `lock4j-redis-template-spring-boot-starter`,我们可以轻松实现 Redis 分布式锁,从而解决分布式系统中多个实例并发访问共享资源的问题。合理配置和使用分布式锁,可以有效提高系统的稳定性和数据的一致性。希望本文对你在实际项目中使用 Redis 分布式锁有所帮助。
58 5
|
1月前
|
消息中间件 NoSQL Java
Spring Boot整合Redis
通过Spring Boot整合Redis,可以显著提升应用的性能和响应速度。在本文中,我们详细介绍了如何配置和使用Redis,包括基本的CRUD操作和具有过期时间的值设置方法。希望本文能帮助你在实际项目中高效地整合和使用Redis。
57 2
|
2月前
|
NoSQL 关系型数据库 MySQL
MySQL与Redis协同作战:优化百万数据查询的实战经验
【10月更文挑战第13天】 在处理大规模数据集时,传统的关系型数据库如MySQL可能会遇到性能瓶颈。为了提升数据处理的效率,我们可以结合使用MySQL和Redis,利用两者的优势来优化数据查询。本文将分享一次实战经验,探讨如何通过MySQL与Redis的协同工作来优化百万级数据统计。
94 5
|
2月前
|
自然语言处理 Java API
Spring Boot 接入大模型实战:通义千问赋能智能应用快速构建
【10月更文挑战第23天】在人工智能(AI)技术飞速发展的今天,大模型如通义千问(阿里云推出的生成式对话引擎)等已成为推动智能应用创新的重要力量。然而,对于许多开发者而言,如何高效、便捷地接入这些大模型并构建出功能丰富的智能应用仍是一个挑战。
254 6
|
2月前
|
缓存 NoSQL Java
Spring Boot与Redis:整合与实战
【10月更文挑战第15天】本文介绍了如何在Spring Boot项目中整合Redis,通过一个电商商品推荐系统的案例,详细展示了从添加依赖、配置连接信息到创建配置类的具体步骤。实战部分演示了如何利用Redis缓存提高系统响应速度,减少数据库访问压力,从而提升用户体验。
139 2
|
1月前
|
JavaScript NoSQL Java
CC-ADMIN后台简介一个基于 Spring Boot 2.1.3 、SpringBootMybatis plus、JWT、Shiro、Redis、Vue quasar 的前后端分离的后台管理系统
CC-ADMIN后台简介一个基于 Spring Boot 2.1.3 、SpringBootMybatis plus、JWT、Shiro、Redis、Vue quasar 的前后端分离的后台管理系统
47 0
|
存储 缓存 NoSQL
Redis 缓存 + Spring 的集成示例
SpringSession和Redis实现Session跨域 http://www.ithao123.cn/content-11111681.html   tomcat中创建session很耗服务器内存 原生session与session in redis对比下面是从stackoverflo...
1393 0
|
存储 缓存 NoSQL
Redis 缓存 + Spring 的集成示例(转)
《整合 spring 4(包括mvc、context、orm) + mybatis 3 示例》一文简要介绍了最新版本的 Spring MVC、IOC、MyBatis ORM 三者的整合以及声明式事务处理。
1451 0