程序员过关斩将--论系统设计的高可扩展性

此文仅仅代表个人意见,并非行业标准

MQ是万能的高扩展方式?

面向接口是万能的高扩展方式?

说到系统设计的三高,每一高都是一个很庞大的话题,甚至可以用一本书甚至N本书来详细阐述。其中高可扩展性是系统架构的众多目标之一。归根结底,系统的架构要为最终的业务服务,脱离业务来谈架构其实比耍流氓更无耻。

在我们心目中最理想的软件架构要像搭积木一样简单,并且快捷,而且高效。但是现实往往比996更残酷,多数的系统在初期为了配合业务快速上线,扩展性这个指标并不理想。别的不谈,一个系统要完美的做到“对修改封闭,对扩展开放”其实一点也不简单,不知道你有没有遇到过修改一个bug蹦出另外一个bug的痛苦经历?

为了做到系统的高扩展性,其实有很多借鉴的案例,尤其是设计模式。但是今天我还是要说一说我自己的看法。无论什么样的系统,抽象起来其实都是模块和模块之间的交互,这里模块的含义是广义的,即可以代表函数,也可以代表进程,甚至可以代表目前流行的微服务,如下图所示

image

图是不是很简单?但是要想把A和B之间的交互做到高扩展其实并不容易,这要求系统的设计者必须要想办法在满足A和B正常交互的情况下尽量解耦A和B,只有正确的解耦,才能从容的应对A和B独立扩展的业务需求

同一进程内

在同一进程内的情况是一种最常见的存在方式,对应到我们平时的代码,表现为函数的调用,而这里的函数调用可以是同一模块内的函数调用,比如最典型的三层架构中,业务层调用持久化层来进行数据的操作,如下代码:

//user 业务层public class UserBLL{UserDAL dal = new UserDAL();public int AddUser(User user){//其他业务return dal.AddUser(user);}}//user持久化层public class UserDAL{public int AddUser(User user){//进行数据库操作return 0;}}

我真的希望实际项目中的代码能像以上代码这么简单,毕竟代码就和项目一样,简单即是美。这段代码排除业务之外,从架构来讲也有很多问题,用开头的A和B的方式来表示,A代表的是UserBLL,B代表的是UserDAL,这里最容易看出的就是强耦合,即:A严重依赖于B,如果B有什么风吹草动,势必会影响A的执行。

怎么办呢?所以有了B的抽象层,对应到代码上是IDAL接口层,当然这个抽象层应该是稳定的,如果三天两头修改抽象层,那说明抽象的有问题。A在执行上改为依赖IDAL,这是系统内设计最常见的面向接口设计模式,其实更准确的说,应该是面向抽象设计模式。由于引入了稳定的抽象层,不再稳定的实现层就可以根据实际的业务去修改,这里体现的是系统设计中依赖倒置的原则,当然为了实现依赖倒置,你可能需要使用IOC等技术来实现项目落地。

image
//user 业务层public class UserBLL{IUserDAL dal = "依赖注入";public int AddUser(User user){//其他业务return dal.AddUser(user);}}//user的持久化层抽象public interface IUserDAL{int AddUser(User user);}//user持久化层public class UserDAL: IUserDAL{public int AddUser(User user){//进行数据库操作return 0;}}

不同进程间

不同的进程之间互相协作是目前分布式模式下主要的交互方式,例如之前的SOA,现在的微服务,都是在利用分散在不同位置的模块来组装系统,这些模块之间的通信是一个分布式系统必备的条件。

和进程内函数调用类似,分布式系统也可以抽象为A和B的关系模型,我们要解决的也是A和B能够独立变化的问题。现在假设A服务依赖于B服务,B服务由于压力大需要扩容,会有哪些影响呢?

  • B自己内部的状态变化,如果B服务是有状态的,扩展起来可能会设计到数据的迁移等操作,如果B是无状态的,理论来说可以很方便的横向扩展

  • B的扩容对A或者其他依赖于B的系统有什么影响,依赖方能否做到自动适配,而不必修改任何配置

和进程内函数调用不同,进程间的通信需要通讯协议的支持,最常见的RPC调用都是基于TCP协议,Restfull基于http协议,使用这些协议底层都需要指定明确的IP和端口。所以需要某种解决方案在被依赖方扩展的时候,依赖方能够得到感知。聪明的你可能想到了“注册中心”,不错,这也是注册中心最主要的职责。

它可能是分布式系统中最重要的枢纽

解决方案2

用注册中心的方式,理论上属于通知依赖方的方案,在依赖方感知被依赖方有扩展变动的时候,需要作出对应的变化。与之对应的其实我们也可以把变动封装在被依赖方,这个时候就引入了以下代理模式,最常见的就是网关模式。

分布式系统使用网关到底是好还是坏?

其实代理模式非常常见,比如Nginx做反向代理,数据库的中间件。这些设施都是对依赖方透明的,依赖方不会因为被依赖方实施了扩展而受影响。

解决方案3

目前很多业务下有一种很常见的场景,依赖方和被依赖方通信并不需要知道执行结果,最典型的场景像:新用户注册给用户发欢迎邮件或者短信欢迎语。如果业务代码中冗余了发邮件或者短信的代码的话,一旦要添加新的欢迎方式就必须要修改业务代码,无论你是否有抽象层,为了不影响主要的业务又最大化解耦系统,一般都会把这种非主要业务通过消息的方式分离出来。最常见的解决方案就是MQ。这也是典型发布订阅模式,但是这种模式如上所说,调用方并不能实时的得到业务处理结果。

利用MQ来进行系统的解耦,来实现系统的高可扩展是一种非常常见的方式,优势有很多,我不再阐述,但是需要注意消息的可靠性,因为消息经过了几个环节之后,难保某个环节出现问题而丢失消息。具体的详细介绍可以查看

分布式系统消息异常该何去何从

真的可以用版本号的方式来保证MQ消费消息的幂等性?

写在最后

A和B之间的通信如果只是单向的话,可以理解为上下级关系,但是在微服务情况下,A和B很多时候是平行的互相调用的兄弟关系。有的架构师不赞成平行关系的微服务互相调用,这是有一定道理的,因为这很容易造成复杂的网络调用模式,如果是符合MQ消息的形式通信,我也推荐首推利用MQ来解耦服务间的依赖。

高可扩展性系统的最终目标是在应对业务变化的时候,用最小的代价去实现。而如何实现系统的扩展性,并非只有以上所说的“面向接口编程”,利用MQ这些方式,你还知道哪些可以帮助系统扩展的解决方案吗?欢迎你给我留言!!

只要一提到解耦,有的“高手”一上来就说利用MQ,真的对吗?如果调用方需要实时的业务处理结果呢?

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

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

相关文章

Docker Vs Podman

翻译自 Chetansingh 2020年4月24日的博文《Docker Vs Podman》 [1]容器化的一场全新革命是从 Docker 开始的,Docker 的守护进程管理着所有的事情,并成为最受欢迎和广泛使用的容器管理系统之一。但是,请稍等!您真的会假设 Docker 是…

算法设计与分析——动态规划——01背包问题

#include<iostream> #include<iomanip> using namespace std; //前i个物品装入容量为j的背包中获得的最大价值//0-1背包动态规划算法 构造二维表 int knapsack_problem( int n,int *weight,int *value,int capacity,int **m,int *flag) {for(int i0;i<capaci…

让 CefSharp.WinForms 应用程序同时支持32位(x86)和64位(x64)的解决方案

当我们为基于 .NET Framework 的 WinForm 程序增加 CefSharp.WinForms 依赖后&#xff0c;可能会遇到以下报错信息&#xff1a;CefSharp.Common is unable to proceeed as your current Platform is ‘AnyCPU’. To target AnyCPU please read https://github.com/cefsharp/Cef…

算法设计与分析——贪心算法——活动安排问题

问题描述&#xff1a;设有n个活动的集合E{1,2,…,n}&#xff0c;其中每个活动都要求使用同一资源&#xff0c;如演讲会场等&#xff0c;而在同一时间内只有一个活动能使用这一资源。 每个活动i都有一个要求使用该资源的起始时间si和一个结束时间fi,且si<fi 。 如果选择了活动…

c语言——刷新控制台界面与返回上一级

#include<iostream> #include<stdlib.h> using namespace std;int main() {while(1){system("cls");//刷新控制台程序界面 cout<<"请输入1&#xff1a;进入下一级程序A:"<<endl;cout<<"请输入2&#xff1a;进入下一级程…

C# :异步编程的注意点

在上一篇《C#&#xff1a;异步编程中的 async 和 await》 中简单介绍了在 C# 中的异步编程以及 async 和 await 编程模型&#xff0c;本文介绍下异步编程的注意事项&#xff0c;主要有以下几个方面。同步中调用异步在同步代码中调用异步代码&#xff0c;容易导致死锁&#xff0…

makefile 打印变量_[Makefile] 缩进与空格--记录踩过的坑

今天折腾了好久&#xff0c;就为了debug两个makefile的bug。虽然最后找到原因了&#xff0c;但是&#xff0c;怎么说呢&#xff0c;用现在流行的话来说&#xff0c;实在是意难平啊&#xff01;必须写一篇记录一下&#xff01;第一个问题&#xff0c;是个语法高亮问题。今天观察…

算法设计与分析——贪心算法——背包问题

0-1背包问题&#xff1a; 前提&#xff1a;给定n种物品和一个背包。物品i的重量是Wi&#xff0c;其价值为Vi&#xff0c;背包的容量为C。 问题&#xff1a;应如何选择装入背包的物品&#xff0c;使得装入背包中物品的总价值最大? 背包问题&#xff1a; 与0-1背包问题类似&…

企业级精致 Blazor 套件 BootstrapBlazor 介绍

BootstrapBlazor1、前言 Blazor 作为一种 Web 开发的新技术已经发展有一段时间了&#xff0c;有些人标称 无 JS 无 TS&#xff0c;我觉得有点误导新人的意味&#xff0c;也有人文章大肆宣传 Blazor 是 JavaScript 的终结者&#xff0c;是为了替代 JavaScript 而生的&#xff0c…

算法设计与分析——贪心算法——最优装载问题

有一批集装箱要装上一艘载重量为c的轮船。其中集装箱i的重量为Wi。最优装载问题要求确定在装载体积不受限制的情况下&#xff0c;将尽可能多的集装箱装上轮船。 #include<iostream> #include<algorithm> #include<cstring> using namespace std;typedef str…

AgileConfig-轻量级配置中心 1.1.0 发布,支持应用间配置继承

AgileConfig轻量级配置中心自第一个版本发布不知不觉已经半年了。在并未进行什么推广的情况下收到了250个star&#xff0c;对我有很大的鼓舞&#xff0c;并且也有不少同学试用&#xff0c;并且给出了宝贵的意见&#xff0c;非常感谢他们。其中有一些意见非常好&#xff0c;但是…

如何在 C# 中使用 Dapper ORM

译文链接&#xff1a;https://www.infoworld.com/article/3025784/how-to-use-the-dapper-orm-in-c.html?nsdrtrue对象关系映射&#xff08;ORM&#xff09;这个概念已经存在很长时间了&#xff0c;ORM的作用就是用来解决 编程领域的 object model 和关系数据库中的 data mode…

GraphQL:简单开开始一个查询

GraphQL 既是一种用于 API 的查询语言也是一个满足你数据查询的运行时。GraphQL 对你的 API 中的数据提供了一套易于理解的完整描述&#xff0c;使得客户端能够准确地获得它需要的数据&#xff0c;而且没有任何冗余&#xff0c;也让 API 更容易地随着时间推移而演进&#xff0c…

算法设计与分析——回溯法——批处理作业调度

问题描述&#xff1a;给定n个作业的集合{J1,J2,…,Jn}。每个作业必须先由机器1处理&#xff0c;然后由机器2处理。作业Ji需要机器j的处理时间为tji。对于一个确定的作业调度&#xff0c;设Fji是作业i在机器j上完成处理的时间。所有作业在机器2上完成处理的时间和称为该作业调度…

探索.NET平台中的SIMD内在函数Vector

概述Vector&#xff08;向量&#xff09;是一种序列式容器&#xff0c;事实上和数组差不多&#xff0c;但它比数组更优越。一般来说数组不能动态拓展&#xff0c;因此在程序运行的时候不是浪费内存&#xff0c;就是造成越界。而Vector刚好弥补了这个缺陷&#xff0c;它的特征是…

算法设计与分析——回溯法——装载问题

0027算法笔记——【回溯法】回溯法与装载问题 自己写的代码&#xff1a; #include <iostream> using namespace std; template <class Type> class Loading {//friend Type MaxLoading(Type[],Type,int,int []);//private:public:void Backtrack(int i);int n, …

深入解析 C# 的 String.Create 方法

作者&#xff1a;Casey McQuillan译者&#xff1a;精致码农原文&#xff1a;http://dwz.win/YVW说明&#xff1a;原文比较长&#xff0c;翻译时精简了很多内容&#xff0c;对于不重要的细枝末节只用了一句话概括&#xff0c;但不并影响阅读。你还记得上一次一个无足轻重的细节点…

算法设计与分析——回溯法——n皇后问题

一、什么是N皇后问题&#xff1f; 在nn格的棋盘上放置彼此不受攻击的n个皇后。按照国际象棋的规则&#xff0c;皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。n后问题等价于再nn的棋盘上放置n个皇后&#xff0c;任何2个皇后不妨在同一行或同一列或同一斜线上。 问题…

全局程序集缓存gac中安装程序集_我就不信2W字把源码拆的这么碎,你还不明白mybatis缓存...

前言不知道大家看到这张图感觉怎么样&#xff0c;不是难&#xff0c;一共也没有几个组件&#xff0c;但是真的让我想当头疼&#xff0c;因为在面试的时候&#xff0c;就这张图&#xff0c;对&#xff0c;你没看错&#xff0c;就这几个组件&#xff0c;那是让我相当难受啊MyBati…

GraphQL:和EntityFramework更配哦

GraphQL 既是一种用于 API 的查询语言也是一个满足你数据查询的运行时。GraphQL 对你的 API 中的数据提供了一套易于理解的完整描述&#xff0c;使得客户端能够准确地获得它需要的数据&#xff0c;而且没有任何冗余&#xff0c;也让 API 更容易地随着时间推移而演进&#xff0c…