记录些Spring+题集(51)

1、你的超高并发10Wqps自动驾驶核心平台的项目,是指的某个查询服务还是指的整体项目支撑10wqps?为什么?

在我的超高并发 10W QPS 自动驾驶核心平台项目中,我们面临的是整体平台的并发性能挑战。

这意味着整个平台,包括路径规划、视觉选图等多个核心服务,都需要设计成低延迟、高性能的架构,以确保在极端情况下也能稳定处理 10 万 QPS 的请求。

为了满足这一要求,我们采取了多种技术和策略:

  1. 微服务架构:将不同的功能拆分成微服务,每个微服务负责一个特定的功能,如路径规划、视觉选图等。这样可以独立扩展和优化每个服务的性能。

  2. 负载均衡:使用负载均衡器来分配请求到不同的服务实例,确保请求均匀分布在各个实例上,避免单个服务成为瓶颈。

  3. 数据库优化:对数据库进行分区、索引优化,以及使用缓存机制来减少数据库访问,提高数据检索速度。

  4. 异步处理:对于耗时的操作,如路径规划,采用异步处理方式,避免阻塞主线程,提高系统的并发处理能力。

  5. 消息队列:使用消息队列来异步处理图像识别等计算密集型任务,这样可以避免阻塞服务响应。

  6. 资源池:对关键资源(如数据库连接、网络连接)使用池化技术,减少资源创建和销毁的开销。

  7. 性能监控和自动扩容:实施实时性能监控,并根据监控数据自动调整资源,如自动扩容实例数量,以应对流量高峰。

  8. 代码优化:对服务端的代码进行性能优化,减少不必要的计算和网络传输。

  9. 容器化和自动部署:使用 Docker 容器化技术,确保服务能够在短的时间内部署和扩展。

  10. 持续性能测试和优化:通过持续的性能测试来发现瓶颈,并进行相应的优化。

综上所述,为了支撑自动驾驶核心平台的超高并发需求,我们采取了一系列技术和策略,不仅针对单个服务进行性能优化,还确保整个平台在架构和部署上的高可用性和可扩展性。这样的设计可以确保在用户请求量激增时,平台仍然能够稳定运行,提供低延迟的服务。

2、路径规划的过程应用场景及路径规划用到哪些算法?

路径规划是自动驾驶、机器人技术以及智能交通系统等领域中的关键技术之一。

它在各种应用场景中发挥着重要作用,例如,自动驾驶车辆中的路径规划可以帮助车辆在复杂环境中找到从起点到终点的最佳路径;

智能交通系统中的路径规划可以优化车辆的行驶路线,减少拥堵和提高交通效率;工业机器人中的路径规划可以指导机器人在工作空间中高效地移动和执行任务。

路径规划算法

在路径规划的过程中,通常会用到多种算法,以适应不同的应用场景和需求。以下是一些常见的路径规划算法:

  1. 基于图的路径规划算法:这类算法通常使用图论中的概念和方法来解决问题。图中的节点代表路径规划中的位置,边代表节点之间的连接关系。常见的算法有A*算法、Dijkstra算法、Bellman-Ford算法等。

  2. 基于采样的路径规划算法:这类算法通过随机采样来探索环境,并基于采样点之间的连接关系来规划路径。常见的算法有随机树搜索、蚁群算法、粒子群优化等。

  3. 基于模型的路径规划算法:这类算法使用环境模型来预测未来可能的位置,并据此规划路径。常见的算法有动态规划、马尔可夫决策过程等。

  4. 基于学习的路径规划算法:这类算法通过从经验中学习来改进路径规划。常见的算法有遗传算法、神经网络、强化学习等。

  5. 基于仿生的路径规划算法:这类算法模仿自然界中的生物行为来规划路径。常见的算法有蚁群算法、遗传算法、粒子群优化等。

应用场景

路径规划的应用场景确实非常广泛,特别是在自动驾驶、智能交通和机器人技术等领域。

以下是针对您提供的两个应用场景的详细解答:

制图人员验证地图准确性:在这个场景中,路径规划服务起到了关键作用。制图人员可以使用路径规划算法来模拟车辆在地图上的行驶路线,以验证地图的准确性。这个过程通常涉及以下步骤:

  • 制图人员提供地图数据和起始、终止点坐标。

  • 路径规划服务接收这些信息,并计算出一条或多条路径。

  • 然后,制图人员可以根据计算出的路径与实际的道路情况进行对比,以检查地图数据的准确性。

  • 如果发现路径规划服务计算出的路径与实际路径有偏差,制图人员可以据此对地图进行调整,确保地图数据的准确性。

车辆刚刚下线之后,车联网平台会进行路径规划验证车辆硬件等配置是否无误:在这个场景中,路径规划被用来验证车辆的硬件配置和系统性能。这个过程通常涉及以下步骤:

  • 车联网平台接收到车辆的硬件配置信息和起始、终止点坐标。

  • 路径规划服务根据车辆的硬件配置(如传感器、控制器等)计算出一条路径。

  • 然后,车联网平台可以将计算出的路径与预期路径进行对比,以检查车辆的硬件配置和系统性能是否满足要求。

  • 如果发现计算出的路径与预期路径有偏差,车联网平台可以据此对车辆的硬件配置进行调整,确保车辆的正常运行。

在实际应用中,路径规划算法的选择和实现需要考虑到多个因素,包括实时性、准确性、鲁棒性等。针对不同的应用场景,可能需要设计不同的路径规划算法,以满足不同的需求。

例如,对于实时性要求较高的自动驾驶车辆,可能需要使用快速计算的路径规划算法;而对于准确性要求较高的场景,可能需要使用更为复杂的路径规划算法。

总之,路径规划算法在现代科技领域中起着至关重要的作用,其应用场景和影响范围将继续扩大。

3、Kafka是如何保证顺序性的?

Kafka 是一个分布式流处理平台,它被设计用来处理高吞吐量的数据。在 Kafka 中,消息被存储在称为分区的日志文件中,每个分区可以有多个副本,以提供容错和高可用性。Kafka 保证分区内的消息顺序性,但不能保证跨分区的顺序性。

kafka保证消息顺序有两种方案:

方案一,kafka topic 只设置一个partition分区

方案二,producer将消息发送到指定partition分区

解析

方案一:kafka默认保证同一个partition分区内的消息是有序的,则可以设置topic只使用一个分区,这样消息就是全局有序,缺点是只能被consumer group里的一个消费者消费,降低了性能,不适用高并发的情况

方案二:既然kafka默认保证同一个partition分区内的消息是有序的,则producer可以在发送消息时可以指定需要保证顺序的几条消息发送到同一个分区,这样消费者消费时,消息就是有序。

producer发送消息时具体到topic的哪一个partition分区,提供了三种方式

  1. 指定分区

  2. 不指定分区,有指定key 则根据key的hash值与分区数进行运算后确定发送到哪个partition分区

  3. 不指定分区,不指定key,则轮询各分区发送

图片

4、你在使用redis的时候,有没有什么最佳实践?

在使用 Redis 的时候,确实有一些最佳实践可以帮助你更有效地使用这个内存中的数据结构存储系统。以下是一些重要的最佳实践:

1. 避免使用 KEYS 命令
  • KEYS 命令会扫描指定pattern的所有键,并返回匹配的键列表。

  • 这个命令会阻塞 Redis 服务器,直到扫描完成。如果 pattern 匹配大量键,可能会导致长时间的阻塞。

  • 最佳做法是使用 SCAN 命令,它以迭代的方式返回匹配的键,不会一次性阻塞整个服务器。

2. 避免使用 FLUSHDB 或 FLUSHALL
  • FLUSHDB 命令会清空当前数据库中的所有键值对,而 FLUSHALL 命令会清空所有数据库中的所有键值对。

  • 使用这些命令会丢失所有数据,包括未过期的数据。

  • 如果需要清除数据,尽量使用删除特定键的命令,如 DEL、UNLINK 或利用 SDIFF、SINTER 等命令来清除特定的键集合。

3. 控制数据大小
  • 避免在 Redis 中存储过大的数据块,这可能会导致内存碎片化和性能下降。

  • 如果需要存储大量数据,考虑使用数据库或其他存储系统。

  • 对于大型数据集,可以使用 Redis 的哈希表或有序集合,但要确保合理地分片和索引数据。

4. 合理设置过期时间
  • 为键值对设置合理的过期时间,可以防止内存溢出和数据长期占用。

  • 过长的过期时间可能会导致内存占用过高,过短的过期时间可能会导致频繁的过期和重入,影响性能。

  • 根据数据的使用情况和访问模式来调整过期时间。

5. 扩展的最佳实践
  • 使用 LRU、LFU 等驱逐策略来管理内存,确保经常访问的数据留在内存中。

  • 考虑使用 Redis 的持久化机制来备份数据,防止数据丢失。

  • 使用 Redis 的监控工具来跟踪内存使用情况和性能指标。

  • 在设计应用时,尽量遵循 Redis 的使用规范,避免不必要的复杂操作。

遵循这些最佳实践可以帮助你更有效地使用 Redis,避免性能问题,并确保数据的持久性和安全性。

5、redis缓存过期时间设置,设置不过期,设置多久过期?

Redis 缓存过期时间设置取决于业务需求和场景。在某些场景下,缓存数据可能需要设置不过期,以保持数据的持久性。而在其他场景下,需要设置合理的过期时间以确保数据的时效性和内存占用。以下是一些建议:

1. 不过期的场景
  • 固定数据,不需要定期更新,如一些配置信息、常量等。

  • 数据量较小,且对内存占用影响不大,可以设置不过期。

2. 设置合理过期时间的场景
  • 动态数据,需要定期更新,如新闻、商品信息等。

  • 数据量较大,需要控制内存占用,如图片、视频等。

