1.单体架构和分布架构对比?
- 1.单体架构
- 创建接口oper,然后接口实现operImp
- new operImp().run()!(同一块内存上面,可以直接调用内存中的方法)
- 完美实现本地方法调用!
- 2.分布架构:
- 服务分别放置在不同的服务器上(不是同一块内存地址)
- 如果服务A想调用服务B的new operImp().run()方法,不可能new operImp(),因为服务A根本没有operImp()的接口实现
- 怎么办?搞一个WebService可以吗?搞一个Restful接口共外部调用可以吗?--都可以
- 那为什么还要搞一个RPC?
2.RPC是什么鬼?
“RPC,就是Remote Procedure Call的简称呀,翻译成中文就是远程过程调用!”
1.解决分布式系统中,服务之间的调用问题。
2.远程调用时,要能够像本地调用一样方便,让调用者感知不到远程调用的逻辑。
3.RPC大致是什么样的运行机制?
那么如何实现远程过程调用,也就是RPC呢,一个完整的RPC流程,可以用下面这张图来描述其中左边的Client,对应的就是前面的Service A,而右边的Server,对应的则是Service B。
下面一步一步详细解释一下。
- Service A的应用层代码中,调用了Calculator的一个实现类的add方法,希望执行一个加法运算;
这个Calculator实现类,内部并不是直接实现计算器的加减乘除逻辑,而是通过远程调用Service B的RPC接口,来获取运算结果,因此称之为Stub; - Stub怎么和Service B建立远程通讯呢?这时候就要用到远程通讯工具了,也就是图中的Run-time Library,这个工具将帮你实现远程通讯的功能,比如Java的Socket,就是这样一个库,当然,你也可以用基于Http协议的HttpClient,或者其他通讯工具类,都可以,RPC并没有规定说你要用何种协议进行通讯;
- Stub通过调用通讯工具提供的方法,和Service B建立起了通讯,然后将请求数据发给Service B。需要注意的是,由于底层的网络通讯是基于二进制格式的,因此这里Stub传给通讯工具类的数据也必须是二进制,比如calculator.add(1,2),你必须把参数值1和2放到一个Request对象里头(这个Request对象当然不只这些信息,还包括要调用哪个服务的哪个RPC接口等其他信息),然后序列化为二进制,再传给通讯工具类,这一点也将在下面的代码实现中体现;
二进制的数据传到Service B这一边了,Service B当然也有自己的通讯工具,通过这个通讯工具接收二进制的请求; - 既然数据是二进制的,那么自然要进行反序列化了,将二进制的数据反序列化为请求对象,然后将这个请求对象交给Service B的Stub处理;
- 和之前的Service A的Stub一样,这里的Stub也同样是个“假玩意”,它所负责的,只是去解析请求对象,知道调用方要调的是哪个RPC接口,传进来的参数又是什么,然后再把这些参数传给对应的RPC接口,也就是Calculator的实际实现类去执行。很明显,如果是Java,那这里肯定用到了反射。
RPC接口执行完毕,返回执行结果,现在轮到Service B要把数据发给Service A了,怎么发?一样的道理,一样的流程,只是现在Service B变成了Client,Service A变成了Server而已:Service B反序列化执行结果->传输给Service A->Service A反序列化执行结果 -> 将结果返回给Application,完毕。
理论的讲完了,是时候把理论变成实践了。
4.如何实现一个RPC
核心内容解说:
- 服务提供端
- 1.创建接口
- 2.用Socket进行监听请求,并判断请求的方法名,将结果传回Socket返回值中
- 服务消费端
- 1.获取Socket
- 2.从Socket返回值中将数据取出来
- 最本质东西
- 请求时:将 "方法名+参数" 序列化,Socket通信传递
- 接受时:将 "方法名+参数" 反序列化,然后进行还原方法的真实调用
服务提供者核心代码
public class Provider {
public static void main(String[] args) throws Exception {
// 将服务提供出去,使用Socket进行模拟
new Provider().privoderServer();
}
private void privoderServer() throws Exception{
// 创建Socket监听器
ServerSocket listener = new ServerSocket(CalculateRpcRequest.PORT);
Calculator calculator = new CalculatorImpl();
System.out.println("----------成功将服务发布");
try {
// 然后执行监听外界的请求
while (true) {
Socket socket = listener.accept();
// 将请求反序列化
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
Object object = objectInputStream.readObject();
// 调用服务
int result = 0;
if (object instanceof CalculateRpcRequest) {
CalculateRpcRequest calculateRpcRequest = (CalculateRpcRequest) object;
if ("add".equals(calculateRpcRequest.getMethod())) {
result = calculator.add(calculateRpcRequest.getA(), calculateRpcRequest.getB());
} else {
throw new UnsupportedOperationException();
}
}
// 返回结果
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
objectOutputStream.writeObject(new Integer(result));
}
} catch (Exception e) {
} finally {
listener.close();
}
}
}
服务消费者核心代码
public class CalculatorRemoteImpl implements Calculator {
public int add(int a, int b) {
String address = "127.0.0.1";// 因为现在是一个服务,所以地址是写死的,以后用负载的话,对比Eureka,根据服务名,去找对应的端口,然后进行负载
Socket socket = null;
try {
socket = new Socket(address, CalculateRpcRequest.PORT);
// 将请求序列化
CalculateRpcRequest calculateRpcRequest = new CalculateRpcRequest("add", 1, 2);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
// 将请求发给服务提供方
objectOutputStream.writeObject(calculateRpcRequest);
// 将响应体反序列化
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
Object response = objectInputStream.readObject();
System.out.println("消费者进行服务调用,结果为:" + response);
if (response instanceof Integer) {
return (Integer) response;
} else {
throw new InternalError();
}
} catch (Exception e) {
}finally{
if (null!=socket) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return 0;
}
}
5.难道创建一个RPC模型就无敌了?
1.要实现一个RPC不算难,难的是实现一个高性能高可靠的RPC框架。
2.既然是分布式了,那么一个服务可能有多个实例,你在调用时,要如何获取这些实例的地址呢?
这时候就需要一个服务注册中心,比如在Dubbo里头,就可以使用Zookeeper作为注册中心,在调用时,从Zookeeper获取服务的实例列表,再从中选择一个进行调用。
3.那么选哪个调用好呢?
这时候就需要负载均衡了,于是你又得考虑如何实现复杂均衡,比如Dubbo就提供了好几种负载均衡策略。
4.总不能每次调用时都去注册中心查询实例列表吧,这样效率多低呀
这时候就需要缓存,有了缓存,就要考虑缓存的更新问题
5.客户端总不能每次调用完都干等着服务端返回数据吧,于是就要支持异步调用;
服务端的接口修改了,老的接口还有人在用,怎么办?总不能让他们都改了吧?这就需要版本控制了;
6.服务端总不能每次接到请求都马上启动一个线程去处理吧?
于是就需要线程池;
- 服务端关闭时,还没处理完的请求怎么办?是直接结束呢,还是等全部请求处理完再关闭呢?
网友评论