Skip to content
标签
通信
字数
15427 字
阅读时间
61 分钟

分布式通信相关概念

img

第一部分:分布式协议和理论

1、分布式协议的基本概念

img

概述

分布式系统是一个硬件或者软件组件分布在不同网络计算机上,彼此之间仅仅通过消息传递来进行通信和协调的系统。

核心概念

分布式系统是一个硬件或软件组件分布在不同的网络计算机上,彼此之间仅仅通过消息传递进行通信和协调系统。——《分布式系统概念与设计》

节点

指一个可以独立按照分布式协议完成一组逻辑的程序个体

有状态与无状态 当一个节点可以在其本机上进行数据交互则称为有状态节点。如果一个节点进行数据交互需要请求其他节点来帮其实现则称为无状态节点

异常 1、机器怠机 指机器异常死机,需要人为重启。在怠机过程中,无法工作的节点的状态 称为 不可用状态,一般怠机节点可以通过读取本地硬盘或者读取其他节点的数据恢复其内存信息从而重新进入正常状态,从不可用到可用称为宕机恢复。 2、网络异常 多为网络拥塞造成丢包或者数据经过对方协议栈ACK后,但因对方服务器怠机导致无法正常处理数据所造成的异常。 3、网络分化 指某些节点的通信不正常或丢包率不在可接受范围内,则称这种特殊的异常为网络分化(network partition),多为跨机房通信时发生

4、存储数据丢失 这个指节点存储的数据不可被读取或读取出的数据错误。这种情况多为 硬件存储介质损坏时发生。对于状态节点来说,数据丢失意味着状态丢失。

副本 副本是分布式系统最常见的概念之一,指的是分布式系统对数据和服务提供一种冗余方式。在常见的分布式系统中,为了对外提供高可用的服务,我们往往会对数据和服务进行副本处理(即备份)。

1、数据副本 数据副本是指在不同的节点上持久化同一份数据,当某个节点上存储的数据丢失时,可以从副本上读取到该数据。(例如:数据库的主从备份)

2、服务副本 服务副本是指多个节点提供同样的服务,每个节点都有能力接收来自外部的请求并进行相应的处理。

副本一致性 1、副本一致性指通过相应的控制协议使得系统外部读取系统内部各个副本的数据在一定的约束条件下结果相同。副本一致性是针对整个分布式系统而言,并非某一个副本而言。

2、强一致性 任何时刻任何用户或节点读取的副本数据都是最新的。 单调一致性 任何时刻,任何用户一旦读到某个数据在某次更新后的值,不会再读到比这个值更旧的值。这个一致性从用户视角程度讲的,可能其他用户读到的值并非与他一致。 3、会话一致性 任何用户在某一次会话一旦读到某次更新后的值,不会在此会话中读到比它更旧的值,这个一致性从会话角度上讲的。 4、最终一致性 指各个副本的数据最终达到完全一致的状态,但到达完全一致状态所需的数据不能保障。对于最终一致性分布式系统而言,只要一个用户一直读一个副本的内容,那么它读到的值只会越来越新类似单调一致性效果,但不同副本间无法保障一致性。

5、弱一致性 指一旦更新,用户无法在一个确定时间内读到这次更新的值,即使可能读到了新的值在某个副本上,也不能保证在其他副本上读到了新的值。

特征

一般情况下,一个分布式系统中的计算机在空间部署上是可以随意分布的,这些计算机可以处于不同的机房,或者不同的城市。因此,一个标准的分布式在没有特定业务逻辑约束的情况下一般具体如下特征:

1、分布式 分布式系统中的多台计算机在空间上可以随意分布,同时机器的分布情况也可以随时变动。

2、对等性 分布式系统中的计算机没有主/从之分,即没有控制整个系统的主机,有没有被控制的从机,组成分布式系统的计算机节点都是对等的。

3、并发性 在一个分布式系统中,不同节点可以并发的操作一些共享资源,例如:数据库或分布式存储等。

4、缺乏全局时钟 由于一个分布式系统是一个由一系列在空间上随意分布的对个进程组成的,进程之间通过消息交换信息来进行通信。因此,在分布式系统中很难第一两个事件之间的先后顺序,原因就是因为分布式系统缺乏一个全局的时钟系列控制。

5、故障总会发生 组成分布式系统的所有计算机,都有可能发生任何形式的故障。一个被大量工程实践所检验过的黄金定理:任何在设计阶段考虑到的异常情况,一定会在系统时间运行中发生,并且,在系统实际运行过程中还会遇到很多在设计时未能考虑到的异常故障。

分布式典型问题

1、通信异常 分布式系统需要再各个节点之间进行网路通信,因此每次网络同行都伴随着网络不可用的风险,网络光纤、路由器或DNS等硬件设备或是系统不可用都会导致最终分布式系统无法顺利完成一次网络通信。

2、网络分区 当网络发生异常情况,导致分布式系统中部分节点之间的网络延时不断增大,最终导致组成分布式系统的所有节点中,只有一部分能够进行正常的通信,而另一部分则不能——我们这种现象为网络分区。

3、三态 三态指在分布式系统中一次请求和响应,存在特有的三种情况:即成功、失败与超时。一般出现超时有以下两种情况:

  • 由于网络原因,请求消息并没有被成功发送到接收方,而是在发送过程中丢失
  • 请求消息成功被及接收方接收后,并进行处理,但是在将响应反馈给发送方的过程中,消息丢失了

4、节点故障 节点故障时分布式环境下另一个比较常见的问题,指的是组成分布式系统的服务器节点发生了宕机或“僵死”现象。

2、从ACID到CAP/BASE

概述

ACID

事务是由一系列对系统中数据进行访问与更新的操作所组成的一个程序执行逻辑单元,一个数据库事务具有以下四个特性:

  • 原子性(Atomicity):指事务必须是一个原子的操作序列单元。在执行过程中要么全部执行,要么全部不执行。
  • 一致性(Consistency):事务的执行不能破坏数据库的完整性和一致性,事务在执行前后数据库必须处于一致的状态。
  • 隔离性(Isolation):并发的事务是互相隔离的,一个事务的执行不能被其他事务干扰。标准的SQL规范中,定义了4个事务隔离级别:Read Uncommitted、Read Committed、Repeatable Read和Serializable。MySQL的默认隔离级别是:Repeatable Read,Oracle的默认隔离级别是:READ COMMITTED。
  • 持久性(Durability):指一个事务一旦提交,它对数据库中对应数据状态的变更是永久性的。

img

CAP帽子理论

1、C(Consistency) 数据的一致性,也叫做数据原子性,系统在执行过某项操作后仍然处于一致的状态。在分布式系统中,更新操作执行成功后所有的用户都应该读到最新的值,这样的系统被认为是具有强一致性的。等同于所有节点访问同一份最新的数据副本。

