前言:由于该方向的内容较多,将分成三篇讲解。
目录
一、定义
Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API,官网 Redis。
redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
Redis 是一个高性能的key-value数据库。 redis的出现,很大程度补偿了memcached这类key/value存储的不足,在部 分场合可以对关系数据库起到很好的补充作用。它提供了Java,C/C++,C#,PHP,JavaScript,Perl,Object-C,Python,Ruby,Erlang等客户端,使用很方便。
Redis支持主从同步。数据可以从主服务器向任意数量的从服务器上同步,从服务器可以是关联其他从服务器的主服务器。这使得Redis可执行单层树复制。存盘可以有意无意的对数据进行写操作。由于完全实现了发布/订阅机制,使得从数据库在任何地方同步树时,可订阅一个频道并接收主服务器完整的消息发布记录。同步对读取操作的可扩展性和数据冗余很有帮助。
二、Redis安装和数据类型讲解
2.1 安装
官方文档(Installing Redis | Redis)有详细的解释,这里只说一下Ubuntu上的安装
sudo apt-get update
# 运行下面程序服务端和客户端会一起安装
sudo apt-get install redis-server
# 开启|关闭|重启 redis服务
service redis {start|stop|restart}
# 打开客户端操作
/usr/bin/redis-cli
若配置远程连接
# 配置远程链接
sudo vim /etc/redis/redis.conf
# 将 bind 127.0.0.1 ::1 改成 bind 0.0.0.0
# 保护模式保持默认 protected-mode yes
# 客户端设置远程访问的验证密码
redis 127.0.0.1:6379> CONFIG SET requirepass YOURPASSPHRASE
OK
redis 127.0.0.1:6379> AUTH YOURPASSPHRASE
Ok
# 取消验证密码
redis 127.0.0.1:6379> AUTH YOURPASSPHRASE
Ok
redis 127.0.0.1:6379> CONFIG SET requirepass ''
OK
2.2 Redis 数据类型讲解
这里主要讲解一下 Redis 的数据类型,全部的命令查询可看 Commands | Redis
2.2.1 Redis 字符串
Redis 字符串存储字节序列,包括文本、序列化对象和二进制数组。因此,字符串是最基本的 Redis 数据类型。它们通常用于缓存,但它们支持额外的功能,让您也可以实现计数器并执行按位操作。
例子
在 Redis 中存储然后检索字符串:
> SET user:1 salvatore
OK
> GET user:1
"salvatore"
存储一个序列化的 JSON 字符串并将其设置为从现在起 100 秒后过期:
> SET ticket:27 "\"{'username': 'zhang', 'ticket_id': 221}\"" EX 100
增加一个计数器:
> INCR views:page:2
(integer) 1
> INCRBY views:page:2 10
(integer) 11
限制
默认情况下,单个 Redis 字符串最大为 512 MB。
基本命令
获取和设置字符串
SET存储一个字符串值。
SETNX仅当键不存在时才存储字符串值。用于实现锁。
GET检索字符串值。
MGET在单个操作中检索多个字符串值。
2.2.2 Redis 列表
Redis 列表是字符串值的链表,Redis 列表经常用于:
实现堆栈和队列。
为后台工作系统构建队列管理。
例子
将列表视为队列(先进先出):
> LPUSH work:queue:ids 100
(integer) 1
> LPUSH work:queue:ids 101
(integer) 2
> RPOP work:queue:ids
"100"
> RPOP work:queue:ids
"101"
将列表视为堆栈(先进后出):
> LPUSH work:queue:ids 100
(integer) 1
> LPUSH work:queue:ids 101
(integer) 2
> LPOP work:queue:ids
"101"
> LPOP work:queue:ids
"100"
检查列表的长度:
> LLEN work:queue:ids
(integer) 0
从一个列表中原子地弹出一个元素并推送到另一个:
> LPUSH board:todo:ids 100
(integer) 1
> LPUSH board:todo:ids 101
(integer) 2
> LMOVE board:todo:ids board:in-progress:ids LEFT LEFT
"101"
> LRANGE board:todo:ids 0 -1
1) "100"
> LRANGE board:in-progress:ids 0 -1
1) "101"
要创建一个永远不会超过 100 个元素的上限列表,您可以LTRIM在每次调用后调用LPUSH:
> LPUSH notifications:user:1 "You've got mail!"
(integer) 1
> LTRIM notifications:user:1 0 99
OK
> LPUSH notifications:user:1 "Your package will be delivered at 12:01 today."
(integer) 2
> LTRIM notifications:user:1 0 99
OK
限制
Redis 列表的最大长度为 2^32 - 1 (4,294,967,295) 个元素。
基本命令
LPUSH将一个新元素添加到列表的头部;
RPUSH添加到尾巴;
LPOP从列表的头部移除并返回一个元素;RPOP做同样的事情,但来自列表的尾部;
LLEN返回列表的长度;
LMOVE原子地将元素从一个列表移动到另一个列表,6.2.0版本之后才有此命令;
LTRIM将列表减少到指定的元素范围。
阻塞命令
列表支持几个阻塞命令,例如:
BLPOP从列表的头部删除并返回一个元素。如果列表为空,则命令会阻塞,直到元素可用或达到指定的超时。
BLMOVE原子地将元素从源列表移动到目标列表。如果源列表为空,则该命令将阻塞,直到有新元素可用。
表现
访问其头部或尾部的列表操作是 O(1),这意味着它们非常高效。但是,操作列表中元素的命令通常是 O(n)。这些示例包括LINDEX、LINSERT和LSET。运行这些命令时要小心,主要是在处理大型列表时。
这里还有跟列表关联的一个常用的命令 scan(浮标)
句法
SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]
例子
> scan 176 MATCH *11* COUNT 1000
1) "0"
2) 1) "key:611"
2) "key:711"
3) "key:118"
4) "key:117"
5) "key:311"
6) "key:112"
7) "key:111"
8) "key:110"
9) "key:113"
10) "key:211"
SCANcommand 和密切相关的 commands ,SSCAN和HSCAN用于ZSCAN增量迭代元素集合。
SCAN迭代当前选定的 Redis 数据库中的一组键。
SSCAN迭代 Sets 类型的元素。
HSCAN迭代 Hash 类型的字段及其关联值。
ZSCAN迭代 Sorted Set 类型的元素及其相关分数。
2.2.3 Redis 字符集
Redis 字符集是唯一字符串(成员)的无序集合。您可以使用 Redis 集高效地:
跟踪唯一项目(例如,跟踪访问给定博客文章的所有唯一 IP 地址)。
表示关系(例如,具有给定角色的所有用户的集合)。
执行常见的集合运算,例如交集、并集和差集。
例子
存储用户 123 和 456 的收藏书籍 ID 集:
> SADD user:123:favorites 100
(integer) 1
> SADD user:123:favorites 101
(integer) 1
> SADD user:123:favorites 102
(integer) 1
> SADD user:456:favorites 101
(integer) 1
检查用户 123 是否喜欢书籍 102 和 105
> SISMEMBER user:123:favorites 102
(integer) 1
> SISMEMBER user:123:favorites 105
(integer) 0
用户 123 和 456 有共同喜欢的书吗?
> SINTER user:123:favorites user:456:favorites
1) "101"
用户 123 收藏了多少本书?
> SCARD user:123:favorites
(integer) 3
限制
Redis 集的最大大小为 2^32 - 1 (4,294,967,295) 个成员。
基本命令
SADD将新成员添加到集合中。
SREM从集合中删除指定的成员。
SISMEMBER测试一个字符串的集合成员资格。
SINTER返回两个或多个集合共有的成员集合(即交集)。
SCARD返回集合的大小(也称为基数)。
表现
大多数集合操作,包括添加、删除和检查项目是否为集合成员,都是 O(1)。这意味着它们非常高效。SMEMBERS但是,对于具有数十万或更多成员的大型集合,在运行该命令时应小心谨慎。此命令为 O(n),并在单个响应中返回整个集合。作为替代方案,请考虑SSCAN,它使您可以迭代地检索集合的所有成员。
2.2.4 Redis 哈希
Redis 哈希是结构为字段值对集合的记录类型。您可以使用散列来表示基本对象并存储计数器分组等。
例子
将基本用户配置文件表示为哈希:
> HSET user:100 username martina firstName Martina lastName Elisa country GB
(integer) 4
> HGET user:100 username
"martina"
> HGETALL user:100
1) "username"
2) "martina"
3) "firstName"
4) "Martina"
5) "lastName"
6) "Elisa"
7) "country"
8) "GB"
存储设备 777 ping 服务器、发出请求或发送错误的次数的计数器:
> HINCRBY device:221:stats pings 1
(integer) 1
> HINCRBY device:221:stats pings 1
(integer) 2
> HINCRBY device:221:stats pings 1
(integer) 3
> HINCRBY device:221:stats errors 1
(integer) 1
> HINCRBY device:221:stats requests 1
(integer) 1
> HGET device:221:stats pings
"3"
> HMGET device:221:stats requests errors
1) "1"
2) "1"
基本命令
HSET设置散列上一个或多个字段的值。
HGET返回给定字段的值。
HMGET返回一个或多个给定字段的值。
HINCRBY将给定字段的值增加提供的整数。
表现
大多数 Redis 哈希命令都是 O(1)。
一些命令 - 例如HKEYS、HVALS和HGETALL- 是 O(n),其中n是字段值对的数量。
限制
每个散列最多可以存储 4,294,967,295 (2^32 - 1) 个字段-值对。实际上,您的哈希值仅受托管 Redis 部署的 VM 上的总内存限制。
2.2.5 Redis 排序集
Redis 排序集是由相关分数排序的唯一字符串(成员)的集合。当多个字符串具有相同的分数时,这些字符串按字典顺序排列。排序集的一些用例包括:
.排行榜,例如,您可以使用排序集轻松维护大型在线游戏中最高分的有序列表。
.速率限制器,特别是,您可以使用排序集来构建滑动窗口速率限制器,以防止过多的 API 请求。
例子
随着玩家分数的变化更新实时排行榜:
> ZADD leaderboard:455 100 user:1
(integer) 1
> ZADD leaderboard:455 75 user:2
(integer) 1
> ZADD leaderboard:455 101 user:3
(integer) 1
> ZADD leaderboard:455 15 user:4
(integer) 1
> ZADD leaderboard:455 275 user:2
(integer) 0
请注意,user:2的分数在最终ZADD调用中更新。
获取前 3 名玩家的分数:
> ZRANGE leaderboard:455 0 4 WITHSCORES
1) "user:4"
2) "15"
3) "user:1"
4) "100"
5) "user:3"
6) "101"
7) "user:2"
8) "275"
> ZREVRANGE leaderboard:455 0 4 WITHSCORES
1) "user:2"
2) "275"
3) "user:3"
4) "101"
5) "user:1"
6) "100"
7) "user:4"
8) "15"
用户2的等级是多少?
> ZREVRANK leaderboard:455 user:2
(integer) 0
基本命令
ZADD将新成员和相关分数添加到排序集中。如果成员已经存在,则更新分数。
ZRANGE返回在给定范围内排序的有序集合的成员。
ZRANK返回提供的成员的排名,假设排序是按升序排列的。
ZREVRANK返回提供的成员的排名,假设排序集是按降序排列的。
2.2.5 Redis stream
Redis 流是一种数据结构,其作用类似于仅附加日志。您可以使用流实时记录和同步事件。Redis 流用例的示例包括:
事件溯源(例如,跟踪用户操作、点击等)
传感器监控(例如,现场设备的读数)
通知(例如,将每个用户的通知记录存储在单独的流中)
Redis 为每个流条目生成一个唯一的 ID。您可以使用这些 ID 稍后检索其关联条目或读取和处理流中的所有后续条目。
Redis 流支持多种修剪策略(以防止流无限增长)和一种以上的消费策略(请参阅XREAD、XREADGROUP和XRANGE)。
例子
将多个温度读数添加到流中
> XADD temperatures:us-ny:10007 * temp_f 87.2 pressure 29.69 humidity 45
"1661416025881-0"
> XADD temperatures:us-ny:10007 * temp_f 83.1 pressure 29.21 humidity 45.5
"1661416027554-0"
> XADD temperatures:us-ny:10007 * temp_f 81.9 pressure 28.37 humidity 43.7
"1661416028809-0"
读取从 ID 开始的前两个流条目1658354934941-0:
> XRANGE temperatures:us-ny:10007 1658354934941-0 + COUNT 2
1) 1) "1661416027554-0"
2) 1) "temp_f"
2) "83.1"
3) "pressure"
4) "29.21"
5) "humidity"
6) "45.5"
2) 1) "1661416028809-0"
2) 1) "temp_f"
2) "81.9"
3) "pressure"
4) "28.37"
5) "humidity"
6) "43.7"
从流的末尾开始读取最多 100 个新的流条目,如果没有写入条目,则最多阻塞 300 毫秒:
> XREAD COUNT 100 BLOCK 300 STREAMS tempertures:us-ny:10007 $
(nil)
基本命令
XADD向流中添加新条目。
XREAD读取一个或多个条目,从给定位置开始并及时向前移动。
XRANGE返回两个提供的条目 ID 之间的条目范围。
XLEN返回流的长度。
表现
向流中添加条目是 O(1)。访问任何单个条目都是 O(n),其中n是 ID 的长度。由于流 ID 通常很短且具有固定长度,因此这有效地减少了恒定时间查找。有关原因的详细信息,请注意流是作为基数树实现的。
简而言之,Redis 流提供了高效的插入和读取。有关详细信息,请参阅每个命令的时间复杂度。
消费群体
当手头的任务是使用来自不同客户端的相同流时,XREAD已经提供了一种向 N 个客户端扇出的方法,可能还使用副本以提供更多的读取可扩展性。然而,在某些问题中,我们想要做的不是向许多客户端提供相同的消息流,而是向许多客户端提供来自同一流的不同消息子集。一个明显有用的例子是处理缓慢的消息:让 N 个不同的工作人员接收流的不同部分的能力允许我们通过将不同的消息路由到准备好处理的不同工作人员来扩展消息处理。做更多的工作。
实际上,如果我们想象有三个消费者 C1、C2、C3 和一个包含消息 1、2、3、4、5、6、7 的流,那么我们想要的是根据下图提供消息:
1 -> C1
2 -> C2
3 -> C3
4 -> C1
5 -> C2
6 -> C3
7 -> C1
为了实现这一点,Redis 使用了一个叫做消费者组的概念。从实现的角度来看,Redis 消费者组与 Kafka (TM) 消费者组无关,了解这一点非常重要。然而它们在功能上相似,所以我决定保留 Kafka (TM) 的术语,因为它最初推广了这个想法。
一个消费者组就像一个伪消费者,从一个流中获取数据,实际上服务于多个消费者,提供一定的保证:
1、每条消息都提供给不同的消费者,因此不可能将相同的消息传递给多个消费者。
2、在一个消费者组中,消费者通过一个名称来标识,该名称是一个区分大小写的字符串,实现消费者的客户端必须选择该字符串。这意味着即使在断开连接后,流消费者组仍保留所有状态,因为客户端将再次声称自己是同一消费者。但是,这也意味着由客户端提供唯一标识符。
3、每个消费者组都有第一个从未消费过的 ID的概念,因此,当消费者请求新消息时,它可以只提供以前未传递的消息。
4、然而,使用消息需要使用特定命令的明确确认。Redis 将确认解释为:此消息已正确处理,因此可以将其从使用者组中逐出。
5、消费者组跟踪当前待处理的所有消息,即已传递给消费者组的某个消费者但尚未确认为已处理的消息。由于这个特性,当访问一个流的消息历史时,每个消费者将只能看到传递给它的消息。
在某种程度上,可以将消费者组想象为有关流的某种状态:
+----------------------------------------+
| consumer_group_name: mygroup |
| consumer_group_stream: somekey |
| last_delivered_id: 1292309234234-92 |
| |
| consumers: |
| "consumer-1" with pending messages |
| 1292309234234-4 |
| 1292309234232-8 |
| "consumer-42" with pending messages |
| ... (and so forth) |
+----------------------------------------+
如果从这个角度来看,很容易理解消费者组可以做什么,它如何能够只向消费者提供他们未决消息的历史,以及如何为请求新消息的消费者提供服务大于 的消息 ID last_delivered_id。同时,如果将消费者组视为 Redis 流的辅助数据结构,很明显单个流可以有多个消费者组,这些消费者组具有不同的消费者集。实际上,同一个流甚至可以让客户端在没有消费者组的情况下通过XREAD读取,而客户端XREADGROUP在不同的消费者组中读取。
现在是时候放大查看基本的消费者组命令了。它们是:
XGROUP用于创建、销毁和管理消费者组。
XREADGROUP用于通过消费者组从流中读取。
XACK是允许消费者将待处理消息标记为已正确处理的命令。
创建消费者组
假设我已经存在一个mystream流类型的密钥,为了创建一个消费者组,我只需要执行以下操作:
> XGROUP CREATE mystream mygroup $
OK
正如您在上面的命令中看到的,在创建消费者组时,我们必须指定一个 ID,在示例中为$. 这是必需的,因为在其他状态中,消费者组必须知道在第一个消费者连接时接下来要提供什么消息,即刚刚创建组时的最后一条消息 ID是什么。如果我们$像以前那样提供,那么从现在开始只有新消息到达流中才会提供给组中的消费者。如果我们指定0消费者组将消费所有流历史记录中的消息开始。当然,您可以指定任何其他有效 ID。您所知道的是,消费者组将开始传递大于您指定的 ID 的消息。因为$表示流中当前最大的 ID,所以指定$将具有仅使用新消息的效果。
XGROUP CREATE还支持自动创建流,如果它不存在,使用可选的MKSTREAM子命令作为最后一个参数:
> XGROUP CREATE newstream mygroup $ MKSTREAM
OK
现在已经创建了消费者组,我们可以立即尝试使用XREADGROUP命令通过消费者组读取消息。我们将从消费者那里得知我们将调用 Alice 和 Bob,以了解系统如何将不同的消息返回给 Alice 或 Bob。
XREADGROUP与BLOCK非常相似XREAD并提供相同的选项,否则是同步命令。但是,有一个必须始终指定的强制选项,即GROUP并且有两个参数:消费者组的名称和尝试读取的消费者的名称。选项COUNT也受支持,与.XREAD
在从流中读取之前,让我们在里面放一些消息:
> XADD mystream * message apple
1526569495631-0
> XADD mystream * message orange
1526569498055-0
> XADD mystream * message strawberry
1526569506935-0
> XADD mystream * message apricot
1526569535168-0
> XADD mystream * message banana
1526569544280-0
注意:这里的message是字段名,fruit是关联的值,记住stream items是小字典。
是时候尝试使用消费者组阅读内容了:
> XREADGROUP GROUP mygroup Alice COUNT 1 STREAMS mystream >
1) 1) "mystream"
2) 1) 1) 1526569495631-0
2) 1) "message"
2) "apple"
XREADGROUP回复就像XREAD回复一样。但是请注意GROUP <group-name> <consumer-name>上面提供的内容。它声明我想使用消费者组从流中读取,mygroup并且我是消费者Alice。消费者每次对消费者组执行操作时,都必须指定其名称,在组内唯一标识该消费者。
上面的命令行中还有一个非常重要的细节,在强制STREAMS选项之后,为密钥请求的 IDmystream是特殊 ID >。这个特殊的 ID 只在消费者组的上下文中有效,它意味着:消息从未传递给其他消费者。
这几乎总是您想要的,但是也可以指定一个真实的 ID,例如0或任何其他有效的 ID,但是,在这种情况下,发生的情况是我们请求XREADGROUP只向我们提供待处理消息的历史记录,在这种情况下,将永远不会在组中看到新消息。XREADGROUP所以根据我们指定的ID基本上有以下行为:
如果 ID 是特殊 ID >,则该命令将仅返回迄今为止从未传递给其他消费者的新消息,并且作为副作用,将更新消费者组的最后一个 ID。
如果 ID 是任何其他有效的数字 ID,则该命令将让我们访问待处理消息的历史记录。也就是说,传递给此指定使用者(由提供的名称标识)的消息集,到目前为止从未使用XACK.
我们可以立即指定 ID 为 0 来测试这种行为,而无需任何COUNT选项:我们只会看到唯一的待处理消息,即关于苹果的消息:
> XREADGROUP GROUP mygroup Alice STREAMS mystream 0
1) 1) "mystream"
2) 1) 1) 1526569495631-0
2) 1) "message"
2) "apple"
但是,如果我们确认消息已处理,它将不再是待处理消息历史记录的一部分,因此系统将不再报告任何内容:
> XACK mystream mygroup 1526569495631-0
(integer) 1
> XREADGROUP GROUP mygroup Alice STREAMS mystream 0
1) 1) "mystream"
2) (empty list or set)
如果您还不知道如何XACK工作,请不要担心,这个想法只是处理过的消息不再是我们可以访问的历史记录的一部分。
现在轮到 Bob 读一些东西了:
> XREADGROUP GROUP mygroup Bob COUNT 2 STREAMS mystream >
1) 1) "mystream"
2) 1) 1) 1526569498055-0
2) 1) "message"
2) "orange"
2) 1) 1526569506935-0
2) 1) "message"
2) "strawberry"
Bob 要求最多两条消息,并且正在通过同一组阅读mygroup。所以发生的事情是 Redis 只报告新消息。如您所见,“apple”消息没有传递,因为它已经传递给 Alice,所以 Bob 得到了橙色和草莓,等等。
这样,Alice、Bob 和组中的任何其他消费者都能够从同一流中读取不同的消息,读取他们尚未处理消息的历史记录,或者将消息标记为已处理。这允许创建不同的拓扑和语义来消费来自流的消息。
有几件事情要记住:
消费者在第一次被提及时自动创建,无需显式创建。
即使XREADGROUP您可以同时读取多个键,但是要使其正常工作,您需要在每个流中创建一个具有相同名称的使用者组。这不是常见的需求,但值得一提的是,该功能在技术上是可用的。
XREADGROUP是一个写命令,因为即使它从流中读取,消费者组也会被修改为读取的副作用,因此只能在主实例上调用。
以下是使用 Ruby 语言编写的使用消费者组的消费者实现示例。Ruby 代码旨在让几乎任何有经验的程序员都能阅读,即使他们不了解 Ruby:
require 'redis'
if ARGV.length == 0
puts "Please specify a consumer name"
exit 1
end
ConsumerName = ARGV[0]
GroupName = "mygroup"
r = Redis.new
def process_message(id,msg)
puts "[#{ConsumerName}] #{id} = #{msg.inspect}"
end
$lastid = '0-0'
puts "Consumer #{ConsumerName} starting..."
check_backlog = true
while true
# Pick the ID based on the iteration: the first time we want to
# read our pending messages, in case we crashed and are recovering.
# Once we consumed our history, we can start getting new messages.
if check_backlog
myid = $lastid
else
myid = '>'
end
items = r.xreadgroup('GROUP',GroupName,ConsumerName,'BLOCK','2000','COUNT','10','STREAMS',:my_stream_key,myid)
if items == nil
puts "Timeout!"
next
end
# If we receive an empty reply, it means we were consuming our history
# and that the history is now empty. Let's start to consume new messages.
check_backlog = false if items[0][1].length == 0
items[0][1].each{|i|
id,fields = i
# Process the message
process_message(id,fields)
# Acknowledge the message as processed
r.xack(:my_stream_key,GroupName,id)
$lastid = id
}
end
如您所见,这里的想法是从消费历史开始,即我们的待处理消息列表。这很有用,因为消费者之前可能已经崩溃,所以在重新启动的情况下,我们希望重新读取传递给我们但没有得到确认的消息。请注意,我们可能会多次或一次处理一条消息(至少在消费者失败的情况下,但也存在 Redis 持久性和复制的限制,请参阅本主题的具体部分)。
一旦历史被消费,我们得到一个空的消息列表,我们可以切换到使用>特殊 ID 来消费新消息。
2.2.6 Redis 地理空间
Redis 地理空间索引可让您存储坐标并搜索它们,此数据结构对于查找给定半径或边界框内的附近点很有用。
例子
假设您正在构建一个移动应用程序,可以让您找到离您当前位置最近的所有电动汽车充电站。
向地理空间索引添加多个位置:
> GEOADD locations:ca -122.276521 37.805184 station:1
(integer) 1
> GEOADD locations:ca -122.2674616 37.8062324 station:2
(integer) 1
> GEOADD locations:ca -122.2469834 37.8104029 station:3
(integer) 1
查找给定位置 1 公里半径内的所有位置,并返回到每个位置的距离:
> GEOSEARCH locations:ca FROMLONLAT -122.2611767 37.7926847 BYRADIUS 5 km WITHDIST
1) 1) "station:1"
2) "1.8521"
2) 1) "station:2"
2) "1.4978"
3) 1) "station:3"
2) "2.2442"
基本命令
GEOADD将位置添加到给定的地理空间索引(请注意,使用此命令,经度位于纬度之前)。
GEOSEARCH返回具有给定半径或边界框的位置。
2.2.8 Redis 超级日志
HyperLogLog 是一种估计集合基数的数据结构。作为一种概率数据结构,HyperLogLog 以完美的准确性换取了高效的空间利用。
Redis HyperLogLog 实现最多使用 12 KB,并提供 0.81% 的标准误差。
例子
在 HyperLogLog 中添加一些项目:
> PFADD members 123
(integer) 1
> PFADD members 500
(integer) 1
> PFADD members 12
(integer) 1
估计集合中的成员数:
> PFCOUNT members
(integer) 3
基本命令
PFADD将项目添加到 HyperLogLog。
PFCOUNT返回集合中项目数的估计值。
PFMERGE将两个或多个 HyperLogLog 合并为一个。
表现
HyperLogLog 的写入 ( PFADD) 和读取 ( PFCOUNT) 是在恒定的时间和空间内完成的。合并 HLL 是 O(n),其中n是草图的数量。
限制
HyperLogLog 可以估计具有多达 18,446,744,073,709,551,616 (2^64) 个成员的集合的基数。
2.2.9 Redis 位图
Redis 位图是字符串数据类型的扩展,可让您将字符串视为位向量。您还可以对一个或多个字符串执行按位运算。位图用例的一些示例包括:
对于集合的成员对应于整数 0-N 的情况,有效的集合表示。
对象权限,每个位代表一个特定的权限,类似于文件系统存储权限的方式。
例子
假设您在现场部署了 1000 个传感器,标记为 0-999。您想快速确定给定传感器是否在一小时内对服务器进行了 ping 操作。
您可以使用其键引用当前时间的位图来表示这种情况。
传感器 123 在 2022 年 10 月 02 日 00:00 时 ping 服务器。
> SETBIT pings:2022-10-02-00:00 123 1
(integer) 0
传感器 123 是否在 2022 年 10 月 2 日 00:00 时 ping 服务器?
> GETBIT pings:2022-10-02-00:00 123
1
服务器456呢?
> GETBIT pings:2022-10-02-00:00 456
0
基本命令
SETBIT将提供的偏移量设置为 0 或 1。
GETBIT返回给定偏移量的位值。
BITOP允许您对一个或多个字符串执行按位运算。
表现
SETBIT并且GETBIT是 O(1)。 BITOP是 O(n),其中n是比较中最长字符串的长度。
2.2.10 Redis 位域
Redis 位域允许您设置、递增和获取任意位长度的整数值。例如,您可以对从无符号 1 位整数到有符号 63 位整数的任何内容进行操作。
这些值使用二进制编码的 Redis 字符串存储。位域支持原子读、写和增量操作,使它们成为管理计数器和类似数值的好选择。
例子
假设您正在跟踪在线游戏中的活动。您希望为每个玩家维护两个关键指标:金币总量和杀死的怪物数量。因为您的游戏非常容易上瘾,所以这些计数器的宽度至少应为 32 位。
您可以用每个播放器一个位域来表示这些计数器。
新玩家以 1000 金币开始教程(偏移量为 0)。
> BITFIELD player:1:stats SET u32 #0 1000
1) (integer) 0
杀死俘虏王子的妖精后,添加获得的 50 金币并增加“被杀”计数器(偏移量 1)。
> BITFIELD player:1:stats INCRBY u32 #0 50 INCRBY u32 #1 1
1) (integer) 1050
2) (integer) 1
向铁匠支付 999 金币,即可购买一把传说中的生锈匕首。
> BITFIELD player:1:stats INCRBY u32 #0 -999
1) (integer) 51
阅读玩家的统计数据:
> BITFIELD player:1:stats GET u32 #0 GET u32 #1
1) (integer) 51
2) (integer) 1
基本命令
BITFIELD原子地设置、递增和读取一个或多个值。
BITFIELD_RO是 的只读变体BITFIELD。
表现
BITFIELD是 O(n),其中n是访问的计数器的数量。