RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。比如两个不同的服务 A、B 部署在两台不同的机器上,那么服务 A 如果想要调用服务 B 中的某个方法该怎么办呢?使用 HTTP请求 当然可以,但是可能会比较慢而且一些优化做的并不好。
最终解决的问题:**让分布式或者微服务系统中不同服务之间的调用像本地调用一样简单。**是一种通过网络从远程计算机程序上请求服务
// Client端
// Student student = Call(ServerAddr, addAge, student)
1. 将这个调用映射为Call ID。
2. 将Call ID,student(params)序列化,以二进制形式打包
3. 把2中得到的数据包发送给ServerAddr,这需要使用网络传输层
4. 等待服务器返回结果
5. 如果服务器调用成功,那么就将结果反序列化,并赋给student,年龄更新
// Server端
1. 在本地维护一个Call ID到函数指针的映射call_id_map,可以用Map<String, Method> callIdMap
2. 等待客户端请求
3. 得到一个请求后,将其数据包反序列化,得到Call ID
4. 通过在callIdMap中查找,得到相应的函数指针
5. 将student(params)反序列化后,在本地调用addAge()函数,得到结果
6. 将student结果序列化后通过网络返回给Client
服务端通过字符串解析出该字符串代表的接口的一切信息,需要用到反射技术
客户端和服务端之间进行通信调用,需要用到socket技术
服务端根据客户端不同请求返回不同的接口类型,此时客户端就要接收不同的接口类型,需要在客户端用到动态代理技术。
基于动态代理生成代理对象,当调用代理对象的方法时,由代理进行相关信息(方法、参数等)的组装并发送到服务器进行远程调用,并由代理接收调用结果并返回
使用 JDK 动态代理类基本步骤:
1、编写需要被代理的类和接口
2、编写代理类,需要实现 InvocationHandler
接口,重写 invoke()
方法;
3、使用Proxy.newProxyInstance(ClassLoader loader, Class>[] interfaces, InvocationHandler h)
动态创建代理类对象,通过代理类对象调用业务方法。
interface DemoInterface {
String hello(String msg);
}
class DemoImpl implements DemoInterface {
@Override
public String hello(String msg) {
System.out.println("msg = " + msg);
return "hello";
}
}
class DemoProxy implements InvocationHandler {
private DemoInterface service;
public DemoProxy(DemoInterface service) {
this.service = service;
}
@Override
public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
System.out.println("调用方法前...");
Object returnValue = method.invoke(service, args);
System.out.println("调用方法后...");
return returnValue;
}
}
public class Solution {
public static void main(String[] args) {
DemoProxy proxy = new DemoProxy(new DemoImpl());
DemoInterface service = Proxy.newInstance(
DemoInterface.class.getClassLoader(),
new Class<?>[]{DemoInterface.class},
proxy
);
System.out.println(service.hello("呀哈喽!"));
}
}
RPC 是一种设计,就是为了解决不同服务之间的调用问题,完整的 RPC 实现一般会包含有 传输协议 和 序列化协议 这两个。
而 HTTP 是一种传输协议,RPC 框架完全可以使用 HTTP 作为传输协议,也可以直接使用 TCP,使用不同的协议一般也是为了适应不同的场景。
使用 TCP 和使用 HTTP 各有优势:
传输效率:
性能消耗,主要在于序列化和反序列化的耗时
跨平台:
总结:
RPC 的 TCP 方式主要用于公司内部的服务调用,性能消耗低,传输效率高。HTTP主要用于对外的异构环境,浏览器接口调用,APP接口调用,第三方接口调用等。
Dubbo是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
Remoting: 网络通信框架,实现了 sync-over-async 和request-response 消息机制.
RPC: 一个远程过程调用的抽象,支持负载均衡、容灾和集群功能
Registry: 服务目录框架用于服务的注册和服务事件发布和订阅
调用关系说明
Container负责启动,加载,运行服务提供者。
Provider在启动时,向注册中心注册自己提供的服务。
Consumer在启动时,向注册中心订阅自己所需的服务。
Register返回Provider地址列表给Consumer,如果有变更,注册中心将基于长连接推送变更数据给消费者。
Consumer从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
要启用服务发现,需要为 Dubbo 增加注册中心配置:在启动服务提供者项目之前要先启动zookeeper,因为我们需要把服务提供方注册到zookeeper中,然后才能被服务消费方发现并调用。
① 有关项目构建的相关内容
<groupId>com.test.gmallgroupId> 表示项目源码中java目录下的包结构
<artifactId>gmall-interfaceartifactId> 表示项目的名称
<version>1.0-SNAPSHOTversion> 版本号
② 服务化最佳实践
分包:建议将服务接口、服务模型、服务异常等均放在 API 包中,因为服务模型和异常也是 API 的一部分,这样做也符合分包原则:重用发布等价原则(REP),共同重用原则(CRP)。
服务接口建议以业务场景为单位划分,并对相近业务做抽象,防止接口数量爆炸。
每个服务方法应代表一个功能,而不是某功能的一个步骤,否则将面临分布式事务问题
http://localhost:8080
详情参考:dubbo admin
将公共的服务接口放在同一个工程gmall-interface
下,将这个项目进行打包成jar包,并加入到本地maven仓库中
IDEA在本项目中 打开Terminal中输入 mvn package,将项目打包
将打包的jar包导入本地Maven仓库中,里面的Id分别对应pom.xml中的Id
在要导入的项目中执行,不是在打包的项目中执行。参考:如何在IDEA Maven项目中导入本地jar包的步骤
mvn install:install-file -DgroupId=com.test.gmall -DartifactId=gmall-interface -Dversion=1.0.0 -Dpackaging=jar -Dfile=D:\MyApp\JavaProject\gmall-interface\target\gmall-interface-1.0.0.jar
public class UserAddress {
private Integer id;
private String userAddress;
private String userId;
private String consignee;//收货人
private String phoneNum;
private String isDefault;
}
//
public interface OrderService {
void initOrder(String userId);
}
//服务提供方,获取用户地址
public interface UserService {
List<UserAddress> getUserAddressList(String userId);
}
服务的提供方是UserService,提供用户的·收货地址
public class UserServiceImpl implements UserService {
public List<UserAddress> getUserAddressList(String s) {
UserAddress userAddress1 = new UserAddress(1,"广东省","001111","Dim","12345678"
,"null");
UserAddress userAddress2 = new UserAddress(2,"广西省","001111","Tom","12345612"
,"null");
return Arrays.asList(userAddress1,userAddress2);
}
}
1、将服务提供者注册到注册中心(暴露服务)
导入dubbo依赖,操作zookeeper的·客户端(curator)
<dependencies>
<dependency>
<groupId>com.test.gmallgroupId>
<artifactId>gmall-interfaceartifactId>
<version>1.0.0version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>dubboartifactId>
<version>2.6.2version>
dependency>
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-frameworkartifactId>
<version>2.12.0version>
dependency>
dependencies>
2、配置服务方,用 Spring 配置声明暴露服务
其实我们不需要注册中心也可以让消费者直接连接服务者(注册中心相当于中介),此时需要制定消费者和服务者之间的通信协议,也就是
这句话
<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"
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">
<dubbo:application name="gmall-Provider">dubbo:application>
<dubbo:registry address="zookeepe://127.0.0.1:2181" protocol="zookeeper" timeout="20000" />
<dubbo:protocol name="dubbo" port="20880"/>
<dubbo:service interface="com.test.gmall.service.UserService" ref="userService"/>
<bean name="userService" class="com.test.gmall.service.Impl.UserServiceImpl">bean>
beans>
3、测试是否已经将服务注册到了zookeeper中
开启zookeeper服务,注意!
zkService.cmd
zkcli.cmd
运行dubbo.admin,打开的cmd不要关闭
新建测试类,运行,此时如果在dubbo.admin中能够看到服务信息,即表示注册服务成功
public class Provider {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Provider.xml");
context.start();//启动服务
System.in.read();//阻塞
}
}
gmall-Consumer工程中创建OrderService的实现类,里面远程调用到了UserService.getUserList方法
/**
* @author:liuliping * @date:2021/7/7 11:28
*///注意此时使用的是dubbo的service注解
@Servicepublic
class OrderServiceImpl implements OrderService {
@Autowired
UserService userService;
public void initOrder(String userId) {
List<UserAddress> userList = userService.getUserAddressList(userId);
for (UserAddress user : userList) {
System.out.println(user.getUserAddress() + user.getUserId());
}
}
}
配置消费者从注册中心订阅服务
1、导入依赖
<dependencies>
<dependency>
<groupId>com.test.gmallgroupId>
<artifactId>gmall-interfaceartifactId>
<version>1.0.0version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>dubboartifactId>
<version>2.6.2version>
dependency>
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-frameworkartifactId>
<version>2.12.0version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-simpleartifactId>
<version>1.7.25version>
<scope>compilescope>
dependency>
dependencies>
2、配置consumer.xml设置
<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-4.2.xsd">
<context:component-scan base-package="com.test.gmall.service.Impl">context:component-scan>
<dubbo:application name="gmall-Consumer">dubbo:application>
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
<dubbo:reference interface="com.test.gmall.service.UserService" id="userService">dubbo:reference>
<bean name="orderService" class="com.test.gmall.service.Impl.OrderServiceImpl">bean>
3、创建测试类
public class Consumer {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Consumer.xml");
OrderService orderService = context.getBean(OrderService.class); //实现输出结果,说明远程调用实现
orderService.initOrder("1");
System.out.println("调用完成.....");
System.in.read();
}
}