redis stream(未完)

8 分钟读完

写这个东西的原因

前些天,一个小伙伴在学习Redis5的新数据类型Stream,他选择了一个翻译的文档。我翻了翻,基本上是机器翻译。于是找了原文来看,想自己翻译一下。结果花费了半天时间,发现官方的那个文档本身写的也有问题。

我个人学习的方式是比较慢的,一般俩个方向来入手:

  • 【主要方式】从使用者的角度来考虑,作者会设计哪些主要的特性和接口
  • 从实现的角度来考虑,它怎么样的实现更简单,更强健

一个典型的例子就是KafkaClient API,它有low和high区分,实际上就是“面向机器实现”和“面向用户调用”的区分。
这样的学习方式比较麻烦,尽量的多看一点议论性的blog。效果的确很好,就是英文比较吃力。

这个文档就是从使用者的角度来说,Stream可以怎么样使用,通过哪些API。

从一个需求案例说起

假定我们有个需求,一个系统下有这些东西:

  • 一个分布式任务模块,下面有多个执行节点,每个任务只能被某个节点处理。
    —— 很明显,redis的List适合做这个,List的数据只能被POP一次。
  • 一个config配置模块,如果配置信息发生改变,需要每个节点都知道这个事件,然后大家刷新数据。
    —— 很自然,Redis的Pub/Sub适合做这个,消息会同时发给全部的监听节点。

很快就做好了,跑的不错。

然后我们有了个新的需求:

  • 需要监控这些指令和事件。客户会追问,每个指令什么时候来的,什么时候被处理。
    —— 对应的解决办法就是增加日志,然后检查日志。好消息是:日志只要生成一份就可以了,可以给好几个场景检查。

但是为什么Redis的数据本身不能像日志文件那样的可以重复消费呢?
有人会说,kafka可以支持这个。
Redis也说:5.0里的Steams也支持这个了,就是完全参考了Kafka的。

Kafka的设计思路特别简单神奇,它的数据结构本质上是一个非常长的数组,

  • 这个数组的编辑方式只有从后面追加,和从前面截断放弃
    ——连续读写发挥了机械磁盘的最好性能。
  • 读取方式是,消费者保存自己的数组位置,
    • 一组消费者共享一个位置,就可以实现组内不重复消费;
    • 不同组的位置相互不影响,消费行为是完全隔离的,也就实现了重复消费。

我们对Redis的三个数据类型进行对比:

类型 多人重复消费 可以保留
List 否,每条数据只能被某一个消费者获得 是,未被消费的数据一直保留
Pub/Sub 是,数据会发给每个消费者 否,消息发出的瞬间就没有了
Stream 都支持
一个group中的消费者得到的数据是不同的;
不同group的消费相互隔离
是,而且数据被消费后依然存在

这个文档就是针对Stream的使用(主要是数据读写)。

更进一步的概念,有兴趣的可以去找TimeSeriesDB(TSDB)看看

怎么样看文档

看文档的时候,最好能一边看一边实践,这里准备了一些脚本:

  • 批量加入stream数据

这个命令向名为s1的stream中插入100个 f1=v1 这样的数据

1
EVAL 'for i = 1, ARGV[1] do redis.call("xadd", KEYS[1], "*", "f1", "v1") ; end' 1 s1 100

可以参数设定插入的stream名称和数据条数

  • 批量加入stream数据——简化版

这个命令向s1中添加3000个数据。

1
EVAL 'for i = 1, 3000 do redis.call("xadd", "s1", "*", "f1", "v1") ; end' 0

案例——任务体系

需求描述

做一个任务分发系统

  • (基础)一个任务只能被执行一次
  • 系统可以随时加入更多的节点,或者减少
  • 可以随时复查系统内部的任务

设计

API

编辑

XADD

1
XADD key ID field string [field string ...]
  • 说明:向stream中插入一条数据,其中可以包含多组field-value值对
  • 参数
    • key:stream的名称
    • ID:插入数据的ID,一般建议为*,由系统自动生成
    • field + string:数值对,可以有多组
  • 返回值:string,插入的数据ID
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
redis> XADD mystream * name Sara surname OConnor
"1542031898153-0"
redis> XADD mystream * field1 value1 field2 value2 field3 value3
"1542031898155-0"
redis> XLEN mystream
(integer) 2
redis> XRANGE mystream - +
1) 1) "1542031898153-0"
   2) 1) "name"
      2) "Sara"
      3) "surname"
      4) "OConnor"
2) 1) "1542031898155-0"
   2) 1) "field1"
      2) "value1"
      3) "field2"
      4) "value2"
      5) "field3"
      6) "value3"

XDEL(不重要)

1
XDEL key ID [ID ...]
  • 说明:从stream中删除指定ID的条目
  • 参数
    • key
    • ID 要删除的entry的ID
  • 返回:int,删除的数据条数

XTRIM

1
XTRIM key MAXLEN [~] count
  • 说明:给stream限定一个容量,超出这个容量范围的 数据会被删除
  • 参数
    • key
    • MAXLEN,关键字
    • [~],关键字,可选项,可以理解为约等于,使用这个关键字后, trim后的结果不是严格的等于指定的数量,而是允许超出几十。官方强烈建议使用,因为stream内部数据结构的特性,这样的性能比较好。
      The ~ argument between the MAXLEN option and the actual count means that the user is not really requesting that the stream length is exactly 1000 items, but instead it could be a few tens of entries more, but never less than 1000 items. When this option modifier is used, the trimming is performed only when Redis is able to remove a whole macro node. This makes it much more efficient, and it is usually what you want.
    • 限制的数量
  • 返回值:删除的条目数量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
127.0.0.1:6379> EVAL 'for i = 1, 3000  do redis.call("xadd", "s1", "*", "f1", "v1") ; end' 0
(nil)
127.0.0.1:6379> XLEN s1
(integer) 3000
127.0.0.1:6379> XTRIM s1 maxlen ~ 1000
(integer) 1919
127.0.0.1:6379> XLEN s1
(integer) 1081
127.0.0.1:6379> XTRIM s1 maxlen ~ 1000
(integer) 0
127.0.0.1:6379> XTRIM s1 maxlen ~ 1000
(integer) 0
127.0.0.1:6379> XLEN s1
(integer) 1081
127.0.0.1:6379> XTRIM s1 maxlen 1000
(integer) 81
127.0.0.1:6379> XLEN s1
(integer) 1000

读取

XRANGE和XREVRANGE

1
2
XRANGE key start end [COUNT count]
XREVRANGE key start end [COUNT count]

ID排序,从大到小或从小到大获取一个范围内的记录。

  • 说明:在stream中获取一个范围值
  • 参数:
    • key
    • start和end:开始和结束的ID,
      • 可以对start使用-,表示最小ID,0-0
      • 可以对end使用+,表示最大ID,18446744073709551615-18446744073709551615
    • [COUNT count] 可选参数,指定返回的数据数量不超过这个限度

待续

标签:

分类:

更新时间:

留下评论