首页 文章 精选 留言 我的

精选列表

搜索[X3D技术],共10000篇文章
优秀的个人博客,低调大师

Apache Pulsar 技术系列 - Pulsar 总览

Apache Pulsar 是一个多租户、高性能的服务间消息传输解决方案,数据持久化依赖 Apache BookKeeper 实现,支持多租户、低延时、读写分离、跨地域复制、快速扩容、灵活容错等特性。本文将从以下几个方面为大家介绍 Apache Pulsar的设计原理和特性。 1、Apache Pulsar 架构 2、架构设计的优势 3、Pulsar 特性 4、总结 Apache Pulsar 架构 存储计算分离 Apache Pulsar 是 Pub/Sub 模型的消息系统,并且从设计上做了存储和计算的分离,如图一所示。 图一 Pulsar 架构 Apache Pulsar 主要包括 Broker, Apache BookKeeper, Producer, Consumer等组件。 Broker:无状态服务层,负责接收和传递消息,集群负载均衡等工作,Broker 不会持久化保存元数据,因此可以快速的上、下线。 Apache BookKeeper:有状态持久层,由一组名为 Bookie 的存储节点组成,持久化地存储消息。 Producer :数据生产者,负责发布数据到 Topic。 Consumer:数据消费者,负责从 Topic 订阅数据。 除了上述的组件之外,Apache Pulsar 还依赖 Zookeeper 作为元数据存储。与传统的消息系统相比,Apache Pulsar 在架构设计上采用了计算与存储分离的模式,Pub/Sub 相关的计算逻辑在 Broker 上完成,数据存储在 Apache BookKeeper 的 Bookie 节点上。 分片存储 除了存储、计算解耦分离的设计之外,Apache Pulsar 在存储设计上也不同于传统 MQ 的分区数据本地存储的模式,采用的是分片存储的模式,存储粒度比分区更细化、存储负载更均衡。Apache Pulsar 中的每个 Topic 分区本质上都是存储在 Apache BookKeeper 中的分布式日志。Topic 可以有多个分区,分区数据持久化时,分区是逻辑上的概念,实际存储的单位是分片(Segment)的,如图二,一个分区 Topic1-Part2 的数据由多个 Segment 组成, 每个 Segment 作为 Apache BookKeeper 中的一个 Ledger,均匀分布并存储在 Apache BookKeeper 群集中的多个 Bookie 节点中, 每个 Segment 具有 3 个副本。 图二 Pulsar 分片存储 下面可以通过图三来看分区和分片存储的区别。 图三 分片存储和分区存储 架构设计的优势 Apache Pulsar 计算与存储分离的架构,以及分片存储的设计为 Apache Pulsar 带来了相比于传统基于分区存储 MQ 的一些优势: Broker 和 Bookie 相互独立,方便实现独立的扩展以及独立的容错。 Broker 无状态,便于快速上、下线,更加适合于云原生场景。 分区存储不受限于单个节点存储容量。 分区数据分布均匀。 ... 可扩展性 由于消息服务层和持久存储层是分开的,因此 Apache Pulsar 可以独立地扩展存储层和服务层。 Broker 扩展 在 Pulsar 中 Broker 是无状态的,可以通过增加节点的方式实现快速扩容。当需要支持更多的消费者或生产者时,可以简单地添加更多的 Broker 节点来满足业务需求。Pulsar 支持自动的分区负载均衡,在 Broker 节点的资源使用率达到阈值时,会将负载迁移到负载较低的 Broker 节点,这个过程中分区也将在多个 Broker 节点中做平衡迁移,一些分区的所有权会转移到新的Broker节点。 Bookie扩展 存储层的扩容,通过增加 Bookie 节点来实现。通过资源感知和数据放置策略,流量将自动切换到新的 Bookie 节点中,整个过程不会涉及到不必要的数据搬迁,即不需要将旧数据从现有存储节点重新复制到新存储节点。 图四 Bookie 扩容 如图四所示,起始状态有四个存储节点,Bookie1, Bookie2, Bookie3, Bookie4,以 Topic1-Part2为例,当这个分区的最新的存储分片是 SegmentX 时,对存储层扩容,添加了新的 Bookie 节点,BookieX,BookieY,那么在存储分片滚动之后,新生成的存储分片, SegmentX+1,SegmentX+2,会优先选择新的 Bookie 节点(BookieX,BookieY)来保存数据。 容错 得益于计算与存储分离以及分片存储的设计,Pulsar 可以实现独立、灵活的容错。 Broker 容错 当 Broker 节点失败时, 以图五为例,当存储分片滚动到 SegmentX 时,Broker2 节点失败,此时生产者和消费者向其他的Broker发起请求,这个过程会触发分区的所有权转移,即将 Broker2 拥有的分区 Topic1-Part2 的所有权转移到其他的 Broker(Broker3)。在 Apache Pulsar 中数据存储和数据服务分离,所以新 Broker 接管分区的所有权时,它不需要复制 Partiton 的数据。新的分区 Owner(Broker3)会产生一个新的分片 SegmentX+1, 如果有新数据到来,会存储在新的分片Segment x+1上,不会影响分区的可用性。 图五 Broker 容错 Bookie容错 当 Bookie 节点失败时,如图六所示, 假设 Bookie 2 上的 Segment 4 损坏。Apache BookKeeper Auditor 会检测到这个错误并进行复制修复。Apache BookKeeper 中的副本修复是 Segment 级别的多对多快速修复,BookKeeper 可以从 Bookie 3 和 Bookie 4 读取 Segment 4 中的消息,并在 Bookie 1 处修复 Segment 4。如果是 Bookie 节点故障,这个 Bookie 节点上所有的 Segment 会按照上述方式复制到其他的Bookie节点。所有的副本修复都在后台进行,对Broker和应用透明,Broker 会产生新的Segment 来处理写入请求,不会影响分区的可用性。 图六 Bookie 容错 无限制的分区存储 分片存储解决了分区容量受单节点存储空间限制的问题,当容量不够时,可以通过扩容 Bookie 节点的方式支撑更多的分区数据,也解决了分区数据倾斜问题,数据可以均匀的分配在 Bookie 节点上。Broker 和 Bookie 灵活的容错以及无缝的扩容能力让 Apache Pulsar 具备非常高的可用性。 Pulsar 特性 基于上述的设计特点,Pulsar 提供了很多特性,以下做简要的介绍。 读写分离 Pulsar另外一个有吸引力的特性是提供了读写分离的能力,读写分离保证了在有大量滞后消费(磁盘IO会增加)时,不会影响服务的正常运行,尤其是不会影响到数据的写入。读写分离的能力由 Apache BookKeeper 提供,简单说一下 Bookie 存储涉及到的概念: Journals:Journal 文件包含了 BookKeeper事务日志,在 Ledger 更新之前,Journal 保证描述更新的事务写入到 Non-volatile 的存储介质上。 Entry logs:Entry 日志文件管理写入的 Entry,来自不同 ledger 的 entry 会被聚合然后顺序写入。 Index files:每个 Ledger都有一个对应的索引文件,记录数据在 Entry 日志文件中的 Offset 信息。 Entry 的读写入过程如图七所示,数据的写入流程: 数据首先会写入 Journal,写入 Journal 的数据会实时落到磁盘。 然后,数据写入到 Memtable ,Memtable 是读写缓存。 写入 Memtable 之后,对写入请求进行响应。 Memtable 写满之后,会 Flush 到 Entry Logger 和 Index cache,Entry Logger 中保存了数据,Index cache 保存了数据的索引信息,然后由后台线程将 Entry Logger 和 Index cache 数据落到磁盘。 数据的读取流程: 如果是 Tailing read 请求,直接从 Memtable 中读取 Entry。 如果是 Catch-up read(滞后消费)请求,先读取 Index信息,然后索引从 Entry Logger 文件读取 Entry。 图七 Bookie的数据写入和读取 一般在进行 Bookie 的配置时,会将 Journal 和Ledger 存储磁盘进行隔离,减少 Ledger 对于 Journal写入的影响,并且推荐 Journal 使用性能较好的 SSD 磁盘,读写分离主要体现在: 写入 Entry 时,Journal 中的数据需要实时写到磁盘,Ledger的数据不需要实时落盘,通过后台线程批量落盘,因此写入的性能主要受到 Journal 磁盘的影响。 读取 Entry 时,首先从 Memtable 读取,命中则返回;如果不命中,再从 Ledger 磁盘中读取,所以对于 Catch-up read 的场景,读取数据会影响 Ledger 磁盘的 IO,对 Journal 磁盘没有影响,也就不会影响到数据的写入。 所以,数据写入是主要是受 Journal 磁盘的负载影响,不会受Ledger 磁盘的影响。另外,Segment 存储的多个副本都可以提供读取服务,相比于主从副本的设计,Apache Pulsar 可以提供更好的数据读取能力。通过以上分析,Apache Pulsar 使用 Apache BookKeeper 作为数据存储,可以带来下列的收益: 支持将多个 Ledger 的数据写入到同一个 Entry logger 文件,可以避免分区膨胀带来的性能下降问题。 支持读写分离,可以在滞后消费场景导致磁盘IO上升时,保证数据写入的不受影响。 支持全副本读取,可以充分利用存储副本的数据读取能力。 多种消费模型 Apache Pulsar 提供了多种订阅方式来消费消息,分为三种类型:独占(Exclusive),故障切换(Failover)或共享(Share)。 图八 消费模型 Exclusive 独占订阅:在任何时间,一个消费者组(订阅)中有且只有一个消费者来消费 Topic 中的消息。 Failover 故障切换:多个消费者(Consumer)可以附加到同一订阅。但是,一个订阅中的所有消费者,只会有一个消费者被选为该订阅的主消费者。其他消费者将被指定为故障转移消费者。当主消费者断开连接时,分区将被重新分配给其中一个故障转移消费者,而新分配的消费者将成为新的主消费者。发生这种情况时,所有未确认(ack)的消息都将传递给新的主消费者。 Share 共享订阅:使用共享订阅,在同一个订阅背后,用户按照应用的需求挂载任意多的消费者。订阅中的所有消息以循环分发形式发送给订阅背后的多个消费者,并且一个消息仅传递给一个消费者。当消费者断开连接时,所有传递给它但是未被确认(ack)的消息将被重新分配和组织,以便发送给该订阅上剩余的剩余消费者。 多种ACK模型 消息确认(ACK)的目的就是保证当发生故障后,消费者能够从上一次停止的地方恢复消费,保证既不会丢失消息,也不会重复处理已经确认(ACK)的消息。在 Pulsar 中,每个订阅中都使用一个专门的数据结构--游标(Cursor)来跟踪订阅中的每条消息的确认(ACK)状态。每当消费者在分区上确认消息时,游标都会更新。Pulsar 提供两种消息确认方法: 单条确认(Individual Ack),单独确认一条消息。被确认后的消息将不会被重新传递。 和累积确认(Cumulative Ack),通过累积确认,消费者只需要确认它收到的最后一条消息。 图九说明了单条确认和累积确认的差异(灰色框中的消息被确认并且不会被重新传递)。对于累计确认,M12 之前的消息被标记为 Acked。对于单独进行 ACK,仅确认消息 M7 和 M12, 在消费者失败的情况下,除了 M7 和 M12 之外,其他所有消息将被重新传送。 图九 ACK模型 跨地域复制 Apache Pulsar 的跨地域复制机制(Geo-Replication)提供了一种全连接的异步复制,可以满足多个数据中心数据同步的使用场景。 图十 Geo-replication 如图十所示,有三个 Apache Pulsar 集群,分布于北京、上海和广州,用户创建的一个 Topic T1 设置了跨越三个数据中心做互备。在三个数据中心中,分别有三个生产者:P1、P2、P3,它们往主题 T1 中发布消息;有两个消费者:C1、C2,订阅了这个主题,接收主题中的消息。当消息由本数据中心的生产者发布成功后,会立即复制到其他两个数据中心。消息复制完成后,消费者不仅可以收到本数据中心产生的消息,也可以收到从其他数据中心复制过来的消息。 总结 Pulsar 采用了计算、存储分离的设计,并且存储在逻辑上分区,物理上分片,具有一些传统类 kafka 的 MQ 所不具备的优势,也解决了一些业界的痛点,Pulsar 目前社区比较活跃,还处于快速发展的阶段,除了以上的特性之外,Pulsar还可以支持事务、SQL查询、Function等功能,另外 Pulsar 支持 protocol handler,比如 KoP(Kafka on Pulsar), 可以原生支持 Kafka 协议的数据,对于这些特性,我们会在后续的文章中做介绍。

