《深入理解redis》之二:高级键管理与数据结构

1. redis键

运行32位还是64位位版本的redis将决定redis键大小的实际限制。对于32位版本来说,任何长于32位的键名需要更多的字节空间,因此增加了redis的内存使用。使用64位版本的redis允许更长的键长度,但是对于短小的键来说,也会分配完整的64位空间,从而导致额外的空间浪费。

redis键模式

虽然redis官方教程data types推荐在命名键时使用一致的模式,但是redis本身并没有模式检测或者验证的功能。不过可以通过使用EXISTS和TYPE这些redis命令实现一些基本的验证。如果应用需要明确特定类型的redis键是否存在于实例中,可以通过使用EXISTS命令,随后使用TYPE命令确认该键是否是期望的redis数据结构。除了这俩命令以外,验证redis键语法和结构需要客户端代码来实现。

如果redis应用会在不同的系统和组织中共享,那么为应用程序添加额外的验证逻辑层将十分有用。一份精确详尽的redis键模式能极大地在排查故障和调试问题方面带来帮助。另一种验证redis键模式的方法是为redis应用引入具体的单元测试。用来测试边界条件、模式键语法和结构,还有每个验证过的键所期望的数据结构。第三种验证redis键模式的方式是使用DTD或者其他基于XML的键结构验证,或者使用新的键验证技术,例如 JSOB Schema.

一份精心设计的键模式应当为现存的(基于redis的)应用程序在添加新的键时提供指引。如果模式描述得足够清楚并且能够保持一致性的话,那么新的redis键的命名就不应当有任何神秘色彩。可以利用名词的单复数形式鉴别存储到redis的实体内容与个数。

例如,book:1作为redis哈希类型存储了单一书本的相关字段,而redis键books:sci-fiction则存储了一套科幻类小说。有序集合可以用作图书销售排行,将books:scale-rank作为键名称,图书销售作为权重(有序集合的分值),并将图书键作为值。

对于一个简单的图书应用来说,基于文本的redis模式实例如下:

即便是简单的、一次性的redis项目,将键模式文档添加到项目的源代码仓库也是一种很好的实践。

键分隔符和命名约定

在上例中,我们使用“:”作为键的分隔符。对于符合redis键来说,推荐使用冒号作为分隔符

另一种redis键分隔符是英文字符句号“.”。大多数流行的编程语言,都青睐这种面向对象的语法。

高效的redis键模式体现在建立命名约定以便将相关的键关联起来。应用程序和业务逻辑通过客户端代码应用在这些松散耦合的redis键上。以之前的图书键模式为例,扩展一下需求,让redis数据库包含其他媒体类型,在redis命令行工具中运行keys命令,并做一些格式化工作。我们可以从redis键模式中观察到一种模式和隐含的关系:

在此redis应用程序中,图书和影片都提供了基本的前缀。那些用于支持的数据结构,通过基本前缀关联到单本图书或者单部影片,或者关联到包含额外实体哈希的集合上。

每件作品都是有redis键作为前缀,同时加上一个全局计数的哈希。在此示例中,其他用于支持的数据结构,即图书和影片的系列与格式,都是redis集合,其中存储了所有图书或者影片的键,并以特别的系列或者格式进行分类。举例来说,Isaac Asimov 的 Foundation 属性将存储在 book:2 哈希表中。同时,book:2 又是 books:genre:sci-fiction 和 books:format:paperback 集合的成员,也是 books:sales-rank 和 all:sales-rank 有序集合中的一项。同样地,Orson Well的 Citizen Kane 存储在 film:1 哈希表中,同时也是 films:genre:drama 和 films:format:dvd 集合的成员,并且也是films:sale-rank 和all:sales-rank有序集合中的一项。

