java技术--传统restful api和RPC及简介

南宫松
2023-12-01

1.对比传统restful api和RPC方式的优缺点
2.RESTful API (http+json):Representational State Transfer,翻译是”表现层状态转化”

(1)首次出现在 2000 年 Roy Fielding 的博士论文中,他是 HTTP 规范的主要编写者之一
(2)通俗来讲就是:资源在网络中以某种表现形式进行状态转移
(3)总结一下什么是RESTful架构:
      <1>每一个URI代表一种资源
      <2>客户端和服务器之间,传递这种资源的某种表现层,比如用JSON,XML,JPEG等
      <3>客户端通过四个HTTP动词,对服务器端资源进行操作,实现"表现层状态转化"
(4)URL定位资源,用HTTP动词(GET,POST,DELETE,DETC)描述操作
     <1>用HTTP协议里的动词来实现资源的添加,修改,删除等操作
     <2>通过HTTP动词来实现资源的状态扭转:
         1.GET 用来获取资源,
    2.POST 用来新建资源(也可以用于更新资源),
    3.PUT 用来更新资源,
    4.DELETE 用来删除资源
(5)RESTful 是一种规范,只要表现层满足这种规范的开发都可视为RESTful风格的开发  
      <1>基于URL的资源访问方式
      <2>URL访问资源还需要指出访问方式(GET,POST,DELETE,DETC)

3.RPC(Remote Procedure Call),翻译是远程过程调用

(1)进程间通信(Inter-Process Communication)指至少两个进程或线程间传送数据或信号的一些技术或方法
(2)进程间通信技术包括消息传递、同步、共享内存和远程过程调用。 IPC是一种标准的Unix通信机制
(3)有两种类型的进程间通信(IPC):
   <1>本地过程调用(LPC):
     1.LPC用在多任务操作系统中,使得同时运行的任务能互相会话
     2.这些任务共享内存空间使任务同步和互相发送信息
   <2>远程过程调用(RPC):
     1.RPC类似于LPC,只是在网上工作
     2.RPC开始是出现在Sun微系统公司和HP公司的运行UNIX操作系统的计算机中
(4)RPC原理解释
   <1>RPC的目的是在本地调用远程的方法,这个调用是透明的,并不知道这个调用的方法是部署哪里   
   <2>通过RPC能解耦服务,这才是使用RPC的真正目的
   <3>RPC的原理主要用到了动态代理模式,至于http协议,只是传输协议而已
   <4>远程过程调用采用客户机/服务器(C/S)模式
       1.请求程序就是一个客户机,而服务提供程序就是一台服务器
       2.和常规或本地过程调用一样,远程过程调用是同步操作,在远程过程结果返回之前,需要暂时中止请求程序
       3.使用相同地址空间的低权进程或低权线程允许同时运行多个远程过程调用

4.实现远程过程调用

(1)默认socket通信
(2)本地机器的RPC框架反序列化出执行结果,函数return这个结果
        <1>这个过程中最重要的就是序列化和反序列化了
        <2>因为数据传输的数据包必须是二进制的,直接丢一个Java对象过去,系统不认识
        <3>必须把Java对象序列化为二进制格式,传给Server端,Server端接收到之后,再反序列化为Java对象

5.RPC应用实例:

(1)实现一个简单的RPC框架,用到的技术
      <1>动态代理:生成 client stub和server stub需要用到 **Java 动态代理技术 **
         1.可以使用JDK原生的动态代理机制
         2.可以使用一些开源字节码工具框架 如:CgLib、Javassist等
     <2>序列化:为了能在网络上传输和接收 Java对象,需要对它进行 序列化和反序列化操作    
         1.序列化:将Java对象转换成byte[]的过程,也就是编码的过程;
         2.反序列化:将byte[]转换成Java对象的过程;
         3.可以使用Java原生的序列化机制,但是效率非常低
         4.推荐使用一些开源的、成熟的序列化技术:protobuf、Thrift、hessian、Kryo、Msgpack
     <3> NIO:同步非阻塞IO,主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector(多路复用器)
         1.当前很多RPC框架都直接基于netty这一IO通信框架:比如阿里巴巴的HSF、dubbo,Hadoop Avro
         2.推荐使用Netty 作为底层通信框架
     <4>服务注册中心
         1.可选技术:Redis,Zookeeper,Consul,Etcd      
     <5>反射,以及Spring容器技术等完成简单实现    
(2)RPC架构分为三部分:
     <1>服务提供者:运行在服务器端,提供服务接口定义与服务实现类
     <2>服务注册中心:运行在服务器端,负责将本地服务发布成远程服务,管理远程服务,提供给服务消费者使用
     <3>服务消费者:运行在客户端,通过远程代理对象调用远程服务
