• 您的支持是我不断创作巨大动力

    • CSDN博客地址(关注,点赞)

    • 人工智能推荐

    • GitHub(Star,Fork,Watch)

前言

最近在做社交的业务,用户进入首页后需要查询附近的人;

项目状况:前期尝试业务阶段;

特点:

  • 快速实现(不需要做太重,满足初期推广运营即可)

  • 快速投入市场去运营

收集用户的经纬度:

  • 用户在每次启动时将当前的地理位置(经度,维度)上报给后台

提到附近的人,脑海中首先浮现特点:

  • 需要记录每位用户的经纬度

  • 查询当前用户附近的人,搜索在N公里内用户

架构设计

  • 时序图

    在这里插入图片描述
  • 技术实现方案

    SpringBoot

    Redis(version>=3.2)

Redis原生命令实现

  • 存入用户的经纬度

  • geoadd 用于存储指定的地理空间位置,可以将一个或多个经度(longitude)、纬度(latitude)、位置名称(member)添加到指定的 key 中

  • 命令格式:

    GEOADD key longitude latitude member [longitude latitude member ...]
  • 模拟五个用户存入经纬度,redis客户端执行如下命令:

    GEOADD zhgeo 116.48105 39.996794 zhangsanGEOADD zhgeo 116.514203 39.905409 lisiGEOADD zhgeo 116.489033 40.007669 wangwuGEOADD zhgeo 116.562108 39.787602 sunliuGEOADD zhgeo 116.334255 40.027400 zhaoqi
  • 通过redis客户端查看效果:

    在这里插入图片描述
  • 查找距当前用户由近到远附近100km用户

  • georadiusbymember可以找出位于指定范围内的元素,georadiusbymember 的中心点是由给定的位置元素决定的

  • 命令格式:

    GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
  • 模拟查找100km里距离sunliu由近到远五个人

    georadiusbymember zhgeo sunliu 100 km asc count 5
  • 命令执行效果如下:

    在这里插入图片描述
  • 如何实现分页查询那?

  • 每次分页查询的请求都计算一次然后拿到程序中在取相应的分页数据,优缺点:

    • 优点:实现简单,无需额外的存储空间

    • 缺点:当用户量大时,很显然不仅效率低,而且容易把程序的内存搞溢出

  • 经过查找发现redis的github官网给出了分页Issues(参考:Will the GEORADIUS support pagination?),解决方案如下:

    • 利用GEORADIUSBYMEMBER 命令中的 STOREDIST 将排好序的数据存入一个Zset集合中,以后分页查直接从Zset

    • 命令如下:

      georadiusbymember zhgeo sunliu 100 km asc count 5 storedist sunliu
    • 有序集合效果如下:

      在这里插入图片描述
    • 以后分页查询命令:

      //首先删除本身元素zrem sunliu sunliu//分页查找元素(在此以:查找第1页,每页数量是3为例)zrange sunliu 0 2 withscores
    • 效果如下:

      在这里插入图片描述

代码实现

  • 完整代码(GitHub,欢迎大家Star,Fork,Watch)

    https://github.com/dangnianchuntian/springboot

  • 主要代码展示

  • Controller

  /*   * Copyright (c) 2020. zhanghan_java@163.com All Rights Reserved.   * 项目名称:Spring Boot实战分页查询附近的人: Redis+GeoHash+Lua   * 类名称:GeoController.java   * 创建人:张晗   * 联系方式:zhanghan_java@163.com   * 开源地址: https://github.com/dangnianchuntian/springboot   * 博客地址: https://zhanghan.blog.csdn.net   */

  package com.zhanghan.zhnearbypeople.controller;

  import org.springframework.beans.factory.annotation.Autowired;  import org.springframework.validation.annotation.Validated;  import org.springframework.web.bind.annotation.RequestBody;  import org.springframework.web.bind.annotation.RequestMapping;  import org.springframework.web.bind.annotation.RequestMethod;  import org.springframework.web.bind.annotation.RestController;

  import com.zhanghan.zhnearbypeople.controller.request.ListNearByPeopleRequest;  import com.zhanghan.zhnearbypeople.controller.request.PostGeoRequest;  import com.zhanghan.zhnearbypeople.service.GeoService;

  @RestController  public class GeoController {

      @Autowired      private GeoService geoService;

      /**       * 记录用户地理位置       */      @RequestMapping(value = "/post/geo", method = RequestMethod.POST)      public Object postGeo(@RequestBody @Validated PostGeoRequest postGeoRequest) {          return geoService.postGeo(postGeoRequest);      }

      /**       * 分页查询当前用户附近的人       */      @RequestMapping(value = "/list/nearby/people", method = RequestMethod.POST)      public Object listNearByPeople(@RequestBody @Validated ListNearByPeopleRequest listNearByPeopleRequest) {          return geoService.listNearByPeople(listNearByPeopleRequest);      }

  }
  • service

  /*   * Copyright (c) 2020. zhanghan_java@163.com All Rights Reserved.   * 项目名称:Spring Boot实战分页查询附近的人: Redis+GeoHash+Lua   * 类名称:GeoServiceImpl.java   * 创建人:张晗   * 联系方式:zhanghan_java@163.com   * 开源地址: https://github.com/dangnianchuntian/springboot   * 博客地址: https://zhanghan.blog.csdn.net   */

  package com.zhanghan.zhnearbypeople.service.impl;

  import java.util.ArrayList;  import java.util.List;  import java.util.Set;

  import org.slf4j.Logger;  import org.slf4j.LoggerFactory;  import org.springframework.beans.factory.annotation.Autowired;  import org.springframework.beans.factory.annotation.Value;  import org.springframework.data.geo.Point;  import org.springframework.data.redis.connection.RedisGeoCommands;  import org.springframework.data.redis.core.RedisTemplate;  import org.springframework.data.redis.core.ZSetOperations;  import org.springframework.stereotype.Service;

  import com.zhanghan.zhnearbypeople.controller.request.ListNearByPeopleRequest;  import com.zhanghan.zhnearbypeople.controller.request.PostGeoRequest;  import com.zhanghan.zhnearbypeople.dto.NearByPeopleDto;  import com.zhanghan.zhnearbypeople.service.GeoService;  import com.zhanghan.zhnearbypeople.util.RedisLuaUtil;  import com.zhanghan.zhnearbypeople.util.wrapper.WrapMapper;

  @Service  public class GeoServiceImpl implements GeoService {

      private static Logger logger = LoggerFactory.getLogger(GeoServiceImpl.class);

      @Autowired      private RedisTemplate objRedisTemplate;@Value("${zh.geo.redis.key:zhgeo}")private String zhGeoRedisKey;@Value("${zh.geo.zset.redis.key:zhgeozset:}")private String zhGeoZsetRedisKey;/**       * 记录用户访问记录       */@Overridepublic Object postGeo(PostGeoRequest postGeoRequest) {//对应redis原生命令:GEOADD zhgeo 116.48105 39.996794 zhangsan          Long flag = objRedisTemplate.opsForGeo().add(zhGeoRedisKey, new RedisGeoCommands.GeoLocation<>(postGeoRequest                  .getCustomerId(), new Point(postGeoRequest.getLatitude(), postGeoRequest.getLongitude())));if (null != flag && flag > 0) {return WrapMapper.ok();          }return WrapMapper.error();      }/**       * 分页查询附近的人       */@Overridepublic Object listNearByPeople(ListNearByPeopleRequest listNearByPeopleRequest) {          String customerId = listNearByPeopleRequest.getCustomerId();          String strZsetUserKey = zhGeoZsetRedisKey + customerId;          List nearByPeopleDtoList = new ArrayList<>();//如果是从第1页开始查,则将附近的人写入zset集合,以后页直接从zset中查if (1 == listNearByPeopleRequest.getPageIndex()) {              List scriptParams = new ArrayList<>();              scriptParams.add(zhGeoRedisKey);              scriptParams.add(customerId);              scriptParams.add("100");              scriptParams.add(RedisGeoCommands.DistanceUnit.KILOMETERS.getAbbreviation());              scriptParams.add("asc");              scriptParams.add("storedist");              scriptParams.add(strZsetUserKey);//用Lua脚本实现georadiusbymember中的storedist参数//对应Redis原生命令:georadiusbymember zhgeo sunliu 100 km asc count 5 storedist sunliu              Long executeResult = objRedisTemplate.execute(RedisLuaUtil.GEO_RADIUS_STOREDIST_SCRIPT(), scriptParams);if (null == executeResult || executeResult 1) {return WrapMapper.ok(nearByPeopleDtoList);              }//zset集合中去除自己//对应Redis原生命令:zrem sunliu sunliu              Long remove = objRedisTemplate.opsForZSet().remove(strZsetUserKey, customerId);          }          nearByPeopleDtoList = listNearByPeopleFromZset(strZsetUserKey, listNearByPeopleRequest.getPageIndex(),                  listNearByPeopleRequest.getPageSize());return WrapMapper.ok(nearByPeopleDtoList);      }/**       * 分页从zset中查询指定用户附近的人       */private List listNearByPeopleFromZset(String strZsetUserKey, Integer pageIndex, Integer pageSize) {          Integer startPage = (pageIndex - 1) * pageSize;          Integer endPage = pageIndex * pageSize - 1;          List nearByPeopleDtoList = new ArrayList<>();//对应Redis原生命令:zrange key 0 2 withscores          Set> zsetUsers = objRedisTemplate.opsForZSet()                  .rangeWithScores(strZsetUserKey, startPage,                          endPage);for (ZSetOperations.TypedTuple zsetUser : zsetUsers) {              NearByPeopleDto nearByPeopleDto = new NearByPeopleDto();              nearByPeopleDto.setCustomerId(zsetUser.getValue().toString());              nearByPeopleDto.setDistance(zsetUser.getScore());              nearByPeopleDtoList.add(nearByPeopleDto);          }return nearByPeopleDtoList;      }  }
  • RedisLuaUtil

  /*   * Copyright (c) 2020. zhanghan_java@163.com All Rights Reserved.   * 项目名称:Spring Boot实战分页查询附近的人: Redis+GeoHash+Lua   * 类名称:RedisLuaUtil.java   * 创建人:张晗   * 联系方式:zhanghan_java@163.com   * 开源地址: https://github.com/dangnianchuntian/springboot   * 博客地址: https://zhanghan.blog.csdn.net   */

  package com.zhanghan.zhnearbypeople.util;

  import org.springframework.data.redis.core.script.DigestUtils;  import org.springframework.data.redis.core.script.RedisScript;

  public class RedisLuaUtil {

      private static final RedisScript GEO_RADIUS_STOREDIST_SCRIPT;public static RedisScript GEO_RADIUS_STOREDIST_SCRIPT() {return GEO_RADIUS_STOREDIST_SCRIPT;      }static {          StringBuilder sb = new StringBuilder();          sb.append("return redis.call('georadiusbymember',KEYS[1],KEYS[2],KEYS[3],KEYS[4],KEYS[5],KEYS[6],KEYS[7])");          GEO_RADIUS_STOREDIST_SCRIPT = new RedisScriptImpl<>(sb.toString(), Long.class);      }private static class RedisScriptImpl<T> implements RedisScript<T> {private final String script;private final String sha1;private final Class resultType;public RedisScriptImpl(String script, Class resultType) {this.script = script;this.sha1 = DigestUtils.sha1DigestAsHex(script);this.resultType = resultType;          }@Overridepublic String getSha1() {return sha1;          }@Overridepublic Class getResultType() {return resultType;          }@Overridepublic String getScriptAsString() {return script;          }      }  }

测试

  • 模拟用户上传地理位置进行存储

  • 进行请求

    在这里插入图片描述
  • 查看效果

    在这里插入图片描述

    在这里插入图片描述
  • 模拟用户sunliu查找附近100km的人,按3条一分页进行查询

    • 进行请求

      在这里插入图片描述

总结

  • 亮点:

  • 分页实现思路:将geo集合中的数据按距离由近到远筛选好后,通过storedist放入一个新的Zset集合

  • redisTemplate没有针对原生命令georadiusbymember的storedist参数实现,灵活运用Lua脚本去实现

  • geo集合在亿级别以内的数据量没有问题,当超过亿后需要根据产品需要对Redis中的大值进行拆分,比如按照地域进行拆分等

  • 有了地理位置,自己正在研究如何通过经纬度绘制出自己的运动路线,验证出来后与大家共享

springboot controller 分页查询_Spring Boot实战分页查询附近的人: Redis+GeoHash+Lua相关推荐

  1. springboot controller 分页查询_Spring Boot实战分页查询附近的人:Redis+GeoHash+Lua

    前言 最近在做社交的业务,用户进入首页后需要查询附近的人: 项目状况:前期尝试业务阶段: 特点: 快速实现(不需要做太重,满足初期推广运营即可) 快速投入市场去运营 收集用户的经纬度: 用户在每次启动 ...

  2. Spring Boot实战解决高并发数据入库: Redis 缓存+MySQL 批量入库

    前言 最近在做阅读类的业务,需要记录用户的PV,UV: 项目状况:前期尝试业务阶段: 特点: 快速实现(不需要做太重,满足初期推广运营即可) 快速投入市场去运营 收集用户的原始数据,三要素: 谁 在什 ...

  3. springboot 事务统一配置_Spring Boot实现分布式微服务开发实战系列(五)

    最近已经推出了好几篇SpringBoot+Dubbo+Redis+Kafka实现电商的文章,今天再次回到分布式微服务项目中来,在开始写今天的系列五文章之前,我先回顾下前面的内容. 系列(一):主要说了 ...

  4. springboot 后台模板_spring boot实战

    亲们,我们今天来看看spring boot.如果你掌握了Servlet.JSP.Filter.Listener等web开发技术,掌握了Spring MVC.Spring.Mybatis框架的使用,掌握 ...

  5. springboot map数据类型注入_Spring Boot(五):春眠不觉晓,Mybatis知多少

    在JavaWeb项目开发中,我们使用最多的ORM框架可能就是Mybatis了,那么对于常用的mybatis,你究竟了解多少呢? 一 全局了解 1 Mybatis是什么 MyBatis 是支持定制化 S ...

  6. springboot 订单重复提交_Spring Boot (一) 校验表单重复提交

    一.前言 在某些情况下,由于网速慢,用户操作有误(连续点击两下提交按钮),页面卡顿等原因,可能会出现表单数据重复提交造成数据库保存多条重复数据. 存在如上问题可以交给前端解决,判断多长时间内不能再次点 ...

  7. springboot 数据库假面_Spring Boot实战:数据库操作

    上篇文章中已经通过一个简单的HelloWorld程序讲解了Spring boot的基本原理和使用.本文主要讲解如何通过spring boot来访问数据库,本文会演示三种方式来访问数据库,第一种是Jdb ...

  8. springboot模板引擎_Spring Boot实战:如何搞定前端模板引擎?

    作者:liuxiaopeng 链接:https://www.cnblogs.com/paddix/p/8905531.html 前言 虽然现在很多开发,都采用了前后端完全分离的模式,即后端只提供数据接 ...

  9. springboot使用netty容器_Spring Boot 2 实战:使用 Undertow 来替代Tomcat

    1. Undertow 简介 Undertow 是一个采用 Java 开发的灵活的高性能 Web 服务器,提供包括阻塞和基于 NIO 的非堵塞机制.Undertow 是红帽公司的开源产品,是 Wild ...

最新文章

  1. An Edge-Guided Image Interpolation Algorithm via Directional Filtering and Data Fusion【翻译】
  2. java弧线_数据可视化API之弧线图实现
  3. python ssh登陆模块_Python sshh包_程序模块 - PyPI - Python中文网
  4. Java中Comparable与Comparator的区别
  5. Microsoft Office 2007的ContentType
  6. 【渝粤教育】电大中专混凝土结构作业 题库
  7. jdbc获取结果行数_java – 如何判断从JDBC执行更改的行数
  8. 在Java编码中,如何减少bug数量
  9. 制造业实施大数据战略面临哪些挑战
  10. stata学习笔记|自相关处理问题
  11. 2022R1快开门式压力容器操作操作证考试题库及在线模拟考试
  12. 精灵图,雪碧图的应用
  13. 图形化——可视化在线绘图引擎
  14. 如何搭建IPv6测试环境
  15. 顺丰控股2019财报解读
  16. Unity - RenderDoc 抓帧导出 FBX(带UV)
  17. windows代码设置默认音频输出设备
  18. J-Link软件和文档包的版本发行说明(2)[V4.96 ~ V6.12j版本]
  19. Autolayout - NSLayoutAnchor
  20. Linux如何新建用户

热门文章

  1. [SCSS] Pure CSS for multiline truncation with ellipsis
  2. day 67 django 之ORM 增删改查基础
  3. Reading Club·Beijing第1期DeepQA框架与Siri架构会场笔记(PPT下载)
  4. asp.net mvc(八)
  5. Windows Vista桌面窗口管理器(3)
  6. Python之进程+线程+协程(进程间通信、进程同步、进程池、回调函数)
  7. 中日文字编码转换_关于编码你必须知道的知识和技巧
  8. 枚举算法:最小连续n个合数。试求出最小的连续n个合数(其中n是键盘输入的任意正整数)。
  9. windows c语言目录操作函数,c语言目录操作在C/C++语言中如何进行目录操作,如得到目录内的 爱问知识人...
  10. python selenium 点击加载更多_如何等待页面加载,然后按selenium中的“加载更多”按钮?...