当前位置: 首页 > news >正文

深入理解网络 I/O:单 Selector 多线程|单线程模型

在这里插入图片描述

🔭 嗨,您好 👋 我是 vnjohn,在互联网企业担任 Java 开发,CSDN 优质创作者
📖 推荐专栏:Spring、MySQL、Nacos、Java,后续其他专栏会持续优化更新迭代
🌲文章所在专栏:网络 I/O
🤔 我当前正在学习微服务领域、云原生领域、消息中间件等架构、原理知识
💬 向我询问任何您想要的东西,ID:vnjohn
🔥觉得博主文章写的还 OK,能够帮助到您的,感谢三连支持博客🙏
😄 代词: vnjohn
⚡ 有趣的事实:音乐、跑步、电影、游戏

目录

  • 前言
  • 非线性 VS 线性
  • 单 Selector 非线性模型
    • 图解分析
    • 源码
    • strace 追踪
    • 小结
  • 单 Selector 线性模型
    • SelectorThread
    • SelectorGroup
    • MainThread
    • 测试单 selector 模型
    • 小结
  • 总结

前言

在之前的文章中,从阻塞 I/O:BIO、非阻塞 I/O:NIO、多路复用 select/poll、多路复用 epoll

重要的 I/O 模型也是现在市场上大部分中间件运用的模型也就是基于 I/O 多路复用:epoll,比如:Redis、RocketMQ、Nginx 等,这些地方都运用了 epoll,只不过在 RocketMQ 的实现采用了 Netty,而 Netty 也基于 epoll 这套多路复用模型进行实现的,所以在后续的这些文章会围绕 Netty 的变种,看它是如何一步步从单 Selector 非线性模型 —> 单 Selector 线性模型 —> 单 Selector Group 混杂模式 —> 多 Selector Group 主从模式一步步演练过来的,本篇博文主要围绕单 Selector 非线性模型 —> 单 Selector 线性模型进行具体的展开.

非线性 VS 线性

非线性指的就是多个线程并行执行完这一段业务,结果并不是按顺序执行的(你以为的执行结果)

线性指的就是由一个线程执行完这一段业务,结果是按顺序执行完毕的

单 Selector 非线性模型

图解分析

假设说,现在给 客户端1 分配到的是 socket fd 2,在客户端读数据时为它分配一个读事件,当它到达读的逻辑时,再给它分配一个对应的写事件,那么如果不对读事件或写事件做 cancel 的话,那么读、写事件会一直存在,也就是它会在被 epfd 所分配的链表结构中一直存放着,其实这些读写事件走完它的流程时,它相当于已完成本次的读写任务了,它没有本质上存在的意义了,如果一直存在,它就会一直被调起,重复的调用!!!

在之前的 epoll 分析时,并没有看到 epoll_ctl(epfd, EPOLL_CTL_DEL, fd, events) 的函数调用,说明在这里就是要分析使用它的地方,它在 Java 代码中相当于就是 java.nio.channels.SelectionKey#cancel 的实现

SelectionKey#cancel:请求取消此事件的通道与 selector 的注册,调用该方法返回时,该事件将无效,并且将添加到 selector 取消事件集合中,在下一个选择 select 操作期间,该事件将从所有的 selector 事件集合中删除,也就是不会再被调用

在这里插入图片描述

如上图:

  1. 主线程 Main Thread 负责接收客户端连接,由单个 selector 管理所有客户端 fds 连接,并对所连接的客户端 fd 注册读 read 事件 > 也就是调用 epoll_ctl
  2. 当 select 方法被调用时,会监听到链表中有读状态的 fd 事件,然后在 Java 程序中会调用 readHandler 方法去新开辟一个线程资源去处理,由于此时新开辟的线程和主线程并不是线程执行的,若此时不加 SelectionKey#cancel,即使已经抛出了线程,在线程执行前后这个时差上,该客户端的 fd 读事件会被重复触发.
  3. 当 readHandler 方法执行完,会向 selector 注册一个客户端 fd 写事件,也就是调用 epoll_ctl,然后下一次循环走到 select 方法被调用时,会监听到客户端写状态的 fd,然后再调用 writeHandler 方法新开辟一个线程资源去处理,由于此时新开辟的写线程也不是和主线程线性执行的,若此时不加 SelectionKey#cancel,select 方法再次被调用时,就会一直调用 writeHandler 方法去执行也就是会一直开辟新的线程去执行写的操作.
  4. 造成客户端 fd R/W 事件重复调用的原因:在主线程中不是通过线性的方式去执行读、写操作的,所以读写事件会被重复调用,解决方案:调用 SelectionKey#cancel 方法,在内核级别相当于 epoll_ctl(epfd, EPOLL_CTL_DEL, socketfd)

源码

以上图解分析的结果会通过以下源码的方式来演练,并会去观察 strace 生成的内核源码,在多线程非线性模型下,加 cancel 与 不加 cancel 方法之间的区别

package org.vnjohn.select;import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;/*** @author vnjohn* @since 2023/12/7*/
public class SelectMultiplexingSocketMultiThread {private Selector selector = null;int port = 8090;public void initServer() {try {ServerSocketChannel server = ServerSocketChannel.open();server.configureBlocking(false);server.bind(new InetSocketAddress(port));//  select、poll、*epoll 都是使用同样的方式打开selector = Selector.open();server.register(selector, SelectionKey.OP_ACCEPT);} catch (IOException e) {e.printStackTrace();}}public void start() {initServer();System.out.println("Socket Server start...");try {while (true) {while (selector.select(50) > 0) {Set<SelectionKey> selectionKeys = selector.selectedKeys();Iterator<SelectionKey> iter = selectionKeys.iterator();while (iter.hasNext()) {SelectionKey key = iter.next();iter.remove();if (key.isAcceptable()) {acceptHandler(key);} else if (key.isReadable()) {// 先在多路复用器里把 key->cancel 了System.out.println("in.....");readHandler(key);} else if (key.isWritable()) {// 1、你准备好要写什么了,这是第一步// 2、第二步你才关心send-queue是否有空间// so,读 read 一开始就要注册,但是 write 依赖 1、2 关系,什么时候用什么时候注册// 如果一开始就注册了write的事件,进入死循环,一直调起!!!key.cancel();writeHandler(key);}}}}} catch (IOException e) {e.printStackTrace();}}private void writeHandler(SelectionKey key) {new Thread(() -> {System.out.println("write handler...");SocketChannel client = (SocketChannel) key.channel();ByteBuffer buffer = (ByteBuffer) key.attachment();buffer.flip();while (buffer.hasRemaining()) {try {client.write(buffer);} catch (IOException e) {e.printStackTrace();}}try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}buffer.clear();}).start();}public void acceptHandler(SelectionKey key) {try {ServerSocketChannel ssc = (ServerSocketChannel) key.channel();SocketChannel client = ssc.accept();client.configureBlocking(false);ByteBuffer buffer = ByteBuffer.allocate(8192);client.register(selector, SelectionKey.OP_READ, buffer);System.out.println("-------------------------------------------");System.out.println("new SocketClient:" + client.getRemoteAddress());System.out.println("-------------------------------------------");} catch (IOException e) {e.printStackTrace();}}public void readHandler(SelectionKey key) {// 即便已经抛出了线程去读取,但是在时差里,这个 key->read 事件会被重复触发new Thread(() -> {System.out.println("read handler.....");SocketChannel client = (SocketChannel) key.channel();ByteBuffer buffer = (ByteBuffer) key.attachment();buffer.clear();int read;try {while (true) {read = client.read(buffer);System.out.println(Thread.currentThread().getName() + " " + read);if (read > 0) {key.interestOps(SelectionKey.OP_READ);client.register(key.selector(), SelectionKey.OP_WRITE, buffer);} else if (read == 0) {break;} else {client.close();break;}}} catch (IOException e) {e.printStackTrace();}}).start();}public static void main(String[] args) {SelectMultiplexingSocketMultiThread service = new SelectMultiplexingSocketMultiThread();service.start();}
}

strace 追踪

先将源码中 key.cannel 代码进行注释,再观察命令窗口是否会重复调用 R/W 事件操作.

1、先将代码首行 package 移除
2、通过 javac 将源文件 .java 生成 .class
3、通过命令启动服务端:strace -ff -o epoll java -Djava.nio.channels.spi.SelectorProvider=sun.nio.ch.EPollSelectorProvider SelectMultiplexingSocketMultiThread
4、通过 nc localhost 8090 模拟客户端连接

若将代码中 key.cannel 移除,在客户端命令窗口输入内容以后,服务端会读取这份内容并会注册一个写事件:EPOLL_OUT,此时的效果就是在后台会一直触发 writeHandler 方法的调用

在这里插入图片描述

在这里插入图片描述

若将代码中 key.cannel 恢复,在程序中每执行完一次读、写事件以后就会将事件注销掉,也就是它会从链表中移除这两个对应的事件,确保下一次 select 不会被再次触发调用.

在这里插入图片描述

在一定的时间差内,read 事件会被重复触发,当执行到了 writeHandler 以后,该事件已经被 cannel 掉了,此时已经不会再重复被调起了.

小结

非线性模型:由单个线程负责 accept 接收客户端连接,然后抛出不同的线程分别去处理读、写

考虑资源利用,为了充分利用好 CPU 核数
若有一个 socket fd 执行特别耗时,在一个单(线性)流程里会阻塞其他的 socket fd 处理

考虑如何处理当有 N 个 fd 同时有 R/W 处理的时候,可以分为以下几步处理:

  1. 将 N 个 FD 分组,每一组对应一个 selector,将每一个 selector 分别放到不同的线程上,selector 与线程的关系是 1:1
  2. 若是多个线程:它们分别在不同的 CPU 上执行,此时会存在多个 selector 并行,此时线程内部是线性执行的方式,最终是多个 FD 在并行的处理 accept、R/W 事件

不是说一个 selector 中的 FD 并行在多个线程里面处理,而是每一个 selector 都会保证一个 FD 在执行,且是线性处理的

以上的考虑都是基于分而治之思想,假设:程序里有 100W 个连接,有四个线程(selector)此时可以拿出其中一个 selector 就单单关注 accept 事件,然后把 accept 接收过后的客户端 FD R/W 事件分配给其他 selector 去进行处理.

单 Selector 线性模型

单个 selector 充当为一个线程 thread,来接收处理客户端的 accept 以及接收客户端读写 R/W 事件

SelectorThread

package org.vnjohn.selector.singleton;import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;/*** @author vnjohn* @since 2023/12/15*/
public class SelectorThread implements Runnable {Selector selector = null;LinkedBlockingQueue<Channel> lbq = new LinkedBlockingQueue<>();SelectorThreadGroup selectorThreadGroup = null;public SelectorThread(SelectorThreadGroup selectorThreadGroup) {try {this.selectorThreadGroup = selectorThreadGroup;selector = Selector.open();} catch (IOException e) {e.printStackTrace();}}@Overridepublic void run() {// loopwhile (true) {try {// 1.select:如果一直没有 fd,该方法会阻塞,一直没有返回,通过调用 wakeup() 唤醒System.out.println(Thread.currentThread().getName() + "   :   before select ......" + selector.keys().size());int num = selector.select();System.out.println(Thread.currentThread().getName() + "   :   after select ......" + selector.keys().size());// 2.处理 selectKeysif (num > 0) {Set<SelectionKey> selectionKeys = selector.selectedKeys();Iterator<SelectionKey> iterator = selectionKeys.iterator();while (iterator.hasNext()) {// 每一个 fd 是线性处理的过程SelectionKey key = iterator.next();iterator.remove();if (key.isAcceptable()) {// 接受客户端的过程acceptHandler(key);} else if (key.isReadable()) {readHandler(key);} else if (key.isWritable()) {}}}// 3.处理 queue runTask,队列是堆里的对象,线程的栈是独立的,堆是共享的,只有方法的逻辑,本地变量是线程隔离的if (!lbq.isEmpty()) {Channel channel = lbq.take();// accept 使用的是 ServerSocketChannelif (channel instanceof ServerSocketChannel) {ServerSocketChannel server = (ServerSocketChannel) channel;server.register(selector, SelectionKey.OP_ACCEPT);System.out.println(Thread.currentThread().getName() + " register server");// read / write 使用的是 SocketChannel} else if (channel instanceof SocketChannel) {SocketChannel client = (SocketChannel) channel;ByteBuffer buffer = ByteBuffer.allocateDirect(4096);client.register(selector, SelectionKey.OP_READ, buffer);System.out.println(Thread.currentThread().getName() + " register client:" + client.getRemoteAddress());}}} catch (IOException | InterruptedException e) {e.printStackTrace();}}}private void readHandler(SelectionKey key) {System.out.println(Thread.currentThread().getName() + "  readHandler.......");ByteBuffer buffer = (ByteBuffer) key.attachment();SocketChannel client = (SocketChannel) key.channel();buffer.clear();while (true) {try {int num = client.read(buffer);if (num > 0) {// 将读到的内容翻转,然后直接写出buffer.flip();while (buffer.hasRemaining()) {client.write(buffer);}buffer.clear();} else if (num == 0) {break;} else {// 有可能客户端断开了-异常情况System.out.println("client:" + client.getRemoteAddress() + "      closed....");key.cancel();client.close();break;}} catch (IOException e) {e.printStackTrace();}}}private void acceptHandler(SelectionKey key) {System.out.println(Thread.currentThread().getName() + "  acceptHandler.......");ServerSocketChannel server = (ServerSocketChannel) key.channel();try {SocketChannel client = server.accept();client.configureBlocking(false);// choose a selector and register !!selectorThreadGroup.nextSelector(client);} catch (IOException e) {e.printStackTrace();}}
}

第一个循环是死循环,让当前的线程一直阻塞运行处理事件

第二个循环是调用 Selector#select 方法,一直等待拿到事件,在这个里面会判断到来的事件是属于 accept | read | write,再执行对应的操作,在这里会做一个事情:当拿到 accept 新连接的客户端,再将它的连接信息绑定到对应的 selector,也就是将它添加到链表队列中

第三个非循环,只是从队列中取出元素,无元素的情况进行下一次的 select,存在元素则判断这个元素是属于服务端的 channel 还是客户端的 channel,若是服务端的 channel 则将它往 epfd 注册一个 accept 事件,若是客户端的 channel 则将它往 epfd 注册一个 read 事件,read 事件是一直存在的!!! 而写事件是由服务端主动发起的,在这里就是模拟业务的过程在 readHandler 处理过程中直接将源数据写回到客户端

SelectorGroup

使用 Selector Group 来用于分配线程执行以及 selector 调度执行,目前在此都是采用的单线程!!

该 Group 用于承担以后 Boss、Worker 角色的核心分配类

package org.vnjohn.selector.singleton;import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.Channel;
import java.nio.channels.ServerSocketChannel;
import java.util.concurrent.atomic.AtomicInteger;/*** @author vnjohn* @since 2023/12/15*/
public class SelectorThreadGroup {SelectorThread[] selectorThreads;ServerSocketChannel server = null;AtomicInteger xid = new AtomicInteger(0);public SelectorThreadGroup(int num) {// num 是线程数selectorThreads = new SelectorThread[num];// 启动多个线程,一个线程对应一个 selectorfor (int i = 0; i < selectorThreads.length; i++) {selectorThreads[i] = new SelectorThread(this);new Thread(selectorThreads[i], "SelectorThread-" + i).start();}}public void bind(int port) {try {server = ServerSocketChannel.open();server.configureBlocking(false);server.bind(new InetSocketAddress(port));// 选择一个 selector 来充当服务端的 accept 连接nextSelector(server);} catch (IOException e) {e.printStackTrace();}}/*** 无论是 ServerSocketChannel 还是 SocketChannel 都复用这个方法*/public void nextSelector(Channel channel) {//  在主线程中,取到堆里的 selectorThread 对象SelectorThread selectorThread = next();// 1.通过队列传递数据、消息selectorThread.lbq.add(channel);// 2.通过打断阻塞,让对应的线程在打断后去自己完成注册 selector,唤醒 select 阻塞的操作selectorThread.selector.wakeup();// 这个时候才有了队列,多线程模型下才能进行相互之间的通信}private SelectorThread next() {// 单个 group 多线程时,会进行轮询处理,有可能也会导致资源倾斜int index = xid.incrementAndGet() % selectorThreads.length;return selectorThreads[index];}
}

重点:channel 有可能是 server 的 ServerSocketChannel 也有可能是 client 的 SocketChannel,在这里做强制转换会出现错误,将 channel 分配的工作延迟给到队列进行 take,由阻塞链表队列来进行区分是属于服务端的 channel 还是客户端的 channel,再去执行对应的操作:accept、read、write

MainThread

以下主线程类不做任何业务 I/O 相关的工作,只是为了创建一个带有指定数量的 SelectorGroup

package org.vnjohn.selector.singleton;/*** @author vnjohn* @since 2023/12/15*/
public class MainThread {public static void main(String[] args) {// 1、创建 IO Thread(一个或多个)SelectorThreadGroup selectorThreadGroup = new SelectorThreadGroup(1);// 混杂模式:只有一个线程负责 accept,每个线程都会被分配 client,进行 R/W// SelectorThreadGroup selectorThreadGroup = new SelectorThreadGroup(3);// 2、应该把监听(8090)的 server 注册到某一个 selector 上selectorThreadGroup.bind(8090);}
}

测试单 selector 模型

1、启动主线程 main 方法,控制台输出内容,如下:

SelectorThread-0   :   before select ......0
SelectorThread-0   :   after select ......0
SelectorThread-0 register server
SelectorThread-0   :   before select ......1

2、nc localhost 8090 模拟客户端来连接服务端进行读、写操作,首先看到的是由当前 SelectorThread 进行 accept,每次都是从队列中取出元素,根据当前元素是属于服务端 channel 还是客户端 channel 进行区分,服务端 channel 则 accept,客户端 channel 则注册 read,以便于客户端从网卡到来的数据在服务端能够进行响应

新客户端到来,并且写入数据:123,控制台输出内容如下:

SelectorThread-0   :   after select ......1
SelectorThread-0  acceptHandler.......
SelectorThread-0 register client:/0:0:0:0:0:0:0:1:60036
SelectorThread-0   :   before select ......2
SelectorThread-0   :   after select ......2
SelectorThread-0   :   before select ......2
SelectorThread-0   :   after select ......2
SelectorThread-0  readHandler.......
SelectorThread-0   :   before select ......2

从返回的内容来看,当前的 SelectorThread 先是进行 accept 然后执行唤醒 Selector 的操作,此时 select 马上不会进行阻塞直接返回打印的日志内容:before select、after select,不管是不是有 accept、read、write 事件,它都会先遍历一次进行处理,在一定时间差内,你可以看到它打印了两次

若不让它打印两次,可以在 before select 打印以后进行 Thread.sleep(50);,但是这种方式是不可取的,它无法应用到高并发的场景下!!!

在正常情况下,客户端只是先进行连接,而不做 R/W 操作,它会一直阻塞在 Selector#select 这个操作下的,只有当客户端从网卡发送了数据,此时 Selector 马上就会通过中断的方式将有状态的事件存在到内核链表中,此时就能获取到 selectKey,而这个 selectKey 是作为读操作存在的,所以会调用 readHandler 进行读和写的操作!!!

小结

小结一下以上单 Selector 线性模型执行的过程:

  • Selector#select:若一直无 FD 事件存在,该方法会一直阻塞,一直不会返回结果,只能通过 Selector#wakeup 方法将 Selector 唤醒
  • accept:通过当前线程所在的 SelectorGroup 将其分配到某个线程的 SelectorThread 下
  • read、write:在 readHandler 方法执行完读操作以后,模拟业务代码,将客户端写入的数据再由服务端写回到客户端!!

每一个线程都是一个单独的 Selector,若在多线程的情况下,以上的程序可能在并发场景下会被分配到多个 Selector 上

注意的要求是:每个客户端只能绑定到一个 Selector 上,不存在多线程顺序交互的问题,简而言之,就是说每个客户端连接进来它都要以线性的方式进行执行

再言之,单线程模型不能充分的利用到多核多 CPU 资源,同时在 100W 客户端进来时,这种模型跑起来会非常的慢,对于一个高并发系统设计而言,是一定不能够被接受的

所以这种模型仍然会存在问题,在下篇会介绍如何去解决这种应用在多线程场景下,客户端不进行乱串执行以及资源有效利用的问题!!

总结

该篇博文主要介绍多路复用模型 Epoll 下单 Selector 多线程与单线程之间的区别,先是说明了在单 Selector 非线性模型下-多线程会造成读、写事件重复触发的问题, 通过图解和 strace 追踪日志的方式说明了它的缺点,解决事件重复触发问题通过 SelectionKey#cannel 来进行解决,莫须有这种方式不可取会造成假死线程|资源停滞不释放问题,后者介绍了单个 Selector 单 Group 解决这种假死资源的存在问题,结合 Selector#wakeup + 链接阻塞队列的方式来完成,在单 Selector 线性模型下是可取的,但是为了应用多核多 CPU 资源,在多线程场景下这种模型会造成一个客户端在多个 Selector 中乱串执行的问题,希望您能够喜欢,感谢三连支持!

参考文献:

  1. 《UNIX网络编程 卷1:套接字联网API(第3版)》— [美] W. Richard Stevens Bill Fenner Andrew M. Rudoff

学习帮助文档:

  • man pages:yum install man
  • pthread man pages:yum -y install man-pages

🌟🌟🌟愿你我都能够在寒冬中相互取暖,互相成长,只有不断积累、沉淀自己,后面有机会自然能破冰而行!

博文放在 网络 I/O 专栏里,欢迎订阅,会持续更新!

如果觉得博文不错,关注我 vnjohn,后续会有更多实战、源码、架构干货分享!

推荐专栏:Spring、MySQL,订阅一波不再迷路

大家的「关注❤️ + 点赞👍 + 收藏⭐」就是我创作的最大动力!谢谢大家的支持,我们下文见!

相关文章:

深入理解网络 I/O:单 Selector 多线程|单线程模型

&#x1f52d; 嗨&#xff0c;您好 &#x1f44b; 我是 vnjohn&#xff0c;在互联网企业担任 Java 开发&#xff0c;CSDN 优质创作者 &#x1f4d6; 推荐专栏&#xff1a;Spring、MySQL、Nacos、Java&#xff0c;后续其他专栏会持续优化更新迭代 &#x1f332;文章所在专栏&…...

Kafka Avro序列化之三:使用Schema Register实现

为什么需要Schema Register 注册表 无论是使用传统的Avro API自定义序列化类和反序列化类 还是 使用Twitter的Bijection类库实现Avro的序列化与反序列化,这两种方法都有一个缺点:在每条Kafka记录里都嵌入了schema,这会让记录的大小成倍地增加。但是不管怎样,在读取记录时…...

EasyExcel

概述 GitHub - alibaba/easyexcel: 快速、简洁、解决大文件内存溢出的java处理Excel工具 EasyExcel官方文档 - 基于Java的Excel处理工具 | Easy Excel EasyExcel是一个基于Java的、快速、简洁、解决大文件内存溢出的Excel处理工具。 他能让你在不用考虑性能、内存的等因素的…...

java 探针两种模式实战

分为两种 程序运行前的agent&#xff1a;premain 程序运行中的agent&#xff1a;agentmain 在程序运行前的agent javaagent是java命令的一个参数&#xff0c;所以需要通过-javaagent 来指定一个jar包&#xff08;就是我们要做的代理包&#xff09;能够实现在主程序运行前来执行…...

uniGUI之MASK遮罩

在页面进行后台数据库操作的时候&#xff0c;不想 用户再进行 页面上的 其他操作&#xff0c;这时候就要 将页面 遮罩。例如UniDBGrid有LoadMask属性。 1]使用ScreenMask函数 2]JS调用 3]一个控件控制遮罩另一个控件(如Button遮罩UniDBGrid) //很简单&#xff0c;本例子就是告…...

DevOps云原生创建devops流水线(微服务项目上传git,打包镜像,部署k8s)

开发和运维人员的解决方案 一、中间件的部署&#xff08;Sentinel/MongoDB/MySQL&#xff09; 二、创建DevOps工程 邀请成员 三、创建流水线 四、编辑流水线 ①、拉取代码&#xff08;若失败&#xff0c;则将制定容器改为maven&#xff09; 若失败&#xff0c;则将命令改…...

【vim 学习系列文章 13.1 -- 自动命令autocmd 根据文件类型设置vim参数】

文章目录 autocmd 根据文件类型配置vim参数vim 文本类型 autocmd 根据文件类型配置vim参数 在 Vim 中&#xff0c;你可以使用 autocmd &#xff08;自动命令&#xff09;来根据文件类型自动执行特定的函数。首先&#xff0c;你需要定义这些函数&#xff0c;然后使用 autocmd 与…...

算法基础概念之数据结构

邻接表 每个点作为头节点接一条链表 链表中元素均为该头节点指向的点 优先队列 参数: ①储存元素类型 ②底层使用的存储结构(一般为vector) ③比较方式(默认小于)...

解决ES伪慢查询

一、问题现象 服务现象 服务接口的TP99性能降低 ES现象 YGC&#xff1a;耗时极其不正常, 峰值200次&#xff0c;耗时7sFULL GC&#xff1a;不正常,次数为1但是频繁&#xff0c;STW 5s慢查询&#xff1a;存在慢查询5 二 解决过程 1、去除干扰因素 从现象上看应用是由于某种…...

关于Ubuntu22.04恢复误删文件的记录

挂载在Ubuntu22.04下的固态盘有文件被误删了&#xff0c;该固态盘是ntfs格式的。 在网上找了很多教程&#xff0c;最后决定用TestDisk工具进行恢复。 现记录如下&#xff1a; Ubuntu安装testdisk sudo apt-get install testdisk运行testdisk sudo testdisk得到 我选择的是…...

Docker笔记:Docker Swarm, Consul, Gateway, Microservices 集群部署

关于 Consul 服务 Consul是Go语言写的开源的服务发现软件Consul具有服务发现、健康检查、 服务治理、微服务熔断处理等功能 Consul 部署方式1: 直接在linux 上面部署 consul 集群 1 &#xff09;下载 在各个服务器上 下载 consul 后解压并将其目录配置到环境变量中&#xff…...

浅析AI视频分析与视频管理系统EasyCVR平台及场景应用

人工智能的战略重要性导致对视频智能分析的需求不断增加。鉴于人工智能视觉技术的巨大潜力&#xff0c;人们的注意力正在从传统的视频监控转移到计算机视觉的监控过程自动化。 1、什么是视频分析&#xff1f; 视频分析或视频识别技术&#xff0c;是指从视频片段中提取有用信息…...

跨站点分布式多活存储建设方案概述

1-伴随着私有云、海量非结构数据的爆炸性增长&#xff0c;软件定义存储已经成为用户构建“敏捷IT” 架构的数据基石&#xff0c;同时越来越多的关键业务接入“敏捷IT” 架构。在分布式软件定义存储的产品架构下&#xff0c;怎样既保证对爆炸数据量的平稳承接&#xff0c;又能对…...

Github 2023-12-16开源项目日报Top10

根据Github Trendings的统计&#xff0c;今日(2023-12-16统计)共有10个项目上榜。根据开发语言中项目的数量&#xff0c;汇总情况如下&#xff1a; 开发语言项目数量Python项目2非开发语言项目2TypeScript项目1Jupyter Notebook项目1Go项目1PHP项目1JavaScript项目1C#项目1 精…...

c++ 中多线程的相关概念与多线程类的使用

1、多线程相关概念 1.1 并发、并行、串行 并发&#xff08;Concurrent&#xff09;&#xff1a;并发是指两个或多个事件在同一时间间隔内运行。在操作系统中&#xff0c;是指一个时间段中有几个程序都处于已启动运行到运行完毕之间&#xff0c;且这几个程序都是在同一个处理机…...

深入理解 hash 和 history:网页导航的基础(下)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…...

腾讯文档助力CRM集成:无代码连接电商与广告

腾讯文档API的简介与优势 腾讯文档API是一个强大的工具&#xff0c;它允许企业通过简单的无代码开发来实现与电商平台和客服系统的智能连接。这种连接不仅提高了工作效率&#xff0c;还优化了数据管理。使用腾讯文档智能表&#xff0c;商家可以享受多样的列类型、多维视图展示…...

学习使用echarts漏斗图的参数配置和应用场景

学习使用echarts漏斗图的参数配置和应用场景 前言什么是漏斗图漏斗图的特点及应用场景漏斗图的特点漏斗图常见的的应用场景&#xff1a; echarts中漏斗的常用属性echart漏斗代码美化漏斗图样式1、设置标题字体大小2、设置标签样式3、设置漏斗图为渐变颜色4、设置高亮效果5、设置…...

npm ,yarn 更换使用国内镜像源,阿里源,清华大学源

在平时开发当中&#xff0c;我们经常会使用 Npm&#xff0c;yarn 来构建 web 项目。但是npm默认的源的服务器是在国外的&#xff0c;如果没有梯子的话。会感觉特别特别慢&#xff0c;所以&#xff0c;使用国内的源是非常有必要的。 在这里插入图片描述 Nnpm&#xff0c; yarn …...

vue+react题集整理

1.Typescript中 interface 和 type 的差别是什么&#xff1f; interface只能用来描述对象类型 type可以描述任何类型组合 type后边需要有 interface后边没有 当多次使用相同名称定义一个 interface 时&#xff0c;它们会自动合并为一个接口。同名属性的不能进行类型覆盖修改&am…...

线程池ThreadPoolExecutor详解

线程池ThreadPoolExecutor详解 大家好&#xff0c;我是微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;今天&#xff0c;让我们深入研究Java中线程池的强大工具——ThreadPoolExecutor&#xff0c;解析它的工作原理、配置参数…...

elasticsearch|大数据|kibana的安装(https+密码)

前言&#xff1a; kibana是比较好安装的&#xff0c;但https密码就比较麻烦一些了&#xff0c;下面将就如何安装一个可在生产使用的kibana做一个简单的讲述 一&#xff0c; kibana版本和下载地址 这里我想还是强调一下&#xff0c;kibana的版本需要和elasticsearch的版本一…...

vue javascript tree 层级数据处理

层级数据是有父子关系的数组&#xff0c;示例&#xff1a; const treeData [{id: 1b7e8e98cb1d4a1f81e4fe2dfd9a8458,name: 层级1,parentId: null,children: [{id: 0d45dd5bb4c14d64a3ab0b738add4b24,name: 层级1-1,parentId: 1b7e8e98cb1d4a1f81e4fe2dfd9a8458,children: [{…...

WPF仿网易云搭建笔记(4):信息流控制之消息订阅

文章目录 专栏和Gitee仓库前言消息订阅最简单的案例简单用例父组件订阅子组件回调 结果 消息订阅机制消息token是A还是B?传递消息的载体。双重token重复订阅问题 结论 专栏和Gitee仓库 WPF仿网易云 Gitee仓库 WPF仿网易云 CSDN博客专栏 前言 上一篇文章中&#xff0c;我们简单…...

持续集成交付CICD:GitLabCI操作Harbor仓库

目录 一、实验 1.GitLabCI操作Harbor仓库 二、问题 1.gitlab-runner连接docker daemon报错 一、实验 1.GitLabCI操作Harbor仓库 &#xff08;1&#xff09;修改GitLabCI共享库代码并提交到mater CI.yaml .pipelineInit:tags:- buildstage: .prevariables:GIT_CHECKOUT: …...

[C++]——学习模板

了解模板——初阶 前言&#xff1a;一、模板1.1 什么是模板1.2 模板的概念1.3 模板可以做什么1.4 泛型模板 二、函数模板2.1 函数模板概念和格式2.2 函数模板原理2.3 函数模板实例化2.3.1 隐式实例化2.3.2 显式实例化 2.4 模板参数的匹配原则2.5 函数模板声明定义分离 三、类模…...

大数据技术14:FlinkCDC数据变更捕获

前言&#xff1a;Flink CDC是Flink社区开发的flink-cdc-connectors 组件&#xff0c;这是⼀个可以直接从 MySQL、PostgreSQL 等数据库直接读取全量数据和增量变更数据的 source 组件。 https://github.com/ververica/flink-cdc-connectors 一、CDC 概述 CDC 的全称是 Change …...

SpringDataRedis 基本使用

1.1 简介 1.1.1 概述 Spring Data 中有一个成员 Spring Data Redis&#xff0c;他提供了 RedisTemplate 可以在 Spring 应用中更简便的访问 Redis 以及异常处理及序列化&#xff0c;支持发布订阅等操作。 1.2 RedisTemplate 常见 API   RedisTemplate 针对 jedis 客户端中大…...

蓝牙物联网智慧工厂解决方案

蓝牙物联网智慧工厂解决方案是一种针对工厂管理的智能化解决方案&#xff0c;通过蓝牙、物联网、大数据、人工智能等技术&#xff0c;实现工厂人员的定位、物资的定位管理、车间的智慧巡检、智慧安防以及数据的可视化等功能。 蓝牙物联网智慧工厂解决方案构成&#xff1a; 人员…...

html的学习笔记

开发工具&#xff1a;vscode 文字标签 h1:一级标题&#xff0c;h2&#xff1a;二级标题h6 p&#xff1a;段落标签 hr&#xff1a;分隔线 br&#xff1a;换行 strong/b&#xff1a;文字加粗 ins/u:下划线 em/i&#xff1a;倾斜 del/s&#xff1a;删除线 媒体标签 图片…...

专做畜牧招聘网站的/热狗seo优化外包

http://bbs.hsw.cn/thread-794950-1-1.html转载于:https://www.cnblogs.com/chinhr/archive/2009/04/30/1446662.html...

网站和网页的设计原则/百度投诉中心在线申诉

– Start 点击此处观看本系列配套视频 废话少说&#xff0c;直接上代码。 package shangbo.kafka.example9;import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class App {…...

徐州网站建设优化宣传/百度平台客服人工电话

一、什么是生产线3D可视化解决方案&#xff1f; 生产线3D可视化解决方案&#xff0c;是结合物联网监控系统&#xff08;智慧工厂&#xff09;、虚实联动与三维建模先进技术&#xff0c;以三维立体模式呈现出来&#xff0c;使得界面直观、简单&#xff0c;便于监控人员识别异常信…...

嘉兴网站建设方案/产品市场营销策划书

文章目录1.Scala入门1.1Windows安装Scala&#xff08;Scala JDK&#xff09;1.2 Linux安装Scala1.3 IDEA插件安装1.4 IDEA创建项目2. 变量和数据类型2.1 变量和常量2.2 字符串输出2.3 键盘输入2.4 数据类型2.5 整数类型&#xff08;Byte、Short、Int、Long&#xff09;2.6 浮点…...

360未经证实的网站如何做/百度地图网页版

关注 M r . m a t e r i a l , \color{Violet} \rm Mr.material\ , Mr.material ,...

做看电视电影的网站赚钱/广告投放渠道有哪些

版权声明&#xff1a;原创作品&#xff0c;谢绝转载&#xff01;否则将追究法律责任。 一个Objective-c类定义了一个对象结合数据相关的行为。有时候&#xff0c;这使得他有意义的表达单个任务或者单元的行为。而不是集合的方法。 blocks是语言的特性&#xff0c;我们可以在C C…...