通常应用程序需要依据共同的特征获取集合的值。在图书示例中,我们尝试使用KEYS命令和 books:genre:* 模式获取所有的图书体裁。强烈建议 不要在生产环境的应用中使用redis的keys命令,这是因为redis需要遍历数据库中的每个键。采用一致的命名约定及诸如集合、哈希或者有序集合这样的数据结构,应用程序理应无须使用keys命令获取数据。虽然scan命令可以用来获取redis数据,但不应被视为keys命令的替代品。scan命令抽取一个随机的键片段,然后将提供的模式和match选项应用到此随机片段上。回顾之前的示例,下述redis-cli 程序中运行的scan命令的用法仅对小型数据库有效:

如果数据库再大一点,那么scan命令可能会无法匹配任何数据或者只返回所有匹配数据的一个子集。因此,将所有的键存储在books:genre集合中才是明智之举,这样应用程序就可以像使用索引一样,使用smembers命令快速获取所有图书体裁的键:

测试键之间的关系及它们是如何通过redis键命名约定来相互关联的,这取决于众多因素,其中包括应用程序是否直接与redis示例交互。添加单元测试来明确地检测redis应用的键分割符及命名约定,可以确保redis数据库中存储的数据精确地表达了应用程序所依赖的假设和需求。

-----------------------------------------------------------------------------------------------------------------

2. 手动创建redis模式

第一步是建立全局信纸计数器,并附加在用于出售的信纸类型和品牌的信纸前缀之后。将颜色和尺寸属性存储为stationery:{id-counter}哈希中的字段,并将信纸张数存储到另一个stationery:{id-counter}:sheets 键所对应的字符串上。

10.143.128.165:6379> incr global:stationery
1

返回的整数1将用作第一个信纸的id:

10.143.128.165:6379> hmset stationery:1 color blue width '30 cm' height '40 cm'
OK

为了将纸张和stationery:1:sheets 集合关联起来,使用increby命令:

10.143.128.165:6379> incrby stationery:1:sheets 20
20

现在,我们再次调用incr命令为第二种信纸类型生成id,填充哈希增加15张信纸:

10.143.128.165:6379> incr global:stationery
2
10.143.128.165:6379> hmset stationery:2 color red width '45 cm' height '45 cm'
OK
10.143.128.165:6379> incrby stationery:2:sheets 15
15

接下来,特定类型的信纸包裹库存存储在stationery:<stationery id>:inventory 键模式中,键对应的值是简单的整数,用来表示那种类型的信纸的可用包裹总数。

初始库存 250件包裹:

10.143.128.165:6379> set stationery:1:inventory 250
OK

当包裹销售出去后,stationery:1:inventory 键对应的整数将减去销售的包裹数量;经销商送来新的信纸包裹时,键被加上新的信纸包裹总数。

10.143.128.165:6379> decr stationery:1:inventory
249
10.143.128.165:6379> incrby stationery:1:inventory 10
259

每件包裹的销售数据存储在一个有序集合中。集合中每行以unix时间戳作为分值,并以销售数量作为值。将stationery:1:sales作为有序集合的redis键,记录一笔20美元的销售:

10.143.128.165:6379> zadd stationery:1:sales 1430861194 20.00
1

--------------------------------------------------------------------

3. 解构redis对象映射器

对node.js来说,redis对象映射器成为Nohm,通过使用JavaScript对象模型创建redis模式。使用Nohm为信纸实体建模,首先需要定义信纸的JavaScript模型的颜色、高度、宽度属性:

会产生下列redis命令:

Nohm在基础命名模式中使用冒号作为键分隔符。

1.paper:meta:version:Stationary   该redis元数据键存储信纸的字符串版本。该键的值被设置为一个随机元数据版本字符串1bf8ca.....8d65c

2.paper:idsets:Stationary 该redis集合存储了所有信纸id。该集合首先被一个负的Unix时间戳检测,然后产生了一个值为i9hiar...9rgc5的id字符串,并被添加到该集合中。该集合是用来追踪信纸对象的,随机值可以最小化重复键的问题。

3.paper:meta:idGenerator:Stationary Nohm使用该redis字符串决定生成id的方法。默认的选项产生随机字符串。递增选项则使用整数计数器。

4.paper:meta:properties:Stationary 该redis字符串存储了信纸对象的序列化json元数据。

