RPC 基本概念

RPC 协议(Remote Procedure Call Protocol)

RPC 远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。在 OSI 网络通信模型中,RPC 跨越了传输层和应用层,使得开发包括网络分布式多程序在内的应用程序更加容易。

客户机(客户端)-服务器模式:当请求没有到达服务端,服务端(全天候)处于休眠状态,当请求到达时,服务端程序会被唤醒,处理客户端请求(计算,图形处理 等),将结果响应给客户端(RPC Socket)。

OSI 七层网络模型(从上至下):

应用层:Http (Https),ftp,smtp,pop3
表示层
会话层
传输层:TCP|UDP
网络层
数据链路层
物理层​

RPC 框架

​ IPC:单机中运行的进程之间的相互通信。

​ RPC:可以在同一台电脑上不同进程进行,也可以在不同电脑上进行。

​ LPC:在windows 里面同一台电脑上不同进程间的通讯还可以采用 LPC(本地访问)。

​ 综上:RPC 或 LPC是上层建筑,IPC 是底层基础。

RPC 与 HTTP、TCP、UDP、Socket 的区别

TCP/UDP:都是传输协议,主要区别是 TCP 协议连接需要 3 次握手,断开需要四次挥手,
是通过流来传输的,就是确定连接后,一直发送信息,传完后断开。udp 不需要进行连接,
直接把信息封装成多个报文,直接发送。所以 UDP 的速度更快写,但是不保证数据的完整
性。

HTTP:超文本传输协议,是一种应用层协议,建立在 TCP 协议之上。

Socket:是在应用程序层面上对 TCP/IP 协议的封装和应用。其实是一个调用接口,方便
程序员使用 TCP/IP 协议栈而已。程序员通过 Socket 来使用 TCP/IP 协议。但是 Socket 并不是
一定要使用 TCP/IP 协议,Socket 编程接口在设计的时候,就希望也能适应其他的网络协议。

RPC: 是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。

所以 RPC 的实现可以通过不同的协议去实现,比如可以使 HTTP、RMI 等。

RPC 的内部运行

**(1)网络通讯问题:**Socket

**(2)网络寻址问题:**本地存根(字典)

**(3)数据序列化:**A 服务器通过寻址和传输将序列化的二进制发送给 B 服务器。

**(4)数据反序列化:**B 服务器收到请求后,需要对参数进行反序列化(序列化的逆操作),恢复为内存中的表达方式,然后找到对应的方法(寻址的一部分)进行本地调用,然后得到返回值。

**(5)再次序列化和反序列化:**返回值还要发送回服务器 A 上的应用,也要经过序列化的方式发送,服务器 A 接到后,再反序列化,恢复为内存中的表达方式,交给 A 服务器上的应用。

RPC 基于 RMI 的简单实现

第一步:定义一个接口,必须继承 remote

public interface IUserService extends Remote{
public User queryUserByUserId(Integer userId) throws Exception;
}

第二步:编写一个实现类即服务端,继承 UnicastRemoteObject 实现 IUserService接口

public class UserServiceImpl extends UnicastRemoteObject implements IUserService{
private Map<Integer, User> users;
public UserServiceImpl() throws Exception{
users = new HashMap<>();
users.put(1, new User(1, "admin", "上海大厦"));
}

@Override
public User queryUserByUserId(Integer userId) throws Exception {
return users.get(userId);
}
}

第三步:发布服务

public class Publisher {
public static void main(String[] args) throws Exception{
LocateRegistry.createRegistry(8989);
// 发布服务
Naming.bind("rmi://127.0.0.1:8989/userService",new UserServiceImpl());
System.out.println("服务发布成功!");
}
}

第四步:客户端调用

public class Test {
public static void main(String[] args) throws Exception{
IUserService userService = (IUserService) Naming.lookup("rmi://127.0.0.1:8989/userService");
System.out.println(userService.queryUserByUserId(1));
}
}

实现效果:

  1. 服务端:

  2. 客户端

Dubbo

Dubbo 框架基本概念

​ Dubbo 是一款高性能、轻量级的开源 Java RPC 框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。

结构流程

1. 服务提供方(Provider):

​ 环境:Spring 环境
​ 应用启动时:服务初始化,服务注册(将服务信息写入到远程注册中心:ip:port、项目名/uri?属性参数配置&)

2. 注册中心(Registry):

