用例

讯息传递

什么是消息代理

现代软件应用程序已从单一的整体单元转变为松散耦合的服务集合,尽管这种新架构带来了许多好处,但这些服务仍然需要彼此交互,从而需要强大而有效的消息传递解决方案

Redis流既可以用作构建流式体系结构的通信渠道,又可以用作持久性数据的类似日志的数据结构,使Streams成为事件源的理想解决方案

Redis Pub Sub是一种非常轻巧的消息传递协议,旨在在系统内广播实时通知。当低延迟和巨大吞吐量至关重要时,它是传播短期消息的理想选择

Redis列表和Redis排序集是实现消息队列的基础,它们既可以直接用于构建定制解决方案,也可以通过使消息处理对于您选择的编程语言更加惯用的框架使用

构建消息代理解决方案的挑战

服务之间的通信必须可靠

当一项服务要与另一项服务进行通信时,它不可能总是立即进行通信,并且发生故障并且独立部署可能会使该服务在一段时间内不可用。对于大规模应用程序,服务是否可用或何时不可用的问题,而是频率是多少为了缓解此问题,最佳实践是限制服务之间的同步通信量,即例如通过发送HTTP S请求直接调用服务的API,并在可行的情况下优先使用持久通道,以便服务随后可以在方便时使用消息。这种异步通信的两个主要范例是事件流和消息队列

消息队列
  1. 消息队列基于可变列表,有时会通过帮助实现常见模式的工具来使用。消息队列和事件流之间有两个主要区别。消息队列使用推送类型的通信,服务在任何时候将消息推送到另一个服务的收件箱新需求需要关注流以相反的方式运行
  2. 消息包含可变状态,例如重试次数,成功处理后,消息将从系统中删除。流事件是不可变的,修剪后的历史记录通常保存在冷存储器中

Redis列表和排序集是实现此类行为的两种数据类型,均可用于构建定制解决方案以及生态系统特定框架的后端,例如芹菜蟒蛇公牛的JavaScriptSidekiq红宝石机械去和许多其他

事件流

事件流基于日志数据类型,该类型非常有效地查找其历史记录并在其末尾添加新项。这两个属性使不可变日志既是一种很好的通信原语,又是一种有效的数据存储方式

通过流进行通信不同于使用消息队列进行通信如前所述,在流被拉动时消息队列是推入的在实践中,这意味着每个服务都将写入其自己的流,而其他服务将可选地进行观察(即从其拉出),这使一对多通信成为可能比消息队列效率高得多

当一个服务要另一个服务执行操作时,消息队列最有效。在那种情况下,第二个服务的消息队列充当请求收件箱。当一个服务需要发布事件时,即多个服务感兴趣的消息,发布服务将需要将消息推送到对该事件感兴趣的每个服务的队列中实际上,大多数工具(例如企业服务总线)可以透明地执行此操作,但是为每个收件人生成和存储单独的消息副本仍然效率低下

通过反转协议,事件流在一对多的通信模式中胜过消息队列,只有原始事件的一个副本存在,并且想要访问它的任何服务都可以在事件流中寻找,即发布服务的流以其自己的速度事件流具有另一个相对于消息队列的实际优势是,您不需要预先指定事件订阅者。在消息队列中,系统需要知道向哪个队列传递事件副本,因此,如果以后添加新服务,它将仅接收带有事件的新事件。解决这个问题并不存在,甚至可以提供一个新的服务来查看完整的事件历史记录,这对于添加新的分析并仍然能够进行追溯计算非常有用,这意味着您不必立即提出每个指标将来可能需要的功能您可以跟踪现在需要的功能,并随需添加更多功能,因为您知道仍然可以看到f完整的历史记录,即使是以后添加的历史记录也是如此

存储空间必须高效

空间效率是保留消息的所有通信通道的一个受欢迎的属性,尽管事件流是基础,因为它经常用于长期信息存储,但对于事件流却是最基本的。我们上面提到,不可变的日志可以快速添加新条目和查找历史记录

Redis流是使用基数树作为基础数据结构的不可变日志的实现,每个流条目都由时间戳标识,并且可以包含任意一组字段值对同一流的条目可以具有不同的字段,但是Redis可以压缩多个共享相同模式的行中的事件这意味着,如果您的事件具有稳定的字段集,则无需为每个字段名支付存储价格,从而使您可以使用更长,更具描述性的键名,而不会遭受任何不利影响

如前所述,可以调整流以删除较旧的条目,并且经常以档案格式保存已删除的历史记录。Redis Streams的另一个功能是可以将任何中间流条目标记为已删除,以帮助遵守GDPR等法规

扩展处理吞吐量

