深入理解Link

一、问题的集合

1.1启动服务提供者一直没有响应?

在这里插入图片描述

和zk 相关!

这是因为你的zk 挂了,或者你的zookeeper 没有连接成功!
因为超时时间是:
在这里插入图片描述
在这里插入图片描述

解决方案:
就是设置连接的超时时间,已经重启你的zookeeper ,2181 的端口放行

1.2找不到ConsumerService对象

发生在启动服务的消费者:
在这里插入图片描述
从ioc 容器里面无法找到该对象:
1 没有包扫描
在这里插入图片描述
2 你没有添加@Service 注解
在这里插入图片描述

1.3输入的集合为空(负载均衡时)

在这里插入图片描述
怎么引起这个原因的?
服务发现失败,没有发现服务提供者
启动的顺序或者时间相关:

  1. 你同时启动服务提供者或服务消费者
    服务提供者还没有来得及注册,你就去服务的发现,肯定为null
  2. 你先启动服务的消费者,后启动服务的提供者
    服务提供者还没有来得及注册,你就去服务的发现,肯定为null
  3. 你没有添加包扫描和注解
    能服务发现成功的原因:服务已经注册成功了,只有你看zk 上面有没有你的机器
    有同学发现没有注册成功:服务提供者启动了,但是没有注册成功

在这里插入图片描述
在这里插入图片描述
没有添加@ExposeService 或者没有包扫描也不行

二、回顾Link

2.1 基于接口编程的方式(场景)

  1. Mapper(mybatis/ plus) UserMapper-> 如何实现对数据库操作的?
    Public interface UserMapper{
    int insert(User user) ;
    }
    代理对象:
    想一想,Mybatis 底层是怎么实现的?
    在这里插入图片描述

  2. dubbo 也是如此,注入的也是代理对象

  3. link 也是如此,注入的也是代理对象

基于接口的编程方法基本上都是动态代理完成的

2.2 编码的理解

为什么需要写一个编码器?

因为socket(网络IO) 不能直接发生一个对象,只能发送字节的数组
之前我们使用jdk的序列化实现了编码器,那好不好?

Jdk的序列化效果一般,因为它序列化出来的字节数组比较大!
若能字节数组小,并且速度快,那就比jdk的序列化更好。

有: 在dubbo 里面,它使用的时Hession
大家在缓存里面:json

2.3 对于负载均衡的理解

从一个列表里面选择一个出来!
我们写的是:随机的负载均衡!
但是dubbo:轮询的,最小响应的!

2.4 对方法调用的理解

在这里插入图片描述

对于服务的提供者而言:它会某种方法,他具备某种功能,->有实现类->我就可以直接调用实现类对象(本地调用)
对于服务的消费者而言:他需要使用某种方法/功能(接口的定义),但是他不会。,只有接口->只能去求别人(远程调用)

本地调用和远程调用的相同点都是:调用某个方法,得到一个结果!
调用某个方法:必须的参数:

  1. 方法的名称
  2. 方法的参数对象
  3. 接口的名称

2.4.1 本地调用

我需要得到实现类的对象,就能完成调用
Object: 怎么得到该对象
Method:怎么得到该方法
method.invoker(object,args);
我们的所有对象都交给ioc 去管理了,得到对象,一般也就是从ioc 里面去获取
Ioc.getBean(接口)
方法得到:只需要方法的名称和参数就可以得到了

2.4.2 远程调用

没有实现类对象,所以无法完成调用,只能求别人调用
给别人发短信,说我需要调用你的那个方法,参数是那些,别人给你计算好了后,把答案发给你,这就是远程调用:
你->Socket->Request->localMethodInvoke->Result->Socket->你

2.5 网络的功能

网络就是一个数据的通道。具备输入和输出
在这里插入图片描述
服务端和客户端:服务端和客户端写数据的流程相同,但是获取输入流和输出流有点不同:
我们的socket 现在有什么问题?

2.6 谈谈对Socket 里面Io的理解?

1 serverSocket.accept(); // 接受客户端连接
2 inputStream.read(); // 从输入流里面读取数据
3 outputStream.write(); // 从输入流里面写数据

在这里插入图片描述

三、对并发的理解

3.1 现在的模式:

我们消费端只有一个线程去消费了提供者
在这里插入图片描述