​ 第三方软件实现(zookeeper) 一种树形的目录服务。(练习中可使用广播address=“multicast://224.5.6.7:1234”)

​ 2.1: 保存服务提供方提供的服务信息

​ 2.2: 支持服务变更推送

**3. 服务消费方(Consumer) **

​ 环境:Spring 环境
​ 应用启动时:订阅注册中西提供的服务列表信息,执行远程服务调用(RPC)。

4. 监控中心(Monitor)

​ 负责项目运行信息的监控操作(消费方运行信息 提供方运行信息)。

Dubbo 入门环境配置

dubbo_par 父工程(pom)

dubbo_api 服务api定义模块(普通工程)

dubbo_provider 服务提供方(普通)

dubbo_consumer 服务消费方(普通)

Dubbo 常用标签配置

dubbo:application :应用名称配置(与项目名一致) (提供方与消费方)

<!-- 配置应用名称 -->
<dubbo:application name="dubbo_provider"/>

dubbo:registry 注册中心配置 可以配置多个 一般情况配置一个 通常使用zookeeper 必须配置(提供方和消费方)

<!-- 配置注册中心 -->
<dubbo:registry address="multicast://224.5.6.7:1234"/>

dubbo:protocol 服务注册使用协议 官方建议 dubbo 默认端口:20880 (提供方)

<!-- 服务注册使用协议(提供方) -->
<dubbo:protocol name="dubbo" port="20880"/>

dubbo:service 注册服务 配置 (提供方)

<!-- 配置注册服务(提供方) -->
<dubbo:service interface="com.shsxt.service.IUserService" ref="userServiceImpl"/>

dubbo:reference 服务订阅 (消费方)

<!-- 配置订阅的服务(消费方) -->
<dubbo:reference interface="com.shsxt.service.IUserService" id="userService"/>

RPC 基于 Dubbo 的简单实现

一、配置结构

二、导包 Dubbo 坐标

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.5.6</version>
</dependency>

三、定义服务接口

/**
* 远程定义服务:IUserService
*/
public interface IUserService {
public User queryUserByUserId(Integer userId);
}

四、服务实现

/**
* 服务实现类 UserServiceImpl
*/
@Service
public class UserServiceImpl implements IUserService{
private Map<Integer,User> users;

public UserServiceImpl() {
users = new HashMap<>();
users.put(1,new User(1,"admin","绿地伯顿大厦"));
}

@Override
public User queryUserByUserId(Integer userId) {
System.out.println("客户端调用参数-->"+userId);
return users.get(userId);
}
}

五、配置生产者

​ 添加 dubbo_provider.xml 文件到 resources 文件下

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">

<!-- 配置扫描器 -->
<context:component-scan base-package="com.shsxt.service"/>

<!-- 配置应用名称 -->
<dubbo:application name="dubbo_provider"/>

<!-- 配置注册中心 -->
<dubbo:registry address="multicast://224.5.6.7:1234"/>

<!-- 服务注册使用协议(提供方) -->
<dubbo:protocol name="dubbo" port="20880"/>

<!-- 配置注册服务(提供方) -->
<dubbo:service interface="com.shsxt.service.IUserService" ref="userServiceImpl"/>

</beans>

六、启动服务提供程序

/**
* 发布服务请求
*/
public class Publisher {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("dubbo_provider.xml");
context.start();
System.in.read();
}
}

七、配置服务的消费端

添加 dubbo_consumer.xml 文件到 resources 文件下

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">

<!-- 配置扫描器 -->
<context:component-scan base-package="com.shsxt.controller"/>

<!-- 配置应用名称 -->
<dubbo:application name="dubbo_consumer"/>

<!-- 配置注册中心 -->
<dubbo:registry address="multicast://224.5.6.7:1234"/>

<!-- 配置订阅的服务(消费方) -->
<dubbo:reference interface="com.shsxt.service.IUserService" id="userService"/>

</beans>

八、调用服务的方法

/**
* 调用服务的方法
*/
@Controller
public class UserController {
@Autowired
private IUserService userService;

public User queryUserByUserId(Integer userId){
return userService.queryUserByUserId(userId);
}
}

九、启动服务消费端进行消费

/**
* 接收请求返回数据
*/
public class Test {
public static void main(String[] args){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("dubbo_consumer.xml");
UserController userController = (UserController) context.getBean("userController");
System.out.println(userController.queryUserByUserId(1));
}
}

实现效果

  1. 服务端

  2. 客户端

  3. 因为启用客户端调用了controller层的方法,传入的id客户端就可以拿到了