Geospatial

在这一周,经纬度的概念非常重要,请参考

在任何一本地图上,你都可以看到经线和纬线纵横交织,它们的作用就是为了确定一个地点或地区的位置。

地球像陀螺那样斜着身子旋转着,南极和北极就是地轴的两个端点,北极对着北极星,是世界的“顶点”,而南极则是地球的“末端”。在地球表面连接地球两极的线,叫经线,也叫子午线,它的方向指示南北。地球上所有的经线都一样长,并汇集到两极。通过英国格林威治天文台的经线为零度,称作本初子午线。由此向东、向西各分成180°,分别称东经和西经。东、西经180°线是重合在一起的。

在地球表面与经线直交的线叫纬线,把地球分成南北两半,到南极和北极距离相等的纬线就是赤道。所有的纬线都呈东西方向、与赤道平行,但长短不等,愈往极地愈短。赤道的纬度是0°,南北极是90°,赤道以北叫北纬,赤道以南叫南纬。习惯上,人们还把纬度0~30°叫低纬,30~60°叫中纬,60~90°叫高纬。南、北纬23.5°,分别被称为南、北回归线。

相关命令:

127.0.0.1:6379> help @geoGEOADD key longitude latitude member [longitude latitude member ...]summary: Add one or more geospatial items in the geospatial index represented using a sorted setsince: 3.2.0GEODIST key member1 member2 [m|km|ft|mi]summary: Returns the distance between two members of a geospatial indexsince: 3.2.0GEOHASH key member [member ...]summary: Returns members of a geospatial index as standard geohash stringssince: 3.2.0GEOPOS key member [member ...]summary: Returns longitude and latitude of members of a geospatial indexsince: 3.2.0GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]summary: Query a sorted set representing a geospatial index to fetch members matching a given maximum distance from a pointsince: 3.2.0GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]summary: Query a sorted set representing a geospatial index to fetch members matching a given maximum distance from a membersince: 3.2.0

Geospatial Explained

Geospatial Index的内容包括经度(longtitude),纬度(latitude)和ID。

经度的范围为东至180°向西至180° W。纬度其数值在0—90度之间。

指定半径内的地点,两点之间距离等。

GEOADD添加地点,GEORADIUS寻找指定半径范围内的地点,并带有很多选项。

# Sicily是key,表示西西里岛,后续的Palermo是西西里岛的首府,Catania是西西里岛第二大城市
127.0.0.1:6379> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"
(integer) 2
# 计算两点之间距离
127.0.0.1:6379> GEODIST Sicily Palermo Catania km
"166.2742"
# 寻找以(15, 37)为中心,半径为100km范围内,属于Sicily的地点
127.0.0.1:6379> GEORADIUS Sicily 15 37 100 km
1) "Catania"

Introduction to Geospatial Data

和其他Geo数据库比,主要特点还是内存。

Adding Geospatial Points

使用了GeoHash作为编码,长度为52bit,数据类型是Sorted Set。

# 类型为Sorted Set
127.0.0.1:6379> type Sicily
zset
127.0.0.1:6379> object encoding Sicily
"ziplist"
127.0.0.1:6379> zrange Sicily 0 -1 withscores
1) "Palermo"
2) "3479099956230698"
3) "Catania"
4) "3479447370796909"

GEOADD可以一次性加多个地点,地点的名称成为Sorted Set中的field,经纬度则转化为score。

GEOHASH返回地点的geo index,11字节,实际上这是一个标准,参见Geohash:

127.0.0.1:6379> geohash Sicily Palermo Catania
1) "sqc8b49rny0"
2) "sqdtr74hyu0"

你可以试一下:http://geohash.org/sqdtr74hyu0

geohash可以只输入前面一部分,例如http://geohash.org/sqd,当然,输入越多,精度越高。

GEOPOS返回地点的经纬度:

127.0.0.1:6379> geopos Sicily Palermo Catania
1) 1) "13.36138933897018433"2) "38.11555639549629859"
2) 1) "15.08726745843887329"2) "37.50266842333162032"

经度从-180到180度,纬度支持-85到85度。为什么支持不到90度,可能是因为地球是一个倾斜的椭圆,类似于南瓜。

由于Geo对象存储使用的是Sorted Set,这表示Sorted Set的操作对于Geo对象都可以用,如ZREM,ZRANGE和集合操作。

GEOADD相同的field,但不同的经纬度,相当于更新。

不过ZINTERSTORE AGGREGATE就不要用了,因为累加后表示地点变了,这并不是你需要的。

Searching for Geospatial Objects

GEODIST可计算key中两个member间的距离, 可指定单位,默认是米:

127.0.0.1:6379> geodist Sicily Palermo Catania
"166274.1516"

GEORADIUS和GEORADIUSBYMEMBER可寻找指定半径范围内的Geo对象的成员,这两个命令的区别是,前者以经纬度为圆心,后者以Geo对象中的member为圆心:

127.0.0.1:6379> GEORADIUS Sicily 15 37 100 km withdist
1) 1) "Catania"2) "56.4413"
127.0.0.1:6379> GEORADIUS Sicily 15 37 100 km withcoord
1) 1) "Catania"2) 1) "15.08726745843887329"2) "37.50266842333162032"
127.0.0.1:6379> GEORADIUS Sicily 15 37 100 km withhash
1) 1) "Catania"2) (integer) 3479447370796909
127.0.0.1:6379> GEORADIUS Sicily 15 37 200 km withhash
1) 1) "Palermo"2) (integer) 3479099956230698
2) 1) "Catania"2) (integer) 3479447370796909
127.0.0.1:6379> GEORADIUS Sicily 15 37 200 km withhash count 1
1) 1) "Catania"2) (integer) 3479447370796909
127.0.0.1:6379> GEORADIUS Sicily 15 37 200 km withdist asc
1) 1) "Catania"2) "56.4413"
2) 1) "Palermo"2) "190.4424"
127.0.0.1:6379> georadiusbymember Sicily "Catania" 100 km
1) "Catania"

这两个命令都支持STORE和STOREDIST命令以支持搜索结果,目标的类型也是Sorted Set。

Review

应用场景包括:

  • 寻找附近的地点
  • 基于地理位置的推送,或者是客户的位置,或者是地点的位置。

Use Case: Finding Events and Venues

示例程序为uc05-finding-venues/finding_venues.py:

$ python finding_venues.py==Test 1 - geo searches around a venue
== Find venues with 5km of 'Tokyo Station'
[['Olympic Stadium', 0.5303], ['Tokyo Tatsumi International Swimming Center', 4.8107], ['Nippon Budokan', 3.4448]]
== Find venues within 25km of 'Olympic Stadium'
[['Olympic Stadium', 0.0], ['Tokyo Tatsumi International Swimming Center', 5.2082], ['Nippon Budokan', 3.3035], ['International Stadium Yokohama', 22.6596], ['Makuhari Messe', 24.3428]]==Test 2 - geo searches around events
== Find venues for 'Football' within 25km of 'Shin-Yokohama Station'
[['International Stadium Yokohama', 0.0145], ['Olympic Stadium', 22.655]]==Test 3 - geo searched around transit
== Find venues 5km from 'Tokyo Station' on the 'Keiyo Line'
[['Tokyo Tatsumi International Swimming Center', 4.8107]]
== Find the distance between 'Makuhari Messe' and 'Tokyo TatsumiInternational Swimming Center' on the 'Keiyo Line'
19.503
== Find venues within 20km of 'Makuhari Messe' on the 'Keiyo Line'
[['Makuhari Messe', 0.0], ['Tokyo Tatsumi International Swimming Center', 19.503]]

Overview of Domain Problem

在Geo对象中存放的地点:

127.0.0.1:6379> keys uc05:geo:venue*
1) "uc05:geo:venues"
127.0.0.1:6379> type "uc05:geo:venues"
zset
127.0.0.1:6379> zrange uc05:geo:venues 0 -1
1) "International Stadium Yokohama"
2) "Olympic Stadium"
3) "Tokyo Tatsumi International Swimming Center"
4) "Nippon Budokan"
5) "Makuhari Messe"
6) "Saitama Super Arena"

Finding Venues from Another Point or Member

寻找指点地理位置周边的地点,相当于Test 1:

127.0.0.1:6379> georadius uc05:geo:venues 139.771977 35.668024 5 km withdist
1) 1) "Olympic Stadium"2) "0.5303"
2) 1) "Tokyo Tatsumi International Swimming Center"2) "4.8107"
3) 1) "Nippon Budokan"2) "3.4448"127.0.0.1:6379> georadiusbymember uc05:geo:venues "Olympic Stadium" 25 km withdist
1) 1) "Olympic Stadium"2) "0.0000"
2) 1) "Tokyo Tatsumi International Swimming Center"2) "5.2082"
3) 1) "Nippon Budokan"2) "3.3035"
4) 1) "International Stadium Yokohama"2) "22.6596"
5) 1) "Makuhari Messe"2) "24.3428"

