使用Redis作为时间序列数据库:原因及方法(3)

如果我们能够预先知道所有可能出现的事件类型,我们就能够对每种类型分别调用以上的count_type()函数,并构建出之前在count_types()中所创建的表。而如果我们无法预先知道所有可能会出现的事件类型,或是有可能在未来出现新的事件类型,我们将可以将每种类型加入一个集合(Set)结构中,并在之后使用这个集合以发现所有的事件类型。以下是经我们修改后的记录事件函数。

def record_event_types(conn, event):
    id = conn.incr('event:id')
    event['id'] = id
    event_key = 'event:{id}'.format(id=id)
    type_key = 'events:{type}'.format(type=event['type'])

ref = {id: event['timestamp']}
pipe = conn.pipeline(True)
pipe.hmset(event_key, event)
pipe.zadd('events', **ref)
pipe.zadd(type_key, **ref)
pipe.sadd('event:types', event['type'])
pipe.execute()

如果某个时间范围内存在大量的事件,那么新的count_types_fast()函数将比旧的count_types()函数执行更快,主要原因在于ZCOUNT命令比起从哈希中获取每个事件类型速度更快。

以Redis作为数据存储

虽然Redis自带的分析工具及其命令和Lua脚本非常灵活并且性能出色,但某些类型的时间序列分析还能够从特定的计算方法、库或工具中受益。对于这些情形来说,将数据保存在Redis中仍然是一种非常有意义的做法,因为Redis对于数据的存取非常快。

举例来说,对于一支股票来说,整个10年的成交金额数据按照每分钟取样也最多不过120万条数据,这点数据能够轻易地保存在Redis中。但如果要通过Redis中的Lua脚本对数据执行任何复杂的函数,则需要对现有的优化库进行移植或是调试,让他们在Redis中也实现相同的功能。而如果使用Redis进行数据存储,你就可以获取时间范围内的数据,将他们保存在已有的经过优化的内核中,以计算不断变化的平均价格、价格波动等等。

那么为什么不选用一种关系型数据库作为替代呢?原因就在于速度。Redis将所有数据都保存在RAM中,并且对数据结构进行了优化(正如我们所举的有序集合的例子一样)。在内存中保存数据及经过优化的数据结构的结合在速度上不仅比起以SSD为存储介质的数据库快了3个数量级,并且对于一般的内存键值存储系统、或是在内存中保存序列化数据的系统也快了1至2个数量级。

结论及后续

当使用Redis进行时间序列分析,乃至任何类型的分析时,一种合理的方式是记录不同事件的某些通用属性与数值,保存在一个通用的地址,以便于搜索包含这些通用属性与数值的事件。我们通过为每个事件类型实现对应的有序集合实现了这一点,并且也提到了集合的使用。虽然这篇文章主要讨论的是有序集合的应用,但Redis中还存在着更多的结构,在分析工作中使用Redis还存在其他许多不同的选择。除了有序集合与哈希之外,在分析工作中还有一些常用的结构,包括(但不限于):位图、数组索引的字节字符串、HyperLogLogs、列表(List)、集合,以及很快将发布的基于地理位置索引的有序集合命令 6 。

在使用Redis时,你会不时地重新思索如何为更特定的数据访问模式添加相关的数据结构。你所选择的数据保存形式既为你提供了保存能力,也限定了你能够执行的查询的类型,这一点几乎总是不变的。理解这一点很重要,因为与传统的、更为人熟悉的关系型数据库不同,在Redis中可用的查询与操作受限于数据保存的类型。

在看过了分析时间序列数据的这些示例之后,你可以进一步阅读《Redis in Action》这本书第7章中关于通过创建索引查找相关数据的各种方法,可以在RedisLabs.com的eBooks栏目中找到它。而在《Redis in Action》一书的第8章中提供了一个近乎完整的、类似于Twitter的社交网络的实现,包括关注者、列表、时间线、以及一个流服务器,这些内容对于理解如何使用Redis保存时间序列中的时间线及事件以及对查询的响应是一个很好的起点。

1 如果你启动了lua-time-limit这一配置选项,并且脚本的执行时间超过了配置的上限,那么只读的脚本也可能会被打断。

2 当分数相同时,将按照成员本身的字母顺序对于项目进行排序。

3 在本文中,我们通常使用冒号作为操作Redis数据时对名称、命名空间以及数据的分割符,但你也可以随意选择任何一种符号。其他Redis用户可能会选择句号“.”或分号“;”等作为分割符。只要选择一种在键或数据中通常不会出现的字符,就是一种比较好的做法。

4 ZRANGE及ZREVRANGE提供了基于排序位置从有序集合中获取元素的功能,ZRANGE的最小分数索引为0,而ZREVRANGE的最大分数索引为0。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/cfdd0e78bc86b64f923c0877d24edd97.html