Reactor反应器模式

news2024/11/23 7:56:50

单线程Reactor反应器模式

在事件驱动模式中,当有事件触发时,事件源会将事件dispatch分发到handler处理器进行事件处理。反应器模式中的反应器角色,类似于事件驱动模式中的dispatcher事件分发器角色。
在反应器模式中,有Reactor反应器和Handler处理器两个重要的组件:
(1)Reactor反应器:负责查询IO事件,当检测到一个IO事件,将其发送给相应的Handler处理器去处理。这里的IO事件,就是NIO中选择器监控的通道IO事件。(2)Handler处理器:与IO事件(或者选择键)绑定,负责IO事件的处理。完成真正的连接建立、通道的读取、处理业务逻辑、负责将结果写出到通道等。

2.1 什么是单线程Reactor反应器

什么是单线程版本的Reactor反应器模式呢?简单地说,Reactor反应器和Handers处理器处于一个线程中执行。它是最简单的反应器模型,如图4-1所示。
在这里插入图片描述基于Java NIO,如何实现简单的单线程版本的反应器模式呢?需要用到SelectionKey选择键的几个重要的成员方法:
方法一:void attach(Object o)

此方法可以将任何的Java POJO对象,作为附件添加到SelectionKey实例,相当于附件属性的setter方法。这方法非常重要,因为在单线程版本的反应器模式中,需要将Handler处理器实例,作为附件添加到SelectionKey实例。

方法二:Object attachment()

此方法的作用是取出之前通过attach(Object o)添加到SelectionKey选择键实例的附件,相当于附件属性的getter方法,与attach(Object o)配套使用。

这个方法同样非常重要,当IO事件发生,选择键被select方法选到,可以直接将事件的附件取出,也就是之前绑定的Handler处理器实例,通过该Handler,完成相应的处理。

总之,在反应器模式中,需要进行attach和attachment结合使用:在选择键注册完成之后,调用attach方法,将Handler处理器绑定到选择键;当事件发生时,调用attachment方法,可以从选择键取出Handler处理器,将事件分发到Handler处理器中,完成业务处理。

2.2 单线程Reactor反应器的参考代码

Doug Lea在《Scalable IO in Java》中,实现了一个单线程Reactor反应器模式的参考代码。这里,我们站在巨人的肩膀上,借鉴Doug Lea的实现,对其进行介绍。为了方便说明,对Doug Lea的参考代码进行一些适当的修改。具体的参考代码如下:

        package com.crazymakercircle.ReactorModel;
        //...
        class Reactor implements Runnable {
            Selector selector;
            ServerSocketChannelserverSocket;
            EchoServerReactor() throws IOException {
              //....省略:打开选择器、serverSocket连接监听通道
              //注册serverSocket的accept事件
              SelectionKeysk =
                        serverSocket.register(selector, SelectionKey.OP_ACCEPT);
              //将新连接处理器作为附件,绑定到sk选择键
              sk.attach(new AcceptorHandler());
            }

            public void run() {
              //选择器轮询
              try {
                  while (! Thread.interrupted()) {
                      selector.select();
                      Set selected = selector.selectedKeys();
                      Iterator it = selected.iterator();
                      while (it.hasNext()) {
                      //反应器负责dispatch收到的事件
                          SelectionKeysk=it.next();
                          dispatch(sk);
                      }
                      selected.clear();
                  }
              } catch (IOException ex) { ex.printStackTrace(); }
            }
            //反应器的分发方法
            void dispatch(SelectionKey k) {
              Runnable handler = (Runnable) (k.attachment());
              //调用之前绑定到选择键的handler处理器对象
              if (handler ! = null) {
                  handler.run();
              }
          }
          // 新连接处理器
          class AcceptorHandler implements Runnable {
              public void run() {
                  //接受新连接
                  //需要为新连接,创建一个输入输出的handler处理器
              }
          }
          //….
        }