(3)具体实现:
     <1>服务提供者接口定义与实现,代码如下:
        //远程服务端定义接口
        public interface HelloService { 
             String sayHi(String name);}      
        //HelloServices接口实现类:        
        public class HelloServiceImpl implements HelloService { 
             public String sayHi(String name) {
              return "Hi, " + name;}} 
     <2>服务注册中心接口及实现,代码如下:  
        //远程服务端定义服务注册中心接口
        public interface Server {
           void stop(); 
           void start() throws IOException; 
           void register(Class serviceInterface, Class impl);
           boolean isRunning(); 
           int getPort();}   
        //Server 接口实现
        public class ServiceCenter implements Server {
          private static ExecutorService executor =         Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());    
          private static final HashMap<String, Class> serviceRegistry = new HashMap<String, Class>();
          private static boolean isRunning = false;
          private static int port;
       public ServiceCenter(int port) {this.port = port;}
       public void stop() {
             isRunning = false;
             executor.shutdown();}
       public void start() throws IOException {
            ServerSocket server = new ServerSocket();
              server.bind(new InetSocketAddress(port));
              System.out.println("start server");
            try {
               while (true) {
              // 1.监听客户端的TCP连接,接到TCP连接后将其封装成task,由线程池执行
             executor.execute(new ServiceTask(server.accept()));}} finally {server.close();}}
        public void register(Class serviceInterface, Class impl) {
             serviceRegistry.put(serviceInterface.getName(), impl);}
        public boolean isRunning() {return isRunning;}
        public int getPort() { return port;}
           private static class ServiceTask implements Runnable {
              Socket clent = null;
        public ServiceTask(Socket client) {this.clent = client;}
        public void run() {
              ObjectInputStream input = null;
              ObjectOutputStream output = null;
             try {
             // 2.将客户端发送的码流反序列化成对象,反射调用服务实现者,获取执行结果
              input = new ObjectInputStream(clent.getInputStream());
              String serviceName = input.readUTF();
              String methodName = input.readUTF();
              Class<?>[] parameterTypes = (Class<?>[]) input.readObject();
              Object[] arguments = (Object[]) input.readObject();
              Class serviceClass = serviceRegistry.get(serviceName);
             if (serviceClass == null) {
                 throw new ClassNotFoundException(serviceName + " not found");}
                 Method method = serviceClass.getMethod(methodName, parameterTypes);
                 Object result = method.invoke(serviceClass.newInstance(), arguments);
                // 3.将执行结果反序列化,通过socket发送给客户端
                output = new ObjectOutputStream(clent.getOutputStream());
                output.writeObject(result);
            } catch (Exception e) {e.printStackTrace();
            } finally {
               if (output != null) {
                  try {output.close();
                    } catch (IOException e) {e.printStackTrace();}}
               if (input != null) {
                  try {input.close();
                   }catch (IOException e) {e.printStackTrace();}}
              if (clent != null) {
                  try {clent.close();
                  } catch (IOException e) { e.printStackTrace();}}}}}}    
      <3>客户端的远程代理对象:代码如下
       public class RPCClient<T> {
          public static <T> T getRemoteProxyObj(final Class<?> serviceInterface, final InetSocketAddress addr) {
           // 1.将本地的接口调用转换成JDK的动态代理,在动态代理中实现接口的远程调用
          return (T) Proxy.newProxyInstance(serviceInterface.getClassLoader(), new Class<?>[]    {serviceInterface},new InvocationHandler() {
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Socket socket = null;
            ObjectOutputStream output = null;
            ObjectInputStream input = null;
            try {
              // 2.创建Socket客户端,根据指定地址连接远程服务提供者
              socket = new Socket();
              socket.connect(addr);
              // 3.将远程服务调用所需的接口类、方法名、参数列表等编码后发送给服务提供者
              output = new ObjectOutputStream(socket.getOutputStream());
              output.writeUTF(serviceInterface.getName());
              output.writeUTF(method.getName());
              output.writeObject(method.getParameterTypes());
              output.writeObject(args);
              // 4.同步阻塞等待服务器返回应答,获取应答后返回
              input = new ObjectInputStream(socket.getInputStream());
              return input.readObject();} finally {
              if (socket != null) socket.close();
              if (output != null) output.close();
              if (input != null) input.close();}}});}}      
    <4>最后为测试类:代码如下
       public class RPCTest {
          public static void main(String[] args) throws IOException {
            new Thread(new Runnable() {
            public void run() {
             try {
              Server serviceServer = new ServiceCenter(8088);
              serviceServer.register(HelloService.class, HelloServiceImpl.class);
              serviceServer.start();
            } catch (IOException e) {e.printStackTrace();}}}).start();
        HelloService service = RPCClient.getRemoteProxyObj(HelloService.class, new          InetSocketAddress("localhost", 8088));
    System.out.println(service.sayHi("test"));}}    
 (4)经过上面4步结合用到的技术实现了一个简单的RPC远程调用过程
      <1>RPC本质为消息处理模型
      <2>RPC屏蔽了底层不同主机间的通信细节,让进程调用远程的服务就像是本地的服务一样
 (5)上面实现的RPC,仅仅用于演示远程调用的执行过程和用到的技术,要是想放到生产环境去用,那是绝对不行的
      <1>缺乏通用性
         1.通过给接口写一个实现类,下一次要是有别的接口需要远程调用,又得再写对应的远程调用实现类
         2.这肯定是很不方便的
      <2>集成Spring
         1.在实现了代理对象通用化之后,下一步就可以考虑集成Spring的IOC功能了,通过Spring来创建代理对象
      <3>长连接or短连接    
         1.每次要调用RPC接口时都去开启一个Socket建立连接
         2.思路:可以保持若干个长连接,然后每次有rpc请求时,把请求放到任务队列中,然后由线程池去消费执行
      <4>服务端线程池
         1.现在的Server端,是单线程的,每次都要等一个请求处理完,才能去accept另一个socket的连接,性能很差 
         2.思路:可以通过一个线程池,来实现同时处理多个RPC请求
      <5>服务注册中心
         1.要调用服务,首先需要一个服务注册中心,告诉对方服务都有哪些实例   
      <6>负载均衡
         1.从多个实例里挑选一个出来,进行调用,这就要用到负载均衡了
         2.负载均衡的策略不只一种,要怎样把策略做成可配置的,又要如何实现这些策略,原始RPC无法实现
      <7>结果缓存
         1.每次调用查询接口时都要去Server端查询
         2.思路:考虑一下支持缓存
      <8>多版本控制
         1.服务端接口修改了,旧的接口怎么办,原始RPC无法实现
      <9>异步调用
         1.客户端调用完接口之后,只能等待服务端返回,不能干点别的事,RPC无法实现异步调用
      <10>优雅停机
         1.服务端要停机了,还没处理完的请求,使用原始RPC无法保存        

