Code-Life icon indicating copy to clipboard operation
Code-Life copied to clipboard

Netty、Redis、Zookeeper高并发实战

Open Draymonders opened this issue 5 years ago • 9 comments

  • 趁热打铁,继续看书学习~

Draymonders avatar May 27 '20 04:05 Draymonders

基础理论

Java中的NIO

  • Java中的nio(New IO) 不是 操作系统层面的(Non-block IO), 而是(multi-IO 多路复用IO)

流程

  • 这里以read系统调用为例,先看下一个完整输入流程的两个阶段:
    • 等待数据准备好。
    • 从内核向进程复制数据。
  • 如果是read一个socket(套接字),那么以上两个阶段的具体处理流程如下:
    • 第一个阶段,等待数据从网络中到达网卡。当所等待的分组到达时,它被复制到内核中的某个缓冲区。这个工作由操作系统自动完成,用户程序无感知。
    • 第二个阶段,就是把数据从内核缓冲区复制到应用进程缓冲区

Draymonders avatar May 27 '20 04:05 Draymonders

Java NIO实践

https://mp.weixin.qq.com/s/rsvAmmoJiseEmjChI95m6Q

BIO

BIO两处阻塞

先知道原先的BIO为啥不好,才能体会到NIO的好

  • Server实现 https://github.com/Draymonders/Java_NIO/blob/master/src/main/java/bio/ServerDemo.java
  • Client实现 https://github.com/Draymonders/Java_NIO/blob/master/src/main/java/bio/ClientDemo.java

运行代码就可以知道,总共产生两次阻塞

  • 第一个是在ServerSocket.accept() 等待请求建立
  • 第二个是在ServerSocket.getInputStream() 获取请求的输入流

BIO处理客户端并发请求

  • 支持并发的Server实现 https://github.com/Draymonders/Java_NIO/blob/master/src/main/java/bio/ServerConcurrencyDemo.java

多线程BIO服务器虽然解决了单线程BIO无法处理并发的弱点,但是也带来一个问题:如果有大量的请求连接到我们的服务器上,但是却不发送消息,那么我们的服务器也会为这些不发送消息的请求创建一个单独的线程,那么如果连接数少还好,连接数一多就会对服务端造成极大的压力。所以如果这种不活跃的线程比较多,我们应该采取单线程的一个解决方案,但是单线程又无法处理并发,这就陷入了一种很矛盾的状态,于是就有了NIO。

Draymonders avatar May 30 '20 07:05 Draymonders

NIO 入门

  • 方案一 https://github.com/Draymonders/Java_NIO/blob/master/src/main/java/nio/ServerDemo1.java

方案一将建立链接数据读取InputStream都进行了非阻塞的处理, 但是有问题的是, 数据非阻塞做了处理后,如果客户端不是刚建立完链接就发送数据,那么服务端是无法读取到客户端的发来的数据的

  • 方案二 https://github.com/Draymonders/Java_NIO/blob/master/src/main/java/nio/ServerDemo1.java

方案二对建立的每个链接都加入到了List里面,因此可以一边判断是否有链接要建立另一边对所有建立的链接进行数据的读取操作

Draymonders avatar May 30 '20 15:05 Draymonders

Java中 BIO和NIO最本质的区别

  • BIO阻塞,NIO非阻塞
  • BIO对于每个网络链接,都需要开辟一个线程去处理相应的请求的输入与响应;(但是对于客户端来说,由于发送数据不是理解的,因此线程会block,也因此大并发场景下,建立了很多线程,系统在线程间上下文切换代价较高)
  • NIO则用Selector一个线程去处理一堆请求的输入与响应

Draymonders avatar May 31 '20 00:05 Draymonders

Buffer

属性

  • capcity 容量, 比如IntegerBuffer capacity=100 代表可以存入100个int
  • position 当前指针位置
  • limit limit即可读可写的最大范围
  • mark 缓存标记position位置

方法

  • allocate 创建一个缓存区
  • putget 分别在写模式下写入和在读模式下读入
  • flip 写模式转换为读模式
  • clear 读模式转换为写模式
  • rewind position置为0,从头开始读
  • mark 代表记录一个临时position, 而reset代表还原那个临时的position

Draymonders avatar May 31 '20 01:05 Draymonders

Channel

最主要的有四种, FileChannel, SocketChannel, ServerSocketChannel, DatagramChannel

Draymonders avatar May 31 '20 02:05 Draymonders

Selector

有四种监听事件OP_READ, OP_WRITE, OP_ACCEPT, OP_CONNECT 分别是可读、可写、接收、连接

Draymonders avatar Jun 05 '20 02:06 Draymonders

Reactor

之前的是对于一个请求,新开一个线程处理。

  • Reactor的流程则是 对于多个请求,只需要一个reactor(因为可以调用系统底层的select/poll/epoll), 然后也只需要一个handler (因为是非阻塞,所以每次执行很快)

Draymonders avatar Jun 06 '20 00:06 Draymonders

并发

阻塞等待线程

主线程需要等待线程A,线程B执行完后才能执行. 因此可以可以使用join()

Thread a = new Thread("A");
Thread b = new Thread("B");
a.start(); b.start();
a.join(); b.join();

FutureTask

上述join无法获取到线程a和b是否执行成功,仅仅是等待线程a和b执行退出(可能正常退出,也可能异常退出),并且无法拿到值

Callable<Boolean> aJob = new AJob();//③
FutureTask<Boolean>aTask =
        new FutureTask<>(aJob);
aJob.get();
// 同理 
bJob.get();

Draymonders avatar Jun 06 '20 03:06 Draymonders