在上面的代码中,设计了一个Handler处理器,叫作AcceptorHandler处理器,它是一个内部类。在注册serverSocket服务监听连接的接受事件之后,创建一个AcceptorHandler新连接处理器的实例,作为附件,被设置(attach)到了SelectionKey中。

        //注册serverSocket的接受(accept)事件
        SelectionKeysk =
                        serverSocket.register(selector, SelectionKey.OP_ACCEPT);
        //将新连接处理器作为附件,绑定到sk选择键
        sk.attach(new AcceptorHandler());

当新连接事件发生后,取出了之前attach到SelectionKey中的Handler业务处理器,进行socket的各种IO处理。

          void dispatch(SelectionKey k) {
              Runnable r = (Runnable) (k.attachment());
              //调用之前绑定到选择键的处理器对象
              if (r ! = null) {
                r.run();
              }
          }

AcceptorHandler处理器的两大职责:一是接受新连接,二是在为新连接创建一个输入输出的Handler处理器,称之为IOHandler。

        // 新连接处理器
            class AcceptorHandler implements Runnable {
              public void run() {
                  // 接受新连接
                  // 需要为新连接创建一个输入输出的handler处理器
              }
            }

IOHandler,顾名思义,就是负责socket的数据输入、业务处理、结果输出。示例代码如下:

        package com.crazymakercircle.ReactorModel;
        //...
        class IOHandler implements Runnable {
            final SocketChannel channel;
            final SelectionKeysk;
            IOHandler (Selector selector, SocketChannel c) throws IOException {
              channel = c;
              c.configureBlocking(false);
              //仅仅取得选择键,稍候设置感兴趣的IO事件
              sk = channel.register(selector, 0);
              //将Handler处理器作为选择键的附件
              sk.attach(this);
              //注册读写就绪事件
              sk.interestOps(SelectionKey.OP_READ|SelectionKey.OP_WRITE);
          }
          public void run()  {
          //...处理输入和输出
          }
        }

在IOHandler的构造器中,有两点比较重要:
(1)将新的SocketChannel传输通道,注册到了反应器Reactor类的同一个选择器中。这样保证了Reactor类和Handler类在同一个线程中执行。
(2)Channel传输通道注册完成后,将IOHandler自身作为附件,attach到了选择键中。这样,在Reactor类分发事件(选择键)时,能执行到IOHandler的run方法。

如果上面的示例代码比较绕口,不要紧。为了彻底地理解个中妙处,自己动手开发一个可以执行的实例。下面基于反应器模式,实现了一个EchoServer回显服务器实例。仔细阅读和运行这个实例,就可以明白上面这段绕口的程序代码的真正含义了。

3 一个Reactor反应器版本的EchoServer实践案例

EchoServer回显服务器的功能很简单:读取客户端的输入,回显到客户端,所以也叫回显服务器。基于Reactor反应器模式来实现,设计3个重要的类:
(1)设计一个反应器类:EchoServerReactor类。
(2)设计两个处理器类:AcceptorHandler新连接处理器、EchoHandler回显处理器。

反应器类EchoServerReactor的实现思路和前面的示例代码基本上相同,具体如下:

        package com.crazymakercircle.ReactorModel;
        //.....
        //反应器
        class EchoServerReactor implements Runnable {
            Selector selector;
            ServerSocketChannelserverSocket;
            EchoServerReactor() throws IOException {
                //...获取选择器、开启serverSocket服务监听通道
                //...绑定AcceptorHandler新连接处理器到selectKey
            }
            //轮询和分发事件
            public void run() {
              try {
                  while (! Thread.interrupted()) {
                      selector.select();
                      Set<SelectionKey> selected = selector.selectedKeys();
                      Iterator<SelectionKey> it = selected.iterator();
                      while (it.hasNext()) {
                          //反应器负责dispatch收到的事件
                          SelectionKey sk = it.next();
                          dispatch(sk);
                      }
                      selected.clear();
                  }
              } catch (IOException ex) {
                  ex.printStackTrace();
                }
          }

          void dispatch(SelectionKeysk) {
              Runnable handler = (Runnable) sk.attachment();
              //调用之前attach绑定到选择键的handler处理器对象
              if (handler ! = null) {
                  handler.run();
              }
          }

          // Handler:新连接处理器
          class AcceptorHandler implements Runnable {
              public void run() {
                  try {
                    SocketChannel channel = serverSocket.accept();
                    if (channel ! = null)
                        new EchoHandler(selector, channel);
                  } catch (IOException e) {
                    e.printStackTrace();
                  }
              }
          }

          public static void main(String[] args) throws IOException {
              new Thread(new EchoServerReactor()).start();
          }
        }

