52、单体应用向微服务转换:替换、代理与测试策略

单体应用转向微服务策略

单体应用向微服务转换:替换、代理与测试策略

在软件开发领域,将单体应用转换为微服务架构是一个重要的趋势。这种转变能够带来诸多好处,如提高系统的灵活性、可维护性和可扩展性。下面将详细介绍替换为微服务、单体到微服务代理以及回放测试这三种关键方法。

替换为微服务

将单体应用中的功能逐步替换为微服务是一个复杂但值得的过程。以下是基本步骤:
1. 逐步重写旧客户端组件 :让旧客户端组件调用新的微服务,而非单体应用中的旧组件。
2. 移除原有功能实现(若可行) :在新微服务稳定运行后,考虑移除单体应用中原来的功能实现。

不过,这些步骤也有其他变体。例如,可以先冻结功能,然后为该功能创建新的接口或抽象层。这个接口可以直接调用原功能的实现(通过代理实现),接着更新单体应用中的客户端代码,使其调用这个新接口。当新的微服务创建完成且你对新实现有信心时,再将接口切换为使用新的微服务实现。

应对单体应用中的挑战区域

单体应用通常存在许多内聚性差且紧密耦合的区域,这使得找出可提取的代码组变得极具挑战性。你需要寻找单体应用中可以分解、提取或替换的区域或分区。有时,分析可以发现一些细微的裂缝,即某些功能可以重构为更好的设计,从而在单体应用中创建更易于提取的组件。在这种情况下,你要寻找代码中可以与单体应用解耦的特定区域。而在很多区域,解耦可能无法实现,这时就需要锁定单体应用中该功能的更改,并使用微服务重新实现。

当处理单体应用中的这些纠缠区域,用微服务替换其功能时,通常最好关注那些变化频繁的关键业务能力,特别是当这些变化引发问题时。纠缠的单体应用通常包含围绕这些能力耦合的功能,目标是通过关注领域来解耦这些能力。可以通过以下方式在单体应用中找到这些区域:
- 寻找相关功能的高级分组 :例如账户管理、发票开具、报价、合同签订、计费、运输等。
- 在功能分组中寻找较小的重复用例 :例如税务计算、运输供应商选择、信用卡支付、忠诚度积分兑换等。
- 确定重复用例中的代码级组件 :将这些组件构建为微服务。

为了在代码中搜索这些区域,你还可以分析提交日志,检查问题跟踪工具(如Jira)中的问题。此外,使用显示耦合和其他代码异味的工具(如SonarQube)也会有所帮助。系统中频繁变化且影响其他部分的部分应作为潜在的迁移到微服务的部分,尽早将这些部分从单体应用中提取出来是很有价值的。

替换为微服务的优缺点

替换功能为微服务提供了灵活性,以及使用新技术、框架和平台的好处。团队可以在不破坏单体应用的情况下尝试新想法。重写这些部分还使功能在未来更容易更改,因为已将其与单体应用解耦。

然而,组织会失去在单体应用中为冻结的代码添加功能的好处。在代码被锁定期间,无法为系统的这部分添加新功能。而且,单体应用中的部分利用微服务中实现的新功能可能会很复杂。最后,单体应用中存储的数据与新微服务中存储的数据之间可能存在数据同步问题,特别是在冻结代码中使用的数据。

示例
  • 大型酒店连锁 :该酒店连锁采用替换为微服务的方法,替换了一个大型Web表单应用中用于查看和兑换酒店奖励积分的现有复杂子系统。由于现有代码存在漏洞且复杂,他们选择将整个奖励部分重写为分布式服务。从长远来看,几年后酒店连锁决定用商业奖励跟踪应用替换其自主开发的奖励跟踪系统,由于之前开发的服务有单一远程API,大大减少了完全更换奖励系统实现所需的工作量。
  • 电子商务 :在一个电子商务系统中,将单体应用中的“授权支付”功能替换为微服务。授权是对信用卡或借记卡的批准,用于验证持卡人是否有足够的资金支付他们试图进行的交易。第三方系统通常会收取交易金额的1.5% - 3.5%来提供此功能。对于大量交易,这笔费用可能会很高,因此有时在内部实现此功能是一个不错的选择。由于“授权支付”功能在电子商务单体应用中紧密耦合,并且会调用第三方服务进行信用卡支付授权,他们决定锁定单体应用中这部分的更改,并在单体应用外将此功能重写为新服务。新服务发布后,他们开始对新客户端进行测试和金丝雀发布。随着时间的推移,他们重写了单体应用的部分代码,使其直接调用新服务。