2、A(Availability)服务可用性,每一个操作总是能够在一定的时间内,返回结果,这里需要注意的是“一定时间内”和“返回结果”,一定时间指的是:在可以容忍的范围内返回结果,结果可以是成功或者是失败。

3、P(Partition-Tolerance)分区容错性,在网络分区的情况下,被分隔的节点仍能正常对外服务(分布式集群,数据被分布存在在不同的服务器上,无论什么情况,服务器都能正常被访问)。

==定律:在分布式系统里面只能同时命中2个。==

1、CA 放弃P ,如果想避免分区容器性问题的发送,一种做法是将所有的数据(与数据相关的)都放在一天机器上。虽然无法100%保证系统不会出错,但是不会碰到由于分区有错性带来的负面效果。当然这个选择会严重的影响系统的扩展性,

2、CP 放弃A,相对于放弃 “分区容错性” 来说,其反面就是放弃可用性。一旦遇到服务器容错故障,那么受到影响的服务需要等待一定的世界,因此,在等待期间系统无法对外提供服务。

3、AP放弃C,这里所说的放弃一致性,并不是完全放弃数据一致性,而是放弃数据的强一致性,而保留数据的最终一致性,以网络购物为例,对只剩下一件库存的商品,如果同时接受到了两次订单,那么较晚的订单将被告知商品售罄。

对比项zookeepereureka描述
capCPAP
Dubbo集成已集成-----------
SpringCloud集成已集成已集成
KV存储服务支持-------------zk支持数据存储,eureka不支持
使用接口提供客户端支持zookeeper的跨语言支持比较弱
watch支持支持支持什么是watch?
就是客户端监听服务器端的情况变化
zk是通过订阅监听来实现
eureka是通过轮询的方式来实现。
集群监控--------------metricsmetrics,运维者可以搜集并警报这些度量信息达到监控的目的

==Zookeeper是CP的原理==,因为zk中有一个leader,当这个leader无法使用的时候,zk会通过一个算法zab算法,选举出一个新的leader节点,也就是它的主从关系。这个主从的目的就是保证写信息的时候指向这个主服务写信息,zk是写数据的时候只向leader上面写,然后这个leader会同步数据到各从节点,这个过程就是数据复制的过程。也就是P。把数据复制到了不同的机器上,他的目的就是保证数据的一致性,也就是C,但是问题就来,它牺牲了A,导致A出问题,因为在leader同步数据给子节点的时候,是需要又时间的,在复制的过程中zk是无法向外界提供服务支持,这个时候所以的节点的可用性都没有了。服务器需要的等待,直到全部复制完同步成功,服务才会打开。

==Eureka是AP的原理==,因为eureka没有zk的leader概念,每个eureka服务节点,都单独保存微服务的注册信息,这个就会出现了无法保证每个eureka服务器都是数据一致性的问题。也就是没有C,首先来说一下A,在Eureka集群中,如果某台服务器宕机了,Eureka是不会类似于Zookeeper选择leader的过程,客户端请求会自动切换到新的Eureka服务器节点上,当宕机的服务器从新回复后,Eureka在此会启用纳入集群管理中,从而保证了Eureka的A,Eureka的P,它本身有一个自我保护的机制,这个机制就是P分区容错,自我保护的目的就是为了在所有的Eureka服务在发生网络瘫痪或者故障时依旧能提供服务和可用性。它自己带一个心跳机制。服务在Eureka 注册,然后发送心跳每30秒更新一次租约。如果客户端无法续订租约几次,则会在大约90秒内将其从服务器注册表中删除。然后eureka不是短时间内,突然丢失了大量的心跳,就开始启用自我保护的机制。同时保留那些心跳死亡的注册信息不过期,这个时候Eureka的节点对于新的服务还能提供注册信息,对于死亡的仍然是保留的,以防止客户向其他或这个服务发送请求,当网络恢复故障后,这个时候eureka节点就会退出自我保护的机制,所以说呢,Eureka就是好坏数据都留着,也就好数据和坏数据都不丢失,都放着,它的认知就是数据是有用的,总比丢失的好,这个就是Eureka的P。这也就是为什么它还保证了C的原因。

3、分布式与集群概念

概述

分布式

可以将分布式理解为,将某一个应用程序,拆分成多个模块来部署,各个模块负责不同的功能;

分布式的优点是细化了应用程序的功能模块,同时也减轻了一个完整的应用程序部署在一台服务器上的负担,用了分布式拆分后,就相当于把一个应用程序的多个功能分配到多台服务器上去处理了。

集群

集群的意思就是将一个应用程序,部署到多台服务器上面,然后在这些服务器的前面通过负载均衡服务器来择优选择哪一台服务器去执行;

集群的优点就是当其中的一个服务器宕机了,其他服务器可以接上继续工作;将应用程序部署在多台服务器时,也提供了数据的吞吐量。

二者之间的联系

将一个应用程序拆分成多个功能模块或节点(分布式)后,可以对每一个功能模块或节点用集群的方式来部署,从而达到提升应用程序的整体性能以及高可用的优点。

==分布式是将不同的业务分布在不同的地方==

==集群是将几台服务器集中在一起,实现同一业务==

分布式中每个节点,都可以做集群

而集群不一定是分布式的

推荐文章:https://www.cnblogs.com/aspirant/p/5697807.html

https://blog.csdn.net/qq_37788067/article/details/79250623

第二部分:微服务架构的设计原则

4、为什么要使用微服务-微服务架构

概述

G:/note/备课/days50-SpringBoot Spring Cloud 实战 k8s & docker/笔记/assets/1559028365155.png

两者的比较

G:/note/备课/days50-SpringBoot Spring Cloud 实战 k8s & docker/笔记/assets/1559029215228.png

G:/note/备课/days50-SpringBoot Spring Cloud 实战 k8s & docker/笔记/assets/1559029628924.png

G:/note/备课/days50-SpringBoot Spring Cloud 实战 k8s & docker/笔记/assets/1559029707307.png

5、如何设计微服务?它的设计原则是什么?

概述

G:/note/备课/days50-SpringBoot Spring Cloud 实战 k8s & docker/笔记/assets/1559029919687.png

前后端分离

G:/note/备课/days50-SpringBoot Spring Cloud 实战 k8s & docker/笔记/assets/1559030026752.png

G:/note/备课/days50-SpringBoot Spring Cloud 实战 k8s & docker/笔记/assets/1559030073807.png

G:/note/备课/days50-SpringBoot Spring Cloud 实战 k8s & docker/笔记/assets/1559030102203.png