Finding Venues for a Specific Event Within a Given Distance

寻找指定地点周边的活动。每一个地点都有举办的活动,如跆拳道,柔道,摔跤等。本例为寻找感兴趣的活动。

地点和活动的关联使用了reverse index, 可以看到每一个活动都存放了举办此活动的地点:

127.0.0.1:6379> keys uc05:geo:event*
1) "uc05:geo:events:Football"
2) "uc05:geo:events:Wrestling"
3) "uc05:geo:events:Athletics"
4) "uc05:geo:events:Water polo"
5) "uc05:geo:events:Basketball"
6) "uc05:geo:events:Taekwondo"
7) "uc05:geo:events:Fencing"
8) "uc05:geo:events:Karate"
9) "uc05:geo:events:Judo"
127.0.0.1:6379> type "uc05:geo:events:Football"
zset
127.0.0.1:6379> zrange "uc05:geo:events:Football" 0 -1 withscores
1) "International Stadium Yokohama"
2) "4171217789000861"
3) "Olympic Stadium"
4) "4171232605494985"

以下相当于Test 2:

127.0.0.1:6379> georadius "uc05:geo:events:Football" 139.606396 35.509996 25 km withdist
1) 1) "International Stadium Yokohama"2) "0.0145"
2) 1) "Olympic Stadium"2) "22.6550"

Finding Transit Locations

以下相当于Test 3:

127.0.0.1:6379> zrange "uc05:geo:transits:Keiyo Line" 0 -1 withscores
1) "Tokyo Tatsumi International Swimming Center"
2) "4171232700405237"
3) "Makuhari Messe"
4) "4171256723753137"# 寻找指定范围内京代线的车站
127.0.0.1:6379> georadius "uc05:geo:transits:Keiyo Line" 139.771977 35.668024 5 km withdist
1) 1) "Tokyo Tatsumi International Swimming Center"2) "4.8107"# 京代线上两车站的距离
127.0.0.1:6379> geodist "uc05:geo:transits:Keiyo Line" "Tokyo Tatsumi International Swimming Center" "Makuhari Messe" km
"19.5030"127.0.0.1:6379> georadiusbymember "uc05:geo:transits:Keiyo Line" "Makuhari Messe" 20 km withdist
1) 1) "Makuhari Messe"2) "0.0000"
2) 1) "Tokyo Tatsumi International Swimming Center"2) "19.5030"

Lua Scripting

命令帮助:

127.0.0.1:6379> help @scriptingEVAL script numkeys key [key ...] arg [arg ...]summary: Execute a Lua script server sidesince: 2.6.0EVALSHA sha1 numkeys key [key ...] arg [arg ...]summary: Execute a Lua script server sidesince: 2.6.0SCRIPT DEBUG YES|SYNC|NOsummary: Set the debug mode for executed scripts.since: 3.2.0SCRIPT EXISTS sha1 [sha1 ...]summary: Check existence of scripts in the script cache.since: 2.6.0SCRIPT FLUSH -summary: Remove all the scripts from the script cache.since: 2.6.0SCRIPT KILL -summary: Kill the script currently in execution.since: 2.6.0SCRIPT LOAD scriptsummary: Load the specified Lua script into the script cache.since: 2.6.0

Overview of Lua in Redis

Lua诞生于里约热内卢的天主教大学,Lua是葡萄牙语月亮的意思,因为巴西的官方语言是葡萄牙语。

Lua是一种嵌入式语言,跨平台,在服务器端(数据所在地)就可以处理数据,类似于存储过程,因此可减少网络往返。

Running a Script

2个例子,注意KEYS和ARGV必须大写,Lua的索引从1开始:

127.0.0.1:6379> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
1) "key1"
2) "key2"
3) "first"
4) "second"127.0.0.1:6379> hset key1 f1 hello f2 world
(integer) 2127.0.0.1:6379> eval "return redis.call('hget', KEYS[1], ARGV[1])" 1 key1 f2
"world"

Lua支持变量,操作符,条件判断和循环。

