快捷搜索:  汽车  科技

软件系统的模块类型有哪些:如何正确理解软件应用系统中关于系统通信的那些事

软件系统的模块类型有哪些:如何正确理解软件应用系统中关于系统通信的那些事同时,从各系统间通信的整合方式,可以分为:在分布式应用时代,业界通常一般两种方式可以来实现系统间的通信,主要如下:实现方式在分布式服务诞生以前,主要采用以下几种方式实现系统间的通信:

从软件系统的发展历程来看,在分布式应用出现之前,市面上几乎所有的软件系统都是集中式的,软件,硬件以及各个组件之间的高度耦合组成了单体架构软件平台,即就是所谓的单机系统。

一般来说,大型应用系统通常会被拆分成多个子系统,这些子系统可能会部署在多台机器上,也有可能只在一台机器上的多个线程中,这就是我们常说的分布式应用。

从部署形态上来说,以多台服务器和多个进程部署服务,都是为了实现一个业务需求和程序功能。分布式系统中的网络通信一般都会采用四层的 TCP 协议或七层的 HTTP 协议,在我的了解中,前者占大多数,这主要得益于 TCP 协议的稳定性和高效性。网络通信说起来简单,但实际上是一个非常复杂的过程,这个过程主要包括:对端节点的查找、网络连接的建立、传输数据的编码解码以及网络连接的管理等等,每一项都很复杂。

对于系统间通信来说,我们需要区分集群和分布式两个标准:

  • 分布式应用:一个业务拆分成多个子业务不熟在不同的服务器
  • 集群:同一个业务部署在不同的多台服务器上

实现方式

软件系统的模块类型有哪些:如何正确理解软件应用系统中关于系统通信的那些事(1)

在分布式服务诞生以前,主要采用以下几种方式实现系统间的通信:

  1. Socket通信,基于TCP/UDP二进制通讯;效率最高,编程最复杂,需要自定义通讯格式;
  1. JavaEE体系中的RMI或EJB,在Socket基础之上封装的实现,直接面象Java对象编程,编程相对简单,不需要考虑低层实现,效率也不错,但只能是Java系统间通信
  2. 基于HTTP的通信,即服务端提供可访问URL,客户端模拟http请求完成通信;可跨平台跨语言,通讯效率相对较低,编程较简单。http json。很多项目中应用。但是如果服务越来越多,服务与服务之间的调用关系复杂,调用URL管理复杂,什么时候添加机器难以确定。
  1. 基于Hessian,Remoting on HTTP,类似于RMI与Socket的关系;
  2. 基于JMS,异步通信等。
  1. 基于WebService,可跨平台跨语言,工具丰富,复杂通信相对编程简单,通信效率低。它是基于SOAP协议(http xml:需要在一个工程中将数据变为xml格式,再传输到另外一个项目,并且xml传输数据过于臃肿)。项目中不推荐使用。

在分布式应用时代,业界通常一般两种方式可以来实现系统间的通信,主要如下:

  • 基于远程过程调用的方式(Remote Procedure Call):RPC服务调用,客户端不需要知道调用具体的实现细节,只用直接调用实际存在于远程计算机上的某个对象即可,调用方式就像调用本地应用程序的对象一样。使用dubbo。使用rpc协议进行远程调用,直接使用scoket通信(底层实现,使用二进制的流,所以效率高)。传输效率高,并且可以统计出系统之间的调用关系、调用次数,管理服务。
  • 基于消息队列的方式(Message Queue):MQ服务是某个系统负责发送消息,对于关心这条消息的系统负责接收消息,并且在接收到消息之后转给其他系统业务处理。

同时,从各系统间通信的整合方式,可以分为:

  • ESB方式:有服务顺序编排/定义,服务实现隔离、多协议支撑、协议翻译、转发代理、事务控制等功能
  • 服务注册中心(很多产品用zookeeper实现):和ESB最大的不同点是:“服务注册中心”主要提供各原子系统的服务注册、服务治理、服务隔离、权限控制。当客户端进行请求时,“服务治理”将告诉客户端到哪里去访问真实的服务,自己并不提供服务的转发。Dubbo就是一个典型的服务治理框架。

RPC服务调用(RPC服务)

软件系统的模块类型有哪些:如何正确理解软件应用系统中关于系统通信的那些事(2)

RPC是一种通过网络从远程计算机程序上请求服务,不需要我们了解底层网络技术的协议。主要体现在以下几个方面:

  1. RPC是一种协议,也是一种规范所有的应用需要遵循这套规范实现。典型的RPC实现主要有Dubbo,Thrift,GRPC等。
  1. RPC通信对于网络来说是透明的,调用方不用关注网络之间的通信协议,网络I/O模型,以及通信的信息格式。
  2. RPC调用来说,是可以跨语言的,而且调用方不用关心服务端使用的是何种语言。