3. 业务场景下的过期时间设置
  • 针对不同的数据类型和业务需求,可以设置不同的过期时间。例如,新闻信息可以设置为 1 天过期,商品信息可以设置为 7 天过期,图片可以设置为 30 天过期等。

  • 可以根据数据的使用频率和访问模式来调整过期时间。例如,访问频率高的数据可以设置较短的过期时间,而访问频率低的数据可以设置较长的过期时间。

总之,在设置 Redis 缓存过期时间时,需要根据业务需求和场景来判断,确保数据的时效性和内存占用之间的平衡。

6、redis内存淘汰策略有哪些?

Redis 的内存淘汰策略主要分为三类,分别针对不同的键集和业务需求:

不淘汰

  • 这种策略下,Redis 不会主动删除任何键。适用于那些不需要定期清理的键,比如静态配置或者数据量非常小的场景。

所有键

  • Redis 会删除所有键,而不是只删除设置了过期时间的键。

  • 这种策略可能会导致大量数据的丢失,因此使用时需要谨慎。

设置过期时间的键

  • Redis 只会删除那些设置了过期时间的键。

  • 这种策略可以根据键的过期时间来决定哪些数据需要被淘汰。

根据上述三类策略,Redis 提供了以下八种具体的内存淘汰算法:

  • volatile-lru:针对设置了过期时间的键,使用 LRU 算法进行淘汰。

  • allkeys-lru:针对所有键,使用 LRU 算法进行淘汰。

  • volatile-lfu:针对设置了过期时间的键,使用 LFU 算法进行淘汰。

  • allkeys-lfu:针对所有键,使用 LFU 算法进行淘汰。

  • volatile-random:针对设置了过期时间的键,随机删除一些键。

  • allkeys-random:针对所有键,随机删除一些键。

  • timestamp:根据键的创建时间进行排序,删除较早创建的键。

  • redis-call:允许用户自定义内存淘汰行为,通过 Lua 脚本来实现复杂的淘汰逻辑。

在实际应用中,选择哪种内存淘汰算法取决于具体的业务场景和数据访问模式。例如,如果业务中频繁访问的是最近的数据,那么 LRU 策略可能更合适。如果数据访问比较均匀,那么 LFU 策略可能更合适。此外,还可以根据数据的使用频率和访问模式来调整过期时间,以优化内存使用和提高性能。

7、对于dubbo服务,在1秒内返回结果的设计思路

设计一个必须在 1 秒内返回结果的 Dubbo 服务时,我们需要考虑以下几个关键点:

参数校验

  • 服务接收请求后,首先对请求参数进行校验。

  • 如果校验失败,则立即返回错误信息,避免后续资源浪费。

服务独立部署

  • 确保服务可以独立部署,以减少依赖复杂性。

  • 采用轻量级容器化技术,如 Docker,快速启动和停止服务。

预加载数据

  • 通过预加载机制,将所需数据提前加载到本地内存。

  • 减少数据查询时间,提高服务响应速度。

异步数据处理

  • 对需要耗时处理的数据,采用异步方式。

  • 例如,通过消息队列(如 Kafka、RabbitMQ)将数据发送到后台处理。

数据库优化

  • 设计表结构时,根据查询频率添加索引。

  • 优化 SQL 查询,使用缓存机制减少数据库访问。

性能监控和调优

  • 监控服务性能,找出瓶颈进行优化。

  • 调整服务参数,如连接池大小、缓存过期时间等。

异常处理

  • 确保服务在异常情况下也能快速响应。

  • 编写详细的错误日志,便于问题追踪。

接口协议优化

  • 使用高效的接口协议,如 gRPC,减少网络通信开销。

服务限流

  • 防止服务被过多请求压垮,通过限流保护系统稳定性。

分布式一致性

  • 如果服务涉及分布式系统,确保一致性机制,如使用分布式锁。

在实现时,可以考虑使用 Dubbo 的异步调用机制,结合 Spring Boot 和 Dubbo 提供的注解和配置,快速搭建服务框架。同时,利用缓存框架(如 Redis)来存储热点数据,减少数据库访问。此外,通过 Dubbo 的监控工具,实时监控服务状态,快速响应服务异常。

扩展思路:

  • 考虑使用 Dubbo 服务治理和控制台,对服务进行实时监控和管理。

  • 实现服务自动扩容和缩容,以应对流量高峰。

  • 使用分布式搜索引擎(如 Elasticsearch)来优化全文搜索性能。

  • 实现自动化的性能测试和基准测试,确保服务在各个环境下都能满足性能要求。

综上所述,设计一个高效的 Dubbo 服务需要综合考虑多个方面,从架构设计到具体实现,都需要围绕性能和稳定性进行优化。

8、你用的jdk是哪个版本?垃圾收集器用的哪个?

我使用的 JDK 版本是 1.8。在 JDK 1.8 中,垃圾收集器默认使用的是 ParallelGC(又称为 Throughput Collector),这是一种并发的垃圾收集器,适用于多核 CPU 的机器,并且适用于对吞吐量有较高要求的场景。在使用 JDK 1.8 的过程中,确实遇到过 Full GC 频繁的问题。起初,这种情况每周只会发生一次,但后来有一天晚上,Full GC 的发生次数突然增加到了四次。为了解决这个问题,我首先采取的措施是重启服务,这样可以立即减轻 Full GC 对系统的影响。

随后,为了从根本上解决问题,我进行了压力测试(压测)。通过分析测试结果,我发现了一个大对象,这个对象包含了商品属性等相关所有商品信息。这个大对象在一次 Full GC 过程中没有被及时回收,导致了问题的发生。为了解决这个问题,我将大对象分隔成了不同的小对象。这样,每个小对象在内存中的占用变得更小,就可以在垃圾收集器进行 Minor GC 时被及时回收,从而减少了 Full GC 的发生频率。同时,这也使得我们可以更灵活地管理商品信息,提高了系统的稳定性。

通过这次问题解决的过程,我认识到了垃圾收集器在系统性能中的重要性,以及在面对大对象时,合理地拆分和优化数据结构对提高系统稳定性也是非常关键的。此外,对于 JDK 1.8 的垃圾收集器,了解不同收集器的特点和适用场景,以及如何根据实际业务需求进行调整,也是保证系统高效运行的关键。

全链路压测,你是怎么设计的?

全链路压力测试的宏观介绍

全链路压测是在模拟实际用户使用场景的基础上,通过对整个应用程序栈进行压力测试,评估系统在高负载下的表现。这包括了从用户界面、前端服务、中间件到后端数据库等所有组成部分,以全面了解整个软件系统的性能。

什么是全链路压测

全链路压测是一种系统性的性能测试方法,旨在模拟真实用户场景下的完整操作流程,全面评估软件系统在不同压力下的性能表现。这种测试方法对于保证应用程序的高可用性、稳定性和可扩展性至关重要。

  • 基于实际的生产业务场景、系统环境,基于真实数据模拟海量的用户请求对整个业务链进行压力测试,并持续调优的过程;

  • 全链路的核心为:业务场景、数据链路、压力模型和环境拓扑;

  • 全链路压测不仅仅是一种测试手段,更确切来说其是一种测试过程,该过程涉及自动化测试/性能测试/高可用测试技术以外,还覆盖性能分析调优以及扩缩容解决方案等等。

全链路压测的测试流程

全链路压测的测试流程通常包括以下步骤:

  • 场景设计:根据实际用户使用情况设计测试场景,考虑不同用户操作、访问路径和数据负载。

  • 脚本录制/流量拷贝:开发测试脚本,录制用户在系统中的实际操作,包括页面浏览、交互和数据输入等。或者通过组件,在请求链路上拷贝一些流量,清洗后放到HBase、MongoDB 这些NoSQL存储组件,称之为流量数据工厂

  • 负载生成:利用自动化测试工具或性能测试工具,模拟多用户同时访问系统,逐步增加负载,直至系统达到极限性能。

  • 性能监测:在测试过程中,监测系统的各项性能指标,包括响应时间、吞吐量、资源利用率等。

  • 问题分析:分析测试过程中发现的性能问题,可能涉及到系统的瓶颈、资源耗尽等方面。

  • 优化建议:基于测试结果提出性能优化的建议,可能包括代码优化、系统配置调整或者硬件升级等。

在进行流量拷贝时,有很多的可选工具,比如goreplay。goreplay 是一个开源工具,抓取生产环境流量,并使用真实流量持续测试系统。

图片

它使用raw-socket 抓取系统流量,并根据指定的url ,将流量进行转发。

GoReplay 劫持流量,拷贝到流量工厂。

  • 要对流量进行染色,比如在请求头标记是压测流量

当需要压测的时候,从这些工厂获取数据,将数据切分成多分下发到测试节点。

压测的时候,压测流量不会一次加完,缓慢加,跑跑,看看监控,有瓶颈,回退,扩容,再加。

正式流量和压测流量隔离如何隔离?

压测流量拷贝下来的同时,我们也需要改造系统,实现将正式流量和压测流量隔离

压测,有些行为影响到用户的统计分析,推荐等,还有一些插入的操作。这些就需要特殊的处理。

MySQL放入影子库、Redis设置不同的前缀,es放到单独索引表中。

写操作是上行流量,读操作是下行流量,都要有特殊处理

全链路压力测试的工业级案例

1、字节跳动全链路压测系统背景

随着企业的持续发展,业务范围的拓宽带来了用户访问量的显著增长,这进一步导致了研发体系的规模扩大和复杂性提升。同时,线上服务的稳定性变得尤为关键,服务性能和资源容量的问题也更加突出。

面对这样的挑战,建立一个高效的压测系统变得迫切需要,它应能确保线上服务的稳定运行,提供安全、高效、真实的全面链路压测服务,从而为线上服务的持续稳定提供保障。

关于构建全面链路的压测系统,虽然业内已有众多讨论,但在技术实现细节上却鲜有深入阐述。本文旨在通过概述从设计到实施的全链路压测系统的整个实践过程,详细阐述该系统的具体设计方法和实施步骤,希望能够从技术实践的角度为同行业的专业人士提供有益的参考和启示。

2、解决方案

2.1 业内实践

全链路压测在业内已经有了广泛的实践,如阿里的 Amazon、PTS [1] [2],美团的 Quake [3] [4],京东的的 ForceBOT [5],高德的 TestPG [6] 等等,它们的实践经验和技术方案为我们提供了宝贵的借鉴。我们综合了各大互联网公司的建设经验,并结合字节跳动的具体业务需求,设计并开发了一套全链路压测系统——Rhino。

2.2 架构图

Rhino 平台作为公司级的全链路压测平台,它的目标是服务于公司所有业务,提供安全、可靠、真实、高效的单服务及全链路压测。这有助于业务部门高效、便捷地完成性能测试任务,并更精确地评估线上服务的性能和容量风险。

因此在 Rhino 平台设计之初,我们就定下以下目标:

  • 安全:由于所有压测均在线完成,理论上都会对线上用户产生一定影响。因此,压测平台需从服务状态和压测数据两方面确保安全性。

  • 高效:较少压测脚本编写成本,数据构造和压测监控成本,尽量自动化完成压测过程的各个阶段。

  • 准确:精确的压力控制,准确的链路压测监控,精确的压测报告结果,以及性能和容量数据。

  • 高覆盖:需要支撑公司内不同的业务线的压测需求,如搜索,广告,电商,教育,游戏等等。

Rhino 是一个分布式全链路压测系统,可以通过水平扩展,来实现模拟海量用户真实的业务操作场景,对线上各种业务进行全方位的性能测试。它主要分为控制中心(Rhino Master)模块,压测链路服务模块,监控系统模块,压测引擎模块,如图。(每一个模块都是由多个微服务来完成的。如下图每个实线图都代表一个微服务或多个微服务)。

图片

3、核心功能介绍

构建全面的链路压测平台,其核心要素包括:数据生成、测试环境的隔离、链路管理、任务分配、压测的断路器、测试引擎和监控系统。接下来,本文将逐一解析在Rhino平台上如何精细地设计和实施这些功能。

3.1 数据构造

首先,数据生成是压测中至关重要的一环,也是最为复杂的环节。压测数据的建模,直接影响了压测结果的准确性。

  • 对于服务性能缺陷扫描,性能调优,以及新上线服务,推荐构造 Fake 数据,来压测指定路径。

  • 对于线上容量规划,性能能力验证,以及性能 Diff,推荐使用线上真实流量,使压测结果更贴近真实情况。

  • 对于涉及到用户账号,用户登录态保持的情况,推荐使用压测专属测试账号,避免影响线上真实用户。

基础数据构造

为了高效的构造特定的 Fake 压测数据,Rhino 压测平台提供大量数据构造方式:

  • CSV 文件:据按列分割,字段名取自CSV文件的首行。读取方式是逐行递增。若一个测试任务被分解为多个子任务(Job),相应的数据文件也会被分割,以防止数据在不同Job间发生重复。

  • 自增:变量类型均为数字类型。每次发压时+1,到最大值后从最小值循环使用。

  • 随机:变量类型均为数字类型。每次发压时随机生成。

  • 常量:Constant,可自定义为任意值。

图片

压测账号

在压测的执行过程中,经常需要模拟用户登录并维持会话状态,同时许多测试请求还涉及到用户的唯一标识信息,如UserID、DeviceID等。如何处理这些测试数据,尤其是在保持用户状态和数据隔离方面,一直是一个挑战。Rhino平台通过其集成的用户中心服务,提供了一套专为压测设计的账号服务,有效地解决了登录状态和测试账号的问题。具体流程和使用界面,如下图。

图片

图片

3.2 压测隔离

压测隔离中需要解决的压测流量隔离,以及压测数据的隔离。

压测流量隔离,主要是通过构建压测环境来解决,如线下压测环境,或者通过泳道化(Swimlane)和Set化(Set-based)的方法来实现。这样做的好处是测试流量不会影响到线上用户,但缺点是它需要更多的机器资源和更高的维护成本,并且测试结果可能需要经过换算才能准确反映线上环境的容量,从而影响结果的准确性。目前,公司在线上集群上进行压测,同时也在积极推进线上环境的泳道化建设。

压测数据隔离,主要是通过对压测流量进行染色,让线上服务能识别哪些是压测流量,哪些是正常流量,从而对测试流量进行特殊处理,以达到数据隔离的目的。目前 Rhino 平台整体压测隔离框架如图。

图片

压测标记

压测标记就是最常见的压测流量染色的方式。

  • 对于 RPC 协议,会在请求的头部中增加一个 Key:Value 的字段作为压测标记。

  • 对于HTTP协议以及其他协议的测试,可以在请求头中自动注入一个特殊的Stress标记(Key-Value对)。

  • 压测标记 Key:Value,其中 key 是固定的 Stress_Tag 值,但是每个压测任务都有唯一的 Stress_Value 值,主要用于解决压测数据冲突,以及性能问题定位。

压测标记透传

目前,公司内的各个基础组件、存储组件以及RPC框架都已支持压测标识的透传功能。透传的原理是将标识的Key-Value对存储在请求的Context中,并在所有的下游请求中携带这个Context。这样,下游服务就可以根据Context中的标识来处理测试流量。在实际的业务代码中,实现起来也非常简单,只需要传递Context即可。

  • Golang 服务:将压测标记写入 Context 中。

  • Python 服务:利用 threading.local()存储线程 Context。

  • Java 服务:利用 ThreadLocal 存储线程 Context。

压测开关

为了解决线上压测安全问题,我们还引入了压测开关组件。

  • 每个服务每个集群,都有一个压测开关。只有打开压测开关时,压测流量才能流入到服务内,否则就会被底层微服务框架直接拒绝,业务层无感。

  • 在每个IDC区域,都设有一个全局的测试控制开关。只有当这个全局开关处于开启状态时,测试流量才可以在该IDC内进行流转。

  • 当线上出现压测问题,除了从源头关闭压测流量以外,关闭目标服务的压测开关,也能立即阻断压测流量。

压测数据隔离

线上压测中,最复杂的问题就是压测链路中涉及到写操作,如何避免污染线上数据,并且能保证压测请求保持和线上相同的请求路径。业界有很多解决方案,常见的有影子表,影子库,以及数据偏移,如图[7]。

Rhino 平台针对不同存储,有不同的解决方案:

  • MySQL、MongoDB:对于MySQL、MongoDB等数据库,采用影子表的策略。SDK会判断流量是否为测试流量,如果是,则根据配置将操作映射到影子表中。配置策略包括读写影子表和读线上表写影子表两种。

  • Redis:Redis Key 加上 Stress 前缀。如 Stress_Tag=Valuex,那么读写 Redis 的 Key=Valuex_Key。这样可以解决多个压测任务数据冲突的问题。压测结束后,只需要对 Prefix=Valuex 做清除或过期操作即可。

  • MQ:对于消息队列,Rhino 平台有两种策略。一是直接丢弃,然后针对消息队列的性能,单独进行压测;二是在 Header 中透传压测标记,Consumer 根据压测标记和业务需求,再做特殊处理。默认走丢弃策略,业务方可根据需求进行配置。

  • 其他存储,如 ES,ClickHouse 等,都有压测集群。压测时,会将压测请求打到指定的压测集群中。

图片

服务压测改造

在压测之前,需要对服务进行压测验证。对于不满足压测要求(即压测数据隔离)的服务,需要进行压测改造。

  1. 压测验证:对于存储服务,在没有开启测试控制开关的情况下,任何读写操作都会被拒绝。如果没有遭到拒绝,说明在操作存储服务时没有携带测试Context,需要进行改造。

  2. 压测改造:压测改造是线上全链路压测推进中非常关键,而又非常困难的一个环节。对于已经上线的服务,压测改造还极有可能会引入新的 BUG,所以经常推动起来比较困难。因此为了解决这些问题,Rhino 平台有以下几个解决方案:

    1. 尽量减少代码改动,并提供完整的指导手册和代码示例,以减少开发人员的工作量并降低代码错误的风险。

    2. 提供简单易用的线上和线下HTTP&RPC的测试请求调试工具,以便于开发人员进行代码改造的验证。

    3. 对于新项目,在项目初期就将测试改造纳入开发规范中,以减少后续的代码改动。

3.3 链路治理

链路梳理

请求调用链,对于线上压测是非常重要的:

  • 提供清晰压测流量地图,并提供完整的链路监控。

  • 完成服务依赖的梳理,检测压测所依赖的服务/中台是否具备压测的条件,是否需要压测改造。

  • 链接压测开关管理,压测上下游周知等。

Rhino 平台通过公司的流式日志系统来完成调用链检索的。一个服务在被请求或者请求下游时,都会透传一个 LogID。RPC 框架会打印调用链日志(包括 RPC 日志-调用者日志,Access 日志-被调用者日志),所有日志中都会包含这个 LogID。通过 LogID 将一个请求所经过的所有服务日志串起来,就完成调用链检索。

图片