redis.call和redis.pcall的区别。出错时,前者直接返回错误,后者可对错误进行处理。

Lua的最佳实际:

  1. key通过参数传递,而非写死在脚本中
  2. 索引是从1开始的
  3. Lua中,integer和float没有区别,因此float转换到redis中会丢失精度,因此需要用string传递以保持精度

Managing Scripts

Lua脚本被送往服务器端执行,脚本需要解析,Redis会提供解析缓存。

SCRIPT LOAD命令可编译脚本并加载到缓存,并返回一个ID,后续可通过EVALSHA直接调用此ID。

127.0.0.1:6379> script load "local val=redis.call('hget', KEYS[1], ARGV[1]) return val"
"aa3b1551f9733d0fca05885b4d08078fa7244c0e"
127.0.0.1:6379> evalsha "aa3b1551f9733d0fca05885b4d08078fa7244c0e" 1 key1 f2
"world"
127.0.0.1:6379> script exists "aa3b1551f9733d0fca05885b4d08078fa7244c0e"
1) (integer) 1

script flush清空缓存,script kill终止当前正在运行的脚本,script debug可开启调试模式,不要在生产环境用。

EVAL是一个原子命令。这表示在它之后的命令必须等待其完成后才能执行。默认执行时间是5秒,超时运行的脚本会被记录但不会终止。

超时运行的脚本可接受管理命令,其它命令均会收到BUSY回复。SCRIPT KILL只有当无数据写入时才会执行,有数据写入时,唯一的终止手段是SHUTDOWN NOSAVE。

因此,最佳建议是:

  1. 脚本尽量短小
  2. 脚本是事务的边界,大的流程可拆分为多个小事务
  3. 测试,测试,再测试

Review & Use Cases

场景包括限制计数器(速率限制),如只有在未达到阈值时允许访问。

Lua脚本在服务器端执行,封装了数据和逻辑,是原子单元,类似于事务。

脚本应尽量简单,短小;使用参数传递而非写死,注意浮点数可能会被截断。

Use Case: Inventory with Lua

Overview: Inventory Management with Lua

本节使用Lua脚本实现之前的库存系统。

最重要是先构建数据模型,sales order和订单使用的Hash,holds使用的Set。

Managing Lua Scripts with Python

程序参见uc06-inventory-with-lua/intro.py

介绍了在Python中如何加载和调用Lua脚本。

Modeling a Purchase Workflow

购票流程如下:

  1. 扣票,并设置超时
  2. 如成功,状态置为RESERVE
  3. 等待支付
  4. 扣票如未超时,状态置为AUTHORIZE
  5. 如付款成功,状态置为COMPLETE

然后演示了状态机。

理想的Lua脚本应简短,简明,象Redis命令一样,并保持原子性。

Reserving Tickets

脚本为uc06-inventory-with-lua/inventory-lua.py

运行这个脚本会清空Redis中所有的数据,通过调试可查看数据模型:

127.0.0.1:6379> keys *uc06*
1) "uc06:customer:1357"
2) "uc06:event:123-ABC"
3) "uc06:events"
4) "uc06:event:456-DEF"
5) "uc06:customer:2468"
127.0.0.1:6379> type "uc06:events"
set
127.0.0.1:6379> smembers "uc06:events"
1) "123-ABC"
2) "456-DEF"
127.0.0.1:6379> type "uc06:event:123-ABC"
hash
127.0.0.1:6379> hgetall "uc06:event:123-ABC"
1) "sku"
2) "123-ABC"
3) "name"
4) "Men's 100m Final"
5) "available:General"
6) "200"
7) "price:General"
8) "25.0"
127.0.0.1:6379> type "uc06:customer:2468"
hash
127.0.0.1:6379> hgetall "uc06:customer:2468"
1) "id"
2) "2468"
3) "customer_name"
4) "mary jane"
127.0.0.1:6379> type "uc06:sales_order:WFJZUB-DXKDOS"
hash
127.0.0.1:6379> hgetall "uc06:sales_order:WFJZUB-DXKDOS"1) "state"2) "RESERVE"3) "order_id"4) "WFJZUB-DXKDOS"5) "customer_id"6) "1357"7) "qty"8) "5"9) "cost"
10) "125.0"
11) "event_sku"
12) "123-ABC"
13) "ts"
14) "1606912351"

Final Purchase Flow and Recap

