在上一篇文章中,我们详细介绍了如何在 Golang 中使用 go-redis 操作 Redis 的事务处理操作(可参考这里)。在本篇文章中,我们将对如何使用 go-redis 操作 Redis 中的 GEO 地理空间数据结构进行详细讲解和示例代码展示。
👉 点击查看《go-redis使用指南》系列文章目录
在《go-redis使用指南》系列文章中,我们将详细介绍如何在 Golang 项目中使用 redis/go-redis 库与 Redis 进行交互。以下是该系列文章的全部内容:
- Golang 操作 Redis:快速上手 - go-redis 使用指南
- Golang 操作 Redis:连接设置与参数详解 - go-redis 使用指南
- Golang 操作 Redis:基础的字符串键值操作 - go-redis 使用指南
- Golang 操作 Redis:如何设置 key 的过期时间 - go-redis 使用指南
- Golang 操作 Redis:Hash 哈希数据类型操作用法 - go-redis 使用指南
- Golang 操作 Redis:Set 集合数据类型操作用法 - go-redis 使用指南
- Golang 操作 Redis:为 Hash 中的字段设置过期时间 - go-redis 使用指南
- Golang 操作 Redis:List 列表数据类型操作用法 - go-redis 使用指南
- Golang 操作 Redis:SortedSet 有序集合数据类型操作用法 - go-redis 使用指南
- Golang 操作 Redis:bitmap 数据类型操作用法 - go-redis 使用指南
- Golang 操作 Redis:事务处理操作用法 - go-redis 使用指南
- Golang 操作 Redis:地理空间数据类型操作用法 - go-redis 使用指南
- Golang 操作 Redis:HyperLogLog 操作用法 - go-redis 使用指南
- Golang 操作 Redis:Pipeline 操作用法 - go-redis 使用指南
- Golang 操作 Redis:PubSub发布订阅用法 - go-redis 使用指南
- Golang 操作 Redis:布隆过滤器(Bloom Filter)操作用法 - go-redis 使用指南
- Golang 操作 Redis:Cuckoo Filter操作用法 - go-redis 使用指南
- Golang 操作 Redis:Stream操作用法 - go-redis 使用指南
Redis GEO 地理空间数据结构简介
Redis 的 GEO 地理空间数据结构允许我们存储、查询和操作地理空间信息。通过 GEO 命令,我们可以实现类似于地理信息系统(GIS)的一些功能,比如添加地理位置、计算距离、查询附近的点等。
常见使用场景:
- 位置存储与查询:存储用户、商店或其他实体的地理位置,并能快速查询某一位置附近的其他位置。
- 距离计算:计算两个地理位置之间的距离。
- 地理围栏:判断某个位置是否在特定区域内等。
Redis GEO 与 Elasticsearch GEO 的对比
在实际应用中,Elasticsearch 通常比 Redis 更常用于地理空间搜索(geo search),Redis 的 GEO 功能相对简单,主要用于半径范围内的基本地理位置查找和简单的排序、距离计算,适合作为简单业务处理小规模地理数据。Elasticsearch 支持多种查询类型,如基于半径的搜索、矩形搜索、多边形搜索等,还能处理复杂的地理过滤条件,它采用文档存储系统,不像 Redis 会受限于内存容量,天然支持横向扩展,能够处理大规模的数据量。
go-redis 中 GEO 地理空间操作的方法
以下是 go-redis 库中用于 GEO 地理空间操作的方法及其功能描述:
GeoAdd
- 添加地理空间位置GeoPos
- 获取地理空间位置GeoRadius
- 根据经纬度查询附近的地理位置GeoRadiusStore
- 根据经纬度查询附近的地理位置并存储结果GeoRadiusByMember
- 根据已有成员的位置查询附近的地理位置GeoRadiusByMemberStore
- 根据已有成员的位置查询附近的地理位置并存储结果GeoSearch
- 根据不同条件搜索地理位置GeoSearchLocation
- 根据不同条件搜索地理位置并返回详细信息GeoSearchStore
- 根据不同条件搜索地理位置并存储结果GeoDist
- 计算两个地理位置之间的距离GeoHash
- 获取地理位置的哈希表示
go-redis GEO 地理空间操作方法详细讲解及示例代码
GeoAdd:将一个或多个地理空间位置添加到指定的 key 中。
方法签名:
GeoAdd(ctx context.Context, key string, geoLocation ...*GeoLocation) *IntCmd
参数说明:
ctx
:上下文参数,用于控制请求的生命周期。key
:存储地理空间数据的 key。geoLocation
:一个或多个地理位置,类型为*GeoLocation
,包含经纬度和成员名称。
返回结果说明:
返回类型为*IntCmd
,表示成功添加的地理位置的数量。
示例代码:
package main
import (
"context"
"fmt"
"github.com/redis/go-redis/v9"
)
func main() {
ctx := context.Background()
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
locations := []*redis.GeoLocation{
{Name: "PalaceMuseum", Longitude: 116.4039, Latitude: 39.915, Dist: 0, GeoHash: 0},
{Name: "SummerPalace", Longitude: 116.3975, Latitude: 39.9087, Dist: 0, GeoHash: 0},
}
result, err := rdb.GeoAdd(ctx, "china:beijing", locations...).Result()
if err != nil {
panic(err)
}
fmt.Printf("Number of locations added: %d\n", result)
}
*输出结果:
Number of locations added: 2
可以看到其实底层是以 zset 作为存储的:
GeoPos:获取指定成员的地理位置。
方法签名:
GeoPos(ctx context.Context, key string, members ...string) *GeoPosCmd
参数说明:
ctx
:上下文参数,用于控制请求的生命周期。key
:存储地理空间数据的 key。members
:一个或多个成员名称。
返回结果说明:
返回类型为*GeoPosCmd
,包含成员的经纬度信息。
示例代码:
package main
import (
"context"
"fmt"
"github.com/redis/go-redis/v9"
)
func main() {
ctx := context.Background()
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
pos, err := rdb.GeoPos(ctx, "china:beijing", "PalaceMuseum", "SummerPalace").Result()
if err != nil {
panic(err)
}
for i, p := range pos {
if p != nil {
fmt.Printf("Position of member %d: (%f, %f)\n", i, p.Longitude, p.Latitude)
} else {
fmt.Printf("Position of member %d not found\n", i)
}
}
}
*输出结果:
Position of member 0: (116.403900, 39.915000)
Position of member 1: (116.397500, 39.908700)
GeoRadius:根据经纬度查询附近的地理位置。
方法签名:
GeoRadius(ctx context.Context, key string, longitude, latitude float64, query *GeoRadiusQuery) *GeoLocationCmd
参数说明:
ctx
:上下文参数,用于控制请求的生命周期。key
:存储地理空间数据的 key。longitude
:查询中心点的经度。latitude
:查询中心点的纬度。query
:查询参数,类型为*GeoRadiusQuery
,包含半径、单位等。
返回结果说明:
返回类型为*GeoLocationCmd
,包含查询到的地理位置的详细信息。
示例代码:
package main
import (
"context"
"fmt"
"github.com/redis/go-redis/v9"
)
func main() {
ctx := context.Background()
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
query := &redis.GeoRadiusQuery{
Radius: 1,
Unit: "km", // Can be m, km, ft, or mi. Default is km.
WithCoord: true,
WithDist: true,
WithGeoHash: true,
Count: 10,
Sort: "asc", // Can be ASC or DESC. Default is no sort order.
}
locations, err := rdb.GeoRadius(ctx, "china:beijing", 116.4030, 39.911, query).Result()
if err != nil {
panic(err)
}
for _, loc := range locations {
fmt.Printf("location: %+v\n", loc)
}
}
*输出结果:
location: {Name:PalaceMuseum Longitude:116.40389889478683 Latitude:39.91500057149189 Dist:0.4515 GeoHash:4069885555039198}
location: {Name:SummerPalace Longitude:116.3974991440773 Latitude:39.90869925468977 Dist:0.5345 GeoHash:4069885365519944}
GeoRadiusStore:根据经纬度查询附近的地理位置,并将结果存储到指定的 key 中。
方法签名:
GeoRadiusStore(ctx context.Context, key string, longitude, latitude float64, query *GeoRadiusQuery) *IntCmd
参数说明:
ctx
:上下文参数,用于控制请求的生命周期。key
:存储地理空间数据的 key。longitude
:查询中心点的经度。latitude
:查询中心点的纬度。query
:查询参数,类型为*GeoRadiusQuery
,包含半径、单位等。
返回结果说明:
返回类型为*IntCmd
,表示存储到新 key 中的位置数量。
示例代码:
package main
import (
"context"
"fmt"
"github.com/redis/go-redis/v9"
)
func main() {
ctx := context.Background()
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
query := &redis.GeoRadiusQuery{
Radius: 1, // 1公里半径
Unit: "km",
Store: "china:beijing:nearby", // 存储结果的key
}
result, err := rdb.GeoRadiusStore(ctx, "china:beijing", 116.4039, 39.915, query).Result()
if err != nil {
panic(err)
}
fmt.Printf("Number of locations stored: %d\n", result)
}
*输出结果:
Number of locations stored: 2
GeoRadiusByMember:根据已有成员的位置查询附近的地理位置。
方法签名:
GeoRadiusByMember(ctx context.Context, key, member string, query *GeoRadiusQuery) *GeoLocationCmd
参数说明:
ctx
:上下文参数,用于控制请求的生命周期。key
:存储地理空间数据的 key。member
:已有成员名称。query
:查询参数,类型为*GeoRadiusQuery
,包含半径、单位等。
返回结果说明:
返回类型为*GeoLocationCmd
,包含查询到的地理位置的详细信息。
示例代码:
package main
import (
"context"
"fmt"
"github.com/redis/go-redis/v9"
)
func main() {
ctx := context.Background()
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
query := &redis.GeoRadiusQuery{
Radius: 1,
Unit: "km",
WithCoord: true,
WithDist: true,
WithGeoHash: true,
Sort: "asc",
}
locations, err := rdb.GeoRadiusByMember(ctx, "china:beijing", "PalaceMuseum", query).Result()
if err != nil {
panic(err)
}
for _, loc := range locations {
fmt.Printf("location: %+v\n", loc)
}
}
*输出结果:
location: {Name:PalaceMuseum Longitude:116.40389889478683 Latitude:39.91500057149189 Dist:0 GeoHash:4069885555039198}
location: {Name:SummerPalace Longitude:116.3974991440773 Latitude:39.90869925468977 Dist:0.8884 GeoHash:4069885365519944}
GeoRadiusByMemberStore:根据已有成员的位置查询附近的地理位置,并将结果存储到指定的 key 中。
方法签名:
GeoRadiusByMemberStore(ctx context.Context, key, member string, query *GeoRadiusQuery) *IntCmd
参数说明:
ctx
:上下文参数,用于控制请求的生命周期。key
:存储地理空间数据的 key。member
:已有成员名称。query
:查询参数,类型为*GeoRadiusQuery
,包含半径、单位等。
返回结果说明:
返回类型为*IntCmd
,表示存储到新 key 中的位置数量。
示例代码:
package main
import (
"context"
"fmt"
"github.com/redis/go-redis/v9"
)
func main() {
ctx := context.Background()
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
query := &redis.GeoRadiusQuery{
Radius: 1, // 1公里半径
Unit: "km",
Store: "china:beijing:nearby", // 存储结果的key
}
result, err := rdb.GeoRadiusByMemberStore(ctx, "china:beijing", "PalaceMuseum", query).Result()
if err != nil {
panic(err)
}
fmt.Printf("Number of locations stored: %d\n", result)
}
*输出结果:
Number of locations stored: 2
GeoSearch:根据不同条件搜索地理位置。
方法签名:
GeoSearch(ctx context.Context, key string, q *GeoSearchQuery) *StringSliceCmd
参数说明:
ctx
:上下文参数,用于控制请求的生命周期。key
:存储地理空间数据的 key。q
:查询参数,类型为*GeoSearchQuery
,包含中心点、半径或边界框、排序等。
返回结果说明:
返回类型为*StringSliceCmd
,表示查询到的成员名称。
示例代码:
package main
import (
"context"
"fmt"
"github.com/redis/go-redis/v9"
)
func main() {
ctx := context.Background()
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
query := &redis.GeoSearchQuery{
Member: "PalaceMuseum",
}
member, err := rdb.GeoSearch(ctx, "china:beijing", query).Result()
if err != nil {
panic(err)
}
fmt.Printf("Found member: %+v\n", member)
query = &redis.GeoSearchQuery{
Member: "",
Longitude: 116.4039,
Latitude: 39.915,
Radius: 1,
RadiusUnit: "km",
BoxWidth: 0.0,
BoxHeight: 0.0,
BoxUnit: "",
Sort: "asc",
Count: 0, // 指定查询结果返回的最大数量。
CountAny: false, // CountAny为false或未设置,则只会返回精确匹配的结果,数量等于Count指定的数量。当CountAny设置为true时,Redis将尽可能返回接近Count指定数量的结果,允许结果集包含不精确的匹配。
}
members, err := rdb.GeoSearch(ctx, "china:beijing", query).Result()
if err != nil {
panic(err)
}
fmt.Printf("Found members: %+v\n", members)
}
*输出结果:
Found member: [PalaceMuseum]
Found members: [PalaceMuseum SummerPalace]
GeoSearchLocation:根据不同条件搜索地理位置,并返回详细信息。
方法签名:
GeoSearchLocation(ctx context.Context, key string, q *GeoSearchLocationQuery) *GeoSearchLocationCmd
参数说明:
ctx
:上下文参数,用于控制请求的生命周期。key
:存储地理空间数据的 key。q
:查询参数,类型为*GeoSearchLocationQuery
,包含中心点、半径或边界框、排序等。
返回结果说明:
返回类型为*GeoSearchLocationCmd
,包含查询到的地理位置的详细信息。
示例代码:
package main
import (
"context"
"fmt"
"github.com/redis/go-redis/v9"
)
func main() {
ctx := context.Background()
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
query := &redis.GeoSearchLocationQuery{
GeoSearchQuery: redis.GeoSearchQuery{
Member: "PalaceMuseum",
},
WithCoord: true,
WithDist: true,
WithHash: true,
}
member, err := rdb.GeoSearchLocation(ctx, "china:beijing", query).Result()
if err != nil {
panic(err)
}
fmt.Printf("Found member: %+v\n", member)
query = &redis.GeoSearchLocationQuery{
GeoSearchQuery: redis.GeoSearchQuery{
Member: "",
Longitude: 116.4039,
Latitude: 39.915,
Radius: 1, // 1公里半径
RadiusUnit: "km",
BoxWidth: 0.0,
BoxHeight: 0.0,
BoxUnit: "",
Sort: "asc",
Count: 0,
CountAny: false,
},
WithCoord: true,
WithDist: true,
WithHash: true,
}
members, err := rdb.GeoSearchLocation(ctx, "china:beijing", query).Result()
if err != nil {
panic(err)
}
for i, member := range members {
fmt.Printf("Found member%d: %+v\n", i, member)
}
}
*输出结果:
Found member: [{Name:PalaceMuseum Longitude:116.40389889478683 Latitude:39.91500057149189 Dist:0 GeoHash:4069885555039198}]
Found member0: {Name:PalaceMuseum Longitude:116.40389889478683 Latitude:39.91500057149189 Dist:0.0001 GeoHash:4069885555039198}
Found member1: {Name:SummerPalace Longitude:116.3974991440773 Latitude:39.90869925468977 Dist:0.8884 GeoHash:4069885365519944}
GeoDist:计算两个地理位置之间的距离。
方法签名:
GeoDist(ctx context.Context, key string, member1, member2, unit string) *FloatCmd
参数说明:
ctx
:上下文参数,用于控制请求的生命周期。key
:存储地理空间数据的 key。member1
:第一个成员名称。member2
:第二个成员名称。unit
:距离单位,如m
(米)、km
(公里)、mi
(英里)、ft
(英尺)。
返回结果说明:
返回类型为*FloatCmd
,表示计算得到的距离。
示例代码:
package main
import (
"context"
"fmt"
"github.com/redis/go-redis/v9"
)
func main() {
ctx := context.Background()
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
dist, err := rdb.GeoDist(ctx, "china:beijing", "PalaceMuseum", "SummerPalace", "km").Result()
if err != nil {
panic(err)
}
fmt.Printf("Distance between PalaceMuseum and SummerPalace: %f km\n", dist)
}
*输出结果:
Distance between PalaceMuseum and SummerPalace: 0.888400 km
GeoHash:获取地理位置的哈希表示。
方法签名:
GeoHash(ctx context.Context, key string, members ...string) *StringSliceCmd
参数说明:
ctx
:上下文参数,用于控制请求的生命周期。key
:存储地理空间数据的 key。members
:一个或多个成员名称。
返回结果说明:
返回类型为*StringSliceCmd
,表示成员的地理位置的哈希值。
示例代码:
package main
import (
"context"
"fmt"
"github.com/redis/go-redis/v9"
)
func main() {
ctx := context.Background()
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
hashes, err := rdb.GeoHash(ctx, "china:beijing", "PalaceMuseum", "SummerPalace").Result()
if err != nil {
panic(err)
}
for i, hash := range hashes {
fmt.Printf("GeoHash of member %d: %s\n", i, hash)
}
}
*输出结果:
GeoHash of member 0: wx4g0f69xr0
GeoHash of member 1: wx4g09nj420
结语
通过本文,我们详细了解了 Redis 中的 GEO 地理空间数据结构及其常见使用场景,并使用 go-redis 库演示了相关的操作方法。希望这篇文章能够帮助你更好地在项目中应用 Redis 的地理空间功能,点击 go-redis 使用指南 可查看更多相关教程!