分布式服务框架学习笔记1 应用架构演进

Posted 编程圈子

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分布式服务框架学习笔记1 应用架构演进相关的知识,希望对你有一定的参考价值。

传统垂直应用架构

业界曾比较流行的有:
LAMP架构:Linux+Apache+php(前后端分离)+mysql(读写分离)
MVC架构:Spring+Struts+iBatis/Hibernate+Tomcat

在高并发、大流量的应用场景中,需要做集群,通常的组网方案是前端通过F5等负载均衡器做七层负载均衡(或者使用SLB等软负载),后端做对等集群部署。

随着业务的不断发展,应用规模日趋庞大,传统垂直架构开发模式的弊端变得越来越突出。这就需要将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使用前端应用能更快速地响应多变的市场市场需求。
同时将公用能力API抽取出来,作为独立的公共服务供其他调用者消费,以实现服务的共享和重用,降低开发和运维成本。应用拆分之后会按照模块独立部署,接口调用由本地API演进成跨进程的远程方法调用,此时RPC框架应运而生。

RPC架构

RPC(Remote Procedure Call),它是一种进程间通信方式。允许像调用本地服务一样调用远程服务,它的具体实现方式可以不同,如Sprign的HTTP Invoker,Facebook的Thrift二进制私有协议通信。
RPC由20世纪80年代Bruce Jay Nelson提出。

RPC框架原理

RPC框架的目标就是让远程过程(服务)调用更加简单、透明,RPC框架负责屏蔽底层的传输方式(TCP或者UDP)、序列化方式(XML/JSON/二进制)和通信细节。框架使和者只需要了解谁在什么位置提供了什么样的远程服务接口即可,开发者不需要关心底层通信细节和调用过程。
这里写图片描述

RPC框架实现的几个核心技术点总结如下:
1. 远程服务提供者需要以某种形式提供服务调用相关的信息,包括但不限于服务接口定义、数据结构,或者中间太空的服务定义文件,如:Thrift的IDL文件、WS-RPC的WSDL文件定义,甚至也可以是服务端的接口说明文档;服务调用者需要通过一定的途径获取远程服务调用相关信息,例如服务端接口定义Jar包导入,获取服务端的IDL文件等
2. 远程代理对象:服务调用者调用的服务实际是远程服务的本地代理,对于Java语言,它的实现就是JDK的动态代理,通过动态代理的拦截机制,将本地调用封装成远程服务调用
3. 通信:RPC框架与具体的协议无关,如Spring的远程调用支持HTTP Invoke、RMI Invoke,MessagePack使用的是私有的二进制压缩协议
4. 序列化:远程通信,需要将对象转换成二进制码流进行传输,不同的序列化框架,支持的数据类型、数据包大小、异常类型及性能等都不同。不同的RPC框架应用场景不同,因此技术选择也会存在很大差异。一些做得比较好的RPC框架,可以支持多种序列化方式,有的甚至支持用户自定义序列化框架(hadoop avro)

最简单 RPC 框架实现

下面通过Java原生的序列化、Socket通信、动态代理和反射机制,实现最简单 RPC 框架。它由三部分组成:
1. 服务提供者:运行在服务端,负责提供服务接口定义和服务实现类
2. 服务发布者:运行在RPC服务端,负责将本地服务发布成远程服务,供其他消费者调用
3. 本地服务代理:运行在RPC客户央,通过代理调用远程服务提供者,然后将结果进行封装返回给本地消费者

代码架构:
这里写图片描述

源代码:

服务端接口定义和实现

EchoService

public interface EchoService {
    String echo(String ping);
}

EchoServiceImpl

public class EchoServiceImpl implements EchoService{

    @Override
    public String echo(String ping) {

        return ping != null ? ping + " --> I am ok.":"I am ok.";
    }

}

RPC服务端 服务发布者

RpcExporter

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

/**
 * RPC 服务端服务发布者
 * @author hs26661
 *
 */