G:/note/备课/days50-SpringBoot Spring Cloud 实战 k8s & docker/笔记/assets/1559030166950.png

G:/note/备课/days50-SpringBoot Spring Cloud 实战 k8s & docker/笔记/assets/1559030182127.png

6、序列化存在的意义

概述

众所周知,类的对象会随着程序的终止而被垃圾收集器销毁。如果要在不重新创建对象的情况下调用该类,该怎么做?这就可以通过序列化将数据转换为字节流。

序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。一般将一个对象存储至一个储存媒介,例如档案或是记亿体缓冲等。在网络传输过程中,可以是字节或是XML等格式。而字节的或XML编码格式可以还原完全相等的对象。这个相反的过程又称为反序列化。

Java对象的序列化与反序列化

在Java中,我们可以通过多种方式来创建对象,并且只要对象没有被回收我们都可以复用该对象。但是,我们创建出来的这些Java对象都是存在于JVM的堆内存中的。只有JVM处于运行状态的时候,这些对象才可能存在。一旦JVM停止运行,这些对象的状态也就随之而丢失了。

但是在真实的应用场景中,我们需要将这些对象持久化下来,并且能够在需要的时候把对象重新读取出来。Java的对象序列化可以帮助我们实现该功能。

对象序列化机制(object serialization)是Java语言内建的一种对象持久化方式,通过对象序列化,可以把对象的状态保存为字节数组,并且可以在有需要的时候将这个字节数组通过反序列化的方式再转换成对象。对象序列化可以很容易的在JVM中的活动对象和字节数组(流)之间进行转换。

在Java中,对象的序列化与反序列化被广泛应用到RMI(远程方法调用)及网络传输中。

img

对象序列化是一个用于将对象状态转换为字节流的过程,可以将其保存到磁盘文件中或通过网络发送到任何其他程序;从字节流创建对象的相反的过程称为反序列化。而创建的字节流是与平台无关的,在一个平台上序列化的对象可以在不同的平台上反序列化。

如何使Java类可序列化?

通过实现java.io.Serializable接口,可以在Java类中启用可序列化。它是一个标记接口,意味着它不包含任何方法或字段,仅用于标识可序列化的语义。

如果我们试图序列化不可序列化的对象怎么办?

我们将得到一个 RuntimeException 异常:主线程中出现异常 java.io.NotSerializableException。

什么是serialVersionUID?

SerialVersionUID是一个标识符,当它通常使用对象的哈希码序列化时会标记在对象上。我们可以通过Java中serialver工具找到该对象的serialVersionUID。

语法:**serialver classname,**SerialVersionUID用于对象的版本控制。当您添加或修改类中的任何字段时,已经序列化的类将无法恢复,因为serialVersionUID已为新类生成与旧的序列化对象将不同。Java序列化过程依赖于正确的serialVersionUID恢复序列化对象的状态,并在serialVersionUID不匹配时抛出java.io.InvalidClassException 异常。

Transient 关键字

Transient修饰符仅适用于变量,不适用于方法和类。在序列化时,如果我们不想序列化特定变量以满足安全约束,那么我们应该将该变量声明为transient。执行序列化时,JVM会忽略transient变量的原始值并将默认值保存到文件中。因此,transient意味着不要序列化。Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。

Transient 与 Static

静态变量不是对象状态的一部分,因此它不参与序列化。所以将静态变量声明为transient变量是没有用处的。

Final 与 Transient

final变量将直接通过值参与序列化,所以将final变量声明为transient变量不会产生任何影响。现在,让我们考虑一个显示Java中的序列化和反序列化的程序。

ObjectOutputStream类和ObjectInputStream类

通过前面的代码片段中我们也能知道,我们一般使用ObjectOutputStream的writeObject方法把一个对象进行持久化。再使用ObjectInputStream的readObject从持久化存储中把对象读取出来。

更多关于ObjectInputStream和ObjectOutputStream的相关知识欢迎阅读我的另外两篇博文:深入分析Java的序列化与反序列化单例与序列化的那些事儿

序列化ID

虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID)

序列化 ID 在 Eclipse 下提供了两种生成策略,一个是固定的 1L,一个是随机生成一个不重复的 long 类型数据(实际上是使用 JDK 工具生成),在这里有一个建议,如果没有特殊需求,就是用默认的 1L 就可以,这样可以确保代码一致时反序列化成功。那么随机生成的序列化 ID 有什么作用呢,有些时候,通过改变序列化 ID 可以用来限制某些用户的使用。

java
package com.itcast.socket;
 
import java.io.Serializable;

/*
 * @Author 徐柯老师
 * @Tel/微信:15074816437
 * @Description 请求实体
 * @Date 9:54 2019/6/10
 * @Param
 * @return
 **/