Rhino 平台在公司流式日志系统提供的链路梳理功能基础上,进行了进一步优化,以满足压测需要:

  • 自动梳理:鉴于公司采用微服务架构,每个请求背后的调用链非常复杂,人工维护几乎是不可能的任务。用户只需提供请求中的LogID,Rhino平台就能迅速地梳理出该请求经过的服务节点,如图所示。

  • 实时梳理:由于线上服务不断在变化,上线下线新增等,因此同一个请求的调用链也是不断变化的。Rhino 平台建议一般使用 1 个小时内的 LogID 进行梳理。

  • 多调链路合并:对于同一个接口,不同参数下的调用链可能各不相同。Rhino平台能够将多个LogID梳理的结果自动合并,以补全调用链,确保链路梳理结果的准确性和完整性。

图片

压测周知

虽然 Rhino 平台对于压测有很多的安全保障措施,但是对于大型压测,保证信息的通畅流通也是非常重要的。因此在压测周知方面,Rhino 平台也提供了很多解决方案:

  • 一键拉群:在完成链路梳理之后,在压测前可以一键拉群,将链路中上下游服务的 Owner 拉到同一个群里,以便及时同步压测信息。

  • 压测周知:每个压测开始执行时,都会向压测周知群里推送消息,包括测试的每秒查询率(QPS)、测试持续时间等关键信息。

图片

  • 压测事件:在压测开始执行时,Rhino平台还会向目标服务的事件队列发布一个测试事件,以便快速判断和定位稳定性问题是否由压测引起,从而降低研发人员在线上环境问题排查时的干扰。

压测开关管理

在进行压测之前,必须确保整体链路的压测开关的开启,否则压测流量就会被服务拒绝,导致压测失败。

  • 一键开启:在压测执行之前,Rhino 平台可以一键开启链接上所有节点的压测开关。

  • 压测开关开启周知:压测开关开启时,Rhino 平台会自动给对应服务 Owner 推送相关信息,确保服务 Owner 了解相关压测信息,上游会有压测流量会经过其服务。

  • 静默关闭:压测开关到期后,Rhino 会自动静默关闭压测开关,以保证线上服务的安全。

图片

服务 Mock

对于调用链中不能压测的服务(敏感服务),或者第三方服务,为了压测请求的完整性,就需要对这些服务进行 Mock。业界通用的 Mock 方案有:

  1. 直接更改业务代码,将服务调用替换为空运行代码。这种方法的优点是成本较低,但缺点是返回值过于固定,且对代码和业务的侵入性较强,特别是在 Mock 位置较为下游时,如果超出了部门所覆盖的业务范围,推动实施将变得异常困难。

  2. 采用通用的 Mock 服务。通用 MockServer 能够根据用户的不同配置,执行相应的响应延迟,并返回相应的响应数据。这种方法的优点是对业务无侵入性,但缺点是实现成本相对较高。

由于字节跳动全面采用了微服务架构,一次压测往往需要涉及较长的链路,因此快速且无需业务侵入的 Mock 方式成为了优先选择。Rhino 平台通过公司的 Service Mesh 和 ByteMock 系统实现了高效且对业务透明的服务模拟。

在压测执行前,Rhino 平台需要向 Service Mesh 注册染色转发规则,并向 Mock 服务注册 Mock 规则。然后,在压测流量中注入 Mock 染色标记,从而完成服务 Mock:

  • 基于 Service Mesh 的染色流量转发。首先,在压测流量中注入转发染色标记,并在 Service Mesh 中注册对应的转发规则。当 Service Mesh 检测到染色流量时,它会将其转发到指定的 Mock Server 上,如图。

图片

  • 基于 Mock Server 的请求规则匹配。首先在 Mock Server 上注册 Mock 规则,以及匹配的 Response 和响应时延。当 Mock Server 接收到请求后,会根据规则进行响应,如图。

图片

3.4 发压模式

最小调度单元

Rhino 平台中,压测 Agent 就是一个最小调度单元。一次压测任务,通常会拆分成多个子 Job,然后下发到多个 Agent 上来完成。

  • 最小化容器部署,减少资源浪费。压测对机器资源消耗是非常高的,通常 CPU &Memory 的使用率都在 80%以上。但是没有压测执行时间内,机器资源使用率<5%。如果长期占用大量的资源,将会对机器资源造成极大的浪费。压测 Agent 都采用容器化部署,并且每个容器的资源规格也尽可能小,这样既能满足日常压测需求,也不会占用太多的机器资源。

  • 独占 Agent,增加压测执行稳定性:单个容器内只启动一个 Agent 进程,单个 Agent 同时只能被一个压测任务占用,避免多任务多进程的干扰和资源竞争,增加压测的稳定性。

  • 动态扩容,支撑海量 QPS 发压:压测高峰期,Rhino 平台会临时申请机器资源,快速扩容,完成海量 QPS 的支撑。压测完成后,会立即释放机器资源,减少资源浪费。

2020 年春节抢红包压测中,Rhino 临时扩容在 4000+个实例,成功支撑了单次 3kw+QPS 的压测,但日常 Rhino 平台只部署了 100+个实例,就能满足日常压测需求。

智能压力调节
  • 动态分配压测 Agent:在压测过程,经常出现压测 Agent 的 CPU/Memory 使用率过高(>90%),导致压力上不去,达不到目标 QPS;或者压测延时过高,压测结果不准确的问题。Rhino 平台在发压的过程中,会实时监控每个压测 Agent 的 CPU/Memory 使用率,当超过阈值时(>90%),会动态分配额外的 Agent,以降低每个 Agent 的负载,保证压测的稳定性。

  • 智能调节压力:在压测过程,通常需要不断的调节 QPS 大小,以达到性能压测目标。这过程非常耗费精力和时间。Rhino 平台,可以根据压测任务设定的性能指标,智能调节 QPS 大小,一旦达到压测目标后,会自动熔断,停止压测。

压测链路模拟

Rhino 平台默认将全链路压测分为公网压测和内网压测。公网压测主要 IDC 网络带宽,延时,IDC 网关新建连接、转发等能力;内网压测,主要是对目标服务和目标集群的性能和容量等进行测试。

  • 对于内网压测,默认都要求同 IDC 内发压,减少网络延时的干扰。

  • 对于公网压测,Rhino 平台在公司 CDN 节点上都有部署 Agent 节点,利用了 CDN 节点剩余计算能力,完成了公网压测能力的建设。

同城多机房,异地多机房

Rhino 平台在各个 IDC 都有部署 Agent 集群。各个 IDC 内服务的压测,默认会就近选择压测 Agent,以减少网络延迟对测试结果的影响,从而使测试结果更加准确,更容易定位问题。

边缘计算节点 Agent

除了多机房部署之外,Rhino 平台还在边缘计算节点上也部署了压测 Agent,以模拟不同地区和不同运营商的流量请求,确保流量来源和分布更接近实际情况。在 Rhino 平台上可以选择不同地域不同运营商,从全国各个地区发起压测流量。

图片

3.5 压测熔断

为了应对线上压测风险,Rhino 平台提供两种熔断方式,来应对压测过程中的突发事件,来降低对线上服务造成的影响。

基于告警监控的熔断

每个压测任务,都可以关联调用链中任意服务的告警规则。在压测任务执行过程,Rhino 平台会主动监听告警服务。当调用链中有服务出现了告警,会立即停止压测。对于没有关联的告警,Rhino 平台也会记录下来,便于压测问题定位。

图片

图片

基于 Metric 的熔断

自定义监控指标及阈值,到达阈值后,也会自动停止压测。目前支持 CPU、Memory、 上游稳定性、错误日志,以及其他自定义指标。

此外,除了 Rhino 平台自身提供的熔断机制以外,公司服务治理架构也提供了很多额外的熔断机制,如压测开关,一键切断压测流量;过载保护,服务过载时自动丢弃压测流量。

3.6 任务模型

HTTP 任务

对于 HTTP 协议,参考了 Postman,全部可视化操作,保证所有人都能上手操作,极大降低了压测的使用门槛和成本。

图片

RPC 任务

对于 RPC 任务,Rhino 也自动完成了对 IDL 的解析,然后转换成 JSON 格式,便于用户参数化处理。

图片

自定义-Go Plugin

对于非 HTTP/RPC 的协议,以及有复杂逻辑的压测任务,Rhino 平台也提供了完善的解决方案——Go Plugin。

Go Plugin 提供了一种方式,通过在主程序和共享库直接定义一系列的约定或者接口,就可以动态加载其他人编译的 Go 语言共享对象,使得主程序可以在编译后动态加载共享库,实现热插拔的插件系统。此外主程序和共享库的开发者不需要共享代码,只要双方的约定不变,修改共享库后也不再需要重新编译主程序。

图片

用户只要根据规范要求,实现一段发压业务逻辑代码即可。Rhino 平台可以自动拉取代码,触发编译。并将编译后的插件 SO 文件分发到多个压测 Agent。Agent 动态加载 SO 文件,并发运行起来,就可以达到压测的目的。此外,Rhino 还针对常见 Go Plugin 压测场景,建立了压测代码示例代码库。对于压测新手,简单修改下业务逻辑代码,就可以完成压测了。这样就解决了非常见协议,以及复杂压测场景等的压测问题。

3.7 压测引擎

单 Agent 多引擎

压测调度的最小单元是压测 Agent,但是实际每个 Agent 中有挂载多种压测引擎的,来支撑不同的压测场景。Rhino 平台在压测数据和压测引擎之间增加了一个压测引擎适配层,实现了压测数据与压测引擎的解耦。压测引擎适配层,会根据选择不同的压测引擎,生成不同 Schema 的压测数据,启用不同的引擎来完成压测,而这些对用户是透明的。

图片

压测引擎

在压测引擎方面,我们有开源的压测引擎,也有自研的压测引擎。

开源测试引擎因其广泛的维护人员、全面的特性、稳定的性能和优秀的可靠性而广受欢迎,但其输入格式的限制和高度定制化的难度较大。此外,由于Agent通常与开源测试引擎在不同的进程中运行,进程间的通信问题可能会带来控制上的挑战。