public class RpcExporter {
    static Executor executor = Executors.newFixedThreadPool(Runtime
            .getRuntime().availableProcessors());

    public static void exporter(String hostName, int port) throws Exception {
        ServerSocket server = new ServerSocket();
        server.bind(new InetSocketAddress(hostName, port));  //监听 客户端的TCP连接
        try {
            while (true) {
                executor.execute(new ExporterTask(server.accept()));
            }
        } finally {
            server.close();
        }
    }

    private static class ExporterTask implements Runnable {
        Socket client = null;

        public ExporterTask(Socket client) {
            this.client = client;
        }

        @Override
        public void run() {
            ObjectInputStream input = null;
            ObjectOutputStream output = null;

            try {
                input = new ObjectInputStream(client.getInputStream());
                String interfaceName = input.readUTF();  //客户端发送的码流
                Class<?> service = Class.forName(interfaceName); //反序列化为对象
                String methodName = input.readUTF();
                Class<?>[] parameterTypes = (Class<?>[]) input.readObject();
                Object[] arguments = (Object[]) input.readObject();
                Method method = service.getMethod(methodName, parameterTypes);
                Object result = method.invoke(service.newInstance(), arguments);   //调用服务端方法
                output = new ObjectOutputStream(client.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 (client != null)
                try {
                    client.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
        }
    }
}

RPC客户端本地服务代理

RPCImporter

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.InetSocketAddress;
import java.net.Socket;

public class RpcImporter<S> {
    @SuppressWarnings("unchecked")
    public S importer(final Class<?> serviceClass,final InetSocketAddress addr){
        return (S) Proxy.newProxyInstance(serviceClass.getClassLoader(), new Class<?>[]{serviceClass.getInterfaces()[0]},
        new InvocationHandler(){
            @Override
            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {

                Socket socket=null;   //创建socket 客户端,根据指定地址连接远程服务提供者
                ObjectOutputStream output = null;
                ObjectInputStream input=null;
                try
                {
                    socket = new Socket();
                    socket.connect(addr);
                    output = new ObjectOutputStream(socket.getOutputStream());
                    output.writeUTF(serviceClass.getName());
                    output.writeUTF(method.getName());
                    output.writeObject(method.getParameterTypes());
                    output.writeObject(args);   //把远程服务调用者需要的接口类、方法名、参数列表等编码后发送给服务提供者
                    input=new ObjectInputStream(socket.getInputStream());    //同步阻塞等待服务端返回应答,获取应答之后返回
                    return input.readObject();    
                }
                finally
                {
                    if(socket!=null)socket.close();
                    if(output!=null)output.close();
                    if(input!=null)input.close();
                }
            }
        }); 
    }
}

测试代码

import java.net.InetSocketAddress;

public class RpcClient {

    public static void main(String[] args) {
        new Thread(new Runnable(){

            @Override
            public void run() {
                try{
                    RpcExporter.exporter("localhost",8088);
                }
                catch(Exception e){
                    e.printStackTrace();
                }

            }

        }).start();
        RpcImporter<EchoService> importer=new RpcImporter<EchoService>();
        EchoService echo = importer.importer(EchoServiceImpl.class, new InetSocketAddress("localhost",8088));
        System.out.println(echo.echo("Are you ok?"));
    }

}

运行过程

Created with Raphaël 2.1.0 创建异步发布服务端的线程并启动,接收RPC客户端的请求 根据请求参数调用服务实现类,返回结果给客户端 创建客户端服务代理类,构造RPC请求参数,发起RPC调用 将调用结果输出到控制台

以上是关于分布式服务框架学习笔记1 应用架构演进的主要内容,如果未能解决你的问题,请参考以下文章

架构学习笔记:淘宝的 14 次架构演进之路

架构学习笔记:淘宝的 14 次架构演进之路

Dubbo架构学习整理

Dubbo架构学习整理

19.《持续演进的Cloud Native 云原生架构下微服务最佳实践》读书笔记-第九章 研发流程

分布式架构演进