redis3.2發佈rc版本已經有一段時間了,估計RedisConf 2016左右,3.2版本就能release了。3.2版本中增長的最大功能就是對GEO(地理位置)的支持。提及Redis的GEO特性,最大的貢獻仍是我們中國人。redis做者在對3.2引進新特性的博客中介紹了爲何支持GEO。GEO hashing的api是在Ardb實現的,Ardb是github用戶yinqiwen實現的基於redis協議實現的nosql系統,Ardb支持除了redis、還有LevelDB、RocksDB
、LMDB等kv引擎。其中Ardb實現了GEO hashing功能。從Ardb做者的用戶名和標識的位置在深圳能夠看出Ardb做者應該是咱中國人。Ardb是用c++寫的。redis另外一個開發者Matt Stancliff從Ardb提取GEO庫,用C語言改寫,整合進redis的一個本身的分支,並被redis做者接受,合併進了3.2版本。GEO目前提供如下6個命令。html
地理位置的座標是以WGS84爲標準,WGS84,全稱World Geodetic System 1984,是爲GPS全球定位系統使用而創建的座標系統。java
下面來看看具體每一個命令的用法。c++
geoadd用來增長地理位置的座標,能夠批量添加地理位置,命令格式爲:git
GEOADD key longitude latitude member [longitude latitude member ...]
key標識一個地理位置的集合。longitude latitude member
標識了一個地理位置的座標。longitude是地理位置的經度,latitude是地理位置的緯度。member是該地理位置的名稱。GEOADD能夠批量給集合添加一批地理位置。github
geopos能夠獲取地理位置的座標,能夠批量獲取多個地理位置的座標,命令格式爲:redis
GEOPOS key member [member ...]
geodist用來獲取兩個地理位置的距離,命令格式爲:算法
GEODIST key member1 member2 [m|km|ft|mi]
單位能夠指定爲如下四種類型:sql
georadius能夠根據給定地理位置座標獲取指定範圍內的地理位置集合。命令格式爲:apache
GEORADIUS key longitude latitude radius [m|km|ft|mi] [WITHCOORD] [WITHDIST] [ASC|DESC] [WITHHASH] [COUNT count]
longitude latitude
標識了地理位置的座標,radius表示範圍距離,距離單位能夠爲m|km|ft|mi,還有一些可選參數:api
georadiusbymember能夠根據給定地理位置獲取指定範圍內的地理位置集合。georadius命令傳遞的是座標,georadiusbymember傳遞的是地理位置。georadius更爲靈活,能夠獲取任何座標點範圍內的地理位置。可是大多數時候,只是想獲取某個地理位置附近的其餘地理位置,使用georadiusbymember則更爲方便。georadiusbymember命令格式爲(命令可選參數與georadius含義同樣):
GEORADIUSBYMEMBER key member radius [m|km|ft|mi] [WITHCOORD] [WITHDIST] [ASC|DESC] [WITHHASH] [COUNT count]
geohash能夠獲取某個地理位置的geohash值。geohash是將二維的經緯度轉換成字符串hash值的算法,後面會具體介紹geohash原理。能夠批量獲取多個地理位置的geohash值。命令格式爲:
GEOHASH key member [member ...]
redis GEO實現主要包含了如下兩項技術:
geohash的思想是將二維的經緯度轉換成一維的字符串,geohash有如下三個特色:
這三個特性讓geohash特別適合表示二維hash值。這篇文章:GeoHash核心原理解析詳細的介紹了geohash的原理,想要了解geohash實現的朋友能夠參考這篇文章。
知道了redis使用有序集合(zset)保存地理位置數據(想了解redis有序集合的,能夠參看這篇文章《有序集合對象》),以及geohash的特性,就很容易理解redis是如何實現redis GEO命令了。細心的讀者可能發現,redis沒有實現地理位置的刪除命令。不過因爲GEO數據保存在zset中,能夠用zrem來刪除某個地理位置。
redis關於geohash使用了Ardb的geohash庫geohash-int
,redis使用的geohash編碼長度爲26位。能夠精確到0.59m的精度。
經過本文,撥開GEO身後的雲霧,能夠看出redis藉助了有序集合(zset)和geohash,加上redis自己實現的命令框架,能夠很容易的實現地理位置相關的命令。
package org.lanqiao.ssm.common.redis; import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.alibaba.druid.pool.vendor.SybaseExceptionSorter; import com.google.common.collect.Maps; import redis.clients.jedis.GeoCoordinate; import redis.clients.jedis.GeoRadiusResponse; import redis.clients.jedis.GeoUnit; import redis.clients.jedis.Jedis; import redis.clients.jedis.params.geo.GeoRadiusParam; /** * * @Title: RedisTest.java * @Package org.lanqiao.ssm.common.redis * @Description: TODO(本地jedis支持的單節點操做redis) * @author 劉偉 15818570028@163.com * @date 2016年10月11日 上午9:29:35 * @version V1.0 */ public class RedisGeoTest { private static final Log log = LogFactory.getLog(RedisGeoTest.class); public static void main(String[] args) { testGeoadds(); testGeoradius(); } /** * * @Description:單個添加地理位置 */ public static void testGeoadd() { Jedis jedis = new Jedis("10.1.10.74", 6379); jedis.auth("123"); jedis.geoadd("citys", 116.41667, 39.91667, "beijin"); jedis.geoadd("citys", 121.48, 31.22, "Shanghai"); jedis.geoadd("citys", 117.20, 39.13, "Tianjin"); List<GeoCoordinate> geopos = jedis.geopos("citys", "beijin"); System.out.println(geopos); } /** * * @Description:批量添加地理位置 */ public static void testGeoadds() { Jedis jedis = new Jedis("10.1.10.74", 6379); jedis.auth("123"); GeoCoordinate beijin = new GeoCoordinate(116.41667, 39.91667); GeoCoordinate Shanghai = new GeoCoordinate(121.48, 31.22); GeoCoordinate Tianjin = new GeoCoordinate(117.20, 39.13); Map<String, GeoCoordinate> map = Maps.newHashMap(); map.put("cs", beijin); map.put("hh", Shanghai); map.put("sy", Tianjin); jedis.geoadd("citys", map); } /** * * @Description:georadius能夠根據給定地理位置座標獲取指定範圍內的地理位置集合 */ public static void testGeoradius() { Jedis jedis = new Jedis("10.1.10.74", 6379); jedis.auth("123"); GeoRadiusParam withCoord = GeoRadiusParam.geoRadiusParam().count(6).sortAscending().withDist().withCoord(); List<GeoRadiusResponse> georadius = jedis.georadius("citys", 121.48, 31.22, 5000, GeoUnit.KM, withCoord); georadius.forEach(e ->{System.out.println(e.getMemberByString());}); } }