相对而言,自研测试引擎虽然在性能上可能略逊一筹,但它通常与Agent在同一进程内运行,使得控制变得更加便捷。而且,得益于Golang原生支持高并发处理,自研与开源引擎在性能上的差距并不大。

  • HTTP 协议:我们默认使用Gatling作为测试工具,它在单机压测方面的性能非常卓越,远超Jmeter。在面对智能压测或动态调节的需求时,我们会切换到自研测试引擎。

  • RPC 协议:我们主要采用自研引擎,利用Golang的协程和RPC连接池来实现高并发压测。

  • GoPlugin 协议:基于自研引擎,借助Golang插件动态加载的特性,自动加载自定义的压测插件以完成测试。

3.8 压测监控

客户端监控

在监控方面,由于公司监控系统的时间粒度最小为30秒,将30秒内的数据聚合成一个监控点,这对于压测来说可能不够精细。因此,Rhino平台自主开发了一套客户端监控系统。

  • 每个 Request 都会以请求开始时间为基准打一个点。

  • 单个 Agent 内,会将相同任务相同接口,1s 内的打点数据在本地做一次汇总,上报到 Kafka 中。

  • 监控服务会消费 Kafka 中的打点数据,将多个 Agent 上报的数据进行再次汇总,然后写入数据库中。

  • 前端监控报表会实时拉取数据库中监控汇总数据,绘制实时监控曲线。

  • 在监控数据汇总流程中,对于请求响应时间的 PCT99 计算,是比较难处理的:

    • 目前 Rhino 平台采用的 T-Digest 算法来计算 1 秒内的 PCT99。

    • 整个时间段内的 PCT99 的计算,则是以 PCT & AGV 的方式聚合。即单位时间内通过 T-Digest 计算 PCT99;整个时间段内的 PCT99,则是对所有点的 PCT99 取平均值。

    • 整体计算方案已与公司服务端监控算法对齐,目的是减少客户端监控与服务端监控之间的 Gap,减少压测结果分析的干扰因素。

图片

服务端监控

服务端监控,直接接入了公司Metric系统。

  • 在压测过程中,Rhino平台会提供整条链路上所有节点核心指标的监控大盘,并高亮显示可能存在风险的节点,来提供实时预警。

  • 对于每个节点也都提供了实时的,详细的监控曲线图。

  • 对于每个节点默认提供CPU、Memory、QPS和Error_Rate等核心监控指标,用户可以在Rhino平台上修改监控配置,增加其他自定义监控指标。

图片

性能 Profile

在压测过程中,Rhino 平台还可以实时采集目标服务进程的性能 Profile,并通过火焰图的方式展示出来,方便用户进行性能问题分析和优化,如图。

图片

4、压测实践

Rhino 压测平台是一个为字节跳动全公司研发人员提供的一站式解决方案,它的核心目标是为了满足公司内所有重点项目和业务的高性能需求。Rhino 的开发团队不仅负责平台的搭建和日常维护工作,还与 QA&RD 团队紧密合作,以确保公司对性能的严格要求得到满足。

4.1 重大项目支撑

在公司的关键项目中,Rhino平台始终积极参与,并提供全面的支持。例如,抖音春晚活动、西瓜百万英雄挑战以及春节期间的红包雨活动等。这些活动都对平台的性能提出了极高的要求,而Rhino团队成功地应对了这些挑战。

以字节跳动春节期间的红包雨活动为例,Rhino 团队承担了整个活动的压测工作。这是一个在春节期间,字节跳动所有客户端共同参与的超大规模红包引流活动,包括抽奖分现金、红包锦鲤、红包雨等。这样的活动带来了巨大的流量,流量突发性强,业务逻辑和网络架构复杂,对 Rhino 平台提出了巨大的挑战。

在红包雨活动中,所有用户流量首先通过运营商专线接入到网络边缘的汇聚机房,经过过滤和验证后,再转发到核心机房。各个 IDC 之间相互备份,具体的流量路线如图所示。在这里,我们需要验证后端服务是否能承受预期的流程,以及各个专线带宽、网关带宽和转发能力、IDC 的承载能力以及它们之间的带宽等。

为了简化压测复杂性,也降低压测问题定位的难度,我们将整个压测拆分成多个阶段:

  • 通过拨测/CDN 压测来分别验证各个汇聚机房的承载能力、带宽以及网关性能。

  • 在各个汇聚机房部署压测 Agent,模拟用户流量分布,以测试核心机房后端服务的性能。

  • 进行单一接口单一实例的压测,单一接口单一机房的压测,场景化的全链路单机房压测,以及场景化的全链路全资源压测,分阶段验证后端服务的性能。

  • 最后,通过全网络的拨测来模拟真实春节期间红包雨的高峰流量,全面验证系统的性能。

图片

在这些大型项目的支撑中,Rhino团队不仅积累了丰富的行业知识和架构设计经验,而且深入掌握了开发团队对压测的观点以及他们如何使用平台。这使我们能够发现并解决平台存在的问题,从而推动平台的持续改进和优化。

4.2 日常压测任务支撑

为了支持日常的压测工作,Rhino平台扮演着极其关键的角色。对于日常压测中遇到的各种问题,我们采用了多种解决方案:

  • 专人 Oncall 值周,一对一指导。

  • 建立了一个详尽完善的压测知识库,不仅介绍了如何使用平台,还包括如何改进压测,如何制定压测方案以及如何定位压测问题。

  • 建立了完善的性能培训体系:定期举办与性能测试相关的分享会,并对QA和RD团队进行专业的压测培训。

4.3 线上流量调度

Rhino平台还实现了线上流量的定期调度,以实现线上实例的自动压测[8]:

  • 逐步将线上流量调度到目标实例,测试服务实例的性能极限,并提供实例性能概况,找出性能瓶颈。

  • 通过长期的流量调度,观察服务实例的性能变化,以监控服务性能的变化趋势。

  • 根据不同资源水位下的实例性能,预估整个集群的容量。完成服务容量预估以及线上风险评估。

  • 采用泳道化的流量调度,可以精确预估服务集群的容量。

其具体实现方案如下:

  • 调整负载均衡中目标实例的权重(Weight)值,逐步增大该权重值,将更多流量集中导向目标实例,直到达到设定的停止阈值。

目前已经有 500+微服务接入,每天定时执行流量调度,来监控线上服务性能变化趋势,如下图。

图片

4.4 常态化压测

Rhino平台正致力于在公司内部推广持续性的压测文化,以提升我们的服务质量和性能。通过定期的自动化全链路压测,我们旨在实现以下目标:

  • 实时监控线上服务集群容量,防止服务性能劣化。

  • 实时监控线上链路容量,防止链路性能劣化。

为了实现这一目标,Rhino平台上的持续性压测会按照预定的周期定时执行,以无人值守的方式自动完成压测任务,并将测试结果实时推送。在测试执行过程中,会根据调用链自动开启压测开关,发起压测流量。同时,实时监控服务性能指标,并根据性能指标和告警监控,自动完成压测熔断,以确保测试的安全性。

目前,多个业务部门已经接入持续性压测,以此确保线上服务的稳定性。此外,我们还提供了一系列的分析和报告工具,帮助开发人员和运维团队理解和优化测试结果。

Rhino平台通过持续性压测和提供一系列的分析报告工具,确保了线上服务的稳定性和性能。通过定期的自动化全链路压测,我们实现了实时监测线上服务集群和链路的容量,防止服务性能降低。此外,持续性压测还确保了测试的安全性,避免了潜在的风险。通过这些措施,我们进一步提升了公司的服务质量和性能,确保了业务的持续发展。

4.5 DevOps 流水线中的压测

在服务上线的过程中,通常会经历预发布、线上小流量灰度、线上全量发布等阶段。在这些阶段,我们可以通过线上测试用例和灰度发布来发现和拦截线上功能缺陷。然而,对于性能缺陷的发现和拦截,这种方法并不足够有效。

线上故障追踪系统揭示了一个重要问题:由于上线前缺乏性能压测,许多性能缺陷未能被及时发现,导致线上问题。

为了更有效地发现和识别性能缺陷,Rhino平台已经与DevOps平台实现了深度集成。我们将压测服务作为DevOps平台中的一个独立服务进行注册,并允许开发人员将压测环节插入到任意流水线的任意位置,确保在服务上线前进行必要的性能压测。DevOps流水线中的集成压测不仅帮助开发人员发现代码中的性能问题,还能通过与性能基线的对比,揭示代码性能退化的趋势。

为了进一步提升性能测试的效率和准确性,我们还提供了一系列的性能测试工具和指标,帮助开发人员和运维团队更好地理解和分析测试结果,从而在上线前及时发现并解决性能问题。

Rhino压测平台在公司的关键项目和业务中发挥着重要作用,确保服务质量和性能得到满足。通过定期的自动化全链路压测,我们实现了实时监控线上服务集群和链路的容量,防止服务性能下降。此外,Rhino平台已经与DevOps平台实现了深度集成,确保在服务上线前进行必要的性能压测。通过集成压测,开发人员可以及时发现代码中的性能问题,并对比性能基线以识别性能退化趋势。此外,我们还提供了一系列的性能测试工具和指标,以帮助开发人员和运维团队更好地理解和分析测试结果。总之,Rhino平台通过持续性压测和与DevOps平台的集成,确保服务在上线前具备高性能,满足公司的业务需求。

5、总结与展望

5.1 总结

Rhino 压测平台从立项到现在,不到两年的时间内,其发展已经初具规模,如图(每月压测执行统计)。这个期间,非常非常感谢公司内所有合作团队,尤其是架构团队,中台团队对压测平台的支撑,没有他们的支撑,全链路压测建设是难以完成的。