EchoHandler回显处理器,主要是完成客户端的内容读取和回显,具体如下:

        import com.crazymakercircle.util.Logger;
        //...
        class EchoHandler implements Runnable {
            final SocketChannel channel;
            final SelectionKeysk;
            final ByteBufferbyteBuffer = ByteBuffer.allocate(1024);
            static final int RECIEVING = 0, SENDING = 1;
            int state = RECIEVING;

            EchoHandler(Selector selector, SocketChannel c) throws IOException {
              channel = c;
              c.configureBlocking(false);
              //取得选择键,再设置感兴趣的IO事件
              sk = channel.register(selector, 0);
              //将Handler自身作为选择键的附件
              sk.attach(this);
              //注册Read就绪事件
              sk.interestOps(SelectionKey.OP_READ);
              selector.wakeup();
            }

            public void run() {
              try {
                  if (state == SENDING) {
                      //写入通道
                      channel.write(byteBuffer);
                      //写完后,准备开始从通道读,byteBuffer切换成写入模式
                      byteBuffer.clear();
                      //写完后,注册read就绪事件
                      sk.interestOps(SelectionKey.OP_READ);
                      //写完后,进入接收的状态
                      state = RECIEVING;
                  } else if (state == RECIEVING) {
                      //从通道读
                      int length = 0;
                      while ((length = channel.read(byteBuffer)) > 0) {
                          Logger.info(new String(byteBuffer.array(), 0, length));
                      }
                      //读完后,准备开始写入通道,byteBuffer切换成读取模式
                      byteBuffer.flip();
                      //读完后,注册write就绪事件
                      sk.interestOps(SelectionKey.OP_WRITE);
                      //读完后,进入发送的状态
                      state = SENDING;
                  }
                  //处理结束了,这里不能关闭select key,需要重复使用
                  //sk.cancel();
                } catch (IOException ex) {
                  ex.printStackTrace();
                }
            }
        }

以上两个类,是一个基于反应器模式的EchoServer回显服务器的完整实现。它是一个单线程版本的反应器模式,Reactor反应器和所有的Handler处理器,都执行在同一条线程中。

运行EchoServerReactor类中的main方法,可以启动回显服务器。如果要看到回显输出,还需要启动客户端。客户端的代码,在同一个包下,类名为EchoClient,负责数据的发送。打开源代码工程,直接运行即可。由于篇幅原因,这里不再贴出客户端的代码。

4 单线程Reactor反应器模式的缺点

单线程Reactor反应器模式,是基于Java的NIO实现的。相对于传统的多线程OIO,反应器模式不再需要启动成千上万条线程,效率自然是大大提升了。

在单线程反应器模式中,Reactor反应器和Handler处理器,都执行在同一条线程上。这样,带来了一个问题:当其中某个Handler阻塞时,会导致其他所有的Handler都得不到执行。在这种场景下,如果被阻塞的Handler不仅仅负责输入和输出处理的业务,还包括负责连接监听的AcceptorHandler处理器。这个是非常严重的问题。为什么?一旦AcceptorHandler处理器阻塞,会导致整个服务不能接收新的连接,使得服务器变得不可用。因为这个缺陷,因此单线程反应器模型用得比较少。

另外,目前的服务器都是多核的,单线程反应器模式模型不能充分利用多核资源。总之,在高性能服务器应用场景中,单线程反应器模式实际使用的很少。

多线程的Reactor反应器模式

既然Reactor反应器和Handler处理器,挤在一个线程会造成非常严重的性能缺陷。那么,可以使用多线程,对基础的反应器模式进行改造和演进。

3.1 多线程池Reactor反应器演进

多线程池Reactor反应器的演进,分为两个方面:
(1)首先是升级Handler处理器。既要使用多线程,又要尽可能的高效率,则可以考虑使用线程池。
(2)其次是升级Reactor反应器。可以考虑引入多个Selector选择器,提升选择大量通道的能力。

总体来说,多线程池反应器的模式,大致如下:
(1)将负责输入输出处理的IOHandler处理器的执行,放入独立的线程池中。这样,业务处理线程与负责服务监听和IO事件查询的反应器线程相隔离,避免服务器的连接监听受到阻塞。
(2)如果服务器为多核的CPU,可以将反应器线程拆分为多个子反应器(SubReactor)线程;同时,引入多个选择器,每一个SubReactor子线程负责一个选择器。这样,充分释放了系统资源的能力;也提高了反应器管理大量连接,提升选择大量通道的能力。

3.2 多线程Reactor反应器的实践案例

在前面的“回显服务器”(EchoServer)的基础上,完成多线程Reactor反应器的升级。多线程反应器的实践案例设计如下:
(1)引入多个选择器。
(2)设计一个新的子反应器(SubReactor)类,一个子反应器负责查询一个选择器。(3)开启多个反应器的处理线程,一个线程负责执行一个子反应器(SubReactor)。

为了提升效率,建议SubReactor的数量和选择器的数量一致。避免多个线程负责一个选择器,导致需要进行线程同步,引起的效率降低。这个实践案例的代码如下:

        package com.crazymakercircle.ReactorModel;
        //....反应器
        class MultiThreadEchoServerReactor {
            ServerSocketChannelserverSocket;
            AtomicInteger next = new AtomicInteger(0);
            //选择器集合,引入多个选择器
            Selector[] selectors = new Selector[2];
            //引入多个子反应器
            SubReactor[] subReactors = null;
            MultiThreadEchoServerReactor() throws IOException {
              //初始化多个选择器
              selectors[0] = Selector.open();
              selectors[1] = Selector.open();
              serverSocket = ServerSocketChannel.open();
              InetSocketAddress address =
                    new InetSocketAddress(NioDemoConfig.SOCKET_SERVER_IP,
              NioDemoConfig.SOCKET_SERVER_PORT);
              serverSocket.socket().bind(address);
              //非阻塞
              serverSocket.configureBlocking(false);
              //第一个选择器,负责监控新连接事件
              SelectionKeysk =
                    serverSocket.register(selectors[0], SelectionKey.OP_ACCEPT);
              //绑定Handler:attach新连接监控handler处理器到SelectionKey(选择键)
              sk.attach(new AcceptorHandler());
              //第一个子反应器,一子反应器负责一个选择器
              SubReactor subReactor1 = new SubReactor(selectors[0]);
              //第二个子反应器,一子反应器负责一个选择器
              SubReactor subReactor2 = new SubReactor(selectors[1]);
              subReactors = new SubReactor[]{subReactor1, subReactor2};
          }

          private void startService() {
              // 一子反应器对应一个线程
              new Thread(subReactors[0]).start();
              new Thread(subReactors[1]).start();
          }

          //子反应器
          class SubReactor implements Runnable {
              //每个线程负责一个选择器的查询和选择
              final Selector selector;
              public SubReactor(Selector selector) {
                  this.selector = selector;
              }
              public void run() {
                  try {
                    while (! Thread.interrupted()) {
                        selector.select();
                        Set<SelectionKey>keySet = selector.selectedKeys();
                        Iterator<SelectionKey> it = keySet.iterator();
                        while (it.hasNext()) {
                            //反应器负责dispatch收到的事件
                            SelectionKeysk = it.next();
                            dispatch(sk);
                        }
                    keySet.clear();
                    }
                  } catch (IOException ex) {
                    ex.printStackTrace();
                    }
              }
              void dispatch(SelectionKeysk) {
                  Runnable handler = (Runnable) sk.attachment();
                  //调用之前attach绑定到选择键的handler处理器对象
                  if (handler ! = null) {
                    handler.run();
                  }
              }
          }
            // Handler:新连接处理器
            class AcceptorHandler implements Runnable {
                public void run() {
                  try {
                      SocketChannel channel = serverSocket.accept();
                      if (channel ! = null)
                          new MultiThreadEchoHandler(selectors[next.get()], channel);
                  } catch (IOException e) {
                      e.printStackTrace();
                    }
                  if (next.incrementAndGet() == selectors.length) {
                      next.set(0);
                  }
                }
            }
            public static void main(String[] args) throws IOException {
                MultiThreadEchoServerReactor server =
                          new MultiThreadEchoServerReactor();
                server.startService();
            }
        }

上面是反应器的演进代码,再来看看Handler处理器的多线程演进实践。

3.3 多线程Handler处理器的实践案例

基于前面的单线程反应器的EchoHandler回显处理器的程序代码,予以改进,新的回显处理器为:MultiThreadEchoHandler。主要的升级是引入了一个线程池(ThreadPool),业务处理的代码执行在自己的线程池中,彻底地做到业务处理线程和反应器IO事件线程的完全隔离。这个实践案例的代码如下:

        package com.crazymakercircle.ReactorModel;
        //...
        class MultiThreadEchoHandler implements Runnable {
            final SocketChannel channel;
            final SelectionKeysk;
            final ByteBufferbyteBuffer = ByteBuffer.allocate(1024);
            static final int RECIEVING = 0, SENDING = 1;
            int state = RECIEVING;
            //引入线程池
            static ExecutorService pool = Executors.newFixedThreadPool(4);
            MultiThreadEchoHandler(Selector selector, SocketChannel c) throws
    IOException {
              channel = c;
              c.configureBlocking(false);
              //取得选择键,、再设置感兴趣的IO事件
              sk = channel.register(selector, 0);
              //将本Handler作为sk选择键的附件,方便事件分发(dispatch)
              sk.attach(this);
              //向sk选择键注册Read就绪事件
              sk.interestOps(SelectionKey.OP_READ);
              selector.wakeup();
            }
            public void run() {
              //异步任务,在独立的线程池中执行
              pool.execute(new AsyncTask());
          }
          //业务处理,不在反应器线程中执行
          public synchronized void asyncRun() {
              try {
                  if (state == SENDING) {
                    //写入通道
                    channel.write(byteBuffer);
                    //写完后,准备开始从通道读,byteBuffer切换成写入模式
                    byteBuffer.clear();
                    //写完后,注册read就绪事件
                    sk.interestOps(SelectionKey.OP_READ);
                    //写完后,进入接收的状态
                    state = RECIEVING;
                  } else if (state == RECIEVING) {
                    //从通道读
                    int length = 0;
                    while ((length = channel.read(byteBuffer)) > 0) {
                        Logger.info(new String(byteBuffer.array(), 0, length));
                    }
                    //读完后,准备开始写入通道,byteBuffer切换成读取模式
                    byteBuffer.flip();
                    //读完后,注册write就绪事件
                    sk.interestOps(SelectionKey.OP_WRITE);
                    //读完后,进入发送的状态
                    state = SENDING;
                  }
                  //处理结束了,这里不能关闭select key,需要重复使用
                  //sk.cancel();
              } catch (IOException ex) {
                  ex.printStackTrace();
                }
          }
          //异步任务的内部类
          class AsyncTask implements Runnable {
              public void run() {
                  MultiThreadEchoHandler.this.asyncRun();
              }
          }

        }

代码中设计了一个内部类AsyncTask,是一个简单的异步任务的提交类。它使得异步业务asyncRun方法,可以独立地提交到线程池中。另外,既然业务处理异步执行,需要在asyncRun方法的前面加上synchronized同步修饰符。

至此,多线程版本的反应器模式,实践案例的代码就演示完了。执行新版本的多线程MultiThreadEchoServerReactor服务器,可以使用之前的EchoClient客户端与之配置,完成整个回显(echo)的通信演示。

演示的输出和之前单线程版本的EchoServer回显服务器示例,是一模一样的。

反应器模式和生产者消费者模式对比

相似之处:在一定程度上,反应器模式有点类似生产者消费者模式。在生产者消费者模式中,一个或多个生产者将事件加入到一个队列中,一个或多个消费者主动地从这个队列中提取(Pull)事件来处理。

不同之处在于:反应器模式是基于查询的,没有专门的队列去缓冲存储IO事件,查询到IO事件之后,反应器会根据不同IO选择键(事件)将其分发给对应的Handler处理器来处理。

反应器模式和观察者模式(Observer Pattern)对比

相似之处在于:在反应器模式中,当查询到IO事件后,服务处理程序使用单路/多路分发(Dispatch)策略,同步地分发这些IO事件。观察者模式(Observer Pattern)也被称作发布/订阅模式,它定义了一种依赖关系,让多个观察者同时监听某一个主题(Topic)。这个主题对象在状态发生变化时,会通知所有观察者,它们能够执行相应的处理。

不同之处在于:在反应器模式中,Handler处理器实例和IO事件(选择键)的订阅关系,基本上是一个事件绑定到一个Handler处理器;每一个IO事件(选择键)被查询后,反应器会将事件分发给所绑定的Handler处理器;而在观察者模式中,同一个时刻,同一个主题可以被订阅过的多个观察者处理。

最后,总结一下反应器模式的优点和缺点。作为高性能的IO模式,反应器模式的优点如下:
· 响应快,虽然同一反应器线程本身是同步的,但不会被单个连接的同步IO所阻塞;· 编程相对简单,最大程度避免了复杂的多线程同步,也避免了多线程的各个进程之间切换的开销;
· 可扩展,可以方便地通过增加反应器线程的个数来充分利用CPU资源。反应器模式的缺点如下:
· 反应器模式增加了一定的复杂性,因而有一定的门槛,并且不易于调试。
· 反应器模式需要操作系统底层的IO多路复用的支持,如Linux中的epoll。如果操作系统的底层不支持IO多路复用,反应器模式不会有那么高效。
· 同一个Handler业务线程中,如果出现一个长时间的数据读写,会影响这个反应器中其他通道的IO处理。例如在大文件传输时,IO操作就会影响其他客户端(Client)的响应时间。因而对于这种操作,还需要进一步对反应器模式进行改进。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2468.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

企业进行高质量数据管理,实施数据治理的关键是什么?

随着数据通过各种方式创造了巨大价值&#xff0c;各领域的企业开始不断挖掘数据的作用&#xff0c;数据的重要性得到了社会各界的共同认可。像我们熟知的数据治理、数据管理、数据标准以及数据资产都是因为数据地位不断提升&#xff0c;企业开始重视起数据全生命周期流程&#…

SpringBoot笔记(一)核心内容

官网&#xff1a;https://spring.io/projects/spring-boot Spring Boot可以轻松创建独立的、基于Spring的生产级应用程序&#xff0c;它可以让你“运行即可”。大多数Spring Boot应用程序只需要少量的Spring配置。 SpringBoot功能&#xff1a; 创建独立的Spring应用程序直接嵌…

2022.11.1 固体物理

Drude Model 原子由原子核和核外电子组成 我们首先看一下不同材料的自由电子密度 知道原子数目基本就知道了核外电子的数目 如果是单位体积内的&#xff0c;知道密度&#xff0c;我们就可以知道质量&#xff0c;根据摩尔质量和阿伏伽德罗常数&#xff0c;我们就可以知道原子…

网络层——IP协议

网络层 网络层概述 网络层主要考虑数据传输的路上问题&#xff0c;在复杂的网络环境中确定一个合适的路径。 网络层设计要尽量简单&#xff0c;向上层只提供简单灵活的、无连接的、不保证可靠性的数据报服务。网络层不提供服务质量的承诺&#xff01; IP 数据报的格式 如何分…

计算机网络---第四章网络层---ipv4---选择题

9# 1IPV4在第一个4B&#xff0c;5678位。当它为0101时&#xff0c;表示首部长度为5420B&#xff0c;这也是最常见的。当它为1111时&#xff0c;表示首部长度为15460B&#xff0c;此时加上了可选字段40B 2协议字段在第三个4B的9到16位&#xff0c;表示IP的上层协议&#xff0c;…

聚观早报 | 吉利汽车拟将极氪独立上市;比亚迪斥资近50亿元造船

今日要闻&#xff1a;吉利汽车拟将极氪独立上市&#xff1b;比亚迪斥资近50亿元造船&#xff1b;华硕开设首个AI智能工厂&#xff1b;升级款Mac将于明年3月推出&#xff1b;世界互联网大会将于11月9日举行吉利汽车拟将极氪独立上市 10 月 31 日消息&#xff0c;吉利汽车午间在港…

个人设计web前端大作业——HTML+CSS华为官网首页

常见网页设计作业题材有 个人、 美食、 公司、 学校、 旅游、 电商、 宠物、 电器、 茶叶、 家居、 酒店、 舞蹈、 动漫、 服装、 体育、 化妆品、 物流、 环保、 书籍、 婚纱、 游戏、 节日、 戒烟、 电影、 摄影、 文化、 家乡、 鲜花、 礼品、 汽车、 其他等网页设计题目, A…

【Java 数据结构】顺序表

篮球哥温馨提示&#xff1a;编程的同时不要忘记锻炼哦&#xff01;我们不过是普通人&#xff0c;只不过在彼此眼中闪闪发光 目录 1、什么是顺序表&#xff1f; 2、模拟实现ArrayList 2.1 模拟实现前的约定 2.2 构造方法 2.3 add方法 2.4 contains 方法 2.5 indexOf 方法…

Python量化初学者入门必备,如何入门Python量化交易?

前言 量化可以简单分为数据管理、策略分析和策略执行三个模块&#xff0c;数据是基础&#xff0c;策略分析是核心&#xff0c;其中策略自动化执行&#xff08;算法交易&#xff09;在国内由于政策限制实施起来比较麻烦。&#xff08;文末送福利&#xff09; 从Python的角度看…

Centos8.2编译安装Nginx

一、介绍 1、Nginx 简介 Nginx 是一个高性能的 HTTP 和反向代理 WEB 服务器&#xff0c;除它之外 Apache、Tomcat、Jetty、IIS&#xff0c;它们都是 WEB 服务器&#xff0c;或者叫做 WWW &#xff08;World Wide Web&#xff09;服务器&#xff0c;相应的也都具备 WEB 服务器的…

服务器的管理IIS 6.0

IIS 6.0 和 Windows Server 2003在网络应用服务器的管理、可用性、可靠性、安全性、性能与可扩展性方面提供了许多新的功能。IIS 6.0同样增强了网络应用的开发与国际性支持。IIS 6.0和 Windows Server 2003提供了最可靠的、高效的、连接的、完整的网络服务器解决方案。 中文名I…

Node.js | MongoDB 入门讲解 Mongoose 模块的初步应用

&#x1f5a5;️ NodeJS专栏&#xff1a;Node.js从入门到精通 &#x1f5a5;️ 博主的前端之路&#xff08;源创征文一等奖作品&#xff09;&#xff1a;前端之行&#xff0c;任重道远&#xff08;来自大三学长的万字自述&#xff09; &#x1f5a5;️ TypeScript知识总结&…

【测试沉思录】10. 我们用到的3种Mock测试方案

欢迎订阅我的新专栏《现代命令行工具指南》&#xff0c;精讲目前最流行的开源命令行工具&#xff0c;大大提升你的工作效率。 作者&#xff1a;王媛媛 编辑&#xff1a;毕小烦 Mock 这个词对于测试人员来说并不陌生&#xff0c;当我们要测试的接口 A 依赖接口 B &#xff0c;可…

CSS3专题-[上篇]:过渡、2D转换、动画

目录 CSS3&#xff1a;前置特性 CSS3&#xff1a;盒子模型 CSS3&#xff1a;图片滤镜与模糊处理 blur()&#xff1a;高斯模糊 CSS3&#xff1a;计算盒子宽度calc()函数 CSS3&#xff1a;过渡效果 transition属性 2D转换&#xff1a;transform属性 translate()方法 * t…

11、Microsoft Visual Studio 2022 Installer Projects踩坑一

前言&#xff1a;VS自带的打包工具对于单文件简单程序很好用&#xff0c;对于多文件涉及到依赖其他程序就需要多一点配置了&#xff0c;之前打包过一个简单程序&#xff0c;后来程序变大后再执行生成的时候就出现问题了&#xff0c;要么执行不成功&#xff0c;要么生成成功安装…

猿创征文|后端开发工程师提升开发效率神器推荐

简介 对于现在的后端工程师来说&#xff0c;并不仅仅局限于编写代码和解决bug&#xff0c;这两个要素了。如果你目前还只是忙碌的写代码和改bug的话&#xff0c;那要深度思考一下&#xff0c;我为什么日复一日的写这些重复代码&#xff1f;这样有何意义&#xff1f;或者说&…

Java开发必须掌握的运维知识 (七)-- 性能监控日志收集,数据实时分析存储工具Influxdb实战

从这节起&#xff0c;我们要循序渐进的学习InfluxDB、Cadvisor、Grafana。这三种工具组合使用可以完成对容器的各项指标实时监控&#xff0c;也为后面的k8s打好基础。 一、InfluxDB的介绍 InfluxDB是一种用Go编写的时间序列数据库&#xff0c;主要用来存储一些时间序列的数据…

项目管理之沟通管理

目录 前言 一、沟通模型是什么&#xff1f; 二、项目沟通管理过程 三、规划沟通管理 1.规划沟通管理的ITO 2.工具与技术的使用 四、管理沟通 1.管理沟通的ITO 2.三种绩效区别 五、控制沟通 1.控制沟通的ITO 2.工具与技术的使用 总结 前言 本文章主要介绍以下项目…

JVM垃圾回收——三色标记法

目录 一、什么是三色标记 二、三色标记的过程 三、三色标记的缺点 四、垃圾回收机如何弥补三色标记的缺点 在CMS、G1这种并发的垃圾收集器收集对象时&#xff0c;假如一个对象A被GC线程标记为不可达对象&#xff0c;但是用户线程又将A对象标记为可达对象&#xff0c;那么此…

基于Session实现短信登录

目录 一、基于Session实现登录 1.1 业务流程图​编辑 二、发送短信验证码 2.1 发送短信请求方式及参数说明 2.2 业务层代码模拟发送短信 三、登录功能 3.1 短信验证的请求方式及路径 3.2 业务层代码实现用户登录 3.3 拦截器——登录验证功能 三、隐藏用户敏感信息 …