Featured image of post Java工程师 分布式微服务设计模式

Java工程师 分布式微服务设计模式

🌏Java工程师 分布式微服务设计模式的学习和使用 🎯这篇文章用于记录 分布式微服务设计模式的学习和使用

🎄微服务设计模式概述

❗ 说明:下面这张图先不着急一举拿下

AzureCAT模式与实践团队在Azure架构中心发布了9个新的设计模式 这九个模式在设计和实现微服务时特别有用 下图说明了如何在微服务体系结构中使用这些模式

初次看这张图会有些懵 因为这张图大概描述了9中微服务设计模式的使用方式 并且除了9中本身设计模式生英文词 还有其他的辅助生词 例如SPA 所以紧接着来一一拆解 经过信息拆解后 最后再来看这张图 应该就能有一个较深的印象了

接下来一一进行拆解 上面的红色数字是分析顺序

🍭SPA(Single-Page Application,单页应用)

SPA(Single-Page Application,单页应用)是一种网络应用程序或网站的模型,它通过动态重写当前页面来与用户交互,这种方法避免了页面之间切换打断用户体验

在单页应用中,所有必要的代码(HTML、JavaScript和CSS)都通过单个页面的加载而检索,或者根据需要(通常是为响应用户操作)动态装载适当的资源并添加到页面

动态重写当前页面是指在单页应用中,当用户与应用程序交互时,不会像传统多页应用程序那样通过完整的页面刷新来更新内容,而是通过JavaScript或特定框架的技术来动态更新页面的部分内容

例如,当用户在单页应用中导航到不同的页面或执行某些操作时,不会重新加载整个页面,而是只更新需要更新的部分,例如页面的某个部分或元素。这种方式可以提供更流畅的用户体验,因为不需要每次都进行完整的页面刷新。

总结 即按需加载

页面在任何时间点都不会重新加载,也不会将控制转移到其他页面 我们熟知的JS框架如react、vue、angular、ember都属于SPA

🍭Strangler Fig 模式(绞杀者模式)

Strangler Fig模式(绞杀者模式)是一种将新系统逐渐迁移到旧系统上的开发方法

其目的是确保旧系统逐步失效,新系统平稳地取代它,并且过程中不会有重大的风险或失败

它主要包括:识别现有系统的各个部分、确定核心的抽象接口、将每个模块用现有组件或现有代码库进行实现、迁移业务功能、最终替换旧系统

Strangler Fig模式是一种架构演进模式,它鼓励在现有系统上逐步增加新的架构,而不是一次性重写整个系统

以下是代码示例:

// 旧系统代码
public class OldSystem {
    public void doSomething() {
        System.out.println("Old system doing something...");
    }
}

// 新系统代码
public class NewSystem {
    public void doSomething() {
        System.out.println("New system doing something...");
    }
}

// Strangler Fig类,用于逐步替换旧系统
public class StranglerFig {
    private OldSystem oldSystem;
    private NewSystem newSystem;

    public StranglerFig(OldSystem oldSystem) {
        this.oldSystem = oldSystem;
        this.newSystem = new NewSystem();
    }

    public void startStrangling() {
        // 逐步替换旧系统的功能
        oldSystem.doSomething(); // 调用旧系统的功能
        newSystem.doSomething(); // 调用新系统的功能
    }
}

// 主程序入口
public class Main {
    public static void main(String[] args) {
        OldSystem oldSystem = new OldSystem();
        StranglerFig stranglerFig = new StranglerFig(oldSystem);
        stranglerFig.startStrangling(); // 输出:Old system doing something... New system doing something...
    }
}

在这个示例中,有一个旧系统OldSystem和一个新系统NewSystemStranglerFig类用于逐步替换旧系统的功能。在startStrangling方法中,我们首先调用旧系统的doSomething方法,然后调用新系统的doSomething方法。这样,我们可以在不中断现有系统的情况下逐步引入新系统的功能。

StranglerFig类继承了OldSystem类和NewSystem类,通过逐步将新系统的功能引入到现有系统中,实现新旧系统的替换。当不再需要旧系统的功能时,可以去除oldSystem.doSomething()的调用,完成绞杀动作

其实旧的类一直都没有变 只不过用了一个新的类持有旧的类 这个新的类既可以使用旧的类提供的功能 也可以在保持原有系统正常工作的情况下自己添加新的功能 直到不再需要旧的类 就可以将其扼杀(其实就是将旧的类从StranglerFig类中删除并将其取代之)

🍭Legacy System(遗留系统)

所谓的Legacy System 是指遗留系统:一种旧的计算机系统或应用程序,由于用户(通常是组织)不想替换或重新设计它,因此仍在使用

如何理解和分析这件事?

建立新的客户机服务器系统以替代现存的保留系统是一种耗资巨大的举动,遗留系统虽然在技术上过时,但其能够发挥的作用是企业的长期积累和开发的投入,且能够提供一些问题的解决方案并伴随不错的性能,如果只是因为技术发展上的过时就将其用新技术重写,代价会非常大,所以想办法进行复用遗留系统才是王道。

🍭Anti-curruption Layer(反腐层)

(1)反腐层(Anti-corruption layer,简称 ACL)介于新应用和旧应用之间,用于确保新应用的设计不受老应用的限制 是一种在不同应用间转换的机制

(2)创建一个反腐层,以根据客户端自己的域模型为客户提供功能 该层通过其现有接口与另一个系统进行通信,几乎不需要对其进行任何修改。因此,反腐层隔离不仅是为了保护你的系统免受异常代码的侵害,还在于分离不同的域并确保它们在将来保持分离

(3)反腐层是将一个域映射到另一个域,这样使用第二个域的服务就不必被第一个域的概念“破坏”

代码示例如下:

import java.lang.reflect.Method;

/**
 * (1)AntiCorruptionLayer类封装了领域服务对象domainService 并提供了一个invokeDomainMethod方法 用于调用领域服务的方法 在调用方法时 我们使用Java反射机制获取方法对象 并调用该方法 
 * (2)这个反腐层可以用于隔离不同的领域 确保一个领域不会直接访问另一个领域的概念或服务
 */
public class AntiCorruptionLayer {
    // 持有一个domainService对象
    private final Object domainService;
    // 构造器
    public AntiCorruptionLayer(Object domainService) {
        this.domainService = domainService;
    }
    public Object invokeDomainMethod(String methodName, Object[] arguments) throws Exception {
        Method method = domainService.getClass().getMethod(methodName, getParameterTypes(arguments));
        return method.invoke(domainService, arguments);
    }
    // 根据参数集合返回各个参数的类型集合
    private Class<?>[] getParameterTypes(Object[] arguments) {
        Class<?>[] parameterTypes = new Class<?>[arguments.length];
        for (int i = 0; i < arguments.length; i++) {
            parameterTypes[i] = arguments[i].getClass();
        }
        return parameterTypes;
    }
}

🌱反腐层的设计模式思想似乎和适配器设计模式很像?

是的 他们的相似之处如下:

(1)首先,反腐层的设计模式思想强调的是通过制定规则、规范和制度来约束和监督权力运行,防止腐败现象的发生。这与适配器设计模式中的“封装”和“转换”思想有一定的相似性。

(2)其次,适配器设计模式是一种结构型设计模式,它通过将一个类的接口转换成客户所希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。在反腐层的设计模式思想中,也可以借鉴这种思想,通过制定统一的规范和接口,使得不同的系统和模块能够相互协作,防止腐败现象的发生。

(3)最后,反腐层的设计模式思想和适配器设计模式都强调了模块化和可复用的设计思想。在反腐层的设计中,可以通过模块化设计,将不同的功能和模块进行分离和封装,提高代码的可复用性和可维护性。在适配器设计模式中,也可以通过模块化设计,将适配器和被适配者进行分离和封装,提高代码的可复用性和可维护性

两者的区别主要在于(他们的区别偏向于概念上具有区别 实操操作上确实有上面阐述的相似点):

(1)反腐层是位于新应用和旧应用之间的层,用于确保新应用的设计不受老应用的限制,主要目的是隔离系统和保护系统免受异常代码的侵害 就相当于反腐层在实现其概念上的功能的同时不可避免地使用了适配器模式的思想

(2)适配器模式是将一个类的接口转换成客户端所期望的另一种接口,从而使得原本由于接口不兼容而无法协同工作的类能够一起工作,主要目的是解决接口不兼容的问题

他们之间的区别必须深刻于心 避免混淆

🍭 RDB && NoSQL && RemoteService

这部分为图中红色标注的第五部分:

(1)RDB:Relational Database 关系型数据库 如MySQL

(2)NoSQL:非关系型数据库 如Redis

(3)RemoteService:远程服务(RPC) 如Dubbo

🍭 Ambassador模式

Ambassador设计模式是一种将代理对象用作服务定位器的微服务设计模式 这种设计模式将客户端和服务之间的直接通信转换为通过代理进行通信,从而实现了服务发现和负载均衡

在Ambassador模式中,客户端并不直接调用目标服务,而是通过代理进行调用。代理负责解析客户端请求,并根据服务注册表中的信息将请求转发到相应的服务实例。这样,客户端无需直接维护服务实例的地址和端口信息,从而降低了系统的复杂性

同时,Ambassador模式还支持负载均衡容错机制 代理可以根据服务注册表中的信息动态选择可用的服务实例,实现负载均衡 当某个服务实例出现故障时,代理可以将其从服务注册表中移除,并继续将请求转发到其他可用的服务实例,从而实现容错

代码示例如下:

// 服务接口
public interface Service {
    void performTask();
}

// 服务实现
public class ServiceImpl implements Service {
    @Override
    public void performTask() {
        System.out.println("Performing task...");
    }
}

// 代理类
public class Ambassador {
    private Service service;
    private List<Service> services;

    public Ambassador(Service service) {
        this.service = service;
        this.services = new ArrayList<>();
    }

    public void registerService(Service service) {
        services.add(service);
    }