图片

5.2 未来发展

业务深层次定制化

通用压测平台已经初步搭建完成,能够满足大多数业务线的日常测试需求,但在实际支持过程中,我们发现不同业务线在进行压测时,仍需完成许多前期和后期工作,这些往往需要人工介入。

为了进一步减少业务方在压测改造上的成本,降低环境数据预置的费用,加快测试数据清理的速度,以及更快速地定位性能问题,Rhino 压测平台计划更深入地融入业务场景,与各业务部门深化合作,提供更精细化的业务定制服务,以提高研发效率,推动业务线的持续发展。

压测与容量规划

在业务资源管理方面,我们需要回答的问题包括:当前的资源是否足够,业务的容量有多大;在当前的业务增长速度下,现有的机器资源能够维持多久?

同时,我们也要审视现有服务资源的利用情况,探讨如何优化以提高资源利用率,并降低机器成本。在面对大型活动时,如何合理申请和分配资源,是否可以避免不必要的压测,或者通过自动化手段利用线上流量数据,或借鉴日常测试数据,来得出合理的结论,这些都是我们未来需要关注的焦点。

压测与 SRE

在确保服务稳定性的同时,如何有效监控服务性能的下降并及时发出预警,如何合理配置限流、超时、重试和熔断等治理措施,以及如何与混沌测试结合进行容灾演练,保证服务的稳定性,这些都是Rhino平台未来将要深入探索的领域。我们致力于通过这些努力,不断提升服务的可靠性和性能,以满足业务发展的需求。

如何保证RocketMQ消息有序?如何解决RocketMQ消息积压?

1、为什么需要消息有序

假设准备去银行存取款,对应两个异步短信消息,要保证先存后取:

  • M1 存钱

  • M2 取钱

图片

而MQ默认发消息到不同Q显然是行不通的,会乱序。因此,需发往同一Q,依赖队列的先进先出机制。

再例如

如果我们有个大数据系统,需要对业务系统的日志进行收集分析,这时候为了减少对业务系统的影响,通常都会通过MQ来做消息中转。而这时候,对消息的顺序就有一定的要求了。例如我们考虑下面这一系列操作:

  1. 用户的积分默认是0分,而新注册用户设置为默认的10分。

  2. 用户有奖励行为,积分+2分

  3. 用户有不正当行为,积分-3分

这样一组操作,正常用户积分要变成9分。但是如果顺序乱了,这个结果就全部对不上。这时,就需要对这一组操作,保证消息都是有序的。

2、基本概念

有序消息,又叫顺序消息(FIFO消息),指消息的消费顺序和产生顺序相同。

如订单的生成、付款、发货,这串消息必须按序处理。顺序消息又可分为:

2.1 全局顺序

一个Topic内所有的消息都发布到同一Q,按FIFO顺序进行发布和消费:

图片

适用场景

性能要求不高,所有消息严格按照FIFO进行消息发布和消费的场景。

2.2 分区顺序

对于指定的一个Topic,所有消息按sharding key进行区块(queue)分区,同一Q内的消息严格按FIFO发布和消费。

  • Sharding key是顺序消息中用来区分不同分区的关键字段,和普通消息的Key完全不同。

图片

适用场景

性能要求高,根据消息中的sharding key去决定消息发送到哪个queue。

2.3 对比

  • 发送方式对比

图片

3、如何保证消息顺序?

在MQ模型中,顺序需由3个阶段去保障

  1. 消息被发送时保持顺序

  2. 消息被存储时保持和发送的顺序一致

  3. 消息被消费时保持和存储的顺序一致

图片

如何保证消息有序

MQ的顺序问题分为全局有序和局部有序

  • 全局有序:整个MQ系统的所有消息严格按照队列先入先出顺序进行消费

  • 局部有序:只保证一部分关键信息的消费顺序

首先,我们需要分析下这个问题,在通常的业务场景中,全局有序和局部有序哪个更重要?其实大部分的MQ业务场景,我们只需要保证局部有序就可以了。例如我们用QQ聊天,只需要保证一个聊天窗口里的消息有序就可以了。而对于电商订单场景,也只要保证一个订单的所有消息是有序的就可以了。至于全局的消息的顺序,并不会太关心。而通常意义下,全局有序都可以压缩程局部有序的问题。例如以前我们常用的聊天室,就是一个典型的需要保证消息全局有序的场景。但是这种场景,通常可以压缩成只有一个聊天窗口的QQ来理解。即整个系统只有一个聊天通道,这样就可以用QQ那种保证一个聊天窗口消息有序的方式来保证整个系统的全局消息有序。

然后,落地到RocketMQ。通常情况下,发送者发送消息时,会通过MessageQueue轮询的方式保证消息尽量均匀分布到所有的MessageQueue上,而消费者也就同样需要从多个MessageQueue上消费消息。而MessageQueue是RocketMQ存储消息的最小单元,他们之间的消息都是互相隔离的,在这种情况下,是无法保证消息全局有序的。

而对于局部有序的要求,只需要将有序的一组消息都存入同一个MessageQueue里,这样MessageQueue的FIFO设计天生就可以保证这一组消息的有序。RocketMQ中,可以在发送者发送消息时指定一个MessageSelector对象,让这个对象来决定消息发入哪一个MessageQueue。这样就可以保证一组有序的消息能够发到同一个MessageQueue里。

另外,通常所谓的保证Topic全局消息有序的方式,就是将Topic配置成只有一个MessageQueue队列(默认是4个)。这样天生就能保证消息全局有序了。这个说法其实就是我们将聊天室场景压缩只有一个聊天窗口的QQ一样的理解方式。而这种方式对整个Topic的消息吞吐影响是非常大的,如果这样用,基本就没有用MQ的必要了。

4、RocketMQ 有序消息实现原理

图片

RocketMQ消费端有两种类型:

  • MQPullConsumer

  • MQPushConsumer

图片

底层都是通过pull机制实现,pushConsumer是一种API封装而已。

  • MQPullConsumer 由用户控制线程,主动从服务端获取消息,每次获取到的是一个MessageQueue中的消息。

    • PullResult中的 List<MessageExt> msgFoundList

图片

  • MQPushConsumer由用户注册MessageListener来消费消息,在客户端中需要保证调用MessageListener时消息的顺序性

图片

图片

看源码

图片

图片

图片

  • 拉取生产端消息

图片

  • 判断是并发的还是有序的,对应不同服务实现类

图片

5、有序消息的缺陷

发送顺序消息无法利用集群的Failover特性,因为不能更换MessageQueue进行重试。

因为发送的路由策略导致的热点问题,可能某一些MessageQueue的数据量特别大

  • 消费的并行读依赖于queue数量

  • 消费失败时无法跳过

使用RocketMQ如何快速处理积压消息

如何确定RocketMQ有大量的消息积压

在正常情况下,使用MQ都会要尽量保证他的消息生产速度和消费速度整体上是平衡的,但是如果部分消费者系统出现故障,就会造成大量的消息积累。这类问题通常在实际工作中会出现的比较隐蔽。

例如某一天一个数据库突然挂了,大家大概率就会集中处理数据库的问题。

等好不容易把数据库恢复过来了,这时基于数据库服务的消费者程序就会积累大量的消息。或者网络波动等情况,也会导致消息大量的积累。这在一些大型的互联网项目中,消息积压的速度是相当恐怖的。所以消息积压是个需要时刻关注的问题。

对于消息积压,如果是RocketMQ或者kafka还好,他们的消息积压不会对性能造成很大的影响。

而如果是RabbitMQ的话,那就惨了,大量的消息积压可以瞬间造成性能曲线下滑。

对于RocketMQ来说,有个最简单的方式来确定消息是否有积压。那就是使用web控制台,就能直接看到消息的积压情况。

在web控制台的主题页面,可以通过Consumer管理按钮实时看到消息的积压情况。

图片

另外,也可以通过mqadmin指令在后台检查各个Topic的消息延迟情况。

还有RocketMQ也会在他的 ${storePathRootDir}/config 目录下落地一系列的json文件,也可以用来跟踪消息积压情况。

如何处理大量积压消息

其实我们回顾下RocketMQ的负载均衡的内容就不难想到解决方案。

如果Topic下的MessageQueue配置的是足够多的,那每个Consumer实际上会分配多个MessageQueue来进行消费。

这个时候,就可以简单的通过增加Consumer的节点个数设置成跟MessageQueue的个数相同,但是如果此时再继续增加Consumer的服务节点就没有用了。

而如果Topic下的MessageQueue配置的不够多的话,那就不能用上面这种增加Consumer节点个数的方法了。这时怎么办呢?

这时如果要快速处理积压的消息,可以创建一个新的Topic,配置足够多的MessageQueue。

然后把所有消费者节点的目标Topic转向新的Topic,并紧急上线一组新的消费者,只负责转储,就是消费老Topic中的积压消息,并转储到新的Topic中,这个速度是可以很快的。

然后在新的Topic上,就可以通过增加消费者个数来提高消费速度了。之后再根据情况恢复成正常情况。

在官网中,还分析了一个特殊情况。就是如果RocketMQ原来是采用的普通方式搭建主从架构,而现在想要中途改为使用Dledger高可用集群,这时候如果不想历史消息丢失,就需要先将消息进行对齐,也就是要消费者先把所有的消息都消费完,再来切换主从架构。

因为Dledger集群会接管RocketMQ原有的CommitLog日志,所以切换主从架构时,如果有消息没有消费完,这些消息是存在旧的CommitLog中的,就无法再进行消费了。这个场景下也是需要尽快的处理掉积压的消息。

参考文献

https://juejin.cn/post/7041137041593237541

