18、实现面向微服务的架构

实现面向微服务的架构

1. 微服务封装后的调用问题

在将系统封装为微服务架构(MSA)后,调用者(如 DisplayManager )无法通过所需接口直接访问被调用类(如 ContentProvider )。因为微服务之间仅通过轻量级机制(如 RPC 或事件)进行通信,所以必须实现一个技术层。具体做法是,在包含被调用类的微服务中,将提供的接口实现或暴露为 Web 服务。

例如,生成一个 WebService 类来暴露 ContentProvider 的方法。为 ContentProvider 的每个公共方法在 WebService 中创建一个对应的方法,作为代理接收请求。代理方法调用相应的方法并返回结果。从调用微服务的角度,生成一个 WebConsumer 类来实现所需接口,并处理与对应 WebService 类的网络调用。

2. 实例处理

在多个微服务之间创建和共享类的实例时,会面临一些问题。这里提出了一种结合设计模式的方法来解决这些问题。
- 工厂模式 :用于解耦不同集群中类的实例创建。例如,使用一个接口作为对象工厂,替代 DisplayManager ContentProvider 的实例化。为了解耦微服务之间的方法调用,使用相同的提供/所需接口来定义这些对象工厂方法。具体来说,在所需/提供的接口中添加一个工厂方法(如 createContentProvider() ),并在 WebConsumer WebService 中实现相应的方法。
- 代理模式 :用于解决实例创建违规问题。为在一个微服务中引用但在另一个微服务中定义的每个类创建一个代理类。代理类具有与真实类相同的公共方法和公共构造函数,但实现方式被重写,使用 WebConsumer 类与真实类进行交互。

在创建代理类实例时,会同时创建真实类的实例。为了区分代理类和真实类的实例,将代理类的实例称为代理实例,真实类的实例称为具体实例。代理实例通过唯一引用指向具体实例,对代理实例的任何操作都会传递到其具体实例。当具体实例在微服务之间交换时,传递的是唯一引用而不是具体实例。

为了保持具体实例在方法调用之间的状态,实现一个类来存储和管理微服务中创建的所有具体实例。当工厂被调用创建具体实例时,将对象发送到存储类(如 Database 类)进行保存,存储类返回一个访问该对象的令牌。工厂方法通过其 Web 服务实现将令牌返回给代理实例,代理实例存储该令牌以供后续方法调用使用。当调用代理实例的方法时,将请求和令牌一起传递给相应的 Web 服务方法,令牌为 Web 服务提供加载具体实例并调用正确方法所需的上下文。

对于在微服务之间作为参数传递的复杂对象,由于微服务通信的轻量级限制,需要在保留应用业务逻辑一致性的同时进行转换。使用令牌机制,发送方和接收方都能处理令牌,复杂对象可以作为令牌在微服务之间传递,类的所有者负责管理实例。当微服务接收到令牌时,能够实例化相应的代理类来访问具体实例。

3. 隐式封装违规解决

在之前的部分,我们处理了显式封装违规问题。接下来,我们将处理与面向对象(OO)机制相关的隐式封装违规问题,主要包括类之间的继承关系和异常处理。
- 继承关系 :当一个类继承自另一个属于不同微服务的类时,被认为是继承违规。为了解决这个问题,需要将继承分解为不同的机制并进行转换,以保留所有机制。具体步骤如下:
- 子/父类定义扩展 :使用双代理模式来保留子/父类之间的继承关系。创建一个父代理类(如 ContentConsumer ),实现从父类提取的接口定义的方法。将子类(如 Message )重构为扩展父代理类。定义子代理类(如 MessageConsumer )来扩展父类,作为父类的子代理。
- 通过代理继承重建子类型 :为了保留通过子类型创建的内部逻辑,将代理类暴露为 Web 服务。创建子对象时,调用父代理的构造函数来消费父 Web 服务,初始化自然继承自父类的子代理。当子类对象调用父类定义的方法时,通过父 Web 服务调用父对象;当父类引用实例时,通过子代理对象调用子类对象。
- 通过接口继承重建多态赋值 :定义一个子接口(如 IMessage )来扩展父接口(如 IContent ),子类实现子接口,从而实现子类对象的多态赋值。

以下是相关的 mermaid 流程图:

graph LR
    A[创建子对象] --> B[调用父代理构造函数]
    B --> C[消费父 Web 服务]
    C --> D[初始化子代理]
    E[子类对象调用父类方法] --> F[通过父 Web 服务调用父对象]
    G[父类引用实例] --> H[通过子代理调用子类对象]
  • 异常处理 :异常处理封装违规涉及在微服务之间创建、抛出和捕获异常对象。为了确保异常的良好抛出和处理,提出了一个两步转换过程:
    • 包装异常响应 :正常方法有两种不同类型的响应,即正常预期类型响应和异常响应。Web 服务方法不适合抛出异常对象,因此引入一个包装类,该类可以容纳正常响应类型或异常响应类型,替换每个方法的返回类型。
    • 转换异常处理源代码 :在 Web 服务操作中,使用 try catch 包围方法调用。当方法返回正常响应类型时,将值安全地添加到字典中;当方法返回异常响应类型时,捕获异常对象并存储,添加相应的访问令牌到字典中。

以下是代码示例:

// 包装异常响应的 Web 服务方法示例
public class ContentProviderWebService {
    public JsonNode pop(int proxy_id){
        JsonNode return_node = new JsonNode();
        IContentProvider contentprovider = InstanceDB.getContentProvider(proxy_id);
        try{
            return_node.put("return", InstanceDB.addContent(contentprovider.pop()));
        } catch(EmptyContentStackException e){
            return_node.put("EmptyContentStackException", InstanceDB.addEmptyContentStackException(e));
        }
        return return_node;
    }
}

// 处理异常响应的 Web 消费者方法示例
public class ContentProviderConsumer {
    public IContent pop() throws EmptyContentStackExceptionImpl {
        JsonNode return_node = getProxy().pop(contentprovider_id);
        if(return_node.get("EmptyContentStackException") != null){
            throw new EmptyContentStackExceptionImpl(return_node.get("EmptyContentStackException"));
        } else {
            return new IContentImpl(return_node.get("return").asInt());
        }
    }
}
4. 违规解决顺序

为了系统地完全解决所有封装违规问题,提出了以下违规解决顺序:
1. 减少属性访问违规,因为它为类添加了公共方法,可能会被继承违规进一步重构。
2. 将抛出的异常减少为实例违规。
3. 将继承违规减少为实例违规,以便一起处理所有实例违规。
4. 将实例违规减少为方法调用违规。
5. 将剩余的方法调用违规转换为一组 Web 服务。

以下是相关的表格:
| 违规类型 | 解决步骤 |
| ---- | ---- |
| 属性访问违规 | 减少并添加公共方法 |
| 抛出异常 | 转换为实例违规 |
| 继承违规 | 转换为实例违规 |
| 实例违规 | 转换为方法调用违规 |
| 方法调用违规 | 转换为 Web 服务 |

5. 评估

为了评估该方法,实现了一个工具,并对一组不同规模的单体应用进行了不同程度的迁移。选择了 7 个应用进行实验,其中 6 个是开源且面向对象的应用,另一个是闭源的遗留应用 Omaje。

  • 数据预处理:微服务识别 :使用半自动化方法恢复微服务架构,将类聚类作为输入。
  • 研究问题及方法
    • RQ1:Mono2Micro 方法是否适用于不同类型的 OO 应用?
      • 目标 :测试 Mono2Micro 在不同应用上的适用性。
      • 方法 :将该方法应用于所选的应用,编译打包目标代码。如果 Mono2Micro 能够成功修复不同类型应用中检测到的封装违规,并且所有 7 个应用都没有编译或执行错误,则认为该方法实现了封装特性。
    • RQ2:Mono2Micro 方法在实现面向微服务架构时的精度如何?
      • 目标 :评估微服务架构的语法和语义正确性。
      • 方法 :根据转换后的微服务的语法和语义正确性来衡量精度。如果转换后的 MSA 应用与单体应用的行为相同,则认为业务逻辑得到了保留。对于语法正确性,检查是否有编译错误;对于语义正确性,比较单体应用和 MSA 在相同执行场景下的输出。精度计算为两个架构都通过的测试数量除以 MSA 通过的测试数量。
    • RQ3:Mono2Micro 方法在实现面向微服务架构时的召回率如何?
      • 目标 :评估 Mono2Micro 方法的召回率。
      • 方法 :与 RQ2 类似,但召回率计算为两个架构都通过的测试数量除以单体应用通过的测试数量。
    • RQ4:Mono2Micro 对性能有什么影响?
      • 目标 :评估将单体应用迁移到微服务架构对性能的影响是否可以忽略不计。
      • 方法 :比较单体应用和 MSA 的用户请求执行时间。使用 Omaje 应用建立用户场景,使用 JMeter 模拟用户负载。随着用户数量的增加,增加单体应用和 MSA 中相关微服务的实例数量。如果两种架构之间的执行时间差异对普通用户来说可以忽略不计,并且资源利用率得到优化,则认为重构结果提高或保持了原始代码的质量和性能。