软件系统的模块类型有哪些:如何正确理解软件应用系统中关于系统通信的那些事(3)

在 RPC 框架里面,我们是怎么支持插件化架构的呢?我们可以将每个功能点抽象成一个接口,将这个接口作为插件的契约,然后把这个功能的接口与功能的实现分离,并提供接口的默认实现。在 Java 里面,JDK 有自带的 SPI(Service Provider Interface)服务发现机制,它可以动态地为某个接口寻找服务实现。使用 SPI 机制需要在 Classpath 下的 META-INF/services 目录里创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体实现类。

但在实际项目中,我们其实很少使用到 JDK 自带的 SPI 机制,首先它不能按需加载,ServiceLoader 加载某个接口实现类的时候,会遍历全部获取,也就是接口的实现类得全部载入并实例化一遍,会造成不必要的浪费。另外就是扩展如果依赖其它的扩展,那就做不到自动注入和装配,这就很难和其他框架集成,比如扩展里面依赖了一个 Spring Bean,原生的 Java SPI 就不支持。

我们将每个功能点抽象成一个接口,将这个接口作为插件的契约,然后把这个功能的接口与功能的实现分离并提供接口的默认实现。这样的架构相比之前的架构,有很多优势。首先它的可扩展性很好,实现了开闭原则,用户可以非常方便地通过插件扩展实现自己的功能,而且不需要修改核心功能的本身;其次就是保持了核心包的精简,依赖外部包少,这样可以有效减少开发人员引入 RPC 导致的包版本冲突问题。

一般一个RPC 框架里面都有会涉及两个模块:

  • 传输模块:RPC 本质上就是一个远程调用,那肯定就需要通过网络来传输数据。虽然传输协议可以有多种选择,但考虑到可靠性的话,我们一般默认采用 TCP 协议。为了屏蔽网络传输的复杂性,我们需要封装一个单独的数据传输模块用来收发二进制数据。
  • 协议封装:用户请求的时候是基于方法调用,方法出入参数都是对象数据,对象是肯定没法直接在网络中传输的,我们需要提前把它转成可传输的二进制,这就是我们说的序列化过程。但只是把方法调用参数的二进制数据传输到服务提供方是不够的,我们需要在方法调用参数的二进制数据后面增加“断句”符号来分隔出不同的请求,在两个“断句”符号中间放的内容就是我们请求的二进制数据。

除此之外,我们还可以在协议模块中加入压缩功能,这是因为压缩过程也是对传输的二进制数据进行操作。在实际的网络传输过程中,我们的请求数据包在数据链路层可能会因为太大而被拆分成多个数据包进行传输,为了减少被拆分的次数,从而导致整个传输过程时间太长的问题,我们可以在 RPC 调用的时候这样操作:在方法调用参数或者返回值的二进制数据大于某个阈值的情况下,我们可以通过压缩框架进行无损压缩,然后在另外一端也用同样的压缩算法进行解压,保证数据可还原。

传输和协议这两个模块是 RPC 里面最基础的功能,它们使对象可以正确地传输到服务提供方。但距离 RPC 的目标——实现像调用本地一样地调用远程,还缺少点东西。因为这两个模块所提供的都是一些基础能力,要让这两个模块同时工作的话,我们需要手写一些黏合的代码,但这些代码对我们使用 RPC 的研发人员来说是没有意义的,而且属于一个重复的工作,会导致使用过程的体验非常不友好。

消息队列(MQ服务)

软件系统的模块类型有哪些:如何正确理解软件应用系统中关于系统通信的那些事(4)

分布式子系统之间需要通信时,就发送消息。一般通信的两个要点是:消息处理和消息传输。

  • 消息处理:例如读取数据和写入数据。基于消息方式实现系统通信的消息处理可以分为同步消息和异步消息。同步消息一般采用的是BIO(Blocking IO)和NIO(Non-Blocking IO);异步消息一般采用AIO方式。
  • 消息传输:消息传输需要借助网络协议来实现,TCP/IP协议和UDP/IP协议可以用来完成消息传输。

消息队列本质上是一种系统间相互协作的通信机制。一般使用消息队列可以业务解耦,流量削峰,日志收集,事务最终一致性,异步处理等业务场景。在我们实际开发工作中,一般消息队列的使用需要实现:

  • 消息处理中心(Message Broker):负责消息的接收,存储,转发等。
  • 消息生产者(Message Producer):负责产生和发送消息的消息处理中心。
  • 消息消费者(Message Consumber):负责从消息处理中心获取消息,并进行相应的处理。

当然,在技术选型的时候,我们需要选择最适合我们的。

版权声明:本文为博主原创文章,遵循相关版权协议,如若转载或者分享请附上原文出处链接和链接来源。

猜您喜欢: