【redis】redis的多线程和IO多路复用

news2024/10/23 20:04:33

【redis】redis的多线程和IO多路复用

  • 【一】前言
  • 【二】Redis单线程和多线程问题的背景
    • 【1】Redis的单线程
    • 【2】Redis为什么选择单线程?
    • 【3】Redis为什么开始利用多核?
    • 【4】Redis当前的性能瓶颈
    • 【5】Redis的主线程如何和IO线程协同
  • 【三】IO多路复用的理解
    • 【1】什么是IO多路复用
    • 【2】深入理解Redis的IO多路复用
      • (1)I/O多路复用机制
      • (2)为什么从bio,nio到io多路复用
      • (3)如何解决服务器主线程阻塞的问题?
      • (4)如何解决多线程资源浪费的问题
      • (5)如何解决NIO模型cpu不友好的问题?
      • (6)IO多路复用的具体实现
        • 1-》select方法
        • 2-》poll方法
        • 3-》epoll方法
        • 4-》三个方法的对比

【一】前言

Redis的单线程主要处理键值对读写,但其他功能如持久化、异步删除由额外线程执行。Redis选择单线程避免上下文切换和线程竞争,利用多路复用技术提高处理多个客户端请求的能力。Redis4.0引入异步删除以减少主线程阻塞。Redis6/7开始使用多IO线程处理网络请求,主线程仍单线程处理命令操作以保证原子性。IO多路复用(如epoll)配合主线程和IO线程协同,提升了Redis的性能。

【二】Redis单线程和多线程问题的背景

Redis版本迭代历史
在这里插入图片描述

【1】Redis的单线程

(1)主要是指Redis的网络IO和键值对读写是由一个线程来完成的,Redis在处理客户端的请求时包括获取(socket读)、解析、执行、内容返回(socket写)等都由一个顺序串行的主线程处理,这就是所谓的“单线程”。这也是Redis对外提供键值存储服务的主要流程。

(2)但Redis的其他功能,比如持久化RDB、AOF、异步删除、集群数据同步等等,其实是由额外的线程执行的。

(3)Redis命令工作线程是单线程的,但是整个Redis来说是多线程的
在这里插入图片描述

【2】Redis为什么选择单线程?

(1)基于内存操作:Redis的所有数据都存在内存中,因此所有的运算都是内存级别的,性能比较高;
(2)数据结构简单:Rdis的数据结构是专门设计的,而这些简单的数据结构的查找和操作的时间大部分复杂度都是O(1),因此性能比较高
(3)多路复用和非阻塞I/O:Redis使用I/O多路复用功能来监听多个socket连接客户端,这样就可以使用一个线程连接来处理多个请求,减少线程切换带来的开销,避免了IO阻塞操作
(4)避免上下文功换:因为是单线程模型,因此就避免了不必要的上下文切换和多线程竞争,这就省去了多线程切换带来的时间和性能上的消耗

Redis采用的是基于内存的采用的是单进程单线程模型的 KV 数据库,由C语言编写,官方提供的数据是可以达到100000+的QPS(每秒内查询次数)。

这个数据不比采用单进程多线程的同样基于内存的 KV 数据库 Memcached 差!
在这里插入图片描述
上图是Redis官方的测试数据,横轴表示连接数,纵轴表示QPS。

如果Redis采用的是多线程,那么CPU会在多个线程之间来回切换,在如此高的QPS下,多个线程的切换时特别频繁的,十分消耗CPU。

假设我们有三个连接同时执行下面的操作:

incr count

由于Redis使用单线程来处理命令的,所以一条命令来了之后不会立即执行,而是将命令加到一个队列中,然后逐个被执行,这样就能保证在不加锁的情况下保证不会产生并发问题。

如果Redis采用多线程执行命令,那么对于三个连接同时执行incr操作的时候,就需要添加锁来保证线程的安全性了,会大大降低执行效率。

Redis采用非阻塞IO,使用epoll作为IO多路复用技术的实现,再加上Redis自身的树碱处理模型将epoll中的连接、读写、关闭都转为事件,不在网络IO上浪费过多的事件。

当然了,单线程机制也不是万能的,也会存在一个的问题:对于每个命令的执行事件是有要求的。如果某个命令执行事件过长,会导致其他命令被阻塞,对于Redis这种高性能的服务来说是致命的。

所以即使Redis性能很高,我们也要合理的去使用Redis,不合理的使用也会导致很多问题。

【3】Redis为什么开始利用多核?

使用多核的必要性

(1)Redis4.0引入异步删除,在删除大key时,单线程会出现阻塞(类似synchronized锁)

unlink key
flushdb async
flushall async

(2)利用惰性删除避免阻塞。因为Redis是单个主线程处理,而lazy free的本质就是把某些cost(主要时间复制度,占用主线程cpu时间片)较高删除操作,从redis主线程剥离让bio子线程来处理,极大地减少主线阻塞时间。从而减少删除导致性能和稳定性问题。

【4】Redis当前的性能瓶颈

(1)redis主要的性能瓶颈是内存或者网络IO,而不太可能是cpu
(2)虽然有些命令操作可以用后台线程或子进程执行(比如数据删除、快照生成、AOF重写)。但是从网络IO处理到实际的读写命令处理,都是由单个线程完成的。
(3)单个主线程处理网络请求的速度跟不上底层网络硬件的速度,为了应对这个问题采用多个IO线程来处理网络请求,提高网络请求处理的并行度,Rdis6/7就是采用的这种方法。

但是,Redis的多IO线程只是用来处理网络请求的,对于读写操作命令Redis仍然使用单线程来处理。这是因为Redis处理请求时,网络处理经常是瓶颈,通过多个IO线程并行处理网络操作,可以提升实例的整体处理性能。而继续使用单线程执行命令操作,就不用为了保证Lua脚本、事务的原子性,额外开发多线程互斥加锁机制,这样一来Redis线程模型实现就简单了

【5】Redis的主线程如何和IO线程协同

既然瓶颈是网络IO,Redis的主线程如何和IO线程协同?

(1)服务端和客户端建立Socket连接,并分配处理线程。
首先,主线程负责接收建立连接请求。当有客户端请求和实例建立Socket连接时,主线程会创健和客户的连接,并把Socket放入全局等待队列中。紧接看主线程通过轮询方法把Socket连接分配给IO线程
(2)IO线程读取并解折请求。
主线程一旦把Socket分配给IO线程,就会进入阻塞状态,等待IO线程完成客户端请求读取和解析。因为有多个IO线程在并行处理,所似这个过程很快完成
(3)主线程执行请求操作。
等IO线程解析完请求,主线程以单线程的方式执行这些命令操作。
(4)IO线程回写Socket和主当主线程清空全局队列。
主线程执行完请求操作后,会把需要返回的结果写入缓冲区,然后主线程会阻塞等待IO线程,把这些结果回写到Socket中(多个线程在并发执行),并返回给客户端。等到lO线程回写Socket完毕,主线程会清空全局队列,等待客户端的后续请求。

在这里插入图片描述

【三】IO多路复用的理解

【1】什么是IO多路复用

当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。

在这里插入图片描述
IO多路复用的解释,即使用一个或一组线程处理多个tcp连接
(1)IO,指代网络IO,需要进行模态转换的读写操作
(2)多路,多个客户端连接(socket)
(3)复用,复用一个或多个线程

一个服务端进程可以同时处理多个socket,IO多路复用模型包括select,poll,epoll

在单个线程通过记录跟踪每一个Sockek(I/O流)的状态来同时管理多个I/O流。一个服务端进程可以同时处理多个套接字描述符。目的是尽量多的提高服务器的吞吐能力。

将用户socket对应的文件描述符(FileDescriptor)注册进epoll,然后epoll监听哪些socket上有消息到达,这样就避免了大量的无用操作。此时的socket应该采用非阻塞模式。这样整个过程只在调用select.、poll、epoll这些调用的时候才会阻塞,收发客户消息是不会阻塞的,整个进程或者线程就被充分利用起来,这就是事件驱动,所谓的reactor模式。

因此IO多路复用+epoll也是Redis速度快的根本保证。redis的多线程即
(1)单个工作线程保证线程安全和高性能
(2)多个IO线程解决网络IO的问题
在这里插入图片描述

【2】深入理解Redis的IO多路复用

(1)I/O多路复用机制

Redis利用epoll来实现IO多路复用,将连接信息和事件放到队列中,依次放到文件事件分派器,事件分派器将事件分发给事件处理器。
在这里插入图片描述

Rdis是跑在单线程中的,所有的操作都是按照顺序线性执行的,但是由于读写操作等待用户输入或输出都是阻塞的,所以I/O操作在一般情况下往往不能直接返回,这会导致某一文件的I/O阻塞导致整个进程无法对其它客户提供服务,而I/O多路复用就是为了解决这个问题而出现

所谓I/O多路复用机制,就是说通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或写就绪),能够通知程序进行相应的读写操作。这种机制的使用需要select、pol川、epol来配合。多个连接共用一个阻塞对象,应用程序只需要在一个阻塞对象上等待,无需阻塞等待所有连接。当某条连接有新的数据可以处理时,操作系统通知应用程序,线程从阻塞状态返回,开始进行业务处理。

Redis服务采用Reactor的方式来实现文件事件处理器(每一个网络连接其实都对应一个文件描述符

Redis基于Reactor模式开发了网络事件处理器,这个处理器被称为文件事件处理器。它的组成结构为4部分:
(1)多个套接字
(2)IO多路复用程序
(3)文件事件分派器
(4)事件处理器。
因为文件事件分派器队列的消费是单线程的,所汉Redis才叫单线程模型

(2)为什么从bio,nio到io多路复用

在bio场景下,当client1请求并和server建立连接后,server在accept和read处发生阻塞,此时新的client2建立连接的请求无法被server处理(处于阻塞中)

注意:这里的应用程序指代的是server端

(3)如何解决服务器主线程阻塞的问题?

考虑使用子线程来处理新的客户端连接和请求。在连接socket后创建线程来处理read操作

client代码

Socket socket = new Scoket("127.0.0.1",6379);
OutputStream outputStream == socket.getOUtputStream();
// 获取标准输入到msg
outputStream.write(msg.getBytes());

server代码

ServerSocket serverSocket = new ServerSocket(6379);
while(true){
	Socket socket = serverSocket.accept(); // 阻塞等待连接
	new Thread(() -> {
		try{
			InputStream inputStream = socket.getInputStream();
			// 从input中获取输入,阻塞在读请求
			socket.close();
		}
	},Thread.currentThread().getName()).start();
}

(4)如何解决多线程资源浪费的问题

多线程模型要求每到来一个请求,就切换到内核态创建对应的的进程。新的进程和上下文切换都会产生性能的损耗

(1)池化技术,但是实际的线程数量未知
(2)NIO,使read方法不阻塞

NIO的特点是使用轮询替代阻塞
在这里插入图片描述
在客户端,如果kernal中的数据没有准备好,则会返回error

在NIO中一切都是非阻塞的
(1)accept方法非阻塞,如果没有客户端连接则返回无连接标识
(2)read方法非阻塞,如果read读不到数据返回空间中标识,有数据则只阻塞read读取数据的时间
(3)nio中服务端只有一个线程,对socker数组进行遍历

客户端代码和bio下相同

Socket socket = new Scoket("127.0.0.1",6379);
OutputStream outputStream == socket.getOUtputStream();
// 获取标准输入到msg
outputStream.write(msg.getBytes());

服务端代码
(1)通过ServerSocketChannel的configureBlocking(false)方法将获得连接设置为非阻塞的。此时若没有连接,accept会返回null
(2)通过SocketChannel的configureBlocking(false)方法将从通道中读取数据设置为非阻塞的。若此时通道中没有数据可读,read会返回-1

static ArrayList<SocketChannel> socketList = new ArrayList<>(); // 容纳socket数组
static ByteBuffer byteBuffer = ByteBuffer.allocate(1024); // 输入缓冲

ServerSocketChannel serverSocket = new ServerSocketChannel.open();
serverSocket.bind(new InetSocketAddress(127.0.0.1,6379));
serverSocket.configureBlocking(false); // jdk1.4开始兼容阻塞模式,此处设置为非阻塞

while(true){
	for (SocketChannel element : socketList){
        int read  = element.read(byteBuffer); //不会阻塞
        if(read > 0){
            byteBuffer.flip();
            byte[] bytes = new byte[read];
            byteBuffer.get(byte);
            byteBuffer.clear();
        }
    }
    SocketChannel socketChannel = serverSocket.accpet(); // 不会阻塞
    if(socketChannel != null){
        sockerChannel.configureBlocking(false);
        socketList.add(socketChannel) // 连接成功
    }
}

(5)如何解决NIO模型cpu不友好的问题?

NIO模型存在的问题
(1)socketlist遍历存在性能瓶颈(空socket仍旧会轮询),cpu不友好
(2)对socket状态的判断仍旧需要切换模态(查询socket仍旧是通过read syscall),开销较大、
(3)轮询会不断询问内核,占用了大量cpu时间,系统资源利用率较低

如果通过linux内核完成上述工作(遍历socket),不需要模态切换,内核本身是非阻塞的。

多路复用就是使用一个信道传输多个信号,多个网络连接使用一个线程,多个socket使用一个通道是在内核和驱动层实现的

IO多路复用模型
在这里插入图片描述

基于I/O复用模型:多个连接共用一个阻塞对象,应用程序只需要在一个阻塞对象上等待,无需阻塞等待所有连接。当某条连接有新的数据可以处理时,操作系统通知应用程序,线程从阻塞状态返回,开始进行业务处理。

Reactor模式,是指通过一个或多个输入同时传递给服务处理器的服务请求的事件驱动处理模式。服务端程序处理传入多路请求,并将它们同步分派给请求对应的处理线程,Reactor模式也叫Dispatcher模式。即I/O多了复用统一监听事件,收到事件后分发(Dispatch给某进程),是编写高性能网络服务器的必备技术。Reactor模式中有2个关键组成:

(1)Reactor,Reactor在一个单独的线程中运行,负贵监听和分发事件,分发给适当的处理程序来对IO事件做出反应。它就像公司的电话接线员,它接听来自客户的电话并将线路转移到适当的联系人
(2)Handlers,处理程序执行I/O事件要完成的实际事件,类似于客户想要与之交谈的公司中的实际办理人。Reactor通过调度适当的处理程序来响应I/O事件,处理程序执行非阻塞操作
(3)
在这里插入图片描述

(6)IO多路复用的具体实现

1-》select方法

在这里插入图片描述
select函数监视的文件描述符分3类,分别是readfds、writefds和exceptfds,将用户传入的数组拷贝到内核空间

(1)调用后select函数会阻塞,直到有描述符就绪(有数据可读、可写、或者有except)或超时(timeout指定等待时间,如果立即返回设为null即可),函数返回
(2)当select函数返回后,可以通过遍历fdset,来找到就绪的描述符。

分析select函数的执行流程:
(1)select是一个阻塞函数,当没有数据时,会一直阻塞在select调用
(2)当有数据时会将rset中对应的位置为1
(3)selecti函数返回,不再阻塞
(4)遍历文件描述符数组,判断哪个fd被置位
(5)读取数据,然后处理

下面的示例代码
(1)实际是将之前java示例的代码从用户态搬到内核态运行
(2)将fd拷贝到内核态,避免模态切换

sockfd = socket(AF_INET,SOCK_STREAM,0);
memse(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(2000);
addr.sin_addr.s_addr = INADDR_ANY;
bind(sockfd,(struct sockaddr*)&addr, sizeof(addr));
listen (sockfd, 5);
for(i=0;i<5;i+){
	// 模拟5个客户端连接
    memset(&client,0,sizeof (client)); 
    addrlen = sizeof(client);
    fds[i] = accept(sockfd, (struct sockaddr*)&client, &addrlen);
    if(fds[i]>max)
        max = fds[i]; //找到一个最大的文件描述符,在下面select中限制范围
}

while(1){
	FD_ZERO(&rset); // 重置rset为0
    for(i=0;i<5;i++){
        FD_SET(fds[i],&rset); // &rset是个bitmap.如果5个文件描述符分别是1,2,4,5,7,那么这个bitmap为01101101
    }
	puts("round again");  
    select(max+1,&rset,NULL,NULL,NULL); // select是一个系统调用,它会堵塞直到有数据发送到socket,select会把&rset相应的位置置位,但并不会返回哪个socket有数据

for(i=0;i<5;i++){
    // 用户态只要遍历&rst,看哪一位被置位了,不需要每次调用系统调用来判断了,效率有很大提升,遍历到被置位的文件描述符就进行读取
    if (FD_ISSET(fds[i],&rset)){
        memset(buffer, 0, MAXBUF);
        read(fds[i], buffer, MAXBUF);
        puts(buffer);
    }
}

select函数的缺点
(1)bitmap默认大小为1024(代码实现较早1983年),虽然可以调整但还是有限度的
(2)rset每次循环都必须重新置位为0,不可重复使用
(3)尽管将rset从用户态拷贝到内核态由内核态判断是否有数据,但是还是有拷贝的开销
(4)当有数据时select就会返回,但是selecti函数并不知道哪个文件描述符有数据了(仅仅置位)。后面还需要再次对文件描述符数组进行遍历,效率比较低

2-》poll方法

函数原型

int poll(struct pollfd *fds,nfds_t nfds,int timeout);
struct pollfd
    int fd; //file descriptor 
    short events; // 读或者写事件
    short revents; // 文件描述符有事件则置1 
};

poll的执行流程:
(1)将五个fd从用户态拷贝到内核态
(2)poll为阻塞方法,执行poll方法,如果有数据会将fd对应的revents置为POLLIN
(3)poll方法返回
(4)循环遍历,查找哪个fd被置位为POLLIN了
(5)将revents重置为0便于复用
(6)对置位的fd进行读取和处理

解决的问题
(1)不在使用bitmap,消除了client的大小限制(1024)
(2)rset不可复用的问题(每次置0)

代码实现

for(1=0;1<5;1++) //模拟5个客户端连接
{
    memset(&client,0,sizeof (client));
    addrlen = sizeof(client);
    pollfds[i].fd = accept(sockfd,(struct sockaddr)&client,&addr);
    pollfds[i].events = POLLIN; //这5个socket只读事件
}c
sleep(1);
while(1){
    puts("round again");
    poll(po11fds,5,50000); // poll中传入pollfds数组,交给内核判断是否有事件发生,如果哪个发生事件则revents置1
    for(1=0; 1<5; 1++){ //遍历数组,找到哪个pollfd有事件发生
        if (pollfds[i].revents & POLLIN){
        pollfds[i].revents=O; // 找到后revents置0
        memset(buffer,0,MAXBUF);
        read(pollfds[i].fd,buffer,MAXBUF); //读取数据
        puts(buffer);
    }
}

缺点
(1)本质上还是select方法
(2)拷贝fd到内核态仍旧存在开销
(3)仍旧需要遍历socket(O(n)复杂度)

3-》epoll方法

函数原型

// 创建一个epoll的句柄,size表示内核监听的数目。并非最大限制,而是内核初始分配数据结构的建议值
int epoll_create(int size); 

// 向内核添加,修改或删除要监控的fd
int  epoll_ctl(
    int epfd, // epfd是epoll_create的返回值
    1nt op, // 表示op操作,用三个宏来表示:添加EPOLL.CTL_ADD,除EPOLL.CTL_DEL,修改EPOLL.CTL_MOD
    int fd, //fd:是需要监听的fd(文件描述符)
	struct epoll_event *event // 告诉内核要监听什么事
); 
struct epoll_event
    // events可以是以下几个宏的熊合:
	// EPOLLIN:表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
	// EPOLLOUT:表示对应的文件描述符可以写;
    __uint32_t events;
    epoll_data_t data; // User data variable 
}

// 类似select调用
int epoll_wait(int epfd,
    struct epoll event *events,
    int maxevents, // 最多返回的事件数
    int timeout
);

具体实现
(1)socket生命周期只会从用户态到内核态拷贝一次
(2)使用event通知级值,每次socket有数据会主动通知内核加入就绪列表,不需要遍历所有socket

struct epoll_event events[5];
int epfd epoll_create(10); // epoll_create在内核开辟一块内存空间,用来存放epolle中fd的数据结构

for(i=0;i<5;i+) { //模拟S个客户端连接
    static struct epoll_event ev; // epoll中fd的数据结构和polI中的差不多,只是没有了revents
    memset(&client,0,sizeof (client));
    addrlen sizeof(client); 
    ev.data.fd = accept(sockfd,(struct sockaddr&client,&addrlen);
    ev.events = EPOLLIN;
    epoll_ctl(epfd,EPOLL_CTL_ADD,ev.data.fd,&ev); // epoll ctl把每-个socket的fd数据结构放到epoll_create创建的内存空间中
}
                      
while(1){
    puts("round again");
    nfds = epoll_wait(epfd,events,5,1O0O0); // epoll_wait阻塞,只有当epoll_create中创建的内存空间中的fd有事件发生,才会把这些fd放入就绪链表中,返回就绪fd的个数
    for(i=0;i<nfds;i++){ //遍历就绪链表,读取数据
        memset(buffer,0,MAXBUF);
        read(events[i].data.fd, buffer, MAXBUF);
        puts(buffer);
    }
}

4-》三个方法的对比

不同操作系统选择的方式不同,windows没有epoll默认为select
在这里插入图片描述

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

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

相关文章

【Trick】IOS系统解决“未受信任的企业级开发者”问题

问题&#xff1a; 本人通过扫码下载了一个软件&#xff0c;下载完毕后出现以下提示&#xff1a; 解决方法&#xff1a; 这个主要是操作系统的问题&#xff0c;需要在设置里面更改&#xff0c;具体步骤如下&#xff1a; 【1】打开设置&#xff0c;选择【通用】 【2】选择【VP…

千鹿 AI ——AI一键生成模特上身图,零成本制作服装电商图片,AI换模特/背景/批量抠图,一个网站就够了!

前言 在电商行业&#xff0c;卖产品主要是卖图片。拍一套产品图加上美工费用&#xff0c;基本要大几百上千&#xff0c;对新手卖家是不小的支出。服装类目的 SKU 很多&#xff0c;一个链接下有几十个款式很常见。 AI 出现对跨境电商有好处是大家都知道的。现在看看电商领域的…

rust aarch64开发-编译

1. 实验代码下载 本实验基于github或gitee上的开源实现。 // 无法访问github可以通过如下地址下载 git clone https://gitee.com/yang_jun_yi/armv8-rust-hypervisor.git // github下载 git clone https://github.com/Comet959/armv8-rust-hypervisor.git2. 编译 make start…

PG实践|数学函数及其操作(一)

文章目录 引言1.1 ABS() - 取绝对值1.2 CEIL() 或 CEILING() - 向上取整1.3 FLOOR() - 向下取整1.4 ROUND() - 四舍五入1.5 TRUNC() - 截断/截取小数总结 &#x1f4eb; 作者简介&#xff1a;「六月暴雪飞梨花」&#xff0c;专注于研究Java&#xff0c;就职于科技型公司后端工程…

Spring配置/管理bean-IOC(控制反转) 非常详细!基于XML及其注解!案例分析! 建议复习收藏!

目录 1.Spring配置/管理bean介绍 2.基于XML配置bean 2.1基于id来获取bean对象 2.2基于类型获取bean对象 2.3通过指定构造器配置bean对象 2.4通过p名称空间配置bean 2.5通过ref配置bean(实现依赖注入) 2.6注入内部Bean对象&#xff0c;依赖注入另一种方式 2.7 注入集合…

AWD的复现

学习awd的相关资料&#xff1a;速成AWD并获奖的学习方法和思考记录- Track 知识社区 - 掌控安全在线教育 - Powered by 掌控者&#xff08;包含使用脚本去批量修改密码&#xff09; 在复现之前去了解了以下AWD的相关脚本 资料&#xff1a;AWD批量攻击脚本使用教程-CSDN博客 …

网络安全有关法律法规

1. 前言 在当今数字化高速发展的时代&#xff0c;网络安全已成为关乎国家、企业和个人的重要议题。为了应对日益复杂的网络安全挑战&#xff0c;一系列网络安全法律法规应运而生&#xff0c;它们如同坚实的盾牌&#xff0c;守护着我们的数字世界。现在是2024年10月&#xff0c…

军团服务QA角度总结

需求背景&#xff1a; 军团业务诞生的时候承接家族群组功能&#xff0c;玩法邀请成员做任务->积分升级->发送奖励。还是拉收入的一个业务载体。收入才是王道。 军团服务端核心就三个&#xff1a;军团创建&#xff0c;人员管理和军团奖励。 军团创建&#xff1a; 创建…

每天练打字8:今日状况——常用字后五百击键4.5第1遍进行中,赛文速度105.75

今日跟打&#xff1a;738字 总跟打&#xff1a;125701字 记录天数&#xff1a;2459天 &#xff08;实际没有这么多天&#xff0c;这个是注册账号的天数&#xff09; 平均每天&#xff1a;50字 本周目标完成进度&#xff1a; 练习常用单字后500&#xff0c;击键3.5&#xff0c;…

kernel32.dll的功能、作用,教大家几种修复kernel32.dll错误的办法

当这个文件出现问题时&#xff0c;用户可能会遇到各种错误消息&#xff0c;例如“缺失kernel32.dll”或“kernel32.dll发生错误”。这些错误不仅令人困扰&#xff0c;还可能威胁到您的数据安全和系统性能。接下来&#xff0c;本文将教大家几种修复kernel32.dll错误的有效方法&a…

群控系统服务端开发模式-业务流程图补充

进天有读者给我反馈&#xff0c;业务流程图看的不是很明确&#xff0c;所以我把未画完的业务流程图补充完毕。也希望以后更多的读者给我评论及意见。 一、业务流程梳理 1、非业务流程 a、添加部门、添加级别、添加执行方式。因为这些参数都是要被其他地方调用的&#xff0c;更…

word中的内容旋转90度

在vsto、Aspose.Words 中&#xff0c;默认没有直接的 API 可以让表格整体旋转 90 度。然而&#xff0c;我们可以通过一些方式来实现类似的效果&#xff0c;具体思路如下&#xff1a; 将表格插入到一个形状&#xff08;Shape&#xff09;或文本框中&#xff0c;然后旋转该形状。…

影刀RPA实战番外:excel函数应用指南

Excel函数是用于执行特定计算、分析和数据处理任务的预定义公式。它们可处理数学计算、文本处理、逻辑判断、日期和时间运算、查找和引用数据等。例如&#xff0c;SUM函数可以计算一系列数字的总和&#xff0c;IF函数进行逻辑测试&#xff0c;VLOOKUP函数在表格中查找数据&…

Oracle CONNECT BY、PRIOR和START WITH关键字详解

Oracle CONNECT BY、PRIOR和START WITH关键字详解 1. 基本概念2. 数据示例3. SQL示例3.1. 查询所有员工及其上级3.2. 显示层次结构3.3. 查询特定员工的子级 4. 结论 在Oracle数据库中&#xff0c;CONNECT BY、PRIOR和START WITH关键字主要用于处理层次结构数据&#xff0c;例如…

我悟了,华为FreeBuds 6i这样戴更稳了!

谁懂啊&#xff0c;才知道华为FreeBuds 6i标配多种尺寸的耳塞&#xff08;如S、M、L&#xff09;&#xff0c;方便大家根据自己耳道大小自由选择。如果耳机戴上后感到耳朵不适或松动&#xff0c;可能是耳塞尺寸不匹配。 小提示&#xff1a; 耳塞过大 可能会造成压迫感&#xf…

kafka自定义配置信息踩坑

org.apache.kafka.common.config.ConfigException: Invalid value 0 for configuration acks: Expected value to be a string, but it was a java.lang.Integer 场景描述&#xff1a; 单个kafka使用springboot框架自带的 yml 配置完全OK&#xff08;因为底层会帮我们处理好类…

CSS 设置网页的背景图片

背景 最近正好在写一个个人博客网站“小石潭记”&#xff0c;需要一张有水&#xff0c;有鱼的图片。正好玩原神遇到了类似场景&#xff0c;于是截图保存&#xff0c;添加到网站里面。以下是效果图&#xff1a; css 写个class&#xff0c;加到整个网页的body上 .bodyBg {ba…

接口测试 —— 如何测试加密接口?

接口加密是指在网络传输过程中&#xff0c;将数据进行加密&#xff0c;以保护数据的安全性。接口加密可以采用多种加密算法&#xff0c;如AES、DES、RSA等。测试接口加密的目的是验证接口加密算法的正确性和安全性。以下是一些详细的测试方法和注意事项&#xff1a; 接口加密字…

A-【项目开发知识管理】Android AIDL跨进程通信

Android AIDL跨进程通信 文章目录 Android AIDL跨进程通信0.我为啥要写这篇文章1.AIDL是干啥的&#xff1f;1.1简述1.2官方话 2.在AndroidStudio中怎么干&#xff1f;2.1准备工作2.2在项目A中创建AIDL文件夹2.3在项目A中创建一个aidl文件2.4将项目A进行一次Rebuild操作2.5在项目…

计算机专业大学四年的学习路线(非常详细),零基础入门到精通,看这一篇就够了

前言 许多学子选择踏上计算机这条充满挑战与机遇的道路。但在大学四年中&#xff0c;如何规划自己的学习路线&#xff0c;才能在毕业时脱颖而出&#xff0c;成为行业的佼佼者呢&#xff1f; 第一学年&#xff1a;基础知识的奠基 1.1 课程安排 在大学的第一年&#xff0c;重…