Day13.高性能RPC设计 学习笔记1

一、引言

  1. 系统架构演变
    随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用(MVC)架构已无法应对,分布式服务架构以及流动计算架构(伸缩性)势在必行,亟需一个治理系统确保架构有条不紊的演进。
    图01
  • 单一架构:例如早期servlet/jsp - ORM(对象关系映射) Hibernate|MyBatis
  • 垂直架构:将一个应用分层,实现协同开发,便于后期项目升级维护 - MVC Struts1/2|SpringMVC
  • 分布式服务:提升服务整体性能,将一些模块独立出去(SOA service orented architecture),独立运行,随着业务发展,不可避免需要调度第三方服务(物流信息、第三支付)。RPC - Remote Process Call (即时消息) webservice、Dubbo、SpringCloud
  • 流动式计算架构:要求服务提供方能够根据负载情况,动态增加或者减少。同时实现服务可视化,简化服务运维,提升系统资源的利用率。 - SOA治理框架 微服务 - 对服务进行集中管理调控。
  1. 高并发互联网设计原则 —— 高负载高存储高容错 也称 AKF拆分原则
    X轴:服务水平扩展
    服务具有可复制性 ,水平分割 ,解决一些并发问题
    (水平拓展,比性能的垂直提升成本低-摩尔定律)
    Y轴:服务间各司其职
    弱耦合,垂直分割,物理节点物尽其用
    (泳道设计)
    Z轴:对x,y打包,进行物理隔离。
    X中有Y,Y中有X x和y互相转换
    y中有x:对y轴做垂直拆分,层与层间独立,为了保证每一层的可用性,每一层做水平分割

  2. 高并发的场景问题 —— 优化思路
    如何让服务支持x轴的水平扩展,利用分布式的思维解决高并发;如何做y中的垂直拆分,是应用之间尽可能隔离,弱化服务件耦合度;结合两个维度来解决高并发的场景问题。(Socket + IO)

二、网络基础(TCP/IP)

  • 网络编程:通过编码的方式,让不同计算机之间相互通信(数据的传递)。

  • 两个核心问题:
    寻址问题:ip地址+port端口号
    协议问题:数据传输格式

  • OSI 七层模型(标准,了解就好)
    图02

  • OSI 五层模型(七层的转变)

  • 协议
    http协议:应用层协议,超文本传输协议,不同计算机间传输文本的协议(字符串),底层tcp协议
    tcp/ip协议:传输层协议

tcp协议:传输安全,效率低
udp协议:传输不安全,效率高

  • 了解
    应用层(Http协议:WebService,tomcat协议)——普通程序员
    传输层(TCP/IP UDP:基于tcp/ip封装的ftp、smtp/pop3、websocket…定制协议rpc协议)——架构程序员 也是代码最终触及到的地方
    物理层(嵌入式开发)

三、传统 BIO 网络编程模型(TCP/IP)

  1. IO模型分类
  • BIO 同步阻塞IO
    传统IO 或者 Blocking IO
    特点:面向流Input | Output
    【每个线程只能处理一个channel(同步的,该线程和该channel绑定)。
    线程发起IO请求,不管内核是否准备好IO操作,从发起请求起,线程一直阻塞,直到操作完成。】

  • NIO 同步非阻塞IO
    New IO 或者Non Blocking IO
    特点:面向缓冲区Buffer(基于通道)
    【每个线程可以处理多个channel(异步)。
    线程发起IO请求,立即返回;内核在做好IO操作的准备之后,通过调用注册的回调函数通知线程做IO操作,线程开始阻塞,直到操作完成 。】

  • AIO(Async Non Blocking IO) 异步非阻塞
    【线程发起IO请求,立即返回;内存做好IO操作的准备之后,做IO操作,直到操作完成或者失败,通过调用注册的回调函数通知线程做IO操作完成或者失败 。】

  1. BIO
    方向—— 输入、输出流(InputStream | OutputStream)
    类型—— 字节、字符流(Reader | Writer)
    功能—— 节点、过滤流(BufferedInputStream | BufferedOutputStream )

  2. BIO网络编程

  • 服务端:ServerSocket 【接收和响应,请求转发 - ServerSocket请求响应- Socket
    ①初始化服务器ServerSocket,绑定监听端口
    ②等待客户端连接serverSocket.accept();
    ③处理请求/响应sockect.getInputStream(); / socket.getOutputStream();
    ④关闭资源

  • 客户端:Socket 【发送和接收,发送请求|接收响应:Socket
    ①初始化客户端Socket,绑定服务器IP/端口
    ②发起请求/获取响应socket.getOutputStream(); / socket.getInputStream();
    ③关闭资源

    socket编程图

  1. BIO代码
  • Server
public class BIOBootstrapServer {
    public static void main(String[] args) throws IOException {
        server();
    }
    public static void server() throws IOException {
        //创建服务转发ServerSocket
        ServerSocket ss = new ServerSocket();
        ss.bind(new InetSocketAddress(9999)); //server可以省略好hostname

        ExecutorService threadPool= Executors.newFixedThreadPool(10);
        while(true) {
            //等待请求到来,转发产生Socket(系统内部资源)
            final Socket socket = ss.accept(); //没有请求就等待,阻塞
            //final 匿名内部类使用局部变量要final修饰
            threadPool.submit(new Runnable() {
                public void run() {
                    try {
                        //利用IO,读取用户请求
                        InputStream req = socket.getInputStream();
                        InputStreamReader isr = new InputStreamReader(req);
                        BufferedReader br = new BufferedReader(isr);

                        String line = null;
                        StringBuilder sb = new StringBuilder();
                        while ((line = br.readLine()) != null) {
                            sb.append(line);
                        }
                        System.out.println("服务器收到:" + sb.toString());
                        //给出用户响应
                        OutputStream res = socket.getOutputStream();
                        PrintWriter pw = new PrintWriter(res);
                        pw.println(new Date().toLocaleString());
                        pw.flush();
                        socket.shutdownOutput(); //告知客户端写结束
                        //关闭系统资源
                        socket.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}
  • Client
public class BIOBootstrapClient {
    public static void main(String[] args) throws IOException {
        send("这是一条来自client 的 message!");
    }
    public static void send(String msg) throws IOException {
        //创建Socket对象
        Socket s = new Socket();
        s.connect(new InetSocketAddress("127.0.0.1",9999)); //所以几乎给ip和端口的,底层走的就是socket

        //发送请求
        OutputStream req = s.getOutputStream();
        PrintWriter pw=new PrintWriter(req);
        pw.println(msg);
        pw.flush();
        s.shutdownOutput();//告知服务端写结束

        //接收响应
        InputStream res = s.getInputStream();
        InputStreamReader isr = new InputStreamReader(res);
        BufferedReader br = new BufferedReader(isr);
        String line=null;
        StringBuilder sb=new StringBuilder();
        while((line=br.readLine())!=null){
            sb.append(line);
        }
        System.out.println("客户端收到:"+sb.toString());
        //关闭资源
        s.close();
    }
}

思考下,BIO模型的缺点?
accept转发产生socket资源,一次请求转发等待请求响应的过程有阻塞。main线程作accept请求转发、子线程作socket读写IO处理。

  • 机器间通信是靠传输 套接字 完成的。

套接字,是支持TCP/IP的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。
简单的举例说明下:Socket = Ip address + TCP/UDP + port。

每个socket代表一个系统的随机端口,用来在服务和客户端之间建立连接。
在这里插入图片描述

  1. BIO网络编程问题
    通过将相应处理和转发进行分离,但是在转发的时候,服务器是通过先开启线程资源(计算),然后在计算中动用IO操作,IO存在总线占用问题,导致当前线程操作的IO资源没有就绪,导致系统计算资源的浪费。如果要解决该问题,必须先考虑让IO就绪,然后在开启线程处理或者理解为让线程去处理一些非阻塞的IO操作。

BIO编程诟病 :多线程模型解决服务端并发,但是无法判断当前处理的IO状态是否就绪,此时就会导致线程在做无畏等待,导致系统资源利用率不高。 先开线程 -> 在线程等待IO就绪->处理IO->线程资源释放

四、NIO编程模型

  • Channel(FileChannel-读写文件、ServerSocketChannel-请求转发、SocketChannel-响应处理)
  • ByteBuffer (字节缓冲区)
  1. 创建FileChannel
//可读的FileChannel
FileChannel fr=new FileInputStream("xxx路径").getChannel();
//可写的FileChannel
FileChannel fw=new FileOutputStream("xxx路径").getChannel();

文件操作

//读文件件
fr.read(缓冲区)
//写文件
fw.write(缓冲区)    
  1. 字节缓冲区
    图03

flip:pos赋值给limit,post归0
clear:恢复变量为初始状态

  1. 一个基于NIO的文件拷贝【面试&笔试】
    【NIO没有流的概念,用通道来处理。】
public class FileCopyDemo {
    public static void main(String[] args) throws IOException {
        //创建读通道 磁盘读入数据 到 ByteBuffer
        FileInputStream in = new FileInputStream("C:\\Users\\Administrator\\Desktop\\123.txt");
        FileChannel fcr = in.getChannel(); //fileChannelRead

        //创建写通道 将 ByteBuffer 数据写入磁盘
        FileOutputStream out = new FileOutputStream("C:\\Users\\Administrator\\Desktop\\321.txt");
        FileChannel fcw= out.getChannel(); //fileChannelWrite

        //创建ByteBuffer
        ByteBuffer buffer = ByteBuffer.wrap(new byte[1024]);//.allocate()也可以

        while (true){
            buffer.clear();
            int n = fcr.read(buffer);
            if(n==-1) break;
            buffer.flip();
            fcw.write(buffer);
        }
        //关闭资源
        fcr.close();
        fcw.close();
    }
}
  1. “挂羊头,卖狗肉” —— NIO的API,BIO的编程模型
public class NIOBootstrapServer_Fake {
    public static void main(String[] args) throws IOException {
        server();
    }
    public static void server() throws IOException {
        //创建服务转发ServerSocket
        ServerSocketChannel ss = ServerSocketChannel.open();
        ss.bind(new InetSocketAddress(9999));
        ExecutorService threadPool= Executors.newFixedThreadPool(10);
        while(true) {
            //等待请求到来,转发产生Socket(系统内部资源)
            final SocketChannel socket = ss.accept();// 阻塞
            threadPool.submit(new Runnable() {
                public void run() {
                    try{
                        //读取客户端响应
                        ByteBuffer buffer=ByteBuffer.allocate(1024);

                        ByteArrayOutputStream baos=new ByteArrayOutputStream();//类似stringbuilder,用来暂存用

                        while (true){
                            buffer.clear();
                            int n = socket.read(buffer);
                            if(n==-1) break;
                            buffer.flip();
                            baos.write(buffer.array(),0,n);
                        }
                        System.out.println("服务器收到:"+new String(baos.toByteArray()));

                        //给出客户端响应

                        ByteBuffer respBuffer = ByteBuffer.wrap((new Date().toLocaleString()).getBytes());
                        socket.write(respBuffer);

                        socket.shutdownOutput();//告知客户端写结束
                        //关闭系统资源
                        socket.close();
                    }catch (Exception e){

                    }
                }
            });
        }
    }
}
  1. 通道选择器
    图
    在这里插入图片描述

NIO核心思想,将网络编程中所有操作(转发,读请求,写响应)封装成事件Event,之后用事件形式通知程序进行计算,事件发生则通知程序,这样处理的IO都是就绪的IO,保证计算资源的充分利用。这时候,引入通道选择器的概念。
我们需要把通道注册到通道选择器的注册列表中,由通道选择器维护注册列表;一旦我们所关注的事件发生了,他会把关注的事件封装到事件处理列表当中;为了防止处理空或无效IO,每个事件处理结束后,要从列表中移除;移除不代表取消注册。只有两种可能才取消注册,一种是通道关闭,一种是主动注销。

猜你喜欢

转载自blog.csdn.net/weixin_42838993/article/details/85111170