事件流和消息队列有助于应对通讯突发,但是直接API调用的另一个问题是,在流量高峰时服务可能会不堪重负异步通信通道可以充当缓冲区,有助于缓解高峰,但处理吞吐量必须足够健壮以维持正常流量,否则系统将崩溃并且缓冲区需要无限期增长

在Redis Streams中,可以通过使用使用者组读取流来提高处理吞吐量,这是同一使用者组的一部分的读者可以以互斥的方式查看消息。当然,单个流可以具有多个使用者组。为每个服务创建一个单独的使用者组,以便每个服务随后可以启动多个读取器实例以根据需要增加并行度

消息传递语义必须清楚

异步通信时,必须考虑可能的故障情况,例如,服务实例可能在处理消息时崩溃或失去连接,因为通信故障是不可避免的,因此消息传递系统将自身分为两类最多一次至少一次传递一些消息传递系统声称仅提供一次传递,但还不完整,在任何可靠的消息传递系统中,有时为了避免失败,消息都需要传递多次,这是通过不可靠网络进行通信所不可避免的特征

为了正确处理故障,参与系统的所有服务必须能够执行幂等消息处理。幂等意味着在重复发送消息的情况下系统的状态不会改变。幂等通常是通过应用任何必要的状态更改并保存最后一条消息来实现的。原子处理,例如在事务中进行处理这样,在发生故障的情况下,故障将永远不会保持不一致的状态,并且读者可以通过检查新消息标识符是否在最后处理的消息之前来判断给定消息是否已被处理信息

Redis流成为可靠的流媒体通信渠道是至少一次系统当通过消费者组读取流时,Redis会记住将哪个事件调度给哪个消费者,然后消费者有责任正确地确认消息已成功处理。当消费者死亡时,事件可能会停留以解决该问题。一种检查未决消息状态以及在必要时将事件重新分配给另一个使用者的方法

我们在上面指出,事务和原子操作是实现幂等的主要方法。Redis Transactions和Lua脚本编写允许包含全部或不包含事务语义的多个命令的组合

Redis Pub Sub是一个最多一次允许发布者将消息广播到一个或多个通道的消息传递系统。更准确地说,Redis Pub Sub设计用于在低延迟至关重要的实例之间进行实时通信,因此实例不具有任何形式的持久性或确认性。尽可能精简的实时消息传递系统,非常适合每毫秒都很重要的金融和游戏应用

为什么使用Redis Enterprise进行消息传递

Redis Enterprise基于没有共享的对称架构允许数据集大小线性无缝地增长,而无需更改应用程序代码

Redis Enterprise提供了多种高可用性和地理分布模型,可在需要时为用户提供本地延迟

每秒写入或每秒多个持久性AOF和快照,而不会影响性能,可确保您不必在故障后重建数据库服务器

支持巨大的数据集通过使用对内存的智能分层访问RAM持久性内存或闪存,可以确保您可以扩展数据集以满足用户的需求,而不会显着影响性能

如何在Redis Enterprise中使用Pub Sub

Redis Streams和Pub Sub具有跨不同编程语言的稳定API,因此以下Python示例可以轻松转换为您选择的语言

连接到Redis

导入redis
连接到本地Redis实例
r redis Redis主机localhost端口db

写入流

事件eventType购买金额项目ID XXX
r xadd流键事件
这意味着redis会自动生成事件ID

直接读取流

最后一个ID表示仅新消息
而真
事件r xread流密钥最后一个id块计数
在事件中
打印新事件数量e数量
最后一个ID

通过消费群读取流

首先阅读任何潜在的未决事件
以前没有被认可的
由于崩溃指示未决事件
待处理的r xreadgroup服务使用者A流密钥
待处理的ID
待定中的e
打印f旧事件发现数量e数量
待处理的ID附加e ID
将待处理事件标记为已处理
r xack流密钥服务未决ID

现在我们已经处理了所有以前的事件
开始询问新事件表明只有新事件
而真
事件r xreadgroup服务使用者流密钥计数
事件编号
在事件中
打印新事件数量e数量
事件ID附加e ID
r xack流密钥服务事件ID
如果我们在重新加载前崩溃
我们将重试此消息批处理

处理一些事件,以确认并自动应用更改

而真
事件r xreadgroup服务使用者流密钥计数
事件编号

发起redis交易
交易多
在事件中
交易增加额f项目e项目id总e金额
事件ID附加e ID
事务Xack流密钥服务事件ID
交易执行
如果在提交交易前崩溃
其他操作将确保一致性

在Pub Sub上发布

将消息发布到redis频道
r发布redis hello world

在Pub Sub上订阅频道

订阅订阅
在订阅回报
而真
信息子获取消息
打印新消息信息数据

在Pub Sub上订阅模式

订阅订阅
此订阅将返回消息
从所有以红色开头的频道
订阅红色
而真
信息子获取消息
在通道msg通道msg数据中打印f条新消息