充分测试,包括边界条件。脚本尽可能简单。利用Lua实现Redis缺乏的功能(条件判断,循环及基本计算),如compare and set。

理解对于原子性的需求,并考虑在应用和服务器故障时的后果。

Redis RU101课程 Introduction to Redis Data Structures 第5周学习笔记相关推荐

  1. Redis RU101课程 Introduction to Redis Data Structures 第2周学习笔记

    Capped Collections & Set Operations Cardinality & Capped Collections 首先谈到了基数(cardinality)的概念 ...

  2. Redis RU101课程 Introduction to Redis Data Structures 第3周学习笔记

    Transactions Introduction 为了保证单条命令的原子性,Redis使用了单线程,命令都是顺序依次执行(unlink是个例外,是异步的). 事务保证了将多条命令作为一个单元执行. ...

  3. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十五章:第一人称摄像机和动态索引...

    Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十五章:第一人称摄像机和动态索引 原文:Introduction to 3 ...

  4. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十三章:计算着色器(The Compute Shader)...

    Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十三章:计算着色器(The Compute Shader) 原文: Int ...

  5. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十九章:法线贴图

    Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十九章:法线贴图 原文:Introduction to 3D Game P ...

  6. 机电传动控制课程第一周学习笔记

    机电传动课程第一周学习笔记 本周的学习内容主要是第一章绪论和第二章机电传动系统的动力学基础,结合课程学习和预习复习回顾内容如下: 1.绪论:学习了机电传动控制目的与任务.发展历程和我们该如何学习这门课 ...

  7. Python_Example_ Data Structures and Algorithm Analysis 学习/示例

    Author: 楚格 2018-11-19   19:05:11 IDE: Pycharm2018.02   Python 3.7 KeyWord :  Data Structures and Alg ...

  8. ASP.NET Core分布式项目实战(课程介绍,MVP,瀑布与敏捷)--学习笔记

    任务1:课程介绍 课程目标: 1.进一步理解 ASP.NET Core 授权认证框架.MVC 管道 2.掌握 Oauth2,结合 Identity Sercer4 实现 OAuth2 和 OpenID ...

  9. 基于Problem Solving with Algorithms and Data Structures using Python的学习记录(4)——Recursion

    4.1.目标 本章的目标如下: 要理解可能难以解决的复杂问题有一个简单的递归解决方案. 学习如何递归地写出程序. 理解和应用递归的三个定律. 将递归理解为一种迭代形式. 实现问题的递归公式化. 了解计 ...

最新文章

  1. 16 条 yyds 的代码规范
  2. 重拾-Spring Transaction
  3. Element DOM Tree jQuery plugin – Firebug like functionality | RockingCode
  4. 科学家们竟用乐高观察细胞,网友:万万没想到啊
  5. Android开源框架——内存泄漏检测工具 LeakCanary
  6. 程序员如何精确评估开发时间?
  7. TensorFlow——本地加载fashion-mnist数据集
  8. 计算机组成输出设备的缩写,信息概念 计算机组成
  9. JScript中的条件注释详解(转载自网络)
  10. 用友nc操作手册_铁军人物汤轩宇, 入职两年,她用努力和汗水编制出单户试算操作手册...
  11. micropython入门教程-Micropython入门实操心得
  12. [RK3399]移植工具i2c-tools
  13. 什么是erp管理系统
  14. 黑苹果常用 工具+Kext+ACPI+UEFI驱动 下载
  15. 如何手动控制Mac的风扇
  16. 图片批量重命名方法(超详细 无需辅助软件 本地运行)
  17. android菜单键 r9,Android OPPO R9 后台 无法启动 Activity 问题
  18. apex java_Apex - 类
  19. 基于MATLAB的人脸识别 (1)
  20. html入门基础笔记(简单实用)

热门文章

  1. 【NCRE】初遇 SQL SERVER 的 CASE WHEN
  2. 软考是什么-有什么用-怎么报名-考试内容
  3. Excel如何按行间隔配置背景颜色
  4. ExoPlayer网速估计方法
  5. JDK8安装时错误1335的解决
  6. 谷歌浏览器,退出时;调用退出的方法,vue
  7. python英雄联盟脚本是什么_用python写王者荣耀脚本!
  8. 自研·学术·文献查找
  9. 每日一句_《南柯子·池水凝新碧》
  10. “建房收租”网络时代人人可做