单体到微服务代理

在将单体应用转换为微服务的过程中,需要让现有的单体代码调用作为微服务提供的新功能。那么,开发人员如何更改单体应用中的代码,以访问和使用被微服务替换的功能呢?

在长期的“绞杀单体应用”过程中,单体应用中的组件会逐渐被微服务替换。微服务可能使用与单体应用不同的协议和消息格式,而让单体应用采用微服务使用的标准化消息格式可能成本高昂。同时,更新单体应用中的大量组件以调用新微服务的成本和风险也很高。而且,旧的客户端应用需要访问正在被提取为微服务的单体应用组件。

因此,当将功能从单体应用组件迁移到微服务时,保留单体应用中的旧组件,但将其重写为代理,以将调用重定向到新的微服务。主要思路是旧客户端组件的接口保持不变。被微服务替换的单体应用组件不再直接实现处理调用的业务逻辑,它们仍然暴露相同的契约,但现在只是将调用路由到基于微服务的新实现。

下面是一个简单的mermaid流程图,展示了单体到微服务代理的过程:

graph LR
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    A[现有客户端]:::process --> B[单体组件X]:::process
    A --> C[单体组件Y]:::process
    B --> B1[微服务X']:::process
    C --> C1[微服务Y']:::process
优缺点

与通用的外观模式解决方案相比,这种模式的主要优点与处理来自单体应用内部的调用有关。单体应用内的客户端和组件不受影响,因为它们仍然看到原始契约。而简单地包装单体应用时,仍然需要以某种方式重定向来自单体应用内部的调用,就没有这个优点。

与包装单体应用不同,这种模式不需要创建、配置和监控外观组件,以使现有客户端能够与新微服务无缝通信。因此,这种模式通常比外观模式更容易实现和管理。然而,与外观模式解决方案类似,存在额外网络跳转的性能开销。

这种模式在每次将组件提取为微服务时都需要重建和重新部署单体应用,而外观模式解决方案则不需要。在外观模式中,内部客户端需要适应调用新的微服务,因为外观通常不会拦截单体应用内的进程内调用。

示例
  • 支付系统 :一个类似PayPal和Square的支付系统,在从大型单体应用向微服务实现的迁移和现代化过程中,将“结账”功能替换为微服务。由于“结账”功能在单体应用中纠缠不清,有许多对其API的调用。在实现并验证了作为微服务的“结账”功能后,通过创建单体到微服务代理来路由对新“结账”服务的调用。这个代理使得能够进行金丝雀发布并验证“结账”服务。
  • 大型零售商 :该零售商有一个非常复杂的Web表单应用,其自定义前端部分用于浏览目录和填充购物车,然后将购物车信息传递给商业订单管理系统(OMS)进行记录和订单履行。原设计存在缺陷,OMS作为库直接集成到单体应用中,导致在黑色星期五(美国感恩节后的一天,一年中零售业最繁忙的一天)系统达到最大容量,瓶颈在于OMS运行的数据库的写入能力,最终导致系统崩溃。第二年,他们将OMS重构为一个独立进程运行的宏服务(并将整个系统转变为分布式架构),并在单体应用的目录部分和OMS之间使用了排队系统,通过单体到微服务代理隐藏了这个排队系统。这样,目录部分可以尽可能快地运行,订单被放置在队列中,而不是立即写入数据库。OMS可以按自己的速度写入数据库,最终在订单减少时赶上并清空队列。
回放测试

在将单体应用中的组件替换为微服务时,如何确保新的微服务架构保持与旧单体系统相同的功能,特别是当对现有应用的详细端到端应用知识可能有限时?

当向新架构演进时,会有一段时间新旧系统共存。规划过渡并考虑共存期以及在此期间出现的特殊挑战非常重要。在新功能上线前,会遇到一个意想不到的困难。重构的一个基本原则是,在开始任何重构工作之前,应该有一套可靠的现有系统测试,并且可以在新系统上运行这些测试。然而,在重构遗留系统时,不能假设存在覆盖所有潜在代码路径的可靠测试集。

那些在设计时没有考虑详细测试的系统通常没有足够的测试覆盖。在很多情况下,团队必须在没有对要重新实现的底层系统有详细了解的情况下,构建测试来验证新系统。这使得团队很难确定重构后的系统是否真的会以与现有系统相同的方式响应。更复杂的是,现有系统中的一些细微错误可能已经融入工作流程和用户界面中,成为隐藏或未记录的功能,而不是真正的错误。

因此,需要一种机制来确保当旧系统的一部分在新系统中重新实现时,新系统的行为可以合理地被确定为与旧系统“相同”。具体做法是:捕获原始系统上的一组输入和操作,在新实现上回放相同的输入和操作,然后比较两个系统的结果,查看是否存在差异。

以下是回放测试的操作步骤:
1. 捕获交易 :可以通过多种方式捕获交易。例如,如果更改的功能是在数据库中创建交易,并且系统运行的结果只是更新和更改数据库,那么可以在两个相同的数据库上运行两个系统,然后比较结果。如果新系统有不同的数据库结构,比较可能会更困难,这时可能需要编写脚本来提取和比较两个系统的结果,可能还需要在比较之前对数据进行一些转换。另一种方法是在一段时间内从旧系统的现有调用中捕获实时交易作为事件,然后根据需要转换这些事件,使其与新系统的API匹配,并将它们作为事件在新系统上回放,以确保新旧系统的行为匹配。这些事件应围绕原始单体系统中发生的领域事件进行选择。
2. 确定捕获事件的时长 :通常,如果回放将覆盖较长时间,可能需要为这些事件创建一个数据库。无论如何,事件必须转换为新(重构)写模型可消费的格式。如果从基于适配器的写模型捕获事件,这一步已经完成;否则,如果从其他源捕获事件,则需要进行转换。
3. 回放捕获的事件 :在新(重构)写模型上回放捕获的事件。一个关键决策是确定每次交易回放和执行后,新写模型的状态是否反映了现有系统的状态。在最简单的情况下,可以在回放结束时进行比较(例如,通过拉取报告比较摘要和总信息);在更复杂的情况下,可能需要对所有实体及其值进行逐步比较。

下面是一个表格,总结了回放测试的三种实现方式:
| 实现方式 | 具体操作 | 适用场景 | 注意事项 |
| — | — | — | — |
| 数据库比较 | 在两个相同的数据库上运行新旧系统,比较结果 | 功能主要涉及数据库操作,且数据库结构相同 | 若新系统数据库结构不同,需编写脚本进行数据转换和比较 |
| 事件捕获与回放 | 从旧系统捕获实时交易作为事件,转换后在新系统回放 | 功能涉及多种交互,可围绕领域事件捕获事件 | 需要处理事件转换和匹配新系统API的问题 |
| 基于CQRS的捕获 | 若采用CQRS方法,捕获对写模型的调用并包装为事件 | 采用CQRS架构的系统 | 若有调用不经过写模型,处理会更复杂 |

示例
  • 发票系统 :一个发票系统的迁移和现代化项目中,客户使用回放测试来验证新系统。原发票系统是一个单体应用,每月底为所有客户计算发票。新系统用发票服务替换了原系统,使为客户创建新发票和计算发票更加高效快捷。由于正确计算发票对该组织至关重要,在发布和使用新发票服务之前,需要确保新实现能为每个客户正确计算费用。因此,在上线前对旧发票系统进行回放测试,将结果与新发票服务进行比较。在对结果满意后,进行金丝雀发布新发票服务,最终淘汰了旧的发票系统。
  • 美国金融机构 :一家美国大型金融机构在一个大型现代化和重构项目中应用了回放测试。团队使用CQRS构建新系统,有单独的写模型和读模型。同时,团队从现有系统过渡到新系统。该示例通过基于适配器的写模型进行捕获,并通过摘要进行回放比较。目标是完全淘汰现有系统,用新(重构)的写模型替换它。只有当在新旧系统上同时运行所有报告,显示相同的结果时,才最终关闭旧系统。实际上,在流量重定向到新系统,使其成为主系统后,旧系统仍运行了一段时间,目的是在新系统成为主系统后的一段时间内继续比较报告。

通过替换为微服务、单体到微服务代理和回放测试这三种方法,可以有效地将单体应用转换为微服务架构,提高系统的灵活性和可维护性。但在实施过程中,需要充分考虑各种挑战和风险,并根据具体情况选择合适的方法和策略。

单体应用向微服务转换:替换、代理与测试策略(下半部分)

替换为微服务策略的深入探讨

替换为微服务的过程中,还需要注意不同场景下的具体操作。例如当新微服务与原单体应用的数据存储结构不同时,数据迁移和同步就成为关键问题。对于数据迁移,可以按照以下步骤进行:
1. 数据评估 :对原单体应用中的数据进行全面评估,确定哪些数据需要迁移到新微服务,了解数据的格式、类型、关联关系等。
2. 数据映射 :建立原数据与新微服务数据结构之间的映射关系。例如,如果原单体应用使用关系型数据库存储用户信息,而新微服务使用文档型数据库,就需要将关系型表结构映射为文档结构。
3. 数据迁移脚本编写 :根据数据映射关系,编写数据迁移脚本。脚本可以使用编程语言(如Python)结合数据库操作库(如SQLAlchemy)来实现。
4. 数据迁移执行 :在测试环境中先执行数据迁移脚本,验证迁移的正确性。然后在生产环境中进行迁移,同时要做好数据备份和回滚准备。

以下是一个简单的数据迁移脚本示例(使用Python和SQLAlchemy):

from sqlalchemy import create_engine, MetaData, Table
import pymongo

# 连接原关系型数据库
engine = create_engine('sqlite:///old_database.db')
metadata = MetaData()
old_table = Table('users', metadata, autoload_with=engine)

# 连接新文档型数据库
client = pymongo.MongoClient('mongodb://localhost:27017/')
new_db = client['new_database']
new_collection = new_db['users']

# 迁移数据
with engine.connect() as connection:
    result = connection.execute(old_table.select())
    for row in result:
        user_data = dict(row)
        new_collection.insert_one(user_data)
单体到微服务代理的扩展应用

在实际应用中,单体到微服务代理还可以结合负载均衡和熔断机制,提高系统的可靠性和性能。以下是具体的操作步骤:
1. 负载均衡配置 :在单体到微服务代理中集成负载均衡器,如Nginx或HAProxy。配置负载均衡算法,如轮询、加权轮询、IP哈希等,将请求均匀地分发到多个微服务实例上。
2. 熔断机制实现 :使用开源的熔断库,如Hystrix或Resilience4j,在代理中实现熔断机制。当微服务出现故障或响应时间过长时,熔断机制会自动切断对该微服务的请求,避免故障扩散。
3. 监控和报警 :使用监控工具,如Prometheus和Grafana,对代理和微服务的性能指标进行监控。设置报警规则,当指标超出阈值时及时通知运维人员。

下面是一个使用Nginx作为负载均衡器的简单配置示例:

http {
    upstream microservice_backend {
        server microservice1.example.com;
        server microservice2.example.com;
    }

    server {
        listen 80;
        server_name proxy.example.com;

        location / {
            proxy_pass http://microservice_backend;
        }
    }
}
回放测试的优化策略

为了提高回放测试的效率和准确性,可以采用以下优化策略:
1. 数据采样 :在捕获交易时,对数据进行采样。例如,只捕获一定比例的实时交易,或者只捕获关键业务流程的交易,减少测试数据量,提高测试速度。
2. 并行测试 :在新系统上并行回放多个捕获的交易,充分利用系统资源,缩短测试时间。可以使用多线程或分布式计算框架来实现并行测试。
3. 自动化脚本 :编写自动化脚本,自动完成捕获、回放和比较的过程。脚本可以定期运行,及时发现新系统和旧系统之间的差异。

以下是一个使用Python实现并行回放测试的简单示例:

import threading

def replay_transaction(transaction):
    # 模拟在新系统上回放交易
    print(f"Replaying transaction: {transaction}")

transactions = ["tx1", "tx2", "tx3", "tx4"]
threads = []

for transaction in transactions:
    thread = threading.Thread(target=replay_transaction, args=(transaction,))
    threads.append(thread)
    thread.start()

for thread in threads:
    thread.join()
总结

通过以上对替换为微服务、单体到微服务代理和回放测试的深入探讨,我们可以看到这些方法在单体应用向微服务转换过程中的重要性和复杂性。在实际应用中,需要根据具体的业务需求和系统特点,选择合适的策略和技术,合理规划转换过程,确保系统的稳定性和性能。

以下是一个mermaid流程图,总结了整个单体应用向微服务转换的过程:

graph LR
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    A[单体应用]:::process --> B[识别可替换功能]:::process
    B --> C[替换为微服务]:::process
    C --> D[创建单体到微服务代理]:::process
    D --> E[进行回放测试]:::process
    E --> F[验证和优化]:::process
    F --> G[全面部署微服务架构]:::process

在这个转换过程中,每个步骤都需要仔细考虑和规划,同时要充分利用各种工具和技术,不断优化和调整,以实现单体应用向微服务架构的平稳过渡。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值