5.paper:hash:Stationary:i9hira0.....9rgc5 信纸JavaScript对象把i9hira0.....9rgc5 作为redis键的末尾部分,将其属性值存储在redis哈希中。这些操作封装在一个事务中。

再添加第二个信纸包裹,即红色方形,45 cm高 * 45 cm 宽,初始纸张数为15.因而在数据库中会有以下redis键:

使用Nohm为信纸项目的销售建模时,要使用来自 schema.org 元数据词汇表的两个支持类,一个名为offer类,另一个名为order类。schema.org 用来表示web上的结构化数据。offer类包含了价格和可用库用,以及用于支持其他货币的priceCurency属性,默认货币单位美元。order类包含了acceptedOffer和orderDate属性,acceptedOffer属性连接到我们为信纸创建具体订单。

order类包含两个属性,分别命名为orderDate和orderedItem。

当交易发生时,Nohm会在offer、order、stationery这三者之间创建关联。在使用交易发生时间创建新的订单实例之后,Nohm使用几个redis集合为这三个不同的类之间的关系进行建模。

首先、红色信纸的哈希键为paper:hash:Offer:ia4ev....968h,并将库存级别属性设置为50,价格为15.

下一步是创建paper:relationKeys:Offer:ia4ev8....6p968h 和 paper:relations:Offer:itemOffered:Stationery:ia4ev8...968h 集合。第一个集合中存储的键对应的集合中,存储了那些通过itemOffered属性创建offer和stationery之间的关联。第二个集合通过创建具体的offer和stationery之间的具体关联,存储了所有单独的信纸ID。

当接受订单并且交易被确认时,redis paper:hash:Order:1 哈希使用订单日期属性被创建出来,同时 通过Nohm,元数据版本id使用哈希值作为属性被存储。

两个额外的集合,分别命名为paper:relationKeys:Order:1和paper:relations:Order:offer:Offer:1,创建了order和offer之间的关联。第一个集合为order存储了所有的关联连接。第二个集合为之前命令添加的order存储了特定的offer。

键过期

redis能够为键设置过期时间。通过自动化删除过期键,redis应用程序能够很好的管理数据库所使用的内存大小和使用情况,同时减少用于追踪数据库中每个键的客户端代码量。可以对redis配置文件的选项进行设置,也可以在运行时向redis数据库发送命令进行设置。

键的注意事项

redis键的大小应该受到限制,不仅因为键的大小超过1024字节会导致内存增长,大尺寸的键还会令redis实例的开发者和用户感到困惑。随着redis实例大小的增长,这些过长的键名称开始消耗更多的内存,从而挤压了正常数据所需的内存空间。

同样的,如果键名称太短,额外节省的内存可能得不偿失。因为对redis进行故障排除或者通过新的redis键添加新的功能时,会遇到各种问题。

redis的keys命令应当在万不得已时使用,因为它对redis实例造成长时间的阻塞,甚至会导致redis内存耗尽

scan命令为redis中所有的键提供了一个迭代器,可以对所有的键进行增量式调用。如果一个元素在从头到尾的迭代中不是始终存在的,那么scan命令并不保证该元素能够返回。

---------------------------------------------------------------

4. 大O符号

数学上对大O符号的定义为“象征性的表达给定函数的渐进行为”。在计算机科学和对redis中大O符号的理解的帮助下,能够通过这些命令在面对不断增长的输入时的性能表现,对redis命令做出性能上的区分。

1. O(1) 随着输入的增加而不会对时间或者处理造成变化。性能的上线是线性时间,随着输入增加不会导致性能的下降,但受算法本身复杂性的限制。

2. O(log n) 对数时间,它对每个输入进行操作,返回的结果大于O(1),但是性能等价于对n求对数。

3.O(n) 遵循尝试观念,即添加额外的单元以恒定比例的量增加处理时间。

4. O(n log n) 对数线性时间, O(log n) 作用于每一个输入之上。实际上,在O(n log n)算法中每次输入都增加了一倍以上。

5. O(n^2) 即平方时间来说,随着n的增长,时间的量也成倍增长。对于每个加倍的n来说,时间处理变为原来的4倍。O(n^2) 算法的性能在n较小的情况下也许是可以接受的,但是当n增长到一定量时,就很快变得不切实际。

6.O(2^n) 指数时间,对于每一个额外的输入,时间都会加倍。

7. O(n!) 阶乘时间,输入的n轻微的增加都会导致处理时间过高。

为自定义代码计算大O符号

根据redis文档中为每个命令提供的大O符号,可以为任何提议的基于redis的解决方案计算出一个粗略的效率估计。一个简单的方案就是对于一定级别的n将所有redis命令的大O符号相加,然后为实现代码估计大O符号,以便为整个解决方案做粗略的时间效率估计。举例来说,用redis实现的缓存只是简单的set和get调用,该解决方案的大O符号就是O(1)+O(1)=2个时间单位。

对数据结构的时间复杂度的评估不仅包括数据结构本身,还包括对数据进行采集与提取等redis命令总数的优化。

-----------------------------------------------------------

5. 回顾redis数据结构的时间复杂度

5.1 字符串

redis值中最基本的数据结构为字符串,也就是和redis键相同的数据类型。

redis有着和其他诸如memecached之类键值数据存储解决方案相似的性能特点。

在redis中,字符串并不仅仅是那些高级编程语言中包括字母数字字符的字符串,而是包含C语言(redis主要采用的编程语言)的序列化字符redis字符串中最基础的get和set命令都是O(1)操作。这使得redis作为简单的键值存储及其快速。在思考redis解决方案时,get和set命令使用起来的快速和简单不容忽视。

对于大多数redis字符串操作来说,访问和采集命令的时间复杂度要么是O(1),要么是O(n)。其中O(n)字符串命令大多是块命令,GETRANGE 、MSET、MGET。GETRANGE 命令是一种O(n)操作,其中n为返回字符串的长度。如果将该操作比作一系列小的get命令(虽然get不返回存储在键中的字符串的子串),则理解起来更直观。

10.143.128.165:6379> set organization:1 "The British Library"
OK
10.143.128.165:6379> GETRANGE organization:1 4 10 
British

在该示例中,对于set命令来说 大O符号 +1 ,同时对于getrange 来说 大O符号+6,等价于发送独立的伪get命令获取6个字符。

由于redis将所有数据作为字符串存储,特定字符串的类型信息也会被维护起来以支持 INCR/DECR 和BITSTRING 命令。对于 INCR/DECR 命令来说,存储的值是以10为基数的64位有符号整数字符串命令。如果该值被其他诸如APPEND的redis字符串命令修改过,可能会导致破坏。因而之后作用在同一键上的与整数相关的redis命令都会失败。

10.143.128.165:6379> incr new:counter
1
10.143.128.165:6379> get new:counter
1
10.143.128.165:6379> dump new:counter
"\x00\xc0\x01\x06\x00\xb0\x95\x8f6$T-o"
10.143.128.165:6379> append new:counter "a"
(integer) 2
10.143.128.165:6379> get new:counter
"1a"
10.143.128.165:6379> incr new:counter
(error) ERR value is not an integer or out of range
10.143.128.165:6379> dump new:counter
"\x00\x021a\x06\x00\x8br\x9a\x98-9\x9a\xa6"

5.2 哈希

将一个或多个字段映射到对应的值的数据结构。

在redis中,所有的哈希值必须是redis字符串,并且有唯一的字段名。字段的值是简单的redis字符串。

通过调用redis的 HGET 或者 HMGET 命令,同时传入合适的redis键和一到多个字段参数,就能返回字段的值。

