当前位置: 首页 > >

redis 深度历险:核心原理与应用实践_Redis深度历险:核心原理和应用实践(阅读笔记)5...

发布时间:

拓展 1 : 耳听八方 ?? Stream

Redis5.0 提供了一个新的数据结构 Stream,它是一个支持多播的可持久化的消息队列。






Redis Stream 的结构如上图所示,它有一个消息链表,将所有加入的消息都串起来,每个消息都有一个唯一的 ID 和对应的内容。消息是持久化的,Redis *艉螅谌莼乖凇


每个 Stream 都有唯一的名称,它就是 Redis 的 key,在我们首次使用 xadd 指令追加消息时自动创建。


每个 Stream 都可以挂多个消费组,每个消费组会有个游标 last_delivered_id 在 Stream数组之上往前移动,表示当前消费组已经消费到哪条消息了。每个消费组都有一个 Stream内唯一的名称,消费组不会自动创建,它需要单独的指令 xgroup create 进行创建,需要指定从 Stream 的某个消息 ID 开始消费,这个 ID 用来初始化 last_delivered_id 变量。


每个消费组 (Consumer Group) 的状态都是独立的,相互不受影响。也就是说同一份Stream 内部的消息会被每个消费组都消费到。


同一个消费组 (Consumer Group) 可以挂接多个消费者 (Consumer),这些消费者之间是竞争关系,任意一个消费者读取了消息都会使游标 last_delivered_id 往前移动。每个消费者有一个组内唯一名称。


消费者 (Consumer) 内部会有个状态变量 pending_ids,它记录了当前已经被客户端读取的消息,但是还没有 ack。如果客户端没有 ack,这个变量里面的消息 ID 会越来越多,一旦某个消息被 ack,它就开始减少。这个 pending_ids 变量在 Redis 官方被称之为 PEL,也就是 Pending Entries List,这是一个很核心的数据结构,它用来确保客户端至少消费了消息一次,而不会在网络传输的中途丢失了没处理。


消息 ID


消息 ID 的形式是 timestampInMillis-sequence,例如 1527846880572-5,它表示当前的消息在毫米时间戳 1527846880572 时产生,并且是该毫秒内产生的第 5 条消息。消息 ID 可以由服务器自动生成,也可以由客户端自己指定,但是形式必须是整数-整数,而且必须是后面加入的消息的 ID 要大于前面的消息 ID。


消息内容


消息内容就是键值对,形如 hash 结构的键值对。


增删改查


1、xadd 追加消息


2、xdel 删除消息,这里的删除仅仅是设置了标志位,不影响消息总长度


3、xrange 获取消息列表,会自动过滤已经删除的消息


4、xlen 消息长度


5、del 删除 Stream


独立消费


可以在不定义消费组的情况下进行 Stream 消息的独立消费,当 Stream 没有新消息时,甚至可以阻塞等待。Redis 设计了一个单独的消费指令 xread,可以将 Stream 当成普通的消息队列 (list) 来使用。使用 xread 时,我们可以完全忽略消费组 (Consumer Group)的存在,就好比 Stream 就是一个普通的列表 (list)。


客户端如果想要使用 xread 进行顺序消费,一定要记住当前消费到哪里了,也就是返回的消息 ID。下次继续调用 xread 时,将上次返回的最后一个消息 ID 作为参数传递进去,就可以继续消费后续的消息。


创建消费组


Stream 通过 xgroup create 指令创建消费组 (Consumer Group),需要传递起始消息 ID 参数用来初始化 last_delivered_id 变量。


消费


Stream 提供了 xreadgroup 指令可以进行消费组的组内消费,需要提供消费组名称、消费者名称和起始消息 ID。同 xread 一样,也可以阻塞等待新消息。


读到新消息后,对应的消息 ID 就会进入消费者的 PEL(正在处理的消息) 结构里,客户端处理完毕后使用 xack指令通知服务器,本条消息已经处理完毕,该消息 ID 就会从 PEL 中移除。


Stream 消息太多怎么办?


Redis提供了一个定长 Stream 功能。在 xadd 的指令提供一个定长长度 maxlen,就可以将老的消息干掉,确保最多不超过指定长度。


消息如果忘记 ACK 会怎样?


Stream 在每个消费者结构中保存了正在处理中的消息 ID 列表 PEL,如果消费者收到了消息处理完了但是没有回复 ack,就会导致 PEL 列表不断增长,如果有很多消费组的话,那么这个 PEL 占用的内存就会放大。


PEL 如何避免消息丢失?


在客户端消费者读取 Stream 消息时,Redis 服务器将消息回复给客户端的过程中,客户端突然断开了连接,消息就丢失了。但是 PEL 里已经保存了发出去的消息 ID。待客户端重新连上之后,可以再次收到 PEL 中的消息 ID 列表。不过此时 xreadgroup 的起始消息ID 不能为参数>,而必须是任意有效的消息 ID,一般将参数设为 0-0,表示读取所有的PEL 消息以及自 last_delivered_id 之后的新消息。


Stream 的高可用


Stream 的高可用是建立主从复制基础上的,它和其它数据结构的复制机制没有区别,也就是说在 Sentinel 和 Cluster 集群环境下 Stream 是可以支持高可用的。不过鉴于 Redis 的指令复制是异步的,在 failover 发生时,Redis 可能会丢失极小部分数据,这点 Redis 的其它数据结构也是一样的。


分区 Partition


Redis 的服务器没有原生支持分区能力,如果想要使用分区,那就需要分配多个Stream,然后在客户端使用一定的策略来生产消息到不同的 Stream。


拓展 2 : 无所不知 ?? Info 指令

Info 指令显示的信息非常繁多,分为 9 大块,每个块都有非常多的参数,这 9 个块分别是:


1、Server 服务器运行的环境参数


2、Clients 客户端相关信息


3、Memory 服务器运行内存统计数据


4、Persistence 持久化信息


5、Stats 通用统计数据


6、Replication 主从复制相关信息


7、CPU CPU 使用情况


8、Cluster 集群信息


9、KeySpace 键值对统计数量信息






拓展 3 : 拾遗漏补 ?? 再谈分布式锁

Redlock


拓展 4 : 朝生暮死 ?? 过期策略

Redis 所有的数据结构都可以设置过期时间,时间一到,就会自动删除。


过期的 key


redis 会将每个设置了过期时间的 key 放入到一个独立的字典中,以后会定时遍历这个字典来删除到期的 key。除了定时遍历之外,它还会使用惰性策略来删除过期的 key,所谓惰性策略就是在客户端访问这个 key 的时候,redis 对 key 的过期时间进行检查,如果过期了就立即删除。定时删除是集中处理,惰性删除是零散处理。


定时扫描策略


Redis 默认会每秒进行十次过期扫描,过期扫描不会遍历过期字典中所有的 key,而是采用了一种简单的贪心策略。


    从过期字典中随机 20 个 key;删除这 20 个 key 中已经过期的 key;如果过期的 key 比率超过 1/4,那就重复步骤 1;

同时,为了保证过期扫描不会出现循环过度,导致线程卡死现象,算法还增加了扫描时间的上限,默认不会超过 25ms。如果有很多key在同一时间过期,name卡顿时间就很长,建议让key的过期时间随机分散开。


从库的过期策略


从库不会进行过期扫描,从库对过期的处理是被动的。主库在 key 到期时,会在 AOF文件里增加一条 del 指令,同步到所有的从库,从库通过执行这条 del 指令来删除过期的key。因为指令同步是异步进行的,所以主库过期的 key 的 del 指令没有及时同步到从库的话,会出现主从数据的不一致,主库没有的数据在从库里还存在,比如上一节的集群环境分布式锁的算法漏洞就是因为这个同步延迟产生的。


拓展 5 :优胜劣汰 ?? LRU

当 Redis 内存超出物理内存限制时,内存的数据会开始和磁盘产生频繁的交换 (swap)。


Redis 提供了配置参数 maxmemory 来限制内存超出期望大小。当实际内存超出 maxmemory 时,Redis 提供了几种可选策略 (maxmemory-policy) 来让用户自己决定该如何腾出新的空间以继续提供读写服务:noeviction、volatile-lru、volatile-ttl 、volatile-random、allkeys-lru、allkeys-random。


拓展 6 : *波缓进 ?? 懒惰删除
拓展 7 : 妙手仁心 ?? 优雅地使用 Jedis





相关资源:Redis 深度历险核心原理应用实践-最新版



友情链接: