科普文:从源码解读5种Redis基本数据类型

news2024/9/9 4:30:10

键值对字符串

char* 与 SDS

char* 的不足:

  • 操作效率低:获取长度需遍历,O(N)复杂度

  • 二进制不安全:无法存储包含 \0 的数据

SDS 的优势:

  • 操作效率高:获取长度无需遍历,O(1)复杂度(通过len和alloc,快速获取字符长度大小以及跳转到字符串末尾)

  • 二进制安全:因单独记录长度字段,所以可存储包含 \0 的数据

  • 兼容 C 字符串函数,可直接使用字符串 API

  • 紧凑型内存设计(按照字符串类型,len和alloc使用不同的类型节约内存,并且关闭内存对齐来达到内存高效利用,在redis中除了sds,intset和ziplist也有类似的目底)

  • 避免频繁的内存分配。除了sds部分类型存在预留空间,sds设计了sdsfree和sdsclear两种字符串清理函数,其中sdsclear,只是修改len为0以及buf为'\0',并不会实际释放内存,避免下次使用带来的内存开销

SDS中的优化

Redis 在操作 SDS 时,为了避免频繁操作字符串时,每次「申请、释放」内存的开销,还做了这些优化:

  • 内存预分配:SDS 扩容,会多申请一些内存(小于 1MB 翻倍扩容,大于 1MB 按 1MB 扩容)

  • 多余内存不释放:SDS 缩容,不释放多余的内存,下次使用可直接复用这些内存

这种策略,是以多占一些内存的方式,换取「追加」操作的速度。这个内存预分配策略,详细逻辑可以看 sds.c 的 sdsMakeRoomFor 函数。

SDS在Redis内部模块中的实现

SDS 字符串在 Redis 内部模块实现中也被广泛使用,在 Redis server 和客户端的实现中,能找到使用 SDS 字符串的地方很多:

  • Redis 中所有 key 的类型就是 SDS(详见 db.c 的 dbAdd 函数)

  • Redis Server 在读取 Client 发来的请求时,会先读到一个缓冲区中,这个缓冲区也是 SDS(详见 server.h 中 struct client 的 querybuf 字段)

  • 写操作追加到 AOF 时,也会先写到 AOF 缓冲区,这个缓冲区也是 SDS (详见 server.h 中 struct client 的 aof_buf 字段)

Hash表

dict数据结构

  • Redis 中的 dict 数据结构,采用「链式哈希」的方式存储,当哈希冲突严重时,会开辟一个新的哈希表,翻倍扩容,并采用「渐进式 rehash」的方式迁移数据

  • Redis 中凡是需要 O(1) 时间获取 k-v 数据的场景,都使用了 dict 这个数据结构,也就是说 dict 是 Redis 中重中之重的「底层数据结构」

  • dict 封装好了友好的「增删改查」API,并在适当时机「自动扩容、缩容」,这给上层数据类型(Hash/Set/Sorted Set)、全局哈希表的实现提供了非常大的便利

  • 例如,Redis 中每个 DB 存放数据的「全局哈希表、过期key」都用到了 dict:
    // server.h
    typedef struct redisDb {
        dict *dict;     // 全局哈希表,数据键值对存在这
        dict *expires;  // 过期 key + 过期时间 存在这
        ...
    }
    

rehash

  • 所谓「渐进式 rehash」是指,把很大块迁移数据的开销,平摊到多次小的操作中,目的是降低主线程的性能影响

  • 「全局哈希表」在触发渐进式 rehash 的情况有 2 个:

    • 增删改查哈希表时:每次迁移 1 个哈希桶( dict.c 中的 _dictRehashStep 函数)

    • 定时 rehash:如果 dict 一直没有操作,无法渐进式迁移数据,那主线程会默认每间隔 100ms 执行一次迁移操作。这里一次会以 100 个桶为基本单位迁移数据,并限制如果一次操作耗时超时 1ms 就结束本次任务,待下次再次触发迁移(dict.c 的 dictRehashMilliseconds 函数)

  • dict 在负载因子超过 1 时(used: bucket size >= 1),会触发 rehash。但如果 Redis 正在 RDB 或 AOF rewrite,为避免父进程大量写时复制,会暂时关闭触发 rehash。但这里有个例外,如果负载因子超过了 5(哈希冲突已非常严重),依旧会强制做 rehash(重点)

  • dict 在 rehash 期间,查询旧哈希表找不到结果,还需要在新哈希表查询一次

  • SipHash 哈希算法是在 Redis 4.0 才开始使用的,3.0-4.0 使用的是 MurmurHash2 哈希算法,3.0 之前是 DJBX33A 哈希算法

问题解答

  • redis的dict结构核心就是链式hash,其原理其实和JDK的HashMap类似(JDK1.7之前的版本,1.8开始是红黑树或链表),这里就有一个问题为什么Redis要使用链式而不引入红黑树呢,或者直接使用红黑树?

    • hash冲突不使用红黑树:redis需要高性能,如果hash冲突使用红黑树,红黑树和链表的转换会引起不必要的开销(hash冲突不大的情况下红黑树其实比链表沉重,还会浪多余的空间)

    • dict不采用红黑树:在负载因子较低,hash冲突较低的情况下,hash表的效率O(1)远远高于红黑树

    • 当采用渐进式rehash的时候,以上问题都可以解决

  • 何为渐进式rehash?本质原理是什么?当内存使用变小会缩容吗?

    • 渐进式rehash的本质是分治思想,通过把大任务划分成一个个小任务,每个小任务只执行一小部分数据,最终完成整个大任务的过程

    • 渐进式rehash可以在不影响运行中的redis使用来完成整改hash表的扩容(每次可以控制只执行1ms)

    • 初步判定会,因为dictResize中用于计算hash表大小的minimal就是来源于实际使用的大小,并且htNeedsResize方法中(used*100/size < HASHTABLE_MIN_FILL)来判断是否触发缩容来节约内存,而缩容也是渐进式rehash

渐进式rehash怎么去执行?

在了解渐进式rehash之前,我们需要了解一个事情,就是正在运行执行任务的redis,其实本身就是一个单线程的死循环(不考虑异步以及其他fork的场景),其循环的方法为aeMain(),位于ae.c文件中,在这个循环中每次执行都会去尝试执行已经触发的时间事件和文件事件,而渐进式rehash的每个小任务就是位于redis,serverCron时间事件中,redis每次循环的时候其实都会经过如下所示的调用流程:

  • serverCron -> updateDictResizePolicy (先判断是否能执行rehash,当AOF重写等高压力操作时候不执行)

  • serverCron -> databasesCron -> incrementallyRehash -> dictRehashMilliseconds -> dictRehash (dictRehashMilliseconds默认要求每次rehash最多只能执行1ms)

  • 通过这种方式最终完成整改hash表的扩容

SDS

redisObject

  • 要想理解 Redis 数据类型的设计,必须要先了解 redisObject。Redis 的 key 是 String 类型,但 value 可以是很多类型(String/List/Hash/Set/ZSet等),所以 Redis 要想存储多种数据类型,就要设计一个通用的对象进行封装,这个对象就是 redisObject。

  • 代码中定义如下:
    // server.h
    typedef struct redisObject {
        unsigned type:4;
        unsigned encoding:4;
        unsigned lru:LRU_BITS;
        int refcount;
        void *ptr;
    } robj;
    
  • 其中,最重要的 2 个字段:

    • type:面向用户的数据类型(String/List/Hash/Set/ZSet等)

    • encoding:每一种数据类型,可以对应不同的底层数据结构来实现(SDS/ziplist/intset/hashtable/skiplist等)

  • 举例说明

    • 例如 String,可以用 embstr(嵌入式字符串,redisObject 和 SDS 一起分配内存),也可以用 rawstr(redisObject 和 SDS 分开存储)实现。

    • 又或者,当用户写入的是一个「数字」时,底层会转成 long 来存储,节省内存。

    • 同理,Hash/Set/ZSet 在数据量少时,采用 ziplist 存储,否则就转为 hashtable 来存。

  • 所以,redisObject 的作用在于:

    • 为多种数据类型提供统一的表示方式

    • 同一种数据类型,底层可以对应不同实现,节省内存

    • 支持对象共享和引用计数,共享对象存储一份,可多次使用,节省内存

  • redisObject 更像是连接「上层数据类型」「底层数据结构」之间的桥梁。