对于大多数使用场景,redis哈希为HSET和HGET命令提供了很棒的 O(1) 性能。与字符串块命令类似,哈希的HGETALL、HMSET、HMGET、HKEYS、HVALS命令均为 O(n)。如果哈希非常小,那么返回所有哈希键和值的HGETALL和HMGET命令之前没有十分明显的差异。当哈希中键和值不断增长时,两者之间的差异可以让应用程序大不相同。假设哈希中有1000个字段,如果应用程序只是经常使用其中的300个,对redis调用HGETALL和HVALS的时间复杂度为O(1000),而使用HMGET的时间复杂度只有O(300)。这是因为虽然HGETALL和HMGET都是 O(n),但是对于 HMGET  其上限为所请求字段的总和而非整个哈希。哈希的总体较小时,将HMGET替换为HGETALL是增加redis性能的一种方式。对于大型哈希来说,返回大量值的 HMGET 命令在完成执行前会阻塞其他客户端接受数据,从而极大的影响redis的总体性能。在这种情况下,有针对性的 HGET 会是更好的选择。

redis哈希的值不能包括哈希、列表 及 其他数据集合结构,但是redis提供了 HINCRBY 和 HINCRBYFLOAT命令,允许将字段中存储的字符串值当做整数或者浮点数操作。如果你尝试更新字段的值 但是弄错了数据类型,redis会返回错误:

10.143.128.165:6379> HMSET weather:2 temperature 46 moisture .001
OK
10.143.128.165:6379> HINCRBY weather:2 temperature -1
(integer) 45
10.143.128.165:6379> HGET weather:2 temperature
"45"
10.143.128.165:6379> HINCRBY weather:2 moisture 1
(error) ERR hash value is not an integer
10.143.128.165:6379> HINCRBYFLOAT weather:2 moisture 1
"1.001"
10.143.128.165:6379> HGET weather:2 moisture
"1.001"

redis会根据命令来区分设置的值1是整数还是浮点数。

5.3 列表

在redis中列表是字符串的有序集合,它允许重复的字符串值。redis中的列表被更准确地标记和实现为链表。由于redis列表以链表的方式实现的,使用 LPUSH 向列表前端或者 RPUSH 向列表末尾添加条目是相对廉价的操作,表现为常数时间复杂度 O(1) .对于 LINSERTLSET 命令来说,时间复杂度是线性的 O(n),但两者有些重要的差别。 LSET  可以指定下标值来设置列表的值。由于本质上是链表,因此变量n是列表的长度,同时不管是设置列表中的第一项还是最后一项,时间复杂度均为 O(1)LINSERT 可以在参考值之前或者之后插入值,上述操作的时间复杂度为 O(n)。其中n为列表元素的个数该命令必须一致查找直到获取到参考值,最坏的情况是将值插入列表的末尾,因此 LINSERT 时间复杂度O(n),即便是特殊情况下参考值是列表当中第一个元素,使得 LINSERT 命令复杂度为O(1)。

LRANGE 时间复杂度 O(s+n),s为从列表的表头(或表尾)到偏移量位置的元素个数,这取决于列表的大小。n 代表返回的元素总数。如果想要返回整个列表(长度=10),该操作的时间复杂度 O(10+10)。

LTRIM时间复杂度O(n),n为返回给客户端的元素数量。

使用 LTRIM 结合 RPUSH 或者 LPUSH,是存储固定长度的集合的常用方法。

示例,只想保存最近7天有价值的平均温度数据:

10.143.128.165:6379> LPUSH temp:last-seven-days 30 45 50 52 49 55 51
(integer) 7
10.143.128.165:6379> LPUSH temp:last-seven-days 56
(integer) 8
10.143.128.165:6379> LTRIM temp:last-seven-days 0 6
OK
10.143.128.165:6379> LRANGE temp:last-seven-days 0 -1
1) "56"
2) "51"
3) "55"
4) "49"
5) "52"
6) "50"
7) "45"

这种模式允许我们存储最近7天的平均温度,并且当采用这种方法时,由于每次只有一个值添加到列表中,LTRIM 时间复杂度接近O(1)。

5.4 集合

redis中的集合保证了字符串值的唯一性,但是不保证这些值的顺序。redis集合实现了集合语义的并集、交集、差集,并在redis实例中将这些集合操作的结果存储为一个新的redis集合。以当前的redis集群的实现来说,并集、交集、差集 这些集合语义受到了诸多限制,并且只能以受限的方式使用。