    public void performTask() {
        if (services.isEmpty()) {
            service.performTask(); // 直接调用服务
        } else {
            Random random = new Random();
            int index = random.nextInt(services.size()); // 随机选择服务实例
            Service selectedService = services.get(index); // 获取选定的服务实例
            selectedService.performTask(); // 调用选定的服务实例
        }
    }
}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        Service service = new ServiceImpl(); // 创建服务实例并注册到代理中
        Ambassador ambassador = new Ambassador(service); // 创建代理对象
        ambassador.registerService(service); // 注册服务实例到代理中
        ambassador.performTask(); // 通过代理调用服务实例执行任务
    }
}

上述示例中,Service是服务接口,ServiceImpl是服务实现类。Ambassador是代理类,它维护了一个服务列表,并在执行任务时随机选择一个服务实例进行调用。客户端通过创建代理对象并注册服务实例到代理中,然后通过代理调用服务实例执行任务。当没有注册服务实例时,代理会直接调用默认的服务实例。

🍭 Sidecar模式

🌱概述

什么是Sidecar模式?

Sidecar模式是一种将应用功能从应用本身剥离出来作为单独进程的方式 该模式允许我们向应用无侵入添加多种功能,避免了为满足第三方组件需求而向应用添加额外的配置代码

就像边车加装在摩托车上一样,在软件架构中,sidecar附加到主应用,或者叫父应用上,以扩展/增强功能特性,同时Sidecar与主应用是松耦合的

举个例子,假设现在有6个相互通信的微服务,每个微服务都需要具有可观察性、监控、日志记录、配置、断路器等功能,而所有这些功能都是在微服务中使用一些第三方库实现的

这样一组服务的实际情况可能会非常复杂,增加了应用的整体复杂性,尤其是当每个微服务用不同的语言编写、使用不同的基于.net、Java、Python等语言的第三方库…… 这个时候如果将日志、监控、认证等功能从核心业务逻辑中分离出来,就能保持核心业务逻辑的简单和清晰

代码示例如下:

// 主服务
public class MainService {
    private final Sidecar sidecar;

    public MainService(Sidecar sidecar) {
        this.sidecar = sidecar;
    }

    public void doMainTask() {
        // 执行主要业务逻辑
        System.out.println("执行主要业务逻辑");
        sidecar.executeSidecarTask();
    }
}

// Sidecar接口
public interface Sidecar {
    void executeSidecarTask();
}

// Sidecar实现类
public class SidecarImpl implements Sidecar {
    @Override
    public void executeSidecarTask() {
        // 执行辅助任务,例如日志记录、监控、认证等
        System.out.println("执行辅助任务");
    }
}

🍭 Backends for Frontends 模式

“Backends for Frontends”(后端为前端服务)模式是一种开发应用程序的方式,它强调后端服务为前端用户界面提供支持和功能

在这种模式中,后端通常负责处理数据、存储和业务逻辑。它的任务是接收前端发送的请求,处理这些请求,并返回数据和响应。后端可能还负责与数据库交互、验证用户身份、处理安全性和权限等任务

前端则负责呈现用户界面和处理用户交互 它从后端接收数据,并在用户界面上展示这些数据

“Backends for Frontends"模式的主要优势是解耦和灵活性。后端和前端可以独立开发和部署,这使得它们可以更容易地扩展和维护。此外,这种模式还提高了应用程序的性能和响应性,因为数据可以从后端服务器直接发送到前端用户界面,减少了网络延迟。

在实践中,“Backends for Frontends"模式通常与微服务架构、API网关和其他现代技术一起使用,以构建可扩展、可维护和高效的应用程序

后端代码简单示例:

@Path("/api/users")
public class UserResource {

    // 模拟数据库中的用户列表
    private List<User> users = new ArrayList<>();

    public UserResource() {
        users.add(new User("Alice", "alice@example.com"));
        users.add(new User("Bob", "bob@example.com"));
    }

    @GET
    @Path("/{id}")
    public Response getUserById(@PathParam("id") String id) {
        for (User user : users) {
            if (user.getId().equals(id)) {
                return ResponseBuilder.ok(user).build();
            }
        }
        return Response.status(404).build(); // 返回404状态码,表示未找到用户
    }
}

前端代码简单示例:

// 导入必要的库(使用Fetch API)
fetch('http://localhost:8080/api/users/1') // 假设后端运行在localhost:8080上,并请求ID为1的用户数据
    .then(response => response.json()) // 将响应转换为JSON格式
    .then(data => { // 处理响应数据,这里假设响应数据是一个User对象,包含id和email属性
        console.log('User ID:', data.id); // 输出用户ID到控制台
        console.log('User Email:', data.email); // 输出用户邮箱到控制台
    })
    .catch(error => { // 处理错误情况,例如网络请求失败或解析JSON出错等
        console.error('Error:', error); // 输出错误信息到控制台
    });

🎄总结分析

现在回过头来重新看这张图 印象就非常深刻了 对整个微服务的设计有了全局的吧认识和把握

【参考链接】

[1] 微服务设计模式:反腐层(Anti-corruption layer)

[2] design-patterns-for-microservices

[3] 扼杀者模式:如何与旧式整体应用保持一致

[4] DDD领域驱动设计架构模式:防腐层(Anti-corruption layer)

[5] sidecar模式:下一代微服务架构的关键