数据库性能太差,有哪些调优方案?

为什么数据库会慢?

慢的本质

查找的时间复杂度

查找算法

存储数据结构

数据总量

数据拆分

高负载

CPU、磁盘繁忙

无论是关系型数据库还是NoSQL,任何存储系统决定于其查询性能的主要有三种:

  • 查找的时间复杂度

  • 数据总量

  • 高负载

而决定于查找时间复杂度主要有两个因素:

  • 查找算法

  • 存储数据结构

无论是哪种存储,数据量越少,自然查询性能就越高,随着数据量增多,资源的消耗(CPU、磁盘读写繁忙)、耗时也会越来越高。

从关系型数据库角度出发,索引结构基本固定是B+Tree,时间复杂度是O(log n),存储结构是行式存储。因此咱们对于关系数据库能优化的一般只有数据量。

而高负载造成原因有高并发请求、复杂查询等,导致CPU、磁盘繁忙等,而服务器资源不足则会导致慢查询等问题。该类型问题一般会选择集群、数据冗余的方式分担压力。

图片

应该站在哪个层面思考优化?

图片

从上图可见,自顶向下的一共有四层,分别是硬件、存储系统、存储结构、具体实现。层与层之间是紧密联系的,每一层的上层是该层的载体;因此越往顶层越能决定性能的上限,同时优化的成本也相对会比较高,性价比也随之越低。以最底层的具体实现为例,那么索引的优化的成本应该是最小的,可以说加了索引后无论是CPU消耗还是响应时间都是立竿见影降低;然而一个简单的语句,无论如何优化加索引也是有局限的,当在具体实现这层没有任何优化空间的时候就得往上一层【存储结构】思考,思考是否从物理表设计的层面出发优化(如分库分表、压缩数据量等),如果是文档型数据库得思考下文档聚合的结果;如果在存储结构这层优化得没效果,得继续往再上一次进行考虑,是否关系型数据库应该不适合用在现在得业务场景?如果要换存储,那么得换怎样得NoSQL?

所以咱们优化的思路,出于性价比的优先考虑具体实现,实在没有优化空间了再往上一层考虑。当然如果公司有钱,直接使用钞能力,绕过了前面三层,这也是一种便捷的应急处理方式。

该篇文章不讨论顶与底的两个层面的优化,主要从存储结构、存储系统中间两层的角度出发进行探讨

八大方案

图片

数据库的优化方案核心本质有三种:减少数据量用空间换性能选择合适的存储系统,这也对应了开篇讲解的慢的三个原因:数据总量、高负载、查找的时间复杂度

这里大概解释下收益类型:短期收益,处理成本低,能紧急应对,久了则会有技术债务;长期收益则跟短期收益相反,短期内处理成本高,但是效果能长久使用,扩展性会更好。

静态数据意思是,相对改动频率比较低的,也无需过多联表的,where过滤比较少。动态数据与之相反,更新频率高,通过动态条件筛选过滤。

减少数据量

减少数据量类型共有四种方案:数据序列化存储、数据归档、中间表生成、分库分表

就如上面所说的,无论是哪种存储,数据量越少,自然查询性能就越高,随着数据量增多,资源的消耗(CPU、磁盘读写繁忙)、耗时也会越来越高。目前市面上的NoSQL基本上都支持分片存储,所以其天然分布式写的能力从数据量上能得到非常的解决方案。而关系型数据库,查找算法与存储结构是可以优化的空间比较少,因此咱们一般思考出发点只有从如何减少数据量的这个角度进行选择优化,因此本类型的优化方案主要针对关系型数据库进行处理。

图片

数据归档

数据归档

做法

场景

优点

缺点

利用数据库作业,定时把历史数据移到历史表或者库

局部的热点数据

结构无需改动,少侵入性

热点数据过多仍会导致性能问题

注意点:别一次性迁移数量过多,建议低频率多次限量迁移。像MySQL由于删除数据后是不会释放空间的,可以执行命令OPTIMIZE TABLE释放存储空间,但是会锁表,如果存储空间还满足,可以不执行。建议优先考虑该方案,主要通过数据库作业把非热点数据迁移到历史表,如果需要查历史数据,可新增业务入口路由到对应的历史表(库)。

图片

中间表(结果表)

中间表(结果表)

做法

场景

优点

缺点

通过调度任务定时,把某个业务以多个维度进行聚合分组

报表型、排行榜等静态数据

压缩比率大

需要开发人员针对场景业务进行开发

中间表(结果表)其实就是利用调度任务把复杂查询的结果跑出来存储到一张额外的物理表,因为这张物理表存放的是通过跑批汇总后的数据,因此可以理解成根据原有的业务进行了高度的数据压缩。以报表为例,如果一个月的源数据有数十万,我们通过调度任务以月的维度生成,那么等于把原有的数据压缩了几十万分之一;接下来的季报和年报可以根据月报*N来进行统计,以这种方式处理的数据,就算三年、五年甚至十年数据量都可以在接受范围之内,而且可以精确计算得到。

那么数据的压缩比率是否越低越好?下面有一段口诀:

  • 字段越多,粒度越细,灵活性越高,可以以中间表进行不同业务联表处理。

  • 字段越少,粒度越粗,灵活性越低,一般作为结果表查询出来。

数据序列化存储

数据序列化存储

做法

场景

优点

缺点

把一对多的数据,通过序列化字符串存储

不需要要求所有字段作为结构化存储

压缩比率高

序列化的字段无法联表

在数据库以序列化存储的方式,对于一些不需要结构化存储的业务来说是一种很好减少数据量的方式,特别是对于一些M*N的数据量的业务场景,如果以M作为主表优化,那么就可以把数据量维持最多是M的量级。另外像订单的地址信息,这种业务一般是不需要根据里面的字段检索出来,也比较适合。

这种方案我认为属于一种临时性的优化方案,无论是从序列化后丢失了部份字段的查询能力,还是这方案的可优化性都是有限的。

分库分表

分库分表作为数据库优化的一种非常经典的优化方案,特别是在以前NoSQL还不是很成熟的年代,这个方案就如救命草一般的存在。

如今也有不少同行也会选择这种优化方式,但是从我角度来看,分库分表是一种优化成本很大的方案。这里我有几个建议:

  1. 分库分表是实在没有办法的办法,应放到最后选择。

  2. 优先选择NoSQL代替,因为NoSQL诞生基本上为了扩展性与高性能。

  3. 究竟分库还是分表?量大则分表,并发高则分库

  4. 不考虑扩容,一部做到位。因为技术更新太快了,每3-5年一大变。

拆分方式

                                            分库分表-拆分方式

拆分方式

角度

优点

垂直拆分

按照业务拆分

降低业务耦合度

减少字段,物理页所拥有的行数则变多

水平拆分

从物理层面分片

从根本上减少数据量

只要涉及到这个拆,那么无论是微服务也好,分库分表也好,拆分的方式主要分两种:垂直拆分、水平拆分

垂直拆分更多是从业务角度进行拆分,主要是为了降低业务耦合度;此外以SQL Server为例,一页是8KB存储,如果在一张表里字段越多,一行数据自然占的空间就越大,那么一页数据所存储的行数就自然越少,那么每次查询所需要IO则越高因此性能自然也越慢;因此反之,减少字段也能很好提高性能。之前我听说某些同行的表有80个字段,几百万的数据就开始慢了。

水平拆分更多是从技术角度进行拆分,拆分后每张表的结构是一模一样的,简而言之就是把原有一张表的数据,通过技术手段进行分片到多张表存储,从根本上解决了数据量的问题。

图片

图片

路由方式

路由方式

算法

优点

缺点

区间范围

查询定位比较容易

容易造成数据不平均(热点数据)

容易忘记创建新表

Hash

分片均匀

必须带分区键,不带分区键则会所有表都扫描一遍

分库情况下无法使用关系型数据库的特性(Join、聚合计算、分页)

分片映射表

补充方案

二次查询

进行水平拆分后,根据分区键(sharding key)原来应该在同一张表的数据拆解写到不同的物理表里,那么查询也得根据分区键进行定位到对应的物理表从而把数据给查询出来。

路由方式一般有三种区间范围、Hash、分片映射表,每种路由方式都有自己的优点和缺点,可以根据对应的业务场景进行选择。

区间范围根据某个元素的区间的进行拆分,以时间为例子,假如有个业务我们希望以月为单位拆分那么表就会拆分像 table_2022-04,这种对于文档型、ElasticSearch这类型的NoSQL也适用,无论是定位查询,还是日后清理维护都是非常的方便的。那么缺点也明显,会因为业务独特性导致数据不平均,甚至不同区间范围之间的数据量差异很大。

Hash也是一种常用的路由方式,根据Hash算法取模以数据量均匀分别存储在物理表里,缺点是对于带分区键的查询依赖特别强,如果不带分区键就无法定位到具体的物理表导致相关所有表都查询一次,而且在分库的情况下对于Join、聚合计算、分页等一些RDBMS的特性功能还无法使用。

图片

一般分区键就一个,假如有时候业务场景得用不是分区键的字段进行查询,那么难道就必须得全部扫描一遍?其实可以使用分片映射表的方式,简单来说就是额外有一张表记录额外字段与分区键的映射关系。举个例子,有张订单表,原本是以UserID作为分区键拆分的,现在希望用OrderID进行查询,那么得有额外得一张物理表记录了OrderID与UserID的映射关系。因此得先查询一次映射表拿到分区键,再根据分区键的值路由到对应的物理表查询出来。可能有些朋友会问,那这映射表是否多一个映射关系就多一张表,还是多个映射关系在同一张表。我优先建议单独处理,如果说映射表字段过多,那跟不进行水平拆分时的状态其实就是一致的,这又跑回去的老问题。