6.RPC和RESTFul API对比

(1)REST是一种设计风格,它的很多思维方式与RPC是完全冲突的
(2)RPC的思想是把本地函数映射到API,也就是说一个API对应的是一个function
     <1>本地有一个getAllUsers,远程也能通过某种约定的协议来调用这个getAllUsers
     <2>至于这个协议是Socket、是HTTP还是别的什么并不重要;
     <3>RPC中的主体都是动作,是个动词,表示要做什么
     <4>REST它的URL主体是资源,是个名词
     <5>仅支持HTTP协议,规定了使用HTTP Method表达本次要做的动作,类型一般也不超过那四五种
     <6>RPC的根本问题是耦合
     <7>RPC客户端以多种方式与服务实现紧密耦合,并且很难在不中断客户端的情况下更改服务实现
     <8>RPC更偏向内部调用,REST更偏向外部调用

7.如果是一个简单的远程调用过程,使用这两种方式都可以

(1)实际开发中,RESTFul API 已经被好多企业使用,同时也是对SOA的具体实现
(2)如果是一个大型分布式/微服务系统,那么这两种方式就不适合了,就要是有分布式框架了

8.开源RPC框架

(1)一个完整的 RPC 框架主要有三部分组成:通信框架、通信协议、序列化和反序列化格式
(2)主要分为两类:一类是跟某种特定语言平台绑定的,另一类是与语言无关即跨语言平台的
      <1>跟语言平台绑定的开源 RPC 框架主要有下面几种:
         1.Dubbo:由阿里巴巴公司开发并于 2011 年末对外开源,仅支持 Java 语言。
         2.Motan:微博内部使用的 RPC 框架,于 2016 年对外开源,仅支持 Java 语言。
         3.Tars:腾讯内部使用的 RPC 框架,于 2017 年对外开源,仅支持 C++ 语言。
         4.Spring Cloud:Pivotal公司2014年对外开源的RPC框架,仅支持Java语言,是比较火的 RPC 框架
       <2>跨语言平台的开源 RPC 框架主要有以下几种:
         1.gRPC:支持常用的 C++、Java、Python、Go、Ruby、PHP、Android Java、Objective-C 等多种语言
         2.Thrift:成为Apache开源项目之一,支持常用的 C++、Java、PHP、Python、Ruby、Erlang 等多种语言
 (3)从长远来看,支持多语言是 RPC 框架未来的发展趋势
 (4)各个RPC框架都提供了Sidecar组件来支持多语言平台之间的 RPC 调用
      <1>Dubbo 又重启了维护,并且宣称要引入Sidecar组件来构建Dubbo Mesh提供多语言支持
      <2>Motan 对外开源了其内部的Sidecar组件:Motan-go,目前支持 PHP、Java 语言之间的相互调用
      <3>Spring Cloud 也提供了Sidecar组件spring-cloud-netflix-sideca,可以让其他语言也可以使用 Spring Cloud 的组件

9.后面将介绍几种比较流行的分布式/微服务框架

 类似资料: