深入Redis线程模型
目录
1.前言
2.Redis为什么快?
3.Redis 为何选择单线程?
3.1可维护性
3.2并发处理
3.3性能瓶颈
4.Reactor设计模式
5.Redis4.0前 单线程模型 - Event Loop
6.Redis4.0后 多线程异步任务
7.Redis6.0后 多线程网络模型
1.前言
这篇文章我们主要围绕这几个问题来展开:
- 什么是Redis线程模型?
- Redis4.0前的单线程模型是什么?
- 为什么Redis之前一直选择单线程?
- Redis4.0后为什么又加入了多线程异步执行?
- Redis6.0后引入的多线程模型是什么?
在目前的技术选型中,Redis 俨然已经成为了系统高性能缓存方案的事实标准,因此现在 Redis 也成为了后端开发的基本技能树之一,Redis 从本质上来讲是一个网络服务器,而对于一个网络服务器来说,网络模型是它的精华,搞懂了一个网络服务器的网络模型,你也就搞懂了它的本质。
Redis 作为广为人知的内存数据库,在玩具项目和复杂的工业级别项目中都看到它的身影,然而 Redis 却是使用单线程模型进行设计的,这与很多人固有的观念有所冲突,为什么单线程的程序能够抗住每秒几百万的请求量呢?这也是我们今天要讨论的问题之一。
除此之外,Redis 4.0 之后的版本却抛弃了单线程模型这一设计,原本使用单线程运行的 Redis 也开始选择性使用多线程模型,这一看似有些矛盾的设计决策是今天需要讨论的另一个问题。

翻译后就是:然而,在 Redis 4.0 中,我们开始使 Redis 更加线程化。目前,这仅限于在后台删除对象,以及阻止通过 Redis 模块实现的命令。对于下一个版本,计划是使 Redis 越来越线程化。
2.Redis为什么快?
根据官方的介绍,在一台普通硬件配置的 Linux 机器上跑单个 Redis 实例,处理简单命令(时间复杂度 O(N) 或者 O(log(N))),QPS 可以达到 8w+,而如果使用 pipeline 批处理功能,则 QPS 至高能达到 100w。仅从性能层面进行评判,Redis 完全可以被称之为高性能缓存方案。
Redis 的高性能得益于以下几个基础:

- C 语言实现,虽然 C 对 Redis 的性能有助力,但语言并不是最核心因素。
- 纯内存 I/O,相较于其他基于磁盘的 DB,Redis 的纯内存操作有着天然的性能优势。
- I/O 多路复用,基于 epoll/select/kqueue 等 I/O 多路复用技术,实现高吞吐的网络 I/O。
- 单线程模型,单线程无法利用多核,但是从另一个层面来说则避免了多线程频繁上下文切换,以及同步机制如锁带来的开销。
3.Redis 为何选择单线程?

核心意思就是,对于一个 DB 来说,CPU 通常不会是瓶颈,因为大多数请求不会是 CPU 密集型的,而是 I/O 密集型。具体到 Redis 的话,如果不考虑 RDB/AOF 等持久化方案,Redis 是完全的纯内存操作,执行速度是非常快的,因此这部分操作通常不会是性能瓶颈,Redis 真正的性能瓶颈在于网络 I/O,也就是客户端和服务端之间的网络传输延迟,因此 Redis 选择了单线程的 I/O 多路复用来实现它的核心网络模型。
其中最重要的几个原因如下:
- 使用单线程模型能带来更好的可维护性,方便开发和调试;
- 使用单线程模型也能并发的处理客户端的请求;
- Redis 服务中运行的绝大多数操作的性能瓶颈都不是 CPU;
上述三个原因中的最后一个是最终使用单线程模型的决定性因素,其他的两个原因都是使用单线程模型额外带来的好处
3.1可维护性
可维护性对于一个项目来说非常重要,如果代码难以调试和测试,问题也经常难以复现,这对于任何一个项目来说都会严重地影响项目的可维护性。多线程模型虽然在某些方面表现优异,但是它却引入了程序执行顺序的不确定性,代码的执行过程不再是串行的,多个线程同时访问的变量如果没有谨慎处理就会带来诡异的问题。
在网络上有一个调侃多线程模型的图片,就很好地展示了多线程模型带来的潜在问题:

避免了过多的上下文切换开销
多线程调度过程中必然需要在 CPU 之间切换线程上下文 context,而上下文的切换又涉及程序计数器、堆栈指针和程序状态字等一系列的寄存器置换、程序堆栈重置甚至是 CPU 高速缓存、TLB 快表的汰换,如果是进程内的多线程切换还好一些,因为单一进程内多线程共享进程地址空间,因此线程上下文比之进程上下文要小得多,如果是跨进程调度,则需要切换掉整个进程地址空间。
如果是单线程则可以规避进程内频繁的线程切换开销,因为程序始终运行在进程中单个线程内,没有多线程切换的场景。
避免了同步机制的开销
引入了多线程,我们就必须要同时引入并发控制来保证在多个线程同时访问数据时程序行为的正确性,这就需要工程师额外维护并发控制的相关代码,例如,我们会需要在可能被并发读写的变量上增加互斥锁:
在访问这些变量或者内存之前也需要先对获取互斥锁,一旦忘记获取锁或者忘记释放锁就可能会导致各种诡异的问题,管理相关的并发控制机制也需要付出额外的研发成本和负担。
简单可维护
Redis 的开发者对 Redis 的设计和代码有着近乎偏执的简洁性理念,你可以在阅读 Redis 的源码时感受到这份偏执。因此代码的简单可维护性必然是 Redis 早期的核心准则之一,而引入多线程必然会导致代码的复杂度上升和可维护性下降。
3.2并发处理
使用单线程模型也并不意味着程序不能并发的处理任务,Redis 虽然使用单线程模型处理用户的请求,但是它却使用 I/O 多路复用机制并发处理来自客户端的多个连接,同时等待多个连接发送的请求。
在 I/O 多路复用模型中,最重要的函数调用就是select以及类似函数,该方法的能够同时监控多个文件描述符(也就是客户端的连接)的可读可写情况,当其中的某些文件描述符可读或者可写时,select方法就会返回可读以及可写的文件描述符个数。
使用 I/O 多路复用技术能够极大地减少系统的开销,系统不再需要额外创建和维护进程和线程来监听来自客户端的大量连接,减少了服务器的开发成本和维护成本。
3.3性能瓶颈
最后要介绍的其实就是 Redis 选择单线程模型的决定性原因 —— 多线程技术能够帮助我们充分利用 CPU 的计算资源来并发的执行不同的任务,但是 CPU 资源往往都不是 Redis 服务器的性能瓶颈。哪怕我们在一个普通的 Linux 服务器上启动 Redis 服务,它也能在 1s 的时间内处理 1,000,000 个用户请求。

如果这种吞吐量不能满足我们的需求,更推荐的做法是使用分片的方式将不同的请求交给不同的 Redis 服务器(同一台机器上的多个Redis实例/进程/服务,即单机集群)来处理,而不是在同一个 Redis 服务中引入大量的多线程操作。
Redis是网络IO密集型的服务,CPU不是它的性能瓶颈,如果不开启 AOF 备份,所有 Redis 的操作都会在内存中完成不会涉及任何的 I/O 操作,这些数据的读写由于只发生在内存中,所以处理速度是非常快的。对于CPU计算使用多线程处理也仅仅是提高了计算部分的性能和CPU的利用率。
AOF 是 Redis 的一种持久化机制,它会在每次收到来自客户端的写请求时,将其记录到日志中,每次 Redis 服务器启动时都会重放 AOF 日志构建原始的数据集,保证数据的持久性。
然而整个Redis服务的瓶颈在于网络传输带来的延迟和等待客户端的数据传输,也就是网络 I/O。CPU计算本来就得等网络IO,使用多线程提高CPU利用率之后更得等网络IO更久,要显著提高性能也得对网络IO多线程处理,这即是Redis6.0后的多线程模型。
总结:
为什么Redis4.0前一直选择单线程模型?
- 单线程编程容易并且更容易维护;多线程就会存在死锁、线程上下文切换等问题,甚至会影响性能
- 单线程也能使用IO多路复用处理并发用户请求
- Redis 的性能瓶颈不在 CPU ,主要在内存和网络IO
4.Reactor设计模式
Redis采用的是IO多路复用模式,所以我们重点来了解下多路复用这种模式,如何在更好的落地到我们系统中,不可避免的我们要聊下Reactor模式。
Reactor:类似Selector,负责I/O事件的派发;
Acceptor:接收到事件后,处理连接的那个分支逻辑;
Handler:消息读写处理等操作类。

处理流程
- Reactor监听连接事件、Socket事件,当有连接事件过来时交给Acceptor处理,当有Socket事件过来时交个对应的Handler处理。
优点
- 模型比较简单,所有的处理过程都在一个连接里;
- 实现上比较容易,模块功能也比较解耦,Reactor负责多路复用和事件分发处理,Acceptor负责连接事件处理,Handler负责Scoket读写事件处理。
缺点
- 只有一个线程,连接处理和业务处理共用一个线程,无法充分利用CPU多核的优势。
- 在流量不是特别大、业务处理比较快的时候系统可以有很好的表现,当流量比较大、读写事件比较耗时情况下,容易导致系统出现性能瓶颈。
怎么去解决上述问题呢?既然业务处理逻辑可能会影响系统瓶颈,那我们是不是可以把业务处理逻辑单拎出来,交给线程池来处理,一方面减小对主线程的影响,另一方面利用CPU多核的优势。

这种模型相对单Reactor单线程模型,只是将业务逻辑的处理逻辑交给了一个线程池来处理。
处理流程
- Reactor监听连接事件、Socket事件,当有连接事件过来时交给Acceptor处理,当有Socket事件过来时交个对应的Handler处理。
- Handler完成读事件后,包装成一个任务对象,交给线程池来处理,把业务处理逻辑交给其他线程来处理。
优点
- 让主线程专注于通用事件的处理(连接、读、写),从设计上进一步解耦;
- 利用CPU多核的优势。
缺点
- 貌似这种模型已经很完美了,我们再思考下,如果客户端很多、流量特别大的时候,通用事件的处理(读、写)也可能会成为主线程的瓶颈,因为每次读、写操作都涉及系统调用。
有没有什么好的办法来解决上述问题呢?通过以上的分析,大家有没有发现一个现象,当某一个点成为系统瓶颈点时,想办法把他拿出来,交个其他线程来处理,那这种场景是否适用呢?

这种模型相对单Reactor多线程模型,只是将Scoket的读写处理从mainReactor中拎出来,交给subReactor线程来处理。
处理流程
- mainReactor主线程负责连接事件的监听和处理,当Acceptor处理完连接过程后,主线程将连接分配给subReactor;
- subReactor负责mainReactor分配过来的Socket的监听和处理,当有Socket事件过来时交个对应的Handler处理;
Handler完成读事件后,包装成一个任务对象,交给线程池来处理,把业务处理逻辑交给其他线程来处理。
优点
- 让主线程专注于连接事件的处理,子线程专注于读写事件,从设计上进一步解耦;
- 利用CPU多核的优势。
缺点
- 实现上会比较复杂,在极度追求单机性能的场景中可以考虑使用。
5.Redis4.0前 单线程模型 - Event Loop
当我们讨论 Redis 的多线程之时,有必要对 Redis 的版本划出两个重要的节点:
- Redis v4.0(引入多线程处理异步任务)
- Redis v6.0(正式在网络模型中实现 I/O 多线程)

IO多路复用负责各事件的监听(连接、读、写等),当有事件发生时,将对应事件放入队列中,由事件分发器根据事件类型来进行分发;
如果是连接事件,则分发至连接应答处理器;GET、SET等redis命令分发至命令请求处理器。
命令处理完后产生命令回复事件,再由事件队列,到事件分发器,到命令回复处理器,回复客户端响应。
再来剖析一下 Redis 的核心网络模型,从 Redis 的 v1.0 到 v6.0 版本之前,Redis 的核心网络模型一直是一个典型的单 Reactor 模型:利用 epoll/select/kqueue 等多路复用技术,在单线程的事件循环中不断去处理事件(客户端请求),最后回写响应数据到客户端:

核心概念:
-
client:客户端对象,Redis 是典型的 CS 架构(Client <---> Server),客户端通过 socket 与服务端建立网络通道然后发送请求命令,服务端执行请求的命令并回复。Redis 使用结构体 client 存储客户端的所有相关信息 -
aeApiPoll:I/O 多路复用 API,是基于 epoll_wait/select/kevent 等系统调用的封装,监听等待读/写事件触发,然后处理,它是事件循环(Event Loop) 中的核心函数,是事件驱动得以运行的基础。 -
acceptTcpHandler:连接应答处理器,底层使用系统调用accept接受来自客户端的新连接,并为新连接注册绑定命令读取处理器,以备后续处理新的客户端 TCP 连接; -
readQueryFromClient:命令读取处理器,解析并执行客户端的请求命令。 -
beforeSleep:事件循环中进入 aeApiPoll 等待事件到来之前会执行的函数,其中包含一些日常的任务,比如把client->buf或者client->reply(后面会解释为什么这里需要两个缓冲区)中的响应写回到客户端,持久化 AOF 缓冲区的数据到磁盘等,相对应的还有一个 afterSleep 函数,在 aeApiPoll 之后执行。 -
sendReplyToClient:命令回复处理器,当一次事件循环之后写出缓冲区中还有数据残留,则这个处理器会被注册绑定到相应的连接上,等连接触发写就绪事件时,它会将写出缓冲区剩余的数据回写到客户端。
至此,我们可以描绘出客户端向 Redis 发起请求命令的工作原理:
- Redis 服务器启动,开启主线程事件循环(Event Loop) ,注册
acceptTcpHandler连接应答处理器到用户配置的监听端口对应的文件描述符,等待新连接到来; - 客户端和服务端建立网络连接;
acceptTcpHandler被调用,主线程使用 AE 的 API 将readQueryFromClient命令读取处理器绑定到新连接对应的文件描述符上,并初始化一个client绑定这个客户端连接;- 客户端发送请求命令,触发读就绪事件,主线程调用
readQueryFromClient通过 socket 读取客户端发送过来的命令存入client->querybuf读入缓冲区; - 接着调用
processInputBuffer,在其中使用processInlineBuffer或者processMultibulkBuffer根据 Redis 协议解析命令,最后调用processCommand执行命令; - 根据请求命令的类型(SET, GET, DEL, EXEC 等),分配相应的命令执行器去执行,最后调用
addReply函数族的一系列函数将响应数据写入到对应client的写出缓冲区:client->buf或者client->reply,client->buf是首选的写出缓冲区,固定大小 16KB,一般来说可以缓冲足够多的响应数据,但是如果客户端在时间窗口内需要响应的数据非常大,那么则会自动切换到client->reply链表上去,使用链表理论上能够保存无限大的数据(受限于机器的物理内存),最后把client添加进一个 LIFO 队列clients_pending_write; - 在事件循环(Event Loop) 中,主线程执行
beforeSleep-->handleClientsWithPendingWrites,遍历clients_pending_write队列,调用writeToClient把client的写出缓冲区里的数据回写到客户端,如果写出缓冲区还有数据遗留,则注册sendReplyToClient命令回复处理器到该连接的写就绪事件,等待客户端可写时在事件循环中再继续回写残余的响应数据。
对于那些想利用多核优势提升性能的用户来说,Redis 官方给出的解决方案也非常简单粗暴:在同一个机器上多跑几个 Redis 实例。事实上,为了保证高可用,不太可能会是单机模式,更加常见的是利用 Redis 分布式集群多节点和数据分片负载均衡来提升性能和保证高可用。
6.Redis4.0后 多线程异步任务
以上便是 Redis 的核心网络模型,这个单线程网络模型一直到 Redis v6.0 才改造成多线程模式,但这并不意味着整个 Redis 一直都只是单线程。
Redis 在 v4.0 版本的时候就已经引入了的多线程来做一些异步操作,此举主要针对的是那些非常耗时的命令,通过将这些命令的执行进行异步化,避免阻塞单线程的事件循环。
我们知道 Redis 的 DEL 命令是用来删除掉一个或多个 key 储存的值,它是一个阻塞的命令,大多数情况下你要删除的 key 里存的值不会特别多,最多也就几十上百个对象,所以可以很快执行完,但是如果你要删的是一个超大的键值对,里面有几百万个对象,那么这条命令可能会阻塞至少好几秒,又因为事件循环是单线程的,所以会阻塞后面的其他事件,导致吞吐量下降。
Redis 的作者 antirez 为了解决这个问题进行了很多思考,一开始他想的办法是一种渐进式的方案:利用定时器和数据游标,每次只删除一小部分的数据,比如 1000 个对象,最终清除掉所有的数据,但是这种方案有个致命的缺陷,如果同时还有其他客户端往某个正在被渐进式删除的 key 里继续写入数据,而且删除的速度跟不上写入的数据,那么将会无止境地消耗内存,虽然后来通过一个巧妙的办法解决了,但是这种实现使 Redis 变得更加复杂,而多线程看起来似乎是一个水到渠成的解决方案:简单、易理解。于是,最终 antirez 选择引入多线程来实现这一类非阻塞的命令。更多 antirez 在这方面的思考可以阅读一下他发表的博客:Lazy Redis is better Redis。
于是,在 Redis v4.0 之后增加了一些的非阻塞命令如 UNLINK、FLUSHALL ASYNC、FLUSHDB ASYNC。

UNLINK 命令其实就是 DEL 的异步版本,它不会同步删除数据,而只是把 key 从 keyspace 中暂时移除掉,然后将任务添加到一个异步队列,最后由后台线程去删除,不过这里需要考虑一种情况是如果用 UNLINK 去删除一个很小的 key,用异步的方式去做反而开销更大,所以它会先计算一个开销的阀值,只有当这个值大于 64 才会使用异步的方式去删除 key,对于基本的数据类型如 List、Set、Hash 这些,阀值就是其中存储的对象数量。
7.Redis6.0后 多线程网络模型
前面提到 Redis 最初选择单线程网络模型的理由是:CPU 通常不会成为性能瓶颈,瓶颈往往是内存和网络,因此单线程足够了。那么为什么现在 Redis 又要引入多线程呢?很简单,就是 Redis 的网络 I/O 瓶颈已经越来越明显了。
随着互联网的飞速发展,互联网业务系统所要处理的线上流量越来越大,Redis 的单线程模式会导致系统消耗很多 CPU 时间在网络 I/O 上从而降低吞吐量,要提升 Redis 的性能有两个方向:
- 优化网络 I/O 模块
- 提高机器内存读写的速度
后者依赖于硬件的发展,暂时只能慢慢来。所以只能从前者下手,网络 I/O 的优化又可以分为两个方向:
- 零拷贝技术或者 DPDK 技术
- 利用多核优势
零拷贝技术有其局限性,无法完全适配 Redis 这一类复杂的网络 I/O 场景。而 DPDK 技术通过旁路网卡 I/O 绕过内核协议栈的方式又太过于复杂以及需要内核甚至是硬件的支持。
因此,利用多核优势成为了优化网络 I/O 性价比最高的方案。
我们先看一下 Redis 多线程网络模型的总体设计:

具体的分析就不展开,感兴趣的可以看看此篇文章:
Redis 多线程网络模型全面揭秘 - 编程札记 - SegmentFault 思否
相关文章:
深入Redis线程模型
目录 1.前言 2.Redis为什么快? 3.Redis 为何选择单线程? 3.1可维护性 3.2并发处理 3.3性能瓶颈 4.Reactor设计模式 5.Redis4.0前 单线程模型 - Event Loop 6.Redis4.0后 多线程异步任务 7.Redis6.0后 多线程网络模型 1.前言 这篇文章我们主要围绕…...
idea cannot download sources 解决方法
问题 点击class文件右上角下载源码失败 解决方案 找到idea terminal 控制台cd 至maven工程执行 mvn dependency:resolve -Dclassifiersources...
CS:GO升级 Linux不再是“法外之地”
在前天的VAC大规模封禁中,有不少Linux平台的作弊玩家也迎来了“迟到”的VAC封禁。 一直以来,Linux就是VAC封禁的法外之地。虽然大部分玩家都使用Windows平台进行游戏。但实际上,使用Linux畅玩CS:GO的玩家也不在少数。 以前V社主要打击W…...
手写spring笔记
手写spring笔记 《Spring 手撸专栏》笔记 IoC部分 Bean初始化和属性注入 Bean的信息封装在BeanDefinition中 /*** 用于记录Bean的相关信息*/ public class BeanDefinition {/*** Bean对象的类型*/private Class beanClass;/*** Bean对象中的属性信息*/private PropertyVal…...
shell的两种属性: 交互(interactive)与登录(login)
1. 背景 在看shell变量的时候引起了兴趣: 局部变量,全局变量,环境变量,shell的配置文件,参考博客: http://c.biancheng.net/view/773.html 2. 交互式与非交互式 参考博客: shell的两个属性:是否交互式(interactive), 是否登录…...
实现简单的element-table的拖拽效果
第一步,先随便创建element表格 <el-table ref"dragTable" :data"tableData" style"width: 100%" border fit highlight-current-row><el-table-column label"日期" width"180"><template slot-sc…...
Web网页浏览器远程访问jupyter notebook服务器【内网穿透】
文章目录 前言1. Python环境安装2. Jupyter 安装3. 启动Jupyter Notebook4. 远程访问4.1 安装配置cpolar内网穿透4.2 创建隧道映射本地端口 5. 固定公网地址 前言 Jupyter Notebook,它是一个交互式的数据科学和计算环境,支持多种编程语言,如…...
干翻Dubbo系列第十一篇:Dubbo常见协议与通信效率对比
文章目录 文章说明 一:协议 1:什么是协议 2:协议和序列化关系 3:协议组成 (一):头信息 (二):体信息 4:Dubbo3中常见的协议 5:…...
春秋云镜 CVE-2020-17530
春秋云镜 CVE-2020-17530 S2-061 靶标介绍 对CVE-2019-0230的绕过,Struts2官方对CVE-2019-0230的修复方式是加强OGNL表达式沙盒,而CVE-2020-17530绕过了该沙盒。当对标签属性中的原始用户输入进行评估时,强制 OGNL 评估可能会导致远程代码执…...
【java毕业设计】基于Spring Boot+Vue+mysql的论坛管理系统设计与实现(程序源码)-论坛管理系统
基于Spring BootVuemysql的论坛管理系统设计与实现(程序源码毕业论文) 大家好,今天给大家介绍基于Spring BootVuemysql的论坛管理系统设计与实现,本论文只截取部分文章重点,文章末尾附有本毕业设计完整源码及论文的获取…...
华为在ospf area 0单区域的情况下结合pbr对数据包的来回路径进行控制
配置思路: 两边去的包在R1上用mqc进行下一跳重定向 两边回程包在R4上用mqc进行下一跳重定向 最终让内网 192.168.10.0出去的数据包来回全走上面R-1-2-4 192.168.20.0出去的数据包来回全走 下面R1-3-4 R2和R3就是简单ospf配置和宣告,其它没有配置&#…...
PyQt5登录界面跳转
目录 1、设计ui界面 2、设计逻辑代码,实现登录界面跳转 3、结果 1、设计ui界面 设计后的ui界面 在这里可以设置密码不显示 这里可以设置快捷键 最后将ui界面转为py文件后获得的逻辑代码为:(文件名为Login.py) # -*- coding: u…...
git add 用法
git add 是 Git 的一个命令,用于将更改的文件加入到暂存区(staging area),准备提交这些更改。以下是该命令的常见用法: 添加单个文件 git add 文件名添加多个文件 git add 文件名1 文件名2 ...添加所有当前目录下的更改…...
系统架构设计师---2018年下午试题1分析与解答(试题三)
系统架构设计师---2018年下午试题1分析与解答 试题三 阅读以下关于嵌入式实时系统相关技术的叙述,在答题纸上回答问题 1 和问题 2。 【说明】 某公司长期从事宇航领域嵌入式实时系统的软件研制任务。公司为了适应未来嵌入式系统网络化、智能化和综合化的技术发展需要,决定…...
面试时如何回答接口测试怎么进行
一、什么是接口测试 接口测试顾名思义就是对测试系统组件间接口的一种测试,接口测试主要用于检测外部系统与系统之间以及内部各个子系统之间的交互点。测试的重点是要检查数据的交换,传递和控制管理过程,以及系统间的相互逻辑依赖关系等。 …...
【LeetCode】647.回文子串
题目 给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。 回文字符串 是正着读和倒过来读一样的字符串。 子字符串 是字符串中的由连续字符组成的一个序列。 具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会…...
【Git】SSH到底是什么
一、SSH初探 1、SSH是什么? SSH是一个安全协议,类似有SSL、TSL Git有四种协议:本地协议、Git协议、HTTP协议、SSH协议 SSH协议的优缺点: 优点:SSH访问更加安全,有利于公司的开发维护,并且可…...
当你出差在外时,怎样轻松访问远程访问企业局域网象过河ERP系统?
文章目录 概述1.查看象过河服务端端口2.内网穿透3. 异地公网连接4. 固定公网地址4.1 保留一个固定TCP地址4.2 配置固定TCP地址 5. 使用固定地址连接 概述 ERP系统对于企业来说重要性不言而喻,不管是财务、生产、销售还是采购,都需要用到ERP系统来协助。…...
机器学习与模型识别1:SVM(支持向量机)
一、简介 SVM是一种二类分类模型,在特征空间中寻找间隔最大的分离超平面,使得数据得到高效的二分类。 二、SVM损失函数 SVM 的三种损失函数衡量模型的性能。 1. 0-1 损失: 当正例样本落在 y0 下方则损失为 0,否则损失为…...
在CentOS7.9上安装最新版本Docker安装步骤
1、查看系统版本: cat /etc/redhat-release 2、卸载原有版本(如有的话) sudo yum remove docker \ docker-client \ docker-client-latest \ docker-common \ d…...
【Axure高保真原型】引导弹窗
今天和大家中分享引导弹窗的原型模板,载入页面后,会显示引导弹窗,适用于引导用户使用页面,点击完成后,会显示下一个引导弹窗,直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…...
【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)
可以使用Sqliteviz这个网站免费编写sql语句,它能够让用户直接在浏览器内练习SQL的语法,不需要安装任何软件。 链接如下: sqliteviz 注意: 在转写SQL语法时,关键字之间有一个特定的顺序,这个顺序会影响到…...
关于 WASM:1. WASM 基础原理
一、WASM 简介 1.1 WebAssembly 是什么? WebAssembly(WASM) 是一种能在现代浏览器中高效运行的二进制指令格式,它不是传统的编程语言,而是一种 低级字节码格式,可由高级语言(如 C、C、Rust&am…...
实现弹窗随键盘上移居中
实现弹窗随键盘上移的核心思路 在Android中,可以通过监听键盘的显示和隐藏事件,动态调整弹窗的位置。关键点在于获取键盘高度,并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...
【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分
一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计,提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合:各模块职责清晰,便于独立开发…...
JAVA后端开发——多租户
数据隔离是多租户系统中的核心概念,确保一个租户(在这个系统中可能是一个公司或一个独立的客户)的数据对其他租户是不可见的。在 RuoYi 框架(您当前项目所使用的基础框架)中,这通常是通过在数据表中增加一个…...
AirSim/Cosys-AirSim 游戏开发(四)外部固定位置监控相机
这个博客介绍了如何通过 settings.json 文件添加一个无人机外的 固定位置监控相机,因为在使用过程中发现 Airsim 对外部监控相机的描述模糊,而 Cosys-Airsim 在官方文档中没有提供外部监控相机设置,最后在源码示例中找到了,所以感…...
纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join
纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join 1、依赖1.1、依赖版本1.2、pom.xml 2、代码2.1、SqlSession 构造器2.2、MybatisPlus代码生成器2.3、获取 config.yml 配置2.3.1、config.yml2.3.2、项目配置类 2.4、ftl 模板2.4.1、…...
Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现企业微信功能
1. 开发环境准备 安装DevEco Studio 3.1: 从华为开发者官网下载最新版DevEco Studio安装HarmonyOS 5.0 SDK 项目配置: // module.json5 {"module": {"requestPermissions": [{"name": "ohos.permis…...