用空间换性能

该类型的两个方案都是用来应对高负载的场景,方案有以下两种:分布式缓存、一主多从。

与其说这个方案叫用空间换性能,我认为用空间换资源更加贴切一些。因此两个方案的本质主要通数据冗余、集群等方式分担负载压力。

对于关系型数据库而言,因为他的ACID特性让它天生不支持写的分布式存储,但是它依然天然的支持分布式读

图片

分布式缓存

分布式缓存

做法

场景

缺点

Cache

Aside

应对高并发读

动态条件比较多的业务场景,缓存命中低

伪静态数据(业务配置、低时效的数据)

实时性要求高的数据场景,处理起来比较花功夫

缓存层级可以分好几种:客户端缓存API服务本地缓存分布式缓存,咱们这次只聊分布式缓存。一般我们选择分布式缓存系统都会优先选择NoSQL的键值型数据库,例如Memcached、Redis,如今Redis的数据结构多样性,高性能,易扩展性也逐渐占据了分布式缓存的主导地位。

缓存策略也主要有很多种:Cache-AsideRead/Wirte-ThroughWrite-Back,咱们用得比较多的方式主要Cache-Aside,具体流程可看下图:

图片

我相信大家对分布式缓存相对都比较熟悉了,但是我在这里还是有几个注意点希望提醒一下大家:

避免滥用缓存

缓存应该是按需使用,从28法则来看,80%的性能问题由主要的20%的功能引起。滥用缓存的后果会导致维护成本增大,而且有一些数据一致性的问题也不好定位。特别像一些动态条件的查询或者分页,key的组装是多样化的,量大又不好用keys指令去处理,当然我们可以用额外的一个key把记录数据的key以集合方式存储,删除时候做两次查询,先查Key的集合,然后再遍历Key集合把对应的内容删除。这一顿操作下来无疑是非常废功夫的,谁弄谁知道。

图片

避免缓存击穿

当缓存没有数据,就得跑去数据库查询出来,这就是缓存穿透

假如某个时间临界点数据是空的例如周排行榜,穿透过去的无论查找多少次数据库仍然是空,而且该查询消耗CPU相对比较高,并发一进来因为缺少了缓存层的对高并发的应对,这个时候就会因为并发导致数据库资源消耗过高,这就是缓存击穿。数据库资源消耗过高就会导致其他查询超时等问题。

该问题的解决方案也简单,对于查询到数据库的空结果也缓存起来,但是给一个相对快过期的时间。有些同行可能又会问,这样不就会造成了数据不一致了么?

一般有数据同步的方案像分布式缓存、后续会说的一主多从、CQRS,只要存在数据同步这几个字,那就意味着会存在数据一致性的问题,因此如果使用上述方案,对应的业务场景应允许容忍一定的数据不一致。

不是所有慢查询都适用

一般来说,慢的查询都意味着比较吃资源的(CPU、磁盘I/O)。

举个例子,假如某个查询功能需要3秒时间,串行查询的时候并没什么问题,我们继续假设这功能每秒大概QPS为100,那么在第一次查询结果返回之前,接下来的所有查询都应该穿透到数据库,也就意味着这几秒时间有300个请求到数据库,如果这个时候数据库CPU达到了100%,那么接下来的所有查询都会超时,也就是无法有第一个查询结果缓存起来,从而还是形成了缓存击穿。

一主多从

一主多从

场景

优点

缺点

分担数据库读压力

应急调整方便,单以运维直接解决。

高硬件成本

还没找到更好的降低数据库负载的临时方案

扩展性有限

常用的分担数据库压力还有一种常用做法,就是读写分离、一主多从。咱们都是知道关系型数据库天生是不具备分布式分片存储的,也就是不支持分布式写,但是它天然的支持分布式读。一主多从是部署多台从库只读实例,通过冗余主库的数据来分担读请求的压力,路由算法可有代码实现或者中间件解决,具体可以根据团队的运维能力与代码组件支持视情况选择。

一主多从在还没找到根治方案前是一个非常好的应急解决方案,特别是在现在云服务的年代,扩展从库是一件非常方便的事情,而且一般情况只需要运维或者DBA解决就行,无需开发人员接入。当然这方案也有缺点,因为数据无法分片,所以主从的数据量完全冗余过去,也会导致高的硬件成本。从库也有其上限,从库过多了会主库的多线程同步数据的压力。

图片

选择合适的存储系统

NoSQL主要以下五种类型:键值型、文档型、列型、图型、搜素引擎,不同的存储系统直接决定了查找算法存储数据结构,也应对了需要解决的不同的业务场景。

NoSQL的出现也解决了关系型数据库之前面临的难题(性能、高并发、扩展性等)。

例如,ElasticSearch的查找算法是倒排索引,可以用来代替关系型数据库的低性能、高消耗的Like搜索(全表扫描)。

例如,Redis的Hash结构决定了时间复杂度为O(1),还有它的内存存储,结合分片集群存储方式以至于可以支撑数十万QPS。

因此本类型的方案主要有两种:

  • CQRS、

  • 替换(选择)存储,

这两种方案的最终本质基本是一样的:主要使用合适存储来弥补关系型数据库的缺点,只不过切换过渡的方式会有点不一样。

CQRS

CQS(命令查询分离)指同一个对象中作为查询或者命令的方法,每个方法或者返回的状态,要么改变状态,但不能两者兼备

CQRS

场景

优点

缺点

需要保留关系型数据库的使用,又要使用NoSQL的高性能与可扩展性

原应用改动范围比较小,兼容旧业务,只需要替换读的底层。

高硬件成本

允许非实时的数据场景

即保留了关系型数据库的ACID特性,又使用NoSQL的可扩展性与高性能

数据同步

讲解CQRS前得了解CQS,我这里用通俗的话解释:某个对象的数据访问的方法里,要么只是查询,要么只是写入(更新)。

CQRS 是一种与领域驱动设计 (DDD) 和事件溯源相关的架构模式。

Greg Young 在 2010 年创造了CQRS 这个术语,CQRS 的内容基于 Bertrand Meyer 的 CQS 设计模式。

CQRS 的背后是什么?

CQS (命令查询分离)设计模式建议将对象的方法映射到两类:方法要么改变对象的内部状态,但不返回任何内容,要么只返回元数据。这种方法称为Command。或者一个方法返回信息但不改变内部状态。这种方法称为Query

根据 CQS,一个方法永远不应该同时存在命令和查询。

比如典型数据结构栈的操作中,push函数是一个命令,而top是一个查询,遵守了CQS。然后,pop 函数违反了 CQS 模式,因为它修改了堆栈的内部状态并同时返回信息。

因此,CQS 的核心是在单个对象上分离写入和读取。

这尤其重要,例如,当代码要并行执行时:由于没有副作用,查询可以并行化而没有任何问题,但命令不能。

在 CQS 模式的基础上,Greg Young 在 2010 年创造了CQRS(Command Query Responsibility Segregation)架构模式。

CQRS它也将写入和读取分开,但在主要说的是 API 方面。

CQRS提出了单独的 API,一个专用于更改应用程序状态的命令路由,另一个专用于返回有关应用程序状态信息的查询路由。

另一个方面,CQRS(命令查询职责分离)基于CQS的基础上,用物理数据库来写入(更新),而用另外的存储系统来查询数据。

因此我们在某些业务场景进行存储架构设计时,可以NOSQL+SQL结合:

  • 可以通过关系型数据库的ACID特性进行数据的更新与写入,

  • 用NoSQL的高性能与扩展性进行数据的查询处理,

NOSQL+SQL结合的好处:

  • 就是关系型数据库和NoSQL的优点都可以兼得,

  • 同时对于某些业务不适于一刀切的替换存储的也可以有一个平滑的过渡。

从代码实现角度来看,不同的存储系统只是调用对应的接口API,因此CQRS的难点主要在于如何进行数据同步。

数据同步方式

图片

一般讨论到数据同步的方式主要是分拉:

  • 推指的是由数据变更端通过直接或者间接的方式把数据变更的记录发送到接收端,从而进行数据的一致性处理,这种主动的方式优点是实时性高。

  • 拉指的是接收端定时的轮询数据库检查是否有数据需要进行同步,这种被动的方式从实现角度来看比推简单,因为推是需要数据变更端支持变更日志的推送的。

而推的方式又分两种:CDC(变更数据捕获)和领域事件。

对于一些旧的项目来说,某些业务的数据入口非常多,无法完整清晰的梳理清楚,这个时候CDC就是一种非常好的方式,只要从最底层数据库层面把变更记录取到就可。

对于已经服务化的项目来说领域事件是一种比较舒服的方式,因为CDC是需要数据库额外开启功能或者部署额外的中间件,而领域事件则不需要,从代码可读性来看会更高,也比较开发人员的维护思维模式。

替换(选择)存储系统

替换(选择)存储系统,  从本质来看该模式与CQRS的核心本质是一样的,主要是要对NoSQL的优缺点有一个全面认识,这样才能在对应业务场景选择与判断出一个合适的存储系统。

这里我像大家介绍一本书马丁.福勒《NoSQL精粹》,这本书我重复看了好几遍,也很好全面介绍各种NoSQL优缺点和使用场景。

当然替换存储的时候,我这里也有个建议:加入一个中间版本,该版本做好数据同步与业务开关,数据同步要保证全量与增量的处理,

数据同步,需要随时可以重来,业务开关主要是为了后续版本的更新做的一个临时型的功能,主要避免后续版本更新不顺利或者因为版本更新时导致的数据不一致的情况出现。

在跑了一段时间后,验证了两个不同的存储系统数据是一致的后,接下来就可以把数据访问层的底层调用替换了。如此一来就可以平滑的更新切换。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值