一、分布式基础
1.1 Java 中 RPC 分布式范例: RMI
1.1.1 分布式的定义和解决的问题
分布式的定义及解决的问题:
- 业务服务器接收到用户的请求根据特定的算法,将计算的不同部分交由不同主机处理,待结果汇总后,由业务服务器反馈给客户端
- 简单来讲,微服务就是将一个项目的功能板块进行了一个拆分工作,把原本一台服务器上的功能分发给不同的服务器来专门处理一个功能业务
- 问题在于如何做到不同服务之间的通信
- 将网络通讯和并发控制对程序开发人员透明化
解决方案,RMI 就是这样的一个范例。
我们的项目是部署在服务器上的,我们想要在远程像本地一样 如何调用一个服务方法,并且得到计算的响应结果。这是如何实现的呢?
这其实就用到 Java SE 中的 网络套接字编程· ,以及数据序列化实现的信息传输,因为这些是 RPC 的基础
1.1.2 什么是 RPC
RPC (Remote Procedure Call Protocol) 远程过程调用
- 两台服务器A、B,分别部署不同的应用a,b。当A服务器想要调用B服务器上应用b提供的函数或方法的时候,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义传达调用的数据。
- 即在远程端的机器上写了一个程序,客户端这边是无法直接调用的,这个时候就出现了一个远程服务调用的概念。
RPC是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。RPC采用客户端/服务器模式。请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户端调用进程接收答复信息,获得进程结果,然后调用执行继续进行。
1.2 RPC 需要解决的问题
1.2.1 通讯问题
主要是通过在客户端和服务器之间建立TCP连接,远程过程调用的所有交换的数据都在这个连接里传输。连接可以是按需连接,调用结束后就断掉,也可以是长连接,多个远程过程调用共享同一个连接。
1.2.2 寻址问题:
A 服务器上的应用怎么告诉底层的RPC 框架,如何连接到B 服务器 (如主机 或 IP 地址)以及特定端口,方法的名称是什么。这样才能完成调用,比如基于 Web 服务协议栈RPC,就要 endpoint URI,或者是从UDP 服务上查找。如果是RMI 调用的话,还需要一个RMI Registry来注册服务的地址。
1.2.3 序列化和反序列化
当 A 服务器上的应用发起远程过程调用时。方法参数需要通过底层的网络协议如 TCP 传递到B服务器,由于网络协议是基于二进制的,内存中的参数的值要序列化成二进制的形式,也就是序列化或编组,通过寻址和传输将序列化的二进制发送给B服务器。同理,B服务器接收参数要将参数反序列化。B服务器应用调用自己的方法处理后返回的结果也要序列化给A服务器,A服务器接收也要经过反序列化的过程。
1.3 实现 RMI 的 RPC 分布式的基本原理
1.3.1 什么是 RMI?
- 远程方法调用 (Remote Method Invocation)。能够让在某个Java虚拟机上的对象像调用本地对象一样调用另一个Java 虚拟机中的对象上的方法
- 客户对象调用客户端辅助对象上的方法;
- 客户端辅助对象打包调用信息(变量,方法名),通过网络发送给服务端辅助对象;
- 服务端辅助对象将客户端辅助对象发送来的信息解包,找出真正被调用的方法以及该方法所在对象;
- 调用真正服务对象上的真正方法,并将结果返回给服务端辅助对象;
- 服务端辅助对象将结果打包,发送给客户端辅助对象;
- 客户端辅助对象将返回值解包,返回给客户对象;
- 客户对象获得返回值。
对于客户端来说, 2-6 是透明的,开发人员无需关注网络数据传输处理
1.4 RMI 代码示例
这里我们需要模拟服务提供端(Provider) 和 服务调用端(客户端)
以下代码在 Java SE 阶段也可以使用
服务远程端,也就是提供服务的一端,客户端会通过网络套接字编程调用服务端的方法
编写服务提供的接口:
package cn.gorit.service;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface ProductService extends Remote {
public String showAllProduct(String msg) throws RemoteException;
}
服务的实现类
package cn.gorit.service.impl;
import cn.gorit.service.ProductService;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class ProductServiceImpl extends UnicastRemoteObject implements ProductService {
public ProductServiceImpl() throws RemoteException {
}
@Override
public String showAllProduct(String msg) throws RemoteException{
System.out.println("我是服务端:正在执行实现展示所有数据的业务");
System.out.println("我接收的参数:"+msg);
return "I Got It" + msg;
}
}
提供服务的 Server 端
package cn.gorit.test.server;
import cn.gorit.service.ProductService;
import cn.gorit.service.impl.ProductServiceImpl;
import java.rmi.Naming;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
public class Test {
public static void main(String[] args) throws RemoteException {
ProductService ps = new ProductServiceImpl();
// 开放端口
LocateRegistry.createRegistry(9527);
try {
Naming.bind("rmi://127.0.0.1:9527/ps", (Remote) ps);
} catch (Exception e) {
e.printStackTrace();
}
}
}
调用服务的客户端 (Client)
package cn.gorit.test.client;
import cn.gorit.service.ProductService;
import java.rmi.Naming;
public class Test {
public static void main(String[] args) {
try {
ProductService ps = (ProductService) Naming.lookup("rmi://127.0.0.1:9527/ps");
String str = ps.showAllProduct("hahah");
System.out.println("这里是客户端:我接收到的消息"+str);
} catch (Exception e) {
e.printStackTrace();
}
}
}
1.5 使用 RMI 的一些限制
- 我们在自己的代码要使用 RMI 的话,需要加上过的指定 API。对大妈的侵入式比较高
- 对 非 Java 语言开发的项目侵入式较高
- RMI 使用了序列化的方式传输数据,但是对于需要加密的项目,则无法达到期望
二、Dubbo 入门
2.1 dubbo 是什么?
- 一款 分布式服务框架
- 高性能 和 透明化的 RPC远程服务调用方案
- SOA 服务治理方案
2.2 dubble 的架构
每一步的调用流程
- .服务容器负责启动,加载,运行服务提供者。
- 服务提供者在启动时,向注册中心注册自己提供的服务。
- 服务消费者在启动时,向注册中心订阅自己所需的服务。
- 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
- 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
- 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心
三、SpringBoot