SADD 将一到多个值添加到集合中,时间复杂度O(n),n为需要添加到集合的元素总数。

SISMEMBER 用于判断值是否为集合的成员,时间复杂度O(1)。

SMEMBERS 返回集合中所有成员的列表,时间复杂度O(n)。

集合可能有着与redis中其他数据结构相似的性能。在某些情况下,相较于哈希,集合拥有更佳的内存使用率

redis中集合特别有用的地方在于对集合 并集、交集、差集操作的支持,所有这些操作有着不同的时间复杂度,在redis集群中使用会受到限制。

SUNION 和 SUNIONSTORE 允许将多个集合的所有成员返回给客户端或者存储为redis中的新集合,时间复杂度O(n),n为所有集合中元素的总数。

SINTER 和 SINTERSTORE 命令返回集合的交集,后者会将返回的集合存储在redis中,时间复杂度O(n*m),n是最小集合的大小,m为集合的总数。

SDIFF 和 SDIFFSTORE 返回或者存储第一个集合和后续集合之间的差异,时间复杂度O(n),n为所有集合中元素的总数。

-------------------------------------------------------

6 有序集合

redis中,有序集合数据类型兼备redis列表和集合的特性。集合中的值是有序的,每个值都是唯一的。

在游戏中使用单一有序集合可以记录玩家得分,ZRANGE或者ZREVRANGE,从排行榜中获取排名靠前和靠后的玩家。

ZADD 将成员和分值一起添加到有序集合中,时间复杂度 O(log(n)),随着有序集合的大小的增加,处理时间的增加比率是一个常量。

如果有序集合中所有或部分元素的分值相同,这些值以字典字母顺序进行排序,用于文本字符串的字母排序。

将7种颜色添加到一个为colors的有序集合中:

10.143.128.165:6379> ZADD colors 0 red 0 blue 0 green 0 orange 0 yellow 0 purple 0 pink
(integer) 7

现在,通过ZRANGE命令命令将颜色以字母顺序取出:

10.143.128.165:6379> ZRANGE colors 0 -1
1) "blue"
2) "green"
3) "orange"
4) "pink"
5) "purple"
6) "red"
7) "yellow"

可以以ZREVRANGE 以字母倒叙的形式获取数据:

10.143.128.165:6379> ZREVRANGE colors 0 -1
1) "yellow"
2) "red"
3) "purple"
4) "pink"
5) "orange"
6) "green"
7) "blue"

不管哪个示例,在colors有序集合中,所有的分值都是相同的。

可以使用 ZREVRANGE 命令上带上 WITHSCORES 关键字:

10.143.128.165:6379> ZREVRANGE colors 0 -1 WITHSCORES
 1) "yellow"
 2) "0"
 3) "red"
 4) "0"
 5) "purple"
 6) "0"
 7) "pink"
 8) "0"
 9) "orange"
10) "0"
11) "green"
12) "0"
13) "blue"
14) "0"

 

LRANGEBYLEX 和 LREVRANGEBYLEX 以字典顺序获取元素,通过特定的语法指定有序集合额起止位置。

10.143.128.165:6379> ZADD myzset 0 a 0 b 0 c 0 d 0 e 0 f 0 g
(integer) 7
10.143.128.165:6379> ZRANGEBYLEX myzset - [c
1) "a"
2) "b"
3) "c"
10.143.128.165:6379> ZRANGEBYLEX myzset - (c
1) "a"
2) "b"
10.143.128.165:6379> ZRANGEBYLEX myzset [a (g
1) "a"
2) "b"
3) "c"
4) "d"
5) "e"
6) "f"

----------------------------------------------------------

7 高级有序集合操作

当redis使用集群时,并集和交集的操作只能在有序集合键被分片分片到同一哈希槽中,且运行在同一个节点上。

ZINTERSTORE 时间复杂度 O(nk)+O(mlog(m)),n为最小有序集合的大小,k为这些求交集的有序集合的总数,m为最后返回的有序集合计算结果中元素的个数。