以下是相关的 mermaid 流程图:

graph LR
    A[选择应用] --> B[数据预处理]
    B --> C[应用 Mono2Micro 方法]
    C --> D[编译打包代码]
    D --> E[执行研究问题测试]
    E --> F[评估结果]

实现面向微服务的架构

6. 实验应用详情

为了更清晰地展示评估过程,下面详细介绍实验所选用的 7 个应用的具体信息,如下表所示:
| 应用名称 | 类的数量 | 代码行数(LOC) | 开源链接 |
| ---- | ---- | ---- | ---- |
| FindSportMates1 | 21 | 4,061 | https://github.com/chihweil5/FindSportMates |
| JPetStore2 | 24 | 4,319 | https://github.com/mybatis/jpetstore - 6 |
| PetClinic3 | 44 | 2,691 | https://github.com/spring - petclinic/spring - framework - petclinic |
| SpringBlog4 | 87 | 4,369 | https://github.com/Raysmond/SpringBlog |
| IMS5 | 94 | 13,423 | https://github.com/gtiwari333/java - inventory - management - system - swing - hibernate - nepal |
| JForum6 | 373 | 60,919 | https://github.com/rafaelsteil/jforum2/ |
| Omaje | 1,821 | 137,420 | 闭源 |

7. 各研究问题的具体操作流程
  • RQ1:Mono2Micro 方法是否适用于不同类型的 OO 应用?

    • 操作步骤:
      1. 准备所选的 7 个应用的源代码。
      2. 对每个应用执行 Mono2Micro 方法进行转换。
      3. 对转换后的目标代码进行编译和打包。
      4. 检查是否存在编译错误和执行错误。
      5. 如果所有应用都没有错误,则认为该方法适用于不同类型的 OO 应用。
  • RQ2:Mono2Micro 方法在实现面向微服务架构时的精度如何?

    • 操作步骤:
      1. 对于有测试用例的应用(如 JPetStore),直接使用开发者定义的测试用例;对于没有测试用例的应用(如 FindSportmates、IMS),识别应用的特征和子特征,建立覆盖所有功能的用户场景。
      2. 在单体应用上执行这些用户场景,并保存结果。
      3. 在转换后的 MSA 上执行相同的用户场景,并记录结果。
      4. 比较单体应用和 MSA 的输出结果。
      5. 计算精度:精度 = 两个架构都通过的测试数量 / MSA 通过的测试数量。
  • RQ3:Mono2Micro 方法在实现面向微服务架构时的召回率如何?

    • 操作步骤:
      1. 与 RQ2 相同,执行用户场景并记录单体应用和 MSA 的结果。
      2. 计算召回率:召回率 = 两个架构都通过的测试数量 / 单体应用通过的测试数量。
  • RQ4:Mono2Micro 对性能有什么影响?

    • 操作步骤:
      1. 选择 Omaje 应用建立用户场景。
      2. 使用 JMeter 模拟用户负载,从少量用户开始,逐渐增加用户数量。
      3. 对于单体应用,通过复制应用来增加实例数量;对于 MSA,复制当前场景涉及的微服务实例。
      4. 记录不同用户数量下,单体应用和 MSA 的用户请求执行时间。
      5. 比较两种架构的执行时间差异,同时观察资源利用率。
      6. 如果执行时间差异对普通用户来说可以忽略不计,且资源利用率得到优化,则认为重构结果提高或保持了原始代码的质量和性能。

以下是 RQ2 - RQ4 操作流程的 mermaid 流程图:

graph LR
    A[选择应用] --> B[确定执行场景]
    B --> C[在单体应用上执行场景并记录结果]
    C --> D[在 MSA 上执行场景并记录结果]
    D --> E{比较结果}
    E -->|RQ2| F[计算精度]
    E -->|RQ3| G[计算召回率]
    E -->|RQ4| H[比较执行时间和资源利用率]
8. 总结

通过上述的研究和实验,我们对 Mono2Micro 方法在实现面向微服务架构方面进行了全面的评估。从解决各种封装违规问题,到提出具体的违规解决顺序,再到通过实际应用进行评估,我们可以看到该方法在处理不同类型的 OO 应用、保证微服务架构的语法和语义正确性、以及评估性能影响等方面都有一定的作用。

在实际应用中,如果需要将单体应用迁移到微服务架构,可以参考本文提出的方法和操作步骤。首先,按照违规解决顺序处理封装违规问题;然后,使用评估方法来验证迁移的效果,确保业务逻辑的一致性和性能的稳定性。

希望本文的内容能够为微服务架构的实现和迁移提供有价值的参考,帮助开发者更好地应对面向微服务架构的挑战。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值