String

  • String 类型的实现,底层对应 3 种数据结构:

    • embstr:小于 44 字节,嵌入式存储,redisObject 和 SDS 一起分配内存,只分配 1 次内存

    • rawstr:大于 44 字节,redisObject 和 SDS 分开存储,需分配 2 次内存

    • long:整数存储(小于 10000,使用共享对象池存储,但有个前提:Redis 没有设置淘汰策略,详见 object.c 的 tryObjectEncoding 函数)

ziplist

  • ziplist 的特点:

    • 连续内存存储:每个元素紧凑排列,内存利用率高

    • 变长编码:存储数据时,采用变长编码(满足数据长度的前提下,尽可能少分配内存)

    • 寻找元素需遍历:存放太多元素,性能会下降(适合少量数据存储)

    • 级联更新:更新、删除元素,会引发级联更新(因为内存连续,前面数据膨胀/删除了,后面要跟着一起动)

  • List、Hash、Set、ZSet 底层都用到了 ziplist。

intset

intset 的特点:

  • Set 存储如果都是数字,采用 intset 存储

  • 变长编码:数字范围不同,intset 会选择 int16/int32/int64 编码(intset.c 的 _intsetValueEncoding 函数)

  • 有序:intset 在存储时是有序的,这意味着查找一个元素,可使用「二分查找」(intset.c 的 intsetSearch 函数)

  • 编码升级/降级:添加、更新、删除元素,数据范围发生变化,会引发编码长度升级或降级

SDS使用嵌入式字符串的条件

  • SDS 判断是否使用嵌入式字符串的条件是 44 字节

  • jemalloc 分配内存机制,jemalloc 为了减少分配的内存空间大小不是2的幂次,在每次分配内存的时候都会返回2的幂次的空间大小,比如我需要分配5字节空间,jemalloc 会返回8字节,15字节会返回16字节。其常见的分配空间大小有:8, 16, 32, 64, ..., 2kb, 4kb, 8kb。

  • 但是这种方式也可能会造成,空间的浪费,比如我需要33字节,结果给我64字节,为了解决这个问题jemalloc将内存分配划分为,小内存(small_class)和大内存(large_class)通过不同的内存大小使用不同阶级策略,比如小内存允许存在48字节等方式。

  • 嵌入式字符串会把 redisObject 和 SDS 一起分配内存,那在存储时结构是这样的:

    • redisObject:16 个字节

    • SDS:sdshdr8(3 个字节)+ SDS 字符数组(N 字节 + \0 结束符 1 个字节)

  • Redis 规定嵌入式字符串最大以 64 字节存储,所以 N = 64 - 16(redisObject) - 3(sdshr8) - 1(\0), N = 44 字节。

redis充分提高内存利用率的手段

  • 淘汰不再使用的内存空间

  • 紧凑型的内存设计

    • 设计实现了SDS

    • 设计实现了ziplist

    • 设计实现了intset

    • 搭配redisObject

    • 设计了嵌入式字符串

  • 实例内存共享

    • 设计了共享对象(共享内存大部是常量实例)

有序集合

ZSet

  • ZSet 当数据比较少时,采用 ziplist 存储,每个 member/score 元素紧凑排列,节省内存

  • 当数据超过阈值(zset-max-ziplist-entries、zset-max-ziplist-value)后,转为 hashtable + skiplist 存储,降低查询的时间复杂度

  • hashtable 存储 member->score 的关系,所以 ZSCORE 的时间复杂度为 O(1)

skiplist

  • skiplist 是一个「有序链表 + 多层索引」的结构,把查询元素的复杂度降到了 O(logN),服务于 ZRANGE/ZREVRANGE 这类命令

  • skiplist 的多层索引,采用「随机」的方式来构建,也就是说每次添加一个元素进来,要不要对这个元素建立「多层索引」?建立「几层索引」?都要通过「随机数」的方式来决定

  • 每次随机一个 0-1 之间的数,如果这个数小于 0.25(25% 概率),那就给这个元素加一层指针,持续随机直到大于 0.25 结束,最终确定这个元素的层数(层数越高,概率越低,且限制最多 64 层,详见 t_zset.c 的 zslRandomLevel 函数)

  • 这个预设「概率」决定了一个跳表的内存占用和查询复杂度:概率设置越低,层数越少,元素指针越少,内存占用也就越少,但查询复杂会变高,反之亦然。这也是 skiplist 的一大特点,可通过控制概率,进而控制内存和查询效率

  • skiplist 新插入一个节点,只需修改这一层前后节点的指针,不影响其它节点的层数,降低了操作复杂度(相比平衡二叉树的再平衡,skiplist 插入性能更优)

问题解答

  • 关于 Redis 的 ZSet 为什么用 skiplist 而不用平衡二叉树实现的问题,原因是:

    • skiplist 更省内存:25% 概率的随机层数,可通过公式计算出 skiplist 平均每个节点的指针数是 1.33 个,平衡二叉树每个节点指针是 2 个(左右子树)

    • skiplist 遍历更友好:skiplist 找到大于目标元素后,向后遍历链表即可,平衡树需要通过中序遍历方式来完成,实现也略复杂

    • skiplist 更易实现和维护:扩展 skiplist 只需要改少量代码即可完成,平衡树维护起来较复杂

  • 在使用跳表和哈希表相结合的双索引机制时,在获得高效范围查询和单点查询的同时,有哪些不足之处?

    • 这种发挥「多个数据结构」的优势,来完成某个功能的场景,最大的特点就是「空间换时间」,所以内存占用多是它的不足。

    • 不过也没办法,想要高效率查询,就得牺牲内存,鱼和熊掌不可兼得。

    • 不过 skiplist 在实现时,Redis 作者应该也考虑到这个问题了,就是上面提到的这个「随机概率」,Redis 后期维护可以通过调整这个概率,进而达到「控制」查询效率和内存平衡的结果。当然,这个预设值是固定写死的,不可配置,应该是 Redis 作者经过测试和权衡后的设定,我们这里只需要知晓原理就好。

  • redis作为一款优化到极致的中间件,不会单纯使用一种数据类型去实现一个功能,而会根据当前的情况选择最合适的数据结构,比如zset就是dict + skiplist,甚至当元素较少的时候zsetAdd方法会优先选择ziplist而不直接使用skiplist,以到达节约内存的效果(当小key泛滥的时候很有效果),当一种数据结构存在不足的情况下,可以通过和其它数据结构搭配来弥补自身的不足(软件设计没有银弹,只有最合适)

  • redis仰仗c语言指针的特性,通过层高level数组实现的skiplist从内存和效率上来说都是非常优秀的,如果对比JDK的ConcurrentSkipListMap的实现(使用了大量引用和频繁的new操作),指针的优势无疑显现出来了

  • skiplist的随机率层高。既保证每层的数量相对为下一层的一半,又保证了代码执行效率

quicklist,listpack

ziplist

  • ziplist 设计的初衷就是「节省内存」,在存储数据时,把内存利用率发挥到了极致:

    • 数字按「整型」编码存储,比直接当字符串存内存占用少

    • 数据「长度」字段,会根据内容的大小选择最小的长度编码

    • 甚至对于极小的数据,干脆把内容直接放到了「长度」字段中(前几个位表示长度,后几个位存数据)

  • 但 ziplist 的劣势也很明显:

    • 寻找元素只能挨个遍历,存储过长数据,查询性能很低

    • 每个元素中保存了「上一个」元素的长度(为了方便反向遍历),这会导致上一个元素内容发生修改,长度超过了原来的编码长度,下一个元素的内容也要跟着变,重新分配内存,进而就有可能再次引起下一级的变化,一级级更新下去,频繁申请内存

quicklist

  • 想要缓解 ziplist 的问题,比较简单直接的方案就是,多个数据项,不再用一个 ziplist 来存,而是分拆到多个 ziplist 中,每个 ziplist 用指针串起来,这样修改其中一个数据项,即便发生级联更新,也只会影响这一个 ziplist,其它 ziplist 不受影响,这种方案就是 quicklist

     qucklist: ziplist1(也叫quicklistNode) <-> ziplist2 <-> ziplist3 <-> ... 
    

List

  • List 数据类型底层实现,就是用的 quicklist,因为它是一个链表,所以 LPUSH/LPOP/RPUSH/RPOP 的复杂度是 O(1)

  • List 中每个 ziplist 节点可以存的元素个数/总大小,可以通过 list-max-ziplist-size 配置:

    • 正数:ziplist 最多包含几个数据项

    • 负数:取值 -1 ~ -5,表示每个 ziplist 存储最大的字节数,默认 -2,每个ziplist 8KB

    • ziplist 超过上述配置,添加新元素就会新建 ziplist 插入到链表中。

  • List 因为更多是两头操作,为了节省内存,还可以把中间的 ziplist「压缩」,具体可看 list-compress-depth 配置项,默认配置不压缩

listpack

  • 要想彻底解决 ziplist 级联更新问题,本质上要修改 ziplist 的存储结构,也就是不要让每个元素保存「上一个」元素的长度即可,所以才有了 listpack

  • listpack 每个元素项不再保存上一个元素的长度,而是优化元素内字段的顺序,来保证既可以从前也可以向后遍历

  • listpack 是为了替代 ziplist 为设计的,但因为 List/Hash/ZSet 都严重依赖 ziplist,所以这个替换之路很漫长,目前只有 Stream 数据类型用到了 listpack。set底层是intset和dict实现的,并没有使用到ziplist。

Stream使用的Radix Tree

Radix Tree的优势和不足

作为有序索引,Radix Tree 也能提供范围查询,和 B+ 树、跳表相比,你觉得 Radix Tree 有什么优势和不足么?

  1. Radix Tree 优势

    Stream 在存消息时,推荐使用默认自动生成的「时间戳+序号」作为消息 ID,不建议自己指定消息 ID,这样才能发挥 Radix Tree 公共前缀的优势。

    • 本质上是前缀树,所以存储有「公共前缀」的数据时,比 B+ 树、跳表节省内存

    • 没有公共前缀的数据项,压缩存储,value 用 listpack 存储,也可以节省内存

    • 查询复杂度是 O(K),只与「目标长度」有关,与总数据量无关

    • 这种数据结构也经常用在搜索引擎提示、文字自动补全等场景

  2. Radix Tree 不足

    • 如果数据集公共前缀较少,会导致内存占用多

    • 增删节点需要处理其它节点的「分裂、合并」,跳表只需调整前后指针即可

    • B+ 树、跳表范围查询友好,直接遍历链表即可,Radix Tree 需遍历树结构

    • 实现难度高比 B+ 树、跳表复杂

每种数据结构都是在面对不同问题场景下,才被设计出来的,结合各自场景中的数据特点,使用优势最大的数据结构才是正解。

B+树和跳跃表的关联

  • B+树和跳跃表这两种数据结构在本身设计上是有亲缘关系的,其实如果把B+树拉直来看不难发现其结构和跳跃表很相似,甚至B+树的父亲结点其实类似跳跃表的level层级。

  • 在当前计算机硬件存储设计上,B+树能比跳表存储更大量级的数据,因为跳表需要通过增加层高来提高索引效率,而B+树只需要增加树的深度。此外B+树同一叶子的连续性更加符合当代计算机的存储结构。然而跳表的层高具有随机性,当层高较大的时候磁盘插入会带来一定的开销,且不利于分块。

Redis不使用B+树而选择跳表

因为数据有序性的实现B+树不如跳表,跳表的时间性能是优于B+树的(B+树不是二叉树,二分的效率是比较高的)。此外跳表最低层就是一条链表,对于需要实现范围查询的功能是比较有利的,而且Redis是基于内存设计的,无需考虑海量数据的场景。

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

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

相关文章

HIS系统搭建|HIS系统功能|HIS系统开发