While(true) 代表一直是在一个线程里面轮询
那怎么变成多线程:
在这里插入图片描述

3.2 就按照我们现在的代码,能不能实现一个并发的访问?

我们现在服务的消费者去调用服务提供者的方法,服务提供者里面,只有一个线程能供我们使用!
在这里插入图片描述

我们的服务端只有一个线程:

在这里插入图片描述
所以我们的服务端,是个单线程的,阻塞的方法,只能让一个服务的消费者消费完毕后,其他的消费者才能去执行!(Redis)
单线程模型并不好:因为没有利用到多核的优势,我们需要把他改进为多线程模型!
为什么redis 不改进:因为redis 是内存操作,他的速度非常快!
为什么我们需要改进,因为在我们的java 的大多数业务逻辑里面,都在数据库的操作,数据库是文件io的操作!

? 我们怎么实现一个多线程的rpc 框架?

3.3 线程池的复习:

面试的重点:(背会)
在这里插入图片描述

int corePoolSize :核心的线程数 -3
int maximumPoolSize: 最大的线程数 5
long keepAliveTime:线程的存活时间,当并发小了,有5 个线程,其中有的线程一直空着,空keepAliveTime ,会自动销毁线程,来释放内存,直到达到corePoolSize 
TimeUnit unit :上面时间的单位
BlockingQueue<Runnable> workQueue:就是任务列表 10-3 = 7 
ThreadFactory threadFactory : 线程工程,怎么创建线程
RejectedExecutionHandler handler:拒绝策略

一个任务一来,放在任务的列表里面:假设任务的列表是有限制的,只能放10 个
1 任务列表的任务会直接交个一个线程去做
2 线程线程池为null ,会直接调用ThreadFactory 去创建线程,直到达到核心的线程数3个
4 现在任务列表里面还存在7 个任务,线程会不会继续创建?
不会,因为任务列表没有满,7 个任务继续在列表里面等待,直到把他处理完毕!
5 7 个任务 ,又来了4 个 = 7 +3 = 10 +1 = 10 ,但是任务列表里面只能放10 ,来了11 个,线程工厂会继续创建线程,来消费
3 线程+1 = 4 线程
11 - 1 = 10
有12 个任务呢?
Max = 5-3 =2
12-2 =10
有13 个:
13-2 = 11 ,任务列表放不下,会走拒绝的策略

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.4 对提供者的改进

/**
 * serverSocket
 * ServerSocket 只需要一个就有可以了,不管有多少个客户端,只需要一个ServerSocket 就可以了
 * 在服务端和客户不同的点,是服务端需要监听
 *
 */
public class ServerSocketNet extends  SocketNet{

    private Coder coder = JDKCoder.INSTANCE;

    private ServerSocket serverSocket ;

    private MethodInvoker methodInvoker = LocalMethodInvoker.INSTANCE;

    /**
     * 线程池:
     * 7 个参数
     * 1 :
     */
    private ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
            3,
            5,
            5,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(10),
            new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                    return new Thread(r);
                }
            },
            new RejectedExecutionHandler() {
                @Override
                public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                    System.out.println("任务来了"+r+"我已经有女朋友了");
                }
            }

    );

    public ServerSocketNet(int port){
        try {
            this.serverSocket = new ServerSocket(port) ;
            // 服务端一启动就需要监听,而且是死循环的监听
            while(true){
                listener();
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void listener(){
        try {
            Socket socket =  this.serverSocket.accept(); // 代表监听 socket 客人


             threadPoolExecutor.submit(new Runnable() {
            @Override
            public void run() {
               handlerSocket( socket) ; // 处理和接待客人
            }
        });
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void handlerSocket(Socket socket){
        System.out.println("正在处理------------------");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            // 该socket 就是客户端 , 客户端会被请求对象发送过来,我们在本地调用成功后,又把该结果发个client
            ThreadLocalUtil.set("client",socket); // 把该变量放在线程里面
            //            -------------------------------
//            消费者1 进来,代码走到 ,读取数据,消费者2 进来,他是否能连接到serverSocket 上面?
//             不行了,因为服务端只有一个线程,这个现在还在被使用读取数据
            byte[] read = read(); // 调用父类里面的读取数据的方法,因为服务的消费者要把请求对象发送过来
            Request request = (Request)coder.deCode(read);
            // 服务的提供者里面,需要本地调用
            Object result = methodInvoker.invoker(request);
            write(coder.code(result)); // 向服务的消费者写答案过去
            // 一次调用结束
            ThreadLocalUtil.clear(); // 调用结束,释放线程里面的数据
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if(socket!=null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }finally {
                    socket = null ;
                }
            }
        }
    }

    @Override
    protected OutputStream getOutputStream() {
        Socket client =(Socket) ThreadLocalUtil.get("client");// 把该变量放在线程里面
        OutputStream outputStream = null;
        try {
            outputStream = client.getOutputStream();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return outputStream;
    }

    /**
     * 获取输入流
     * @return
     */
    @Override
    protected InputStream getInputStream() {
        Socket client = (Socket)ThreadLocalUtil.get("client");
        InputStream inputStream = null;
        try {
            inputStream = client.getInputStream();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return inputStream;
    }
}

3.5 多线程的消费

在这里插入图片描述

效果:

在这里插入图片描述

四、我们系统存在的缺陷

现在我们是多线程的并发消费模型吧,但是我们有个问题,socket 里面的io 阻塞问题
在这里插入图片描述

我们能不能在服务提供者提供少许的线程就能完成对对大并发的处理?

4.1 单线程的轮询模型

在这里插入图片描述

4.2 利用单线程的轮询模型来设计我们的系统

为什么设计,我们的系统就是在单线程轮询:
在这里插入图片描述
但是和js的不同在于:我们的系统是单线程接受一事件/客人,该客人交个多线程去处理了
在这里插入图片描述
这也就是我们多线程的用处!若客人非常多的话,我们就需要很多的线程数!
在这里插入图片描述

1 假设有个空间:
Buff(缓存区)
在这里插入图片描述
Jdk 给我们提供的nio ,非常难用:但是有个3个概念大家需要了解:
1 通道(代替之前的socket)
2 选择器(就是事件)
3 buff(缓冲区,数据直接放在里面)
Nio:核心思想在于:使用缓冲区,充分利用线程!
但是nio 也不是比io 非常完美:
1 若有多个缓冲区同时满了,io 和nio 的速度,到底那个快?
Io 快,因为io 可以使用多个线程来同时写
Nio:场景: 高并发少数据量(rpc)
Io 的场景:并发少,数据量大(文件的上传和下载)
推荐一篇文章:
https://zhuanlan.zhihu.com/p/23488863
对nio 理解非常到位,可以看看

五、我们怎么利用nio 来对系统进行改造

5.1 jdk 原生提供的nio有点难懂,一般不使用推荐使用Netty

Jdk:提供的nio有点难懂,linux和window的实现策略都不一样!(意味着我们需要写2 套代码)
Netty: 就是一个Socket,在Dubbo 里面默认使用Netty 来做网络的传输功能! ,netty 可以很简单的利用nio的能力

5.2 改造的代码:(参考)

在这里插入图片描述
在这里插入图片描述

Netty非常的简单,他写起来和Controller->Service->Dao 很像,我们没有讲Netty,如果大家对Netty 有兴趣,可以想参考我之前写的rpc,把netty 添加进去!

https://github.com/NymphWeb/Link/tree/master/Link

如果你想学习Netty ?
有本书:Netty实战
https://github.com/NymphWeb/Link/tree/master/Link
建议大家看一看,对面试有帮助

六、Link 的资源的关闭的问题

在这里插入图片描述
是否可以资源关闭?
将导致Socket 直接关闭,因为Socket 是个双通道(全双工),可以输出也可以输入
,如果你的Socket 关闭了,那你以后读数据时,获取输入流就报错,说你的socket 已经关闭了
在这里插入图片描述
但是你不能直接关闭Stream,我们可以这样关闭:
因为现在socket 和线程绑定,socket 的生命周期,当有请求来,它的生命开始,响应后,它的生命结束:
在这里插入图片描述
我们可以在ThreadLocaUtil 里面关闭

在这里插入图片描述
因为服务端和客户端一次调用结束后都会执行ThreadLocalUtil.clear() 方法,所以我们在里面关闭最好!

猜你喜欢

转载自blog.csdn.net/qq_43623447/article/details/104252984