【RocketMQ】架构设计 叁歲伎倆 2022-10-14 01:17 130阅读 0赞 # 前言 # 文章基于 RocketMQ 4.8.0 源码。 # 一 RocketMQ 架构 # ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70][] **NameServer(注册中心和配置中心)**: 1)维护路由元数据信息,管理 Broker 注册的信息,包括 brokerAddr、topic、queue; 2)定时接收 Broker 心跳信息,检测 Broker 存活状态,支持 Producer 和 Consumer 获取 brokerAddr,topic,queue 等元信息; 3)支持集群部署,节点无状态,相互之间不进行信息同步,Broker 注册信息时根据 NameServer 地址信息列表,轮询向 NameServer 注册信息,使 NameServer 集群间保证信息的一致性; **Broker(消息存储端)**: 1)负责消息的存储、转发、HA,与 NameServer 建立长连接,定时向 NameServer 注册 Broker 信息, 并发送心跳信息,保持长连接存活; 2)Broker 通过 Master-Slave 集群保证高可用,brokerId 为 0 是 Master, brokerId 大于 0 是 Slave; 3)支持四种部署类型:单 Master 部署,多 Master 部署、多 Master 多 Slave 异步复制部署, 多 Master 多 Slave 同步复制部署; **Producer(消息生产端)**: 1)Producer 与 NameServer 随机建立长连接,定时从 NameServer 获取 topic 的路由信息, 然后与 topic 的 Broker-Master 建立长连接,定时向 Broker-Master 发送心跳; 2)Producer 无状态,支持集群部署; **Consumer(消息消费端)**: 1)Consumer 与 NameServer 随机建立长连接,定时从 NameServer 获取 topic 的路由信息, 然后与 Topic 的 Broker-Master 或 Broker-Slave 建立长连接,Consumer 可以从 Broker-Master 获取消息消费,也可以从 Broker-Slave 获取消息消费; 2)支持集群消费和广播消费,默认是集群消费,集群消费是指 topic 下 ConsumerGroup 里的 多个 consumer 负载消费 topic 信息,而广播消费则是 topic 下的 ConsumerGroup 里的每个 consumer 消费队列的全量信息; # 二 NameServer # ## **1、启动逻辑** ## **启动入口** org.apache.rocketmq.namesrv.NamesrvStartup\#main() **创建 NamesrvController** org.apache.rocketmq.namesrv.NamesrvStartup\#createNamesrvController() ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 1][] 初始化 NettyServer 端配置,设置监听 9876 端口。 **启动 NameServer** org.apache.rocketmq.namesrv.NamesrvStartup\#start() org.apache.rocketmq.namesrv.NamesrvController\#initialize() 初始化启动前置配置。 ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 2][] org.apache.rocketmq.namesrv.NamesrvController\#start() ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 3][] 启动 NettyServer 端,监听 9876 端口,等待 Broker、Producer、Consumer 建立长连接。 ## 2、NameServer 对本地 Broker 信息进行存活检测 ## ![2021060718011689.png][] org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager\#scanNotActiveBroker() ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 4][] NameServer 每隔 10s 检测 Broker 是否存活,检测逻辑是判断 RouteinfoManager 中本地缓存 在 2 分钟内是否有被 Broker 发送的心跳进行更新操作,否则从 NameServer 中剔除 Broker 注册的路由信息。 # 三 Broker # ## 1、启动逻辑 ## **启动入口** org.apache.rocketmq.broker.BrokerStartup\#main() **创建 BrokerController** org.apache.rocketmq.broker.BrokerStartup\#createBrokerController() ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 5][] 设置 Broker 监听 10911 端口。 org.apache.rocketmq.broker.BrokerController\#initialize() 初始化 topic 配置管理、消费进度管理、订阅分组管理、消费过滤管理等。 org.apache.rocketmq.broker.BrokerController\#start() 启动 消息存储任务,消息拉取线程等。 启动 NettyServer 端,监听 10911 端口,等待 Producer 和 Consumer 建立长连接。 ## 2、Broker 定时注册到 NameServer ## ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 6][] 默认每隔 10 秒定时注册 Broker 信息到 NameServer。 ## 3、消息接收入口 ## org.apache.rocketmq.broker.processor.SendMessageProcessor\#processRequest() Broker 收到 RequestCode 为 SEND\_MESSAGE = 10 的 cmd 码,调用 SendMessageProcessor 处理器进行消息处理。 ## 4、顺序写 CommitLog ## RocketMQ 将所有的 Topic 消息转换为顺序写到 CommitLog 存储文件,CommitLog 默认 1G 大小,超过 1G 会轮到下一个 CommitLog 文件,以顺序 IO 方式写入磁盘,减少 IO 争用,提高 消息存储的性能,提高 Broker 的吞吐量。 ## 5、自旋锁 与 重入锁 ## Broker 并发收到消息,并且还进行提交线程池异步处理任务,所以在消息存储时会存在并发 操作,保证不了顺序性写入不丢失,需要通过某种方式保证线程安全的顺序写入? org.apache.rocketmq.remoting.netty.NettyRemotingAbstract\#processRequestCommand() ![2021060817071210.png][] 如果让我们自己设计并发请求如何保证线程安全保证消息的顺序写入,有什么方式呢? 线程池排队单线程处理,这么做的话队列会爆满,内存会崩,机器会挂,应对不了大流量。 Synchronized?ReentrantLock ?CAS ?哪种更好,更合适呢? RocketMQ 保证顺序写消息时,并发线程安全顺序处理,提供给了我们两种实现方式选择, 一种是 ReentrantLock(metex lock 互斥锁),一种是 CAS(spin lock 自旋锁), 像 Synchronized 重量级锁处理高并发性能偏弱,不考虑。 org.apache.rocketmq.store.CommitLog\#asyncPutMessage ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 7][] **PutMessageLock 有两个实现。** // 默认是 false private boolean useReentrantLockWhenPutMessage = false; ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 8][] 默认使用的是 PutMessageSpinLock(CAS 自旋锁),可以选择使用 PutMessageReetrantLock( 独占锁或互斥锁),为什么默认不使用 PutMessageReetrantLock?先看下他们对应的实现,然后 再从性能上进行二者的对比。 **互斥锁标准接口(PutMessageLock)** ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 9][] **ReentrantLock 实现** ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 10][] **CAS 实现** ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 11][] 可以看到独占锁基于 ReentrantLock 实现,ReentrantLock 底层基于 AQS 实现, 通过 volatile int state 共享资源控制锁的状态,获取锁失败的线程进入双向链表构建的队列, 并被挂起,通过等待、唤醒实现锁机制,当并发量高时,存在大量线程上下文切换,CPU 会持续飙升,机器崩溃。 CAS 能够减少加锁和解锁上下文切换,但是高并发时,也会存在大量线程轮询,CPU 也会飙升,但是 CAS 性能会更快一些。ReentrantLock 和 CAS 性能比较可以自己写两个程序, 压测下加锁和释放锁的耗时。当竞争异常激烈时,CAS 性能比 ReentrantLock 性能高。 所以 RocketMQ 默认使用 CAS 加锁机制将并发消息转换为串行消息顺序写入 CommitLog 存储文件。 ## 6、Page cache 与 MappedFile 文件预热 ## **page cache** page chace 的概念。系统的所有 I/O 请求,操作系统都是通过 page cache 机制机制实现的。 对于操作系统来说,磁盘文件都是由一系列的数据块顺序组成,数据块的大小由操作系统本身 而决定。消息写入到 page cache,并没有写入到磁盘,什么时候写入磁盘,由操作系统决定。 所以,当消息写入 page cache 的时候,跟写入内存一样,性能很高。 ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 12][] **mmap** **private MappedByteBuffer mappedByteBuffer;** ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 13][] mmap 概念。mmap 是将一个文件或者对象映射进内存的一种技术,名字源于函数原型。 **头文件** <sys/mman.h> **函数原型** void\* mmap(void\* start,size\_t length,int prot,int flags,int fd,off\_t offset); 重点是知道这个函数的作用,在用户空间映射调用中作用非常大。 RocketMQ 根据这种技术的特性,将 commitLog 文件映射到内存中 mmap。 每次可以在内存中对 commitLog 进行读写操作,不用每次都调用操作系统的 read,write 函数进行读写操作,目的是减少磁盘 I/O 操作,提高 commitLog I/O 操作的性能。因为 mmap 映射支持文件大小是 1.5G ~ 2G,所以 RocketMQ 定义 commitLog 为 1G,这也是 commigLog 文件大小默认 1G 的原因,没有必要 就不要乱调 commitLog 的大小,默认 1G 是个择优的选择。消息写入时会存在 MappedFile 的 MappdByteBuffer 中,在调用 force() 的时候将数据刷到磁盘中。 **文件预热** Broker 在启动 messageStore 时,提前将 commitLog 通过 mmap 技术映射好数据, 避免在消息处理时再加载损耗消息处理的性能。 ![2021060820060264.png][] ## 7、刷盘 ## 当消息写入到 page cache 时,RocketMQ 提供了同步和异步刷盘方式, 这个是在 Broker 配置文件里面可以选择的。如果要求吞吐量高,可以容忍极端情况 下消息丢失,可以配置步刷盘,如果可以允许吞吐量稍弱,必须保证消息不丢, 可以配置同步刷盘。 **刷盘线程** Broker 在启动时,启动了刷盘线程。 ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 14][] org.apache.rocketmq.store.CommitLog\#putMessage() \--> org.apache.rocketmq.store.CommitLog\#handleDiskFlush() ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 15][] **同步刷盘** 既然是刷盘线程,任务从service.putRequest() 放进去,future 等待返回结果。 收到结构后,再返回给客户端消息发送成功。 ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 16][] **异步刷盘** 唤醒刷盘线程,不需要关心刷盘是否成功,直接返回客户端消息发送成功。 ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 17][] 刷盘的线程逻辑入口在 CommitLog 构造器,Broker 启动时会启动刷盘线程,刷盘线程的本质 逻辑通过 CountDownLatch2 其实与 CountDownLatch 逻辑一样,结合一个 ArrayList 实现 等待、通知机制。同步与异步,关键在于要不要一直死死的等待刷盘结果。 ## 8、零拷贝机制 ## 在操作系统内部,操作空间分为用户态和内核态,一般数据发送流程是用户态进程进入内核态, 内核态处理完成后,将数据拷贝到用户态,用户态再将数据拷贝到 socket,多一次内核态到用户 态操作过程,RocketMQ 直接将数据从内核态拷贝到 socket,不经过用户态,性能更好。 ## 9、读写分离(ConsumerQueue) ## RocketMQ 将消息均存储在 CommitLog 中,并分别提供了 CosumerQueue 和 IndexFile 两个索引,来快速检索消息。 RocketMQ 通过 ReputMessageService 线程持续读取 commitLog 生成 ConsumerQueue, 读取消息时先从 ConsumerQueue 读取 commitLog 的 offset,有则根据 offset 去 commitLog 读取消息,无则再从 commitLog 遍历读取消息,写的时候只直接写 commitLog,是一种读写 分离思想,根据 topic 读取消息时,不需直接遍历 commitLog,哪样会导致时间复杂度变为 O(n)。 ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 18][] 每个 topic 生成了对应的 consumerQueue,直接去 topic 对应的 consumerQueue 读取消费的 commitLog Offset 然后再根据索引从 commitLog 获取消息,时间复杂度比 O(n) 小,比直接扫 描整个 commitLog 文件性能高。 ## 10、消息服务端过滤 ## 消息的过滤结合 Broker 服务端 + Consumer 消费端过滤。消费端通常可以选择 tag 进行消费。 consumer.subscribe("TopicTest", "TagA || TagB"); 服务端判断 consumerQueue tag 不符合,则不发送消费消息,减少网咯 I/O 开销。 消费端再加一层过滤,避免消费错误风险,双重保证正确的进行消息消费。 # 四 Producer # ## 1、启动逻辑 ## **启动入口** 默认 DefaultMQProducer。 org.apache.rocketmq.client.producer.DefaultMQProducer\#start() org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl\#start(boolean) 主要启动默认 3s 一次定时从 NameServer 获取 topic 对应的 broker 信息刷新到 producer 本地缓存。 ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 19][] ## 2、消息发送 ## **topic 创建** 消息发送时首先要根据 topic 找到对应 Broker 信息,才能知道将消息发送到哪个 Broker 上。 ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 20][] Producer 定时 30 s 去 NameServer 获取 topic 对应的 Broker 信息在本地进行缓存, 如果 Broker 有改变,会进行本地缓存刷新。 ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 21][] org.apache.rocketmq.client.impl.factory.MQClientInstance\#updateTopicRouteInfoFromNameServer() RocketMQ 创建 Topic 有两种方式,一种是手动创建,一种是自动创建,默认是自动创建, 这里讨论自动创建逻辑。当第一次发送消息时,Producer 的本地缓存没有 topic 对应的 Broker 信息,因为 Broker 在启动的时候,往 NameServer 注册的只有默认的 topic "TWB102" 信息, 没有别的任何别的 topic 信息,哪我们第一次发送消息怎么知道往哪个 topic 发送呢? topic 又是什么时候创建的? ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 22][] ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 23][] 因为 Producer 本地缓存没有 topic 元数据信息,第一次从 NameServer 获取,也获取不到, 将用默认 topic "TWB102" 对应的元数据信息复制一份 Broker 信息,构建发送 topic 消息的 Broker 信息。采用的是"偷梁换柱"的手段,通过一个默认 topic "TWB102"对应的 Broker 信息克隆出发送消息的 Broker 信息,从而使 topic 消息能够发送到 Broker 端。 ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 24][] 克隆的方式消息是可以让 topic 知道发送到哪个 Broker 端,但是 Broker 并没有真正创建, 也没有注册到 NameServer 中,真正的 topic 创建在 Broker 端完成的。 **Broker 端真正创建 topic,通过 Broker 端的定时任务自动注册到 NameServer 中。** org.apache.rocketmq.broker.processor.SendMessageProcessor\#asyncSendMessage() org.apache.rocketmq.broker.processor.SendMessageProcessor\#preSend() org.apache.rocketmq.broker.processor.AbstractSendMessageProcessor\#msgCheck() org.apache.rocketmq.broker.topic.TopicConfigManager\#createTopicInSendMessageMethod() ![20210609173142383.png][] Topic 信息构建到 Broker 端 TopicConfigManger 属性的一个 ConcurrentHashMap 中, 之后通过定时任务默认是定时 10s 注册到 NameServer 中,完成 topic 的真正创建。 org.apache.rocketmq.broker.BrokerController\#start() ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 25][] **发送方式** RocketMQ 提供了三种发送方式供选择,用哪种方式,根据你的实际业务场景决定。 同步发送:发送消息请求,同步等待发送结果响应,失败会自动尝试重发 3 次,适合发送 非常重要消息,需要保证不丢失情况,但是吞吐量会有损失; 异步发送:发送消息请求,注册 Callback 回调函数,当消息发送成功时,通过回调方式通知 发送端,发送失败没有重试机制,消息可能丢失,适合发送消息不是非常重要,但是还想 知道发送结果,可以容忍部分丢失,吞吐量较高。 单向发送:发送消息请求,消息爱丢不丢,发完拉倒,吞吐量超大,消息丢失将无法补救, 因为跟 broker 没有是否丢失的交互,即使你想做消息丢失补偿,都没法做逻辑,适合发送 日志消息,收集日志数据,做业务,接口,系统稳定性分析; ## 3、Broker 故障转移 ## Producer 发送消息到 Broker,当 Broker 宕机,或者,Broker 网络异常时,进行故障转移, 但是,需要注意,默认情况下不开启故障转移机制。 RocketMQ 消息如果发送太久,mq 会认为 Broker 不可用的时间越久, Producer 端会维护 Broker 不可用时间,这个时间是个经验值,在这个时间内,不往该 Broker 上发送消息, 是一个故障转移的思想。如何实现的? 首先将 Broker 发送未成功的维护到不可用 Broker 信息列表到 ConcurrentHashMap 中。 ![20210609191342863.png][] ![20210609191416769.png][] ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 26][] 然后在发送消息时,选择队列的时候,屏蔽掉哪些还不可用的 Broker。 ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 27][] # 五 Consumer # 上面已将完成消息发送到 Broker 端,消费端如何去 Broker 服务器获取指定 topic 的消息列表? RocketMQ 提供了两种获取消息列表方式,push 和 pull 模式,本质上都是 pull 模式。 消费者在启动的时候,会启动一个拉取的线程。 ![20210609194312893.png][] 根据订阅 topic 和 tag 构建 pullRequest 请求,从 Broker 获取消息,如果是 push 模式, 通过 PullBack 回调消费者注册的监听,让消费者监听获取消息进行消费。这里还有个消费 进度的概念,消费进度保存了每个消费者消费的 offset,如果是广播消费模式,消费进度保存 在消费者本地,如果是集群消费模式,将消息进度保存在远端,即 Broker 端。 ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 28][] RocketMQ 默认 push 模式获取消息列表,集群模式消费消息,这是 4.8.0 版本的默认情况。 # 六 总结 # 1、RocketMQ 底层基于 Netty 进行通信,整个通信流程如下。 NameServer 启动服务,即 Netty 的服务端,监听 9876 端口,等待 Broker,Producer, Consumer 建立长连接; Broker 启动服务,Broker 端启动时启动 Netty 的服务端,监听 10911 端口,等待 Producer, Consumer 建立长连接,同时,启动 Netty 客户端,与 NameServer 建立长连接,并注册 默认 topic “TWB102” 到 NameServer; Producer 启动服务,与 NameServer 建立长连接,定期获取 topic 元数据缓存到本地,作为 发送消息时使用,并根据 topic 元数据发送消息时与 Broker 建立长连接; Consumer 启动服务,与 NameServer 建立长连接定期获取 topic 元数据缓存到本地,作为 消费消息时使用,并根据 topic 元数据,与 Broker 建立长连接,定时长轮询从 Broker 拉取 消息列表进行消费,本质上 push 也是 pull Broker 服务器; 2、底层使用了大量的线程池,定时任务,本地缓存,以及 同步锁,互斥锁; 3、高可用机制在于 NameServer 无状态集群,Broker 多主多从集群,以及 Producer 和 Consumer 集群,还要一些 故障机制,保证整个 RocketMQ 的高可用; 4、高性能机制在于 Netty 长连接通信,减少网络 I/O 损耗,commitLog 文件的 mmap 内存映射机制和消息顺序写机制,以及底层 Socket 通信数据零拷贝机制,还要一些高并发 时 CAS 自旋锁替代 ReentrantLock 互斥锁减少上线文切换等性能开销,将每一个可以优化的 点发挥到机制,让 RocketMQ 性能非常高效; [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70]: /images/20221014/2fee3012b14d44c194c761cffd89b3ff.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 1]: /images/20221014/4b0b49f94191479fa87572985620c00a.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 2]: /images/20221014/b079dd5ff2ff4b9380e0c05894018311.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 3]: /images/20221014/0c839173a11a476f870a984aa36b3995.png [2021060718011689.png]: /images/20221014/345d6fa025b24f47bfd2b1d7df385b4f.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 4]: /images/20221014/5808f7bdfc3242188f62c4ed99dabc92.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 5]: /images/20221014/b6cca031525f4055bf204e62525ce431.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 6]: /images/20221014/58b507aa967b484c96797e16251e43e9.png [2021060817071210.png]: /images/20221014/0ef7e984c7ef4360ba491140f726b762.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 7]: /images/20221014/688385052e84460a9fc500425f1b8b1e.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 8]: /images/20221014/71ee3c32c828492c88d1126fde546783.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 9]: /images/20221014/dd9e5837cbd04f8f9585b984d5e6b40c.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 10]: /images/20221014/b2b7d90f04ba4403ae1a90228a45d942.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 11]: /images/20221014/04792376cfd14cff90835ea8837195e6.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 12]: /images/20221014/533680cc0b2c4d8196d3a21052675ebd.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 13]: /images/20221014/b7b45622ad7e4b629e7e4208ade4fef3.png [2021060820060264.png]: /images/20221014/95243ca0e9084873ac51fd755468b8a4.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 14]: /images/20221014/265f7f7cbe8140c299c743318e8b762b.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 15]: /images/20221014/310853e3eb044b209bc485fe69994a0e.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 16]: /images/20221014/3b8f78f930e646c8bf02f19e45c94b5a.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 17]: /images/20221014/ae25c66fa064483a966d56647007af6a.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 18]: /images/20221014/e06e4d7d798446f6adb17a3db5563138.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 19]: /images/20221014/e473b83592ef44aab8fef8b03c78a240.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 20]: /images/20221014/a11369a8238542eab861caffe260f58e.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 21]: /images/20221014/7b91bc782434429aa08fbb540609daaf.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 22]: /images/20221014/d2988aca11dd4ddb940cc805e22311a4.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 23]: /images/20221014/cdbccf24816e4c89966bf31f7755927d.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 24]: /images/20221014/d0a2bd3d3ccb4567a7a5a2239951288a.png [20210609173142383.png]: /images/20221014/5a0f60d419394fc993de683f12ca7268.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 25]: /images/20221014/ba27a15896fb4c7391ed778cf22cf0a4.png [20210609191342863.png]: /images/20221014/0c919b19548243668f7e6cea493d441e.png [20210609191416769.png]: /images/20221014/7bbf24b21b9b43798549f70c2d5b9ca6.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 26]: /images/20221014/fba1b57751bf4e1eaff961ad9ccaf9c2.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 27]: /images/20221014/114c918b402c4678875058fb623e5933.png [20210609194312893.png]: /images/20221014/183976fdcee942d3a0cf230617e83781.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lobF9qeHk_size_16_color_FFFFFF_t_70 28]: /images/20221014/4fb444d1a9424caa9158259d1bdc5f26.png
还没有评论,来说两句吧...