在当今医疗信息化的浪潮中&#xff0c;医院信息系统&#xff08;HIS&#xff09;的搭建成为了提升医疗服务效率和质量的关键。HIS系统不仅仅是一个简单的数据记录工具&#xff0c;它是一个集成了预约挂号、病历管理、药品管理、财务结算等多个功能模块的综合性平台。通过这一系…

使用idea集成的springboot实现注册接口

跟黑马程序员学pringboot3vue3,代码都是黑马程序员视频里的代码 实战篇-03_注册接口_哔哩哔哩_bilibili 本文仅仅用于学习记录 开发用户接口 注册接口 开发流程&#xff1a;明确需求>>阅读接口文档>>思路分析>>开发>>测试 分析&#xff1a; 这个…

使用SDL库以及C++实现的简单的贪吃蛇:AI Fitten生成

简单使用AI代码生成器做了一个贪吃蛇游戏 设计的基本逻辑都是正确的&#xff0c;能流畅运行 免费准确率高&#xff0c;非常不错&#xff01;支持Visual Studio系列 Fitten&#xff1a;https://codewebchat.fittenlab.cn/ SDL 入门指南&#xff1a;安装配置https://blog.csdn.n…

高效数据抓取:Scrapy框架详解

一、Scrapy框架简介 Scrapy是一个为了爬取网站数据、提取结构性数据而编写的爬虫框架。它支持异步处理&#xff0c;能够快速抓取大量网页&#xff0c;并且易于扩展。Scrapy使用Twisted这个事件驱动的网络引擎&#xff0c;可以处理大量的并发请求&#xff0c;从而提高数据抓取的…

C语言分支结构作业

作业 输入你的身高和体重&#xff0c;测试你的健康状况。 计算bmi的值&#xff0c; bmi &#xff08;体重/身高的平方) 如果bmi 小于18.5&#xff0c;则显示“偏瘦&#xff0c;注意加强营养” 如果bmi 在18.5和23.9之间&#xff0c;则显示“体重指数良好&#xff0c;注意保持…

【全栈实战】大模型自学:从入门到实战打怪升级,20W字总结(二)

&#x1f60a;你好&#xff0c;我是小航&#xff0c;一个正在变秃、变强的文艺倾年。 &#x1f514;本栏讲解【全栈实战】大模型自学&#xff1a;从入门到实战打怪升级。 &#x1f514;专栏持续更新&#xff0c;适合人群&#xff1a;本科生、研究生、大模型爱好者&#xff0c;期…

基于单片机的电梯控制系统的设计

摘 要: 本文提出了一种基于单片机的电梯控制系统设计 。 设计以单片机为核心&#xff0c;通过使用和设计新型先进的硬件和控制程序来模拟和控制整个电梯的运行&#xff0c;在使用过程中具有成本低廉、 维护方便、 运行稳定 、 易于操作 、 安全系数高等优点 。 主要设计思路是…

聚焦全局应用可用性的提升策略,详解GLSB是什么

伴随互联网的快速发展和全球化趋势的深入&#xff0c;企业对网络应用的需求日渐增长。为满足全球范围内用户大量的访问需求&#xff0c;同时解决容灾、用户就近访问以及全球应用交付等问题&#xff0c;GLSB&#xff08;全局负载均衡&#xff09;也因此应运而生。那么GLSB是什么…

Axure RP:打造动态交互的大屏可视化设计利器

Axure大屏可视化是指使用Axure RP这款原型设计工具来创建具有视觉冲击力和数据展示功能的大屏幕界面。Axure以其强大的交互设计和丰富的组件库&#xff0c;成为了实现大屏可视化的重要工具之一。以下是对Axure大屏可视化的详细阐述&#xff1a; 一、Axure在大屏可视化中的优势 …

​易能医药董事长易跃能博士荣获“湖湘药学领航奖”

近日&#xff0c;湖南省药学会主办的“湖南省药学会70周年庆典暨第六届湖南药学大会”在湖南长沙隆重召开。易能医药董事长易跃能博士荣获由湖南省药学会颁发的“湖湘药学领航奖”。此次“湖湘药学领航奖”由湖南药学大会学术委员会组织评选&#xff0c;湖南省全省仅有八个名额…

六、3 PWM 舵机代码

目录 1、通道选择 2、参数计算 3、代码部分 1、通道选择 PA1对应通道2 注意&#xff1a;同一个定时器不同通道输出PWM的特点 同一个定时器的不同通道输出的PWM&#xff0c;频率相同&#xff08;因为它们共用一个计数器&#xff09;&#xff0c;占空比可以各自设定&#xff…

Kubernetes 学习记录

https://note.youdao.com/ynoteshare/index.html?idbc7bee305611b52d6900ba209a92bd4d&typenote&_time1694072007342 概览 K8S官网文档&#xff1a;https://kubernetes.io/zh/docs/home/ K8S 是Kubernetes的全称&#xff0c;源于希腊语&#xff0c;意为“舵手”或“…

ITSS:IT服务工程师

证书亮点&#xff1a;适中的费用、较低的难度、广泛的应用范围以及专业的运维认证。 总体评价&#xff1a;性价比良好&#xff01; 证书名称&#xff1a;ITSS服务工程师 证书有效期&#xff1a;持续3年 培训要求&#xff1a;必须参加培训&#xff0c;否则将无法参与考试 发…

Aboboo一些操作

常用快捷键⌨ 快捷键/操作方式 功能 鼠标中键/Esc 进入/退出全屏 空格/Tab 暂停/恢复播放 左/右箭头 快退/快进 Ctrl-左/右箭头 30秒快退/快进 Alt-左/右箭头 60秒快退/快进 Ctrl-Alt-左/右箭头 播放速率调节 PageUp/PageDown 上一句/下一句 上下箭头/滚轮 …

WSL配置镜像网络使用本地端口调试Linux程序

一、安装WSL 二、配置WSL为镜像 在C:\Users\XXUser目录下添加.wslconfig文件 [wsl2] networkingModemirrored # 开启镜像网络 dnsTunnelingtrue # 开启 DNS Tunneling firewalltrue # 开启 Windows 防火墙 autoProxytrue # 开启自动同步代理重启WSL wsl --shutdown wsl三、…

计算机毕业设计选题推荐-音乐播放系统-Java/Python项目实战

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Py…

计算机网络04

文章目录 IP 基本认识**IP 地址的基础知识****IP 地址的分类**无分类地址 CIDR公有 IP 地址与私有 IP 地址IP 地址与路由控制IP 分片与重组IPv6 基本认识IPv4 首部与 IPv6 首部 IP 协议相关技术DNS 域名解析ARP 与 RARP 协议DHCP 动态获取 IP 地址NAT 网络地址转换ICMP 互联网控…

NACOS保姆笔记(5)——Nacos的集群教程

前面我们介绍过: NACOS保姆笔记(1)——NACOS的安装和启动NACOS保姆笔记(2)——Spring Cloud Alibaba Nacos服务注册与发现以及负载均衡NACOS保姆笔记(3)——Spring Cloud Alibaba Nacos配置中心NACOS保姆笔记(4)——Spring Cloud Alibaba Nacos鉴权本篇主要介绍下Na…

5种IO模型简述

文章目录 前言什么是IO模型&#xff1f;阻塞IO非阻塞IO多路复用IO信号驱动IO异步IO 结语 前言 最近学netty&#xff0c;当然无法避免IO模型这部分知识。 我尽量用最简洁的语言来讲清楚这个东西。 什么是IO模型&#xff1f; 既然最近学netty&#xff0c;就拿它来举例子。 比如…

ITPUB专访 | 张宏波:一场关于编程语言速度与效率的深度对话

ITPUB专访 | 张宏波&#xff1a;一场关于编程语言速度与效率的深度对 随着 AI 大语言模型&#xff08;LLM&#xff09;不断突破和开源社区活跃程度达到前所未有的高度&#xff0c;以 OpenAI 的 GPT-4、Meta-LLaMA 等为代表的重量级产品和服务相继发布&#xff0c;AI 技术的蓬勃…