Geo redis实现附近的车辆/人
前段时间业务需要实现一个用户查看附近车辆的服务:用户根据自己的定位坐标(经度、纬度)来快速搜索到附近的车辆,可以根据半径进行搜索。
根据需求也制定了几个技术方案,思路是这样的:将用户的坐标跟数据库中各个车辆的最新坐标进行距离计算,找到指定距离内的车辆并按照距离从小到大排序返回给业务。这种方法需要找到每辆车的最新定位,数据库中车辆定位数庞大,查询时数据库压力较大,并且每辆车需要计算一次距离,整个流程的耗时耗费数据库资源可想而知。考虑第二种方案,因为现在业务所有车辆的最新定位都保存在缓存中(memcached),可以采用把所有车辆的定位先从缓存中拿出来,然后进行距离计算,这种方法同样存在问题,就是操作缓存过于频繁。
以上两种方案,都是通过计算距离来判断的,后来发现geohash算法在这方面很高效(geohash原理参加 http://blog.jobbole.com/80633/)。于是将该思路应用到以上两个方案中,无非是多保存一份每个定位点的geohash值,然后根据该hash值来进行排序找到附近的车(当然该hash值在数据库中做索引)。沿着这个思路的技术实现也考虑了一些,等技术实现(具体实现思路就不啰嗦了)基本考虑好的时候,突然发现redis 自带的geo api能够完美地解决该问题。于是,啥也别说,直接使用现成的就好啦。
闲聊结束,直接操作,按照步骤来就能轻松搞定:
一、Jedis包引入项目
项目代码使用java编写,操作redis使用的是Jedis库,所以第一步先把Jedis找到并导入项目中。使用maven构建工程的话,在pom.xml中加入如下:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
二、Redis Geo Api使用
其实Redis Geo Api很多,本文只讲两个,因为实现这个功能就只需要要两个API(无非一个插入,一个搜索),非常简单。为了便于理解,将流程分为插入端以及搜索端。
注:两个API都存在于redis.clients.jedis.JedisCluster类中。
1、插入端(geoadd)
插入端很好理解,就是将车辆的经纬度插入redis(服务接收到各个车辆的定位数据,然后使用geoadd接口将数据存入redis)。当然这个跟普通的redis插入操作不同,这个不同体现在内部实现上不同(可以理解为内部实现了geo算法并且存储机制不同,以满足GEO功能)。
# key:任意值。使用者可以根据车辆属性将车进行分类,key作为类别名称。例如按照城市划分,这样在搜索时知道车辆属于哪个城市时只在这个key中进行搜索
# longitude: 经度
# latitude: 纬度
# member: 成员。这里就是车牌号
public Long geoadd(final String key, final double longitude, final double latitude,final String member)
2、搜索端(georadius)
搜索端同样好理解,就是使用方嘛。用户给点一个定位坐标和搜索半径,列出附近的车辆以及经纬度。
# key: 对应geoadd中的key
# longitude: 经度
# latitude: 纬度
# unit: 距离单位(GeoUnit.M:米,GeoUnit.KM:千米, GeoUnit.MI:英里, GeoUnit.FT:英尺)
# param: 参数 参见 http://redisdoc.com/geo/georadius.html
public List<GeoRadiusResponse> georadius(String key, double longitude, double latitude,double radius, GeoUnit unit, GeoRadiusParam param)
可以看到,这个函数返回的是GeoRadiusResponse的集合,拿到这个集合整个工作就完成了。该集合即附近的车的集合,元素为车牌号以及对应的经纬度。
结束了,就这些,把这两个Jedis自带的函数看明白,然后使用在正确的地方就能实现附近的车辆服务了,很简单吧!
-——————————————————————————————————————————————————————————————————————————————————————————————————————————————————-
帮助理解,看一下GeoRadiusResponse这个类的结构:
public class GeoRadiusResponse {
private byte[] member;
private double distance;
private GeoCoordinate coordinate;
public GeoRadiusResponse(byte[] member) {
this.member = member;
}
public void setDistance(double distance) {
this.distance = distance;
}
public void setCoordinate(GeoCoordinate coordinate) {
this.coordinate = coordinate;
}
public byte[] getMember() {
return member;
}
public String getMemberByString() {
return SafeEncoder.encode(member);
}
public double getDistance() {
return distance;
}
public GeoCoordinate getCoordinate() {
return coordinate;
}
}
这里的member就是这里的车牌号,distance为用户跟这个车的距离,coordinate为经纬度坐标。
顺便看下GeoCoordinate这个类的结构:
public class GeoCoordinate {
private double longitude;
private double latitude;
public GeoCoordinate(double longitude, double latitude) {
this.longitude = longitude;
this.latitude = latitude;
}
public double getLongitude() {
return longitude;
}
public double getLatitude() {
return latitude;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof GeoCoordinate)) return false;
GeoCoordinate that = (GeoCoordinate) o;
if (Double.compare(that.longitude, longitude) != 0) return false;
return Double.compare(that.latitude, latitude) == 0;
}
@Override
public int hashCode() {
// follows IntelliJ default hashCode implementation
int result;
long temp;
temp = Double.doubleToLongBits(longitude);
result = (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(latitude);
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public String toString() {
return "(" + longitude + "," + latitude + ")";
}
}
还没有评论,来说两句吧...