public class RequestBean implements Serializable {
 
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	//可以放任何类型的数据
	private  String name;
	private  String password;
	/**
	 * 
	 */
	public RequestBean() {
		
	}
	/**
	 * @param name
	 * @param password
	 */
	public RequestBean(String name, String password) {
		this.name = name;
		this.password = password;
	}
	public String getName() {
		return this.name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getPassword() {
		return this.password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
}
java
package com.itcast.socket;
 
import java.io.Serializable;
 
/*
 * @Author 徐柯老师
 * @Tel/微信:15074816437
 * @Description 响应实体
 * @Date 9:54 2019/6/10
 * @Param 
 * @return 
 **/
public class ResponseBean implements Serializable {
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private int id;
	private String name;
	private String pwd;
	private int age;
	private String sex;
	/**
	 * 
	 */
	public ResponseBean() {
		
	}
	/**
	 * @param id
	 * @param name
	 * @param pwd
	 * @param age
	 * @param sex
	 */
	public ResponseBean(int id, String name, String pwd, int age, String sex) {
		this.id = id;
		this.name = name;
		this.pwd = pwd;
		this.age = age;
		this.sex = sex;
	}
	public int getId() {
		return this.id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getName() {
		return this.name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getPwd() {
		return this.pwd;
	}
	public void setPwd(String pwd) {
		this.pwd = pwd;
	}
	public int getAge() {
		return this.age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public String getSex() {
		return this.sex;
	}
	public void setSex(String sex) {
		this.sex = sex;
	}
}

客户端

java
package com.itcast.socket;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;

/**
 * @Author 徐柯老师
 * @Tel/微信:15074816437
 * @Description 客户端
 * @Date 9:53 2019/6/10
 * @Param
 * @return
 **/
public class SocketClient {
	private final static Logger logger = LoggerFactory.getLogger(SocketClient.class
			.getName());
 
	public static void main(String[] args) throws Exception {
		Socket socket = null;
		ObjectOutputStream os = null;
		ObjectInputStream is = null;
 
		try {
			socket = new Socket("127.0.0.1", 6666);
			os = new ObjectOutputStream(socket.getOutputStream());
			RequestBean req = new RequestBean("sk", "123456");
			os.writeObject(req);
			os.flush();
 
			is = new ObjectInputStream(new BufferedInputStream(
					socket.getInputStream()));
			Object obj = is.readObject();
			if (obj != null) {
				ResponseBean res = (ResponseBean) obj;
				System.out.println("age: " + res.getAge() + "/" + res.getSex());
			}
		} catch (IOException ex) {
			logger.info("读取数据出错!");
		} finally {
			try {
				is.close();
			} catch (Exception ex) {
				ex.printStackTrace();
			}
			try {
				os.close();
			} catch (Exception ex) {
				ex.printStackTrace();
			}
			try {
				socket.close();
			} catch (Exception ex) {
				ex.printStackTrace();
			}
		}
	}
}

服务端

java
package com.itcast.socket;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/*
 * @Author 徐柯老师
 * @Tel/微信:15074816437
 * @Description 服务器端
 * @Date 9:53 2019/6/10
 * @Param
 * @return
 **/
public class SocketServer {
	private final static Logger logger = LoggerFactory.getLogger(SocketServer.class
			.getName());
 
	public static void main(String[] args) throws IOException {
		ServerSocket server = new ServerSocket(6666);
 
		while (true) {
			logger.info("scoket start!");
			Socket socket = server.accept();
			invoke(socket);
		}
	}
 
	private static void invoke(final Socket socket) throws IOException {
		new Thread(new Runnable() {
			public void run() {
				ObjectInputStream is = null;
				ObjectOutputStream os = null;
				try {
					is = new ObjectInputStream(new BufferedInputStream(
							socket.getInputStream()));
					os = new ObjectOutputStream(socket.getOutputStream());
 
					Object obj = is.readObject();
					RequestBean req = (RequestBean) obj;
					System.out.println("user: " + req.getName() + "/"
							+ req.getPassword());
					//TOD 获取请求参数后可以查询数据信息
					ResponseBean res = new ResponseBean(1, "sk", "123456", 22, "男");
					
					os.writeObject(res);
					os.flush();
				} catch (IOException ex) {
					logger.info("读取数据出错!");
					ex.printStackTrace();
				} catch (ClassNotFoundException ex) {
					logger.info("缺少类信息!");
					ex.printStackTrace();
				} finally {
					try {
						is.close();
					} catch (Exception ex) {
					}
					try {
						os.close();
					} catch (Exception ex) {
					}
					try {
						socket.close();
					} catch (Exception ex) {
						ex.printStackTrace();
					}
				}
			}
		}).start();
	}
 
}

7、Externalizable接口

概述

除了Serializable 之外,java中还提供了另一个序列化接口Externalizable

为了了解Externalizable接口和Serializable接口的区别,先来看代码,我们把上面的代码改成使用Externalizable的形式。

java
package com.itcast.searialize;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
 
/**
 * Created by hollis on 16/2/17.
 * 实现Externalizable接口
 */
public class User1 implements Externalizable {
 
    private String name;
    private int age;
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public int getAge() {
        return age;
    }
 
    public void setAge(int age) {
        this.age = age;
    }
 
    public void writeExternal(ObjectOutput out) throws IOException {
 
    }
 
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
 
    }
 
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
java
package com.itcast.searialize;

import java.io.*;
 
/**
 * Created by hollis on 16/2/17.
 */
public class ExternalizableDemo1 {
 
    //为了便于理解和节省篇幅,忽略关闭流操作及删除文件操作。真正编码时千万不要忘记
    //IOException直接抛出
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //Write Obj to file
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
        User1 user = new User1();
        user.setName("hollis");
        user.setAge(23);
        oos.writeObject(user);
        //Read Obj from file
        File file = new File("tempFile");
        ObjectInputStream ois =  new ObjectInputStream(new FileInputStream(file));
        User1 newInstance = (User1) ois.readObject();
        //output
        System.out.println(newInstance);
    }
}

通过上面的实例可以发现,对User1类进行序列化及反序列化之后得到的对象的所有属性的值都变成了默认值。也就是说,之前的那个对象的状态并没有被持久化下来。这就是Externalizable接口和Serializable接口的区别:

Externalizable继承了Serializable,该接口中定义了两个抽象方法:writeExternal()readExternal()。当使用Externalizable接口来进行序列化与反序列化的时候需要开发人员重写writeExternal()readExternal()方法。由于上面的代码中,并没有在这两个方法中定义序列化实现细节,所以输出的内容为空。还有一点值得注意:在使用Externalizable进行序列化的时候,在读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。所以,实现Externalizable接口的类必须要提供一个public的无参的构造器。

按照要求修改之后代码如下:

java
package com.itcast.searialize;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
 
/**
 * Created by hollis on 16/2/17.
 * 实现Externalizable接口,并实现writeExternal和readExternal方法
 */
public class User2 implements Externalizable {
 
    private String name;
    private int age;
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public int getAge() {
        return age;
    }
 
    public void setAge(int age) {
        this.age = age;
    }
 
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(name);
        out.writeInt(age);
    }
 
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        name = (String) in.readObject();
        age = in.readInt();
    }
 
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
java
package com.itcast.searialize;

import java.io.*;
 
/**
 * Created by hollis on 16/2/17.
 */
public class ExternalizableDemo2 {
 
    //为了便于理解和节省篇幅,忽略关闭流操作及删除文件操作。真正编码时千万不要忘记
    //IOException直接抛出
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //Write Obj to file
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
        User2 user = new User2();
        user.setName("hollis");
        user.setAge(23);
        oos.writeObject(user);
        //Read Obj from file
        File file = new File("tempFile");
        ObjectInputStream ois =  new ObjectInputStream(new FileInputStream(file));
        User2 newInstance = (User2) ois.readObject();
        //output
        System.out.println(newInstance);
    }
}

这次,就可以把之前的对象状态持久化下来了。

如果User类中没有无参数的构造函数,在运行时会抛出异常:java.io.InvalidClassException

第三部分:RMI、JMS、WebService和RPC

9、RMI

概述

Java RMIJava远程方法调用,即Java RMI(Java Remote Method Invocation)是Java编程语言里,一种用于实现远程过程调用的应用程序编程接口。它使客户机上运行的程序可以调用远程服务器上的对象。远程方法调用特性使Java编程人员能够在网络环境中分布操作。RMI全部的宗旨就是尽可能简化远程接口对象的使用。

我们知道远程过程调用(Remote Procedure Call, RPC)可以用于一个进程调用另一个进程(很可能在另一个远程主机上)中的过程,从而提供了过程的分布能力。Java 的 RMI 则在 RPC 的基础上向前又迈进了一步,即提供分布式对象间的通讯。

RMI(Remote Method Invocation)为远程方法调用,是允许运行在一个Java虚拟机的对象调用运行在另一个Java虚拟机上的对象的方法。

这两个虚拟机可以是运行在相同计算机上的不同进程中,也可以是运行在网络上的不同计算机中。

一、工作原理

RMI能让一个Java程序去调用网络中另一台计算机的Java对象的方法,那么调用的效果就像是在本机上调用一样。通俗的讲:==A机器上面有一个class,通过远程调用,B机器调用这个class 中的方法。==

RMI,远程方法调用(Remote Method Invocation)是Enterprise JavaBeans的支柱,是建立分布式Java应用程序的方便途径。RMI是非常容易使用的,但是它非常的强大。

RMI的基础是接口,RMI构架基于一个重要的原理:定义接口和定义接口的具体实现是分开的。下面我们通过具体的例子,建立一个简单的远程计算服务和使用它的客户程序

RMI是Java最初的远程方法调用技术。RMI最初在JDK1.1被引入到Java平台中,它为Java开发者提供了一种强大的方法来实现Java程序间的交互。在RMI之前,对于Java开发者来说,远程调用的唯一选择就是CORBA(在当时,需要购买一种第三方产品,叫做Object Request Broker[ORB]),或者手工编写Socker程序。

G:/note/备课/分布式通信相关概念(协议、序列化、http、rmi、webservice,JMS,RPC)/笔记/assets/loa4jdhyca-1560235142153.png

  • 1、客户调用客户端辅助对象stub上的方法
  • 2、客户端辅助对象stub打包调用信息(变量、方法名),通过网络发送给服务端辅助对象skeleton
  • 3、服务端辅助对象skeleton将客户端辅助对象发送来的信息解包,找出真正被调用的方法以及该方法所在对象
  • 4、调用真正服务对象上的真正方法,并将结果返回给服务端辅助对象skeleton
  • 5、服务端辅助对象将结果打包,发送给客户端辅助对象stub
  • 6、客户端辅助对象将返回值解包,返回给调用者
  • 7、客户获得返回值

二、RMI包含部分

  1. 远程服务的接口定义
  2. 远程服务接口的具体实现
  3. 桩(Stub)和框架(Skeleton)文件
  4. 一个运行远程服务的服务器
  5. 一个RMI命名服务,它允许客户端去发现这个远程服务
  6. 类文件的提供者(一个HTTP或者FTP服务器)
  7. 一个需要这个远程服务的客户端程序

三、RMI的用途

​ RMI的用途是为分布式Java应用程序之间的远程通信提供服务,提供分布式服务。

​ 目前主要应用时封装在各个J2EE项目框架中,例如Spring,EJB(Spring和EJB均封装了RMI技术)

​ 在Spring中实现RMI:

​ ①在服务器端定义服务的接口,定义特定的类实现这些接口;

​ ②在服务器端使用org.springframework.remoting.rmi.RmiServiceExporter类来注册服务;

​ ③在客户端使用org.springframework.remoting.rmi.RmiProxyFactoryBean来实现远程服务的代理功能;

​ ④在客户端定义访问与服务器端服务接口相同的类

四、RMI的局限?

==RMI目前使用Java远程消息交换协议JRMP(Java Remote Messaging Protocol)进行通信。JRMP是专为Java的远程对象制定的协议,由于JRMP是专为Java对象制定的,因此,RMI对于用非Java语言开发的应用系统的支持不足。不能与用非Java语言书写的对象进行通信(意思是只支持客户端和服务器端都是Java程序的代码的远程调用)。==

五、RMI的使用局限

​ 由于客户机和服务器都是使用Java编写的,二者平台兼容性的要求仅仅是双方都运行在版本兼容的Java虚拟机上。

六、RMI调用远程方法的参数和返回值

​ 当调用远程对象上的方法时,客户机除了可以将原始类型的数据作为参数一外,还可以将对象作为参数来传递,与之相对应的是返回值,可以返回原始类型或对象,这些都是通过Java的对象序列化(serialization)技术来实现的。(换而言之:参数或者返回值如果是对象的话必须实现Serializable接口)

七、 RMI应用程序的基本模型

img

八、RMI体系结构

G:/note/精品教程/锁定offer必备,深度剖析Java核心技术资料/笔记/assets/021050169371150.jpg

桩/框架(Stub/Skeleton)层:客户端的桩和服务器端的框架;

远程引用(remote reference)层:处理远程引用行为

传送层(transport):连接的建立和管理,以及远程对象的跟踪

九、RMI类和接口(完成一个简单RMI需要用到的类)

img

img

img

(一) Remote接口:是一个不定义方法的标记接口

​ Public interface Remote{}

​ 在RMI中,远程接口声明了可以从远程Java虚拟机中调用的方法集。远程接口满足下列要求:

​ 1、远程接口必须直接或间接扩展Java.rmi.Remote接口,且必须声明为public,除非客户端于远程接口在同一包中

​ 2、在远程接口中的方法在声明时,除了要抛出与应用程序有关的一场之外,还必须包括RemoteException(或它的超类,IOExcepion或Exception)异常

​ 3、在远程方法声明中,作为参数或返回值声明的远程对象必须声明为远程接口,而非该接口的实现类。

(二) RemoteObject抽象类实现了Remote接口和序列化Serializable接口,它和它的子类提供RMI服务器函数。

(三) LocateRegistry final()类用于获得特定主机的引导远程对象注册服务器程序的引用(即创建stub),或者创建能在特定端口接收调用的远程对象注册服务程序。

服务器端:向其他客户机提供远程对象服务

​ SomeService servcie=……;//远程对象服务

  1. Registry registry=LocateRegisty.getRegistry();//Registry是个接口,他继承了Remote,此方法返回本地主机在默认注册表端口 1099 上对远程对象 Registry 的引用。
  2. getRegistry(int port) 返回本地主机在指定 port 上对远程对象 Registry 的引用;
  3. getRegistry(String host) 返回指定 host 在默认注册表端口 1099 上对远程对象 Registry的引用;
  4. getRegistry(String host, int port) 返回指定的 hostport 上对远程对象 Registry 的引用
  5. registry.bind(“I serve”,service);// bind(String name,Remote obj) 绑定对此注册表中指定 name 的远程引用。name : 与该远程引用相关的名称 obj : 对远程对象(通常是一个 stub)的引用
  6. unbind(String name)移除注册表中指定name的绑定。
  7. rebind(String name,Remote obj)重新绑定,如果name已存在,但是Remote不一样则替换,如果Remote一样则丢弃现有的绑定
  8. lookup(String name) 返回注册表中绑定到指定 name 的远程引用,返回Remote
  9. String[] list() 返回在此注册表中绑定的名称的数组。该数组将包含一个此注册表中调用此方法时绑定的名称快照。

客户机端:向服务器提供相应的服务请求。

Registry registry=LocateRegisty.getRegistry();
SomeService servcie=(SomeService)registry.lookup(“I serve”);
Servcie.requestService();

(四) Naming类和Registry类类似。

客户端:

Naming.lookup(String url)

url 格式如下"rmi://localhost/"+远程对象引用 服务器端: Registry registry=LocateRegistry.createRegistry(int port); Naming.rebind(“service”,service);

(五) RMISecurityManager类

在RMI引用程序中,如果没有设置安全管理器,则只能从本地类路径加载stub和类,这可以确保应用程序不受由远程方法调用所下载的代码侵害

在从远程主机下载代码之前必须执行以下代码来安装RMISecurityManager:

System.setSecurityManager(new RMISecurityManager());

RMI是一种实现远程服务交互的好办法,但是RMI有一些缺点和不足:

==RMI很难穿越防火墙。因为RMI使用任意端口来交互——这是防火墙通常所不允许的。如果是内网,就不需要担心这个问题。如果是在互联网上运行,这就麻烦了。==

==RMI是基于JAVA的,使用了JAVA的序列化机制,所以通过网络传输的对象类型必须要保证在调用两端的Java运行时中是完全相同的版本。所以就意味着客户端和服务端都必须使用JAVA来开发才行。这就是一个限制了。==

10、SpringBoot搭建RMI环境

概述

img

公共工程springboot-rmi-interface

Account.java

java
package com.itheima.bean;

/**
 * @Author 徐柯老师
 * @Description
 * @Tel/微信:15074816437
 * @Version 1.0
 **/
public class Account implements  java.io.Serializable {



    private String username;
    private String password;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "Account{" +
                "username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

接口

java
package com.itheima.rmi;

import com.itheima.bean.Account;

public interface RMIExService {
 
	public String invokingRemoteService();
	public Account getAccount();
}

服务端:

pom.xml

xml
<dependency>
    <groupId>com.itcast</groupId>
    <artifactId>springboot-rmi-interface</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

配置类

java
package com.itheima.springbootrmiserver.rmi;

import com.itheima.rmi.RMIExService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.remoting.rmi.RmiServiceExporter;

@Configuration
public class RMIConfig {
 
	@Autowired
	@Qualifier("rmiExServiceImpl")
	private RMIExServiceImpl serviceImpl;
	/**    
	 * 方法描述:   
	 * 注意事项:    
	 * @return 
	 * @Exception 异常对象 
	*/
	@Bean
	public RmiServiceExporter initRmiServiceExporter(){
		RmiServiceExporter exporter=new RmiServiceExporter();
		exporter.setServiceInterface(RMIExService.class);
		exporter.setServiceName("rmiService");//服务名必须唯一
		exporter.setService(serviceImpl);
		exporter.setServicePort(1099);//监听的端口
		return exporter;
	}
}

实现类

java
package com.itheima.springbootrmiserver.rmi;

import com.itheima.bean.Account;
import com.itheima.rmi.RMIExService;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;

@Service(value="rmiExServiceImpl")
public class RMIExServiceImpl implements RMIExService {
 
	@PostConstruct
	public void initMethod(){
		System.out.println("我是初始化方法时调用的");
	}
	@Override
	public String invokingRemoteService() {
		// TODO Auto-generated method stub
		String result="欢迎你调用远程接口";
		return result;
	}

	@Override
	public Account getAccount() {
		Account account = new Account();
		account.setUsername("keke水电费");
		account.setPassword("12345678");
		return account;
	}

}

客户端

pom.xml

xml
<dependency>
    <groupId>com.itcast</groupId>
    <artifactId>springboot-rmi-interface</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

配置类

java
package com.itcast.springbootrmiclient.rmi;

import com.itheima.rmi.RMIExService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.remoting.rmi.RmiProxyFactoryBean;

@Configuration
public class RMIClientConfig {

	@Bean(name = "rmiService")
	public RmiProxyFactoryBean initRmiProxyFactoryBean() {
		RmiProxyFactoryBean factoryBean = new RmiProxyFactoryBean();
		factoryBean.setServiceUrl("rmi://127.0.0.1:1099/rmiService");
		factoryBean.setServiceInterface(RMIExService.class);
		return factoryBean;
	}


}

实现类

java
package com.itcast.springbootrmiclient.service;

import com.itheima.bean.Account;
import com.itheima.rmi.RMIExService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.remoting.rmi.RmiProxyFactoryBean;
import org.springframework.stereotype.Service;

/**
 * @Author 徐柯老师
 * @Description
 * @Tel/微信:15074816437
 * @Version 1.0
 **/
@Service
public class AccoutService {

    @Autowired
    @Qualifier("rmiService")
    private RmiProxyFactoryBean factoryBean;


    public Account getAccount(){
        RMIExService rmiExService=(RMIExService)factoryBean.getObject();
        return rmiExService.getAccount();
    }


    public String invokingRemoteService(){
        RMIExService rmiExService=(RMIExService)factoryBean.getObject();
        return rmiExService.invokingRemoteService();
    }
}

测试类

java
package com.itcast.springbootrmiclient;

import com.itcast.springbootrmiclient.service.AccoutService;
import com.itheima.bean.Account;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootRmiClientApplicationTests {

    @Autowired
    private AccoutService accoutService;

    @Test
    public void contextLoads() {
        String message = accoutService.invokingRemoteService();
        System.out.println(message);

        Account account = accoutService.getAccount();
        System.out.println(account);
    }

}

11、SOA

概述

SOA本质是一种组件模型。下面看一下百度的定义:

面向服务的架构(SOA)是一个组件模型,它将应用程序的不同功能单元(称为服务)通过这些服务之间定义良好的接口和契约联系起来。接口是采用中立的方式进行定义的,它应该独立于实现服务的硬件平台、操作系统和编程语言(与平台无关,与语言无关,与操作系统无关)。这使得构建在各种各样的系统中的服务可以以一种统一和通用的方式进行交互。

SOA的应用场景: (1)一开始我们的系统可能是这样的:

​ G:/note/备课/分布式通信相关概念(协议、序列化、http、rmi、webservice,JMS,RPC)/笔记/assets/880309-20170514184133566-87049508.png

当我们的项目比较小时,我们只有一个系统,并且把他们写到一起,放在一个服务器上,但是随着平台越来越大,数据量越来越大,我们不得不通过分库,把多个模块的数据库分别放在对应得服务器上,每个模块调用自己的子系统即可。

​ G:/note/备课/分布式通信相关概念(协议、序列化、http、rmi、webservice,JMS,RPC)/笔记/assets/880309-20170514184441129-2088380382.png

随着我们系统的进一步复杂度的提示,我们不得不进一步对系统的性能进行提升,我们将多个模块分成多个子系统,多个子系统直接互相调用(因为SOA一般用于大型项目,比较复杂,所以一般总系统不会再集成,会拆分多个,分别做成服务,相互调用)。当我们的电商UI进行一个下订单的任务时,多个服务直接互相调用,系统通过数据总线,分别调用对于的子系统即可。

企业数据总线:企业数据总线不是对多个子模块的集成,他在这里充当数据通道的作用,数据总线不关心业务,数据总线根据给的地址和协议去调服务,上端不关心服务在哪里是什么,只找数据总线。

上面几个图应该算是比较清楚了,随着业务的深入,我们不得不对系统进行调整,分别是对数据和业务的拆分,最后每个子系统对面提供服务。

SOA主要的使用场景:

G:/note/备课/分布式通信相关概念(协议、序列化、http、rmi、webservice,JMS,RPC)/笔记/assets/880309-20170514183454566-1912534474.png

通过上面的图我们可以看出,多个子系统直接相互交互,相互调用非常凌乱,这样我们就很不爽,所以我们就用到了我们的SOA架构,SOA又叫服务治理,SOA就是帮助我们把服务之间调用的乱七八糟的关系给治理起来,然后提供一个统一的标准,把我们的服务治理成下图所示,以前我们的服务是互相交互,现在是只对数据总线进行交互,这样系统就变得统一起来。

G:/note/备课/分布式通信相关概念(协议、序列化、http、rmi、webservice,JMS,RPC)/笔记/assets/880309-20170514190347066-1095632262.png

统一标准:各系统的协议、地址、交互方式。

新的交互方式:各个系统分别根据统一标准向数据总线进行注册,各子系统调用其他子系统时,我们并不关心如果找到其他子系统,我们只招数据总线,数据总线再根据统一标准找其他子系统,所以数据总线在这里充当一个只路人的作用。

数据总线是什么?

其实我在上面写了,数据总线是起到调度服务的作用,数据总线不是集成服务,数据总线更新一个调度框架,每个服务需要根据约定向数据总线注册服务,那么如何注册那?其实数据总线就像一个字典结构,

数据总线里面一个key对于一个value,key指的是服务名,value则是服务的调度方式,还有一点需要说明的是,数据总线只是指路人,服务是不经过数据总线的,如上图的黄色线的路径。

数据总线通过域名解析实现:一个域名绑定多台服务器,ajax也可以,dns也可以,解析域名嘛。

其实数据总线还有一些高级应用,比如心跳检测,实现负载均衡等等,就不细说了,目前应用数据总线的有阿里的dubbo,还有zookeeper,以及Spring Cloud的Eureka

SOA最显著的优势: (1)SOA具有低耦合性特点,业务伙伴对整个业务系统的影响较低

(2)SOA与平台无关,减少了业务应用实现的限制

更深入理解SOA,请看这篇文章:https://www.cnblogs.com/renzhitian/p/6853289.html

SOA与微服务架构的区别: 从下面一张图基本可以看出微服务架构的区别:

我觉得SOA与微服务的区别在于如下几个方面:

微服务相比于SOA更加精细,微服务更多的以独立的进程的方式存在,互相之间并无影响; 微服务提供的接口方式更加通用化,例如HTTP RESTful方式,各种终端都可以调用,无关语言、平台限制;

微服务更倾向于分布式去中心化的部署方式,在互联网业务场景下更适合;

总结

SOA和微服务架构都是一种组件模型,一种架构方式,不依赖于任何技术,因此SOAP、RPC、REST是对SOA和微服务架构的组件或服务之间通信方式的不同实现。

12、WebService

概述

Web service是一个平台独立的,低耦合的,自包含的、基于可编程的web的应用程序,可使用开放的XML(标准通用标记语言下的一个子集)标准来描述、发布、发现、协调和配置这些应用程序,用于开发分布式的互操作的应用程序。 [1]

Web Service技术, 能使得运行在不同机器上的不同应用无须借助附加的、专门的第三方软件或硬件, 就可相互交换数据或集成。依据Web Service规范实施的应用之间, 无论它们所使用的语言、 平台或内部协议是什么, 都可以相互交换数据。Web Service是自描述、 自包含的可用网络模块, 可以执行具体的业务功能。Web Service也很容易部署, 因为它们基于一些常规的产业标准以及已有的一些技术,诸如标准通用标记语言下的子集XML、HTTP。Web Service减少了应用接口的花费。Web Service为整个企业甚至多个组织之间的业务流程的集成提供了一个通用机制。

所以WebService是一种技术,比如可以让使用.NET开发的应用程序调用使用Java开发的应用程序的接口,互相交换数据或集成,或者反过来。

所以只要是能够平台独立,无关语言,无关操作系统,基于XML作为数据交换格式的应用程序都可以叫做Web Service。

要实现Web Service,需要一套协议来支持(WebService三要素:SOAP、WSDL、UDDI):

(1) SOAP: SOAP(Simple Object Access Protocol:简单对象访问协议)是微软、IBM等大公司联合制定的一个协议规范。SOAP是交换数据的一种协议规范,是一种轻量的、简单的、基于XML(标准通用标记语言下的一个子集)的协议,它被设计成在WEB上交换结构化的和固化的信息。

(2)WSDL Web Service描述语言WSDL,用于描述Web Service及其函数、参数和返回值(也就是描述如何访问具体的接口)。因为是基于XML的,所以WSDL既是机器可阅读的,又是人可阅读的。

(3)UDDI

UDDI 的目的是为电子商务建立标准;UDDI是一套基于Web的、分布式的、为Web Service提供的、信息注册中心的实现标准规范,同时也包含一组使企业能将自身提供的Web Service注册,以使别的企业能够发现的访问协议的实现标准。(简单一句话概括就是:用来管理,分发,查询webService)

13、JMS

概述
  • JMS:Java 消息服务(Java Messaging Service) 是一种允许应用程序创建、发送、接受和读取消息的Java API。JMS 在其中扮演的角色与JDBC 很相似,正如 JDBC 提供了一套用于访问各种不同关系数据库的公共APIJMS 也提供了独立于特定厂商的企业消息系统访问方式
  • 使用JMS 的应用程序被称为JMS客户端,处理消息路由与传递的消息系统被称为 JMS Provider,而JMS 应用则是由多个JMS 客户端和一个 JMS Provider 构成的业务系统。发送消息的JMS 客户端被称为生产者(producer),而接收消息的JMS 客户端则被称为消费者(consumer)。同一JMS 客户端既可以是生产者也可以是消费者。
  • JMS 的编程过程很简单,概括为:应用程序A 发送一条消息到消息服务器(也就是JMS Provider)的某个目的地(Destination),然后消息服务器把消息转发给应用程序B。因为应用程序A 和应用程序B 没有直接的代码关连,所以两者实现了解偶
  • 典型实现:ActiveMQ
步骤
RMI和JMS的区别:
  • 1、传输方式上
    • JMS 与 RMI 的区别在于:采用 JMS 服务,对象是在物理上被异步从网络的某个 JVM 上直接移动到另一个 JVM 上。
    • RMI 对象是绑定在本地 JVM 中,只有函数参数和返回值是通过网络传送的。
  • 2、方法调用上
    • RMI 一般都是同步的,也就是说,当client端调用Server端的一个方法的时候,需要等到对方的返回,才能继续执行client端,这个过程跟调用本地方法感觉上是一样的,这也是RMI的一个特点。
    • JMS 一般只是一个点发出一个Message到Message Server端,发出之后一般不会关心谁用了这个message。
    • 一般RMI的应用是紧耦合,JMS的应用相对来说是松散耦合的应用。

14、RPC

概述

简单来说:SOAP=HTTP+XML,HTTP是基于TCP的超文本传送协议。

RPC(remote procedure call:远程过程调用):简单的说,RPC就是从一台机器(客户端)上通过参数传递的方式调用另一台机器(服务器)上的一个函数或方法(可以统称为服务)并得到返回的结果。

RPC 会隐藏底层的通讯细节(不需要直接处理Socket通讯或Http通讯) RPC 是一个请求响应模型。客户端发起请求,服务器返回响应(类似于Http的工作方式) RPC 在使用形式上像调用本地函数(或方法)一样去调用远程的函数(或方法) RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。 RPC采用客户机/服务器模式。请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户端调用进程接收答复信息,获得进程结果,然后调用执行继续进行。

RPC工作原理: 运行时,一次客户机对服务器的RPC调用,其内部操作大致有如下十步:

RPCæ¡æ¶

  • RPC(Remote Procedure Call Protocol)远程过程调用协议,通过网络从远程计算机上请求调用某种服务。它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。 使用代表:Dubbo
  • 一次RPC调用的过程大概有10步:
    • 1、执行客户端调用语句,传送参数
    • 2、调用本地系统发送网络消息
    • 3、消息传送到远程主机
    • 4、服务器得到消息并取得参数
    • 5、根据调用请求以及参数执行远程过程(服务)
    • 6、执行过程完毕,将结果返回服务器句柄
    • 7、服务器句柄返回结果,调用远程主机的系统网络服务发送结果
    • 8、消息传回本地主机
    • 9、客户端句柄由本地主机的网络服务接收消息
    • 10、客户端接收到调用语句返回的结果数据

JAVA能够使用的远程调用技术:

远程方法调用(Remote Method Invocation,RMI)

Caucho的Hession和Burlap(Hession是二进制协议,而Burlap是基于XML的)

Spring基于HTTP的远程服务HttpInvoker

使用JAX-RPC和JAX-WS的Web Service(基于SOAP的web服务)

注意:RPC都是同步返回技术,也就是说程序会一直等待,直到超时或者得到返回结果。JMS(具体实现有ActiveMQ等),AMQP(具体实现有RabbitMQ等)才是异步返回技术

不管我们使用哪种远程调用技术,Spring为使用这几种不同的技术访问和创建远程服务都提供了广泛的支持。

img

15、RMI与RPC的区别

区别
  • 1、方法调用方式不同:
    • RMI调用方法,RMI中是通过在客户端的Stub对象作为远程接口进行远程方法的调用。每个远程方法都具有方法签名。如果一个方法在服务器上执行,但是没有相匹配的签名被添加到这个远程接口(stub)上,那么这个新方法就不能被RMI客户方所调用。
    • RPC调用函数,RPC中是通过网络服务协议向远程主机发送请求,请求包含了一个参数集和一个文本值,通常形成**“classname.methodname(参数集)”**的形式。这就向RPC服务器表明,被请求的方法在“classname”的类中,名叫“methodname”。然后RPC服务器就去搜索与之相匹配的类和方法,并把它作为那种方法参数类型的输入。这里的参数类型是与RPC请求中的类型是匹配的。一旦匹配成功,这个方法就被调用了,其结果被编码后通过网络协议发回。
  • 2、适用语言范围不同:
    • RMI只用于Java,支持传输对象。
    • RPC是基于C语言的,不支持传输对象,是网络服务协议,与操作系统和语言无关。
  • 3、调用结果的返回形式不同:
    • RMI是面向对象的,Java是面向对象的,所以RMI的调用结果可以是对象类型或者基本数据类型。
    • RPC的结果统一由外部数据表示(External Data Representation,XDR)语言表示,这种语言抽象了字节序类和数据类型结构之间的差异。只有由XDR定义的数据类型才能被传递,可以说RMI是面向对象方式的Java RPC。

16、Rest

概述

REST全称:Representational State Transfer。Rest不是协议也不是规范,而是一种接口、服务、系统之间通讯的风格。

REST可以用来替代传统的SOAP Web服务。SOAP一般会关注行为和处理(比如RMI,Hessian,Spring的HttpInvoker,jaw-xs,知名的XFile(新的如CXF)、Axis1、Axis2 等等),而REST关注的是要处理的数据。

REST与RPC几乎没有任何关系。RPC是面向服务的,并关注于行为和动作;而REST是面向资源的,强调描述应用程序的事物和名词。REST就是将资源的状态以最适合客户端或服务端的形式从服务器端转移到客户端(或者反过来)。

RESTful : 简称 REST,是描述了一个架构样式的网络系统,其核心是面向资源,REST专门针对网络应用设计和开发方式,以降低开发的复杂性,提高系统的可伸缩性。REST提出设计概念和准则为:

1.网络上的所有事物都可以被抽象为资源(resource)

2.每一个资源都有唯一的资源标识(resource identifier),对资源的操作不会改变这些标识

3.所有的操作都是无状态的

关于RPC和RMI的区别,各类博客有很多,我就不说了。WebserviceRESTful ,我不知道你说的是那个(Webservice这个是个大类,包括RESTful )你可以看看 SOAP Webservice和RESTful Webservice 的区别。不要刻意去混淆找区别,有些是从不同角度、层次而言。也有可能同一个东西兼顾(不同层次、角度的兼顾)。在如今这个软件泛滥的年代,不同的人叫法不同很多,偷换概念的也有很多。