优秀的个人博客,低调大师

使用 dotMemory 优化 dotMemory | 技术解析

dotMemory1是 JetBrains 推出的一款 .NET 内存分析器。我要讲一个经典的内部测试故事,在故事里我们用自己的工具 dotMemory 和dotTrace2 优化了 dotMemory 的一种算法。我们还使用 dotTrace 对其进行了更多改进,并使用 BenchmarkDotNet3完成了优化过程。 最开始,一位同事在 Slack 中给我发送消息,告诉我他在 dotMemory 的支配树4上遇到了问题。树的数据计算时间太长了,他实在等不到进程结束。好在问题是内部的,我们收集内存快照并展开了调查。 在本地机器上重现问题时,我们发现内存使用以每秒 1.17 GB 的速度快速增长,迫使进程开始使用交换文件。发生这种情况通常就意味着无法在合理的时间范围内收到结果。接下来,该用 dotMemory 解决 dotMemory 中的问题了。 进程完全耗尽物理内存后,快照会难以捕获,甚至操作系统也不再稳定。因此,我们决定在内存使用开始快速增长时捕获快照,但物理内存还有一些剩余。dotMemory 控制台分析器5是完成这项工作的最佳工具: 此命令以分析模式启动dotMemory.UI.64.exe。它会在“private bytes”量达到 20 GB 时立即捕获快照,并在分析完成后在 dotMemory 中打开快照。在我们的情况中,我们不得不手动停止分析(否则我们最终会再次交换)。我们一直等到数据足够,然后按 Ctrl+C。 现在有了快照,我们在 dotMemory 中将其打开。首先,分析支配树 – 我的同事无法获得的支配树,我在快照中得到了: 内存中充斥着大约 6000 万个CompactDominatorTreeNode+Builder对象,每个对象都包含一个字典。显然,对于大多数字典来说,大小 < 容量,这意味着大量内存被浪费了。 我们必须看看这些字典的用途,也许我们不需要这么多?我选择了 CompactDominatorTreeNode+Builder节点并导航到它的声明(使用上下文菜单或按 Ctrl+L)。这将我导航到我的 IDE (Rider6) 并打开了相应的类。运行 Find Usages7(查找用法)将我带到了CompactDominatorTree.Build方法,其中包含支配树压缩算法。 介绍一点背景:首先,dotMemory 构建一个支配树,如下所示。对于每个对象,它会搜索专门保留它的对象并保存该保留对象,如果没有,则保存表示缺少此类保留对象的标记。树可能会变得非常大,没有必要“按原样”向用户展示。因此,dotMemory 在树的每一级按对象类型对树节点进行分组。CompactDominatorTreeNode+Builder表示正在构建的紧凑(压缩)树的节点,字典用于按类型对节点进行分组。 算法以“下级→上级”格式接收树,每个对象都与其支配上级匹配。它按顺序遍历快照中的所有对象。每有一个支配上级尚未被处理的对象,紧凑树中就会创建一个新的组节点。当前节点成为组节点中的下级节点(现有节点或新创建的节点)。 使用这种方法,树以任意顺序遍历,每个节点都必须存储一个字典,字典按类型对节点进行分组。在大多数情况下,这都不是问题,因为紧凑树比包含所有对象的树要小得多,并且没有显著的内存过度使用。但是这个快照属于极端情况,因为即使是紧凑的树也很大。 现在的问题是,“我们怎样才能使用更少的字典?”,如果我们只有一个带组合键的字典,该怎么办?这将迫使我们必须执行分解,再次导致内存过度使用或更复杂的计算。我们不得不考虑其他方案。 我们退后一步,更全面地分析问题。使用不同的方式遍历树如何?比如广度优先搜索?这种方式看起来很可行。一个具有常规键的字典就足够了。这样,每当我们构建完成紧凑树的新级别,我们都会清除字典,从而可以在下一次迭代中重用它。 不过注意:“下级→上级”格式不适合广度优先搜索,因为检索给定节点的下级节点的计算开销过大。我们需要不同的输入树格式。好在我们已经有了一个备选方案:dotMemory 也能将支配树存储为“上级→下级”邻接列表。这意味着,我们可以在不增加计算复杂性的情况下更改算法以使用广度优先搜索。 我们进行了相应编辑并运行。我们看到内存使用的增长速度比以前慢得多,从每秒 1.17 GB 变为仅仅每秒 7 MB,但该进程仍然未能完成而且再次开始使用交换文件。 我们回到绘图板。我们捕获了另一个快照并将其打开。新的支配树看起来像这样: 这次仅紧凑树就占用了 16 GB。这个大小合理吗?也许我们还可以用更经济的方式存储这棵树? 我们按类型对节点进行分组以查找我们有多少个节点: 在 16 GB 的总大小中,树的 9000 万个节点仅占用 6 GB。这有些可疑。我们导航到 CompactDominatorTree2+Node声明的代码: publicsealedclassNode{publicDfsNumberDfsNumber{get;}//8bytespublicTypeIdObjectsType{get;}//4bytespublicintObjectsCount{get;}//4bytespublicintRetainedObjectsCount{get;}//4bytespubliculongRetainedBytes{get;}//8bytespublicboolIsContainedInSet{get;}//1bytepublicNodeParent{get;}//8bytesinternalJetArray<Node>ChildrenImpl{get;}//8bytes//wehaveomittedunnecessarydetails} 这里的有效负载只有 45 字节,但加上标题和对齐,一个 Node 对象就占用了 72 字节。它保留的更多,平均 192 字节。为了查看详细信息,我们切换到 Instances(实例)选项卡,使用查询CompactDominatorTree2+Node !g !a筛选内容(!g排除泛型,!a排除数组)。这是一个随机实例: 实例详情: 这可是好些开销。 现在,我们可以清楚地看到JetMutableArray并不是为大型树存储下级节点的最有效方式。我们想到的第一个解决方案当然是优化下级节点的存储。例如,我们可以将它们存储在标准数组中。但我们选择了一条更激进的道路。 大型数据结构在 dotMemory 中很常见。例如,对象图被存储为邻接列表,使用两个通过整数数组索引相互引用的结构数组。这很有趣,因为在 dotMemory 中,我们刚刚停止使用托管数组而转移到我们自己的基于内存映射文件的实现。这有助于我们在访问元素时实现最小开销(C 中的原生内存索引形式),以及加载和保存数组的快速方式(完全在操作系统级别),并且在序列化/反序列化时没有开销。另外,即使没有我们的参与,这些数组的片段也可以从内存中卸载,帮助我们在顺序遍历数组时释放物理内存。 我们决定在紧凑支配树上使用相同的方式。我们还将IsContainedInSet特性变成了一个位标志,并消除了没有使用的Parent属性。最后,我们为树节点得到了以下结构: [StructLayout(LayoutKind.Sequential,Pack=4)]publicreadonlystructNode{publicreadonlyTypeIdObjectsType;privatereadonlyuint_objectsCount;publicintObjectsCount=>(int)(_objectsCount&0x7FFFFFFF);publicboolIsContainedInSet=>(_objectsCount&0x80000000)!=0;publicreadonlyintRetainedObjectsCount;publicreadonlyulongRetainedBytes;publicreadonlyDfsNumberDfsEnter;publicreadonlyRange<uint>Children; 值得注意的是,通过这种顺序表示,我们甚至可以只使用一个指向下级节点的整数链接。不过,我们使用了一个范围,该范围允许我们按RetainedBytes升序对构建树的下级节点进行排序以帮助我们构建旭日图。对于上面显示的表示,节点 #1、#2 和 #3 的顺序可能会改变。 我们屏住呼吸,运行了程序。成了!内存使用峰值仅为 12 GB。支配树成功构建,耗时 55 分钟 – 仍然比我们期望的要长,但可以接受。快照现在总共包含 2.75 亿个对象,紧凑树包含 2 亿个节点。 这显然是一个好结果。我们一开始无法将大小降至 32 GB 以下,计算需要很长时间,现在,它已经降到 12 GB 并能在 55 分钟内就绪。然而,我们还是感觉我们可以做得更好。55 分钟绝对算不上理想。我们决定查明这些时间都用在了哪里。进入 dotTrace: 好在我们不需要等待进程完成,记录几分钟的性能就足够了。我们使用--timeout=3m键在 3 分钟后自动停止分析。我们打开快照,应用筛选以识别正在执行我们任务的线程,这是长时间保持完全加载的线程。调用树是这样的: 令人震惊的是,92% 的时间显然都被Dictionary.Clear占用了!我们简直不敢相信。Clear在标准库字典上怎么会表现得如此糟糕?原来,问题是字典在算法的第一次迭代中已经膨胀到了 22K 的巨大元素,而随后的迭代通常只涉及几十个元素。Dictionary.Clear的计算复杂度与Capacity(不是Count)成正比,特别是buckets.Length,它与Capacity成线性关系。这意味着我们在不断清除空元素!事实证明,选择清除和重用字典时,我们对模型进行了过多的优化。 再退一步,我们现在尝试了最直接的方式 – 为每次迭代创建一个新字典。我们再次启动秒表,运行代码,这次充满期待。哇!1 分 46 秒!垃圾回收器上的负载增加了,但它分布均匀并且管理良好,我们使用 dotTrace 验证了这一事实。就是这样。我们在不到 2 分钟的时间内处理了 2.75 亿个对象,内存使用峰值仅为 12 GB。我们相当高兴,准备好将优化的代码提交到仓库。 后来,我们决定再做一次测试。我们在不同的快照上运行了旧算法和新算法,比较性能。我们找到了一个大小相同但拓扑不同的快照,旧算法也可以处理,然后把它馈送给两种算法。令我们大吃一惊的是,新算法比旧算法慢了 33%,尽管计算复杂度没有变化。但这就是另一个故事了,我们以后再讲。如果您有兴趣,请在下方留言或直接联系我们! 相关链接: dotMemory:https://www.jetbrains.com.cn/dotmemory/ dotTrace:https://www.jetbrains.com.cn/profiler/ BenchmarkDotNet:https://github.com/dotnet/BenchmarkDotNet 支配树:https://www.jetbrains.com/help/dotmemory/Retained_by.html dotMemory 控制台分析器:https://www.jetbrains.com.cn/dotmemory/download/#section=commandline Rider:https://www.jetbrains.com.cn/rider/ Find Usages:https://www.jetbrains.com/help/rider/Navigation_and_Search__Finding_Usages.html 本博文英文原作者:Ilya Ivanov 关于 dotMemory .NET 内存分析器 用于优化 .NET 应用程序的内存使用,检测内存泄漏和克服各类内存问题。 扫码查看详情页开启30天免费试用 »»» ⏬ 戳「阅读原文」了解更多 本文分享自微信公众号 - JetBrains(JetBrainsChina)。如有侵权,请联系 support@oschina.cn 删除。本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册