ZUNIONSTORE 时间复杂度  O(n)+O(M log(M)),n代表所有有序集合的大小总数,m代表最终有序集合的元素总数。

大型集合和有序集合之间的性能差异 与 是否需要排序无关。

---------------------------------------------------------------------------

8 位串和位操作

redis字符串和对应命令的特殊用法 允许为redis中相对较少数量的比特使用内存高效的数据结构。

同时,取决于具体的用例场景和数据,使用集合和哈希会提供更好的性能。

在位串中,每个字节存储8位,其中位置0处为最高有效位,它被设置为0或者1。

redis的位串最大为512MB,这和redis所有的键和值的限制是一致的。

位串如此高效快速的一个原因是大多数针对它的时间复杂度为O(1)或O(n)。

使用SETBIT和GETBIT 命令,可以将位设置为0或者1,或者获取值,时间复杂度均为 O(1) 。

位串对于存储一系列连续值的二进制信息来说 及其迅速。

BITOP、BITPOS、BITCOUNT,时间复杂度 O(n) ,对位串的使用提供了强大的语义。

位串常用的用例场景是 存储用于表示 一系列顺序的键的布尔值,即0或者1。

举例,如果想在网站上追踪每日的使用情况,可以从简单的“customer:”模式开始,用来为每位客户存储用户名、哈希过的密码和电子邮件地址:

10.143.128.165:6379> INCR global:customer
(integer) 2445
10.143.128.165:6379> HMSET global:customer:2445 username mmaxwell password '49dffdfsdfdfd' email mmaxwell@gmail.com
OK

假设顾客计数从0开始,顾客mmaxwell是连续第2445位顾客。现在,为了记录mmaxwell在2016-02-11 访问了我们的网站,我们会设置 2016/02/11:usage 位串的第2445位位,如下:

10.143.128.165:6379> SETBIT 2016/02/11:usage 2445 1

如果我们想查看mmaxwell是否在那天访问了我们的网站,可以通过GETBIT命令获取存储在2445上位的值:

10.143.128.165:6379> GETBIT 2016/02/11:usage 2445
(integer) 1

查找2月11号当天的网站顾客访问统计可以简单地通过 BITCOUNT 命令达成:

10.143.128.165:6379> BITCOUNT 2016/02/11:usage
(integer) 365

在2月的这一天,我们共有365位客户访问。

假设我们正在跟踪每周顾客的使用情况,可以使用  BITOP 命令和 OR 操作,根据多个位串生成使用情况,统计的结果存储在新的键中:

10.143.128.165:6379> BITOP OR 2016/02/week2:usage 2016/02/07:usage 2016/02/08:usage 2016/02/09:usage 2016/02/10:usage 2016/02/11:usage 2016/02/12:usage 2016/02/13:usage
(integer) 306
10.143.128.165:6379> BITCOUNT 2016/02/week2:usage
(integer) 4

为了计算月度总计并将结果存储在新的键 2016/02:usage 中,可以再次执行 BITOP 命令:

10.143.128.165:6379> BITOP OR 2016/02:usage 2016/02/week2:usage 2016/02/week3:usage
(integer) 306
10.143.128.165:6379> BITCOUNT 2016/02:usage
(integer) 7

最后,整个网站的年度总计 可以针对12个月的位串再次调用BITOP OR操作:

10.143.128.165:6379> BITOP OR 2016:usage 2016/02:usage
(integer) 306
10.143.128.165:6379> BITCOUNT 2016:usage
(integer) 7

---------------------------------------------------------------------------

9. HyperLogLogs

最新的redis数据类型是一个概率数据结构,用来对集合中的唯一项做估计总数。

使用 PFADD 将一到多个元素添加到 HyperLogLogs 中的时间复杂度为O(1) ,

通过PFCOUNT 获取单个HyperLogLogs中唯一元素的总计,时间复杂度也是O(1)。

PFCOUNT 计算多个 HyperLogLogs 中元素唯一的总计,性能为O(n),n代表键的总数。

 

 

 

 

待续 P61

转载于:https://my.oschina.net/u/1862478/blog/1934188

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值