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

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,真的对吗?如果调用方需要实时的业务处理结果呢?

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

  1. 程序员过关斩将--少年派登录安全的奇幻遐想

    " 据说,这篇也是快餐,完全符合年轻人口味 说到登录,无人不知无人不晓.每一个有用户体系的相关系统都会有登录的入口,登录是为了确认操作人的正确性.说到登录安全,其实是一个很伟大的命题,不过常 ...

  2. 程序员过关斩将--请不要误会redis 6.0 的多线程

    " 你对redis的单线程是不是有点误会? " 你对redis 6.0的多线程是不是也有点误会? " redis多线程一定可以提高性能吗? redis官方刚刚发布的6.0 ...

  3. 程序员过关斩将--应对高并发系统有没有通用的解决方案呢?

    " 灵魂拷问: 应对高并发系统有没有一些通用的解决方案呢? 这些方案解决了什么问题呢? 这些方案有那些优势和劣势呢? 对性能孜孜不倦的追求是互联网技术不断发展的根本驱动力,从最初的大型机到现 ...

  4. 程序员修神之路--高并发系统设计负载均衡架构

    点击上方"蓝字"关注,酷爽一夏 菜菜哥,上次你给我讲的分库分表策略对我帮助很大 有帮助就好,上次请我的咖啡也很好喝~ 呵呵,不过随着访问量的不断加大,网站我又加了nginx做负载均 ...

  5. 程序员过关斩将--错误的IOC和DI

    什么是IOC? 什么是DI? IOC和DI有什么关系? 作为程序员,天天撸代码,怎么能不知道IOC和DI呢.很多面试官也喜欢问这两个概念,虽然概念很简单,但是可以从面试者的回答当中,大体的可以估算到面 ...

  6. 程序员过关斩将--从未停止过的系统架构设计步伐

    " 首先,这篇文章肯定会得罪一些人 " 其次,此文只代表我个人的意见,仅供参考 从分层说起 谈到系统架构的分层和系统领域边界的划分,每个架构师,每个技术经理,甚至每个程序员都有自己 ...

  7. 程序员过关斩将--解决分布式session问题

    微信搜一搜 架构师修行之路 session 说到 session,我相信每个程序员都不陌生,或多或少在项目中使用过.session 这个词,其实是一个抽象的概念,它不像 Cookie 那样有着明确的定 ...

  8. 程序员过关斩将--Http请求中如何保持状态?

    微信搜一搜 架构师修行之路 这是一个被无数程序员撸过的问题,却只有少数人了解了真相.大体上搜了一下,网上关于http协议保持状态误导大家的文章还是有的,比如:有人说利用ViewState,那是asp. ...

  9. 程序员过关斩将--作为一个架构师,我是不是应该有很多职责?

    点击上方"蓝字"关注我们领取架构书籍 每一个程序员都有一个架构梦. 上面其实本质上是一句富有事实哲理的废话,要不然也不会有这么多人关注你的公众号.这些年随着"企业数字化& ...

最新文章

  1. Java入门教程系列【1】Java基本数据类型 小白必入系列
  2. 用gdb调试core dump文件
  3. Openfire及Spark配置(Mac)
  4. Redis常用命令之操作List类型
  5. flink中的java匿名函数修改为实名函数
  6. Web请求中同步与异步的区别
  7. java 异步调用webapi_Async Await异步调用WebApi
  8. Gopher一定要会的代码自动化检查
  9. angularjs的三种注入方式
  10. 阿拉伯数字转换成中文大写
  11. 解读absolute与relative(转载)
  12. Seaborn绘制kdeplot和distplot
  13. Python实战项目(一)刷网页访问量程序
  14. 数据存储---内存列式数据库KDB+(Q)文档
  15. CH340G的RTS#和DTR#引脚输出
  16. 京东商品列表API接口-(item_search-按关键字搜索京东商品API接口),京东API接口
  17. 视频播放器 layui-ckplayer
  18. Scratch编程 烧脑算法——换位密码
  19. WLAN@Wi-Fi
  20. EeePC各项硬件参数

热门文章

  1. 【网络爬虫入门02】HTTP客户端库Requests的基本原理与基础应用
  2. CSS选择器的权重与优先规
  3. 洛谷 P3391 文艺平衡树
  4. 查看MySQL的当前日期
  5. zepto源码研究 - ajax.js($.ajaxJSONP 的分析)
  6. ASP.NET 连接MySql数据库
  7. 关于Webapp的注意事项
  8. 一款不错的编程字体Source Code Pro
  9. 初学ASP.NET 必看
  10. css两栏式布局示例