源码|redis7.2.2|sds

文章目录

  • 前言
  • Type && Encoding
  • sds
  • encoding
    • createStringObject
      • createEmbeddedStringObject
        • 总结
      • createRawStringObject
        • 总结
    • createStringObjectFromLongDouble
      • 总结
    • createStringObjectFromLongLongWithOptions
      • 总结
  • 相关操作
    • sdscatlen
      • 总结
  • 阈值44
  • sds VS C字符串

前言

从本篇文章开始会持续更新有关"redis数据结构源码"的分析。[分析的源码是redis7.2.2版本的,有时候会结合之前的版本]。由于能力有限,有些地方可能有些错误,还望指正。

Type && Encoding

redis的每种数据结构都被封装在"redisObject"中,先来看一下"redisObject"长什么样子。

struct redisObject {
    unsigned type:4;//表示数据结构的类型,占4bits
    unsigned encoding:4;//数据结构底层使用的编码,占4bits
    unsigned lru:LRU_BITS; //用于LRU或者LFU内存淘汰算法,占24bits
    int refcount;//引用计数,占4bytes,引用计数变为0对象被销毁,内存被回收
    void *ptr;//指向具体的数据,占8bytes
};

接下来看一下"type"都有哪些[以下是五种基本的数据结构]

#define OBJ_STRING 0    /* String object. */
#define OBJ_LIST 1      /* List object. */
#define OBJ_SET 2       /* Set object. */
#define OBJ_ZSET 3      /* Sorted set object. */
#define OBJ_HASH 4      /* Hash object. */

接下来看一下数据结构对应的"encoding"
在这里插入图片描述
redis在3.2版本时引入了"quicklist"[也就是linkedlist+ziplist]
在这里插入图片描述
在这里插入图片描述
redis在5.0版本引入"listpack"
在这里插入图片描述
在这里插入图片描述

redis在7版本将"ziplist"替换为"listpack"
在这里插入图片描述
在这里插入图片描述

接下来分析"sds"的源码

sds

首先看一下sds的结构
redis-7.2.2\src\sds.h

typedef char * sds;//sds的本质就是char*指向一块连续的内存

sds分为“sdshdr5”、“sdshdr8”、“sdshdr16”、“sdshdr32”、“sdshdr64”五种结构。其中"sdshdr5"从未使用过。

/* Note: sdshdr5 is never used, we just access the flags byte directly.However is here to document the layout of type 5 SDS strings. */
//sdshdr5从未被使用过,只是用它来直接访问flags字节
struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; //1byte
    uint8_t alloc;//1byte,不包括头部和终止符
    unsigned char flags; /* 3 lsb of type, 5 unused bits  1byte*/
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; /* used  2byte*/
    uint16_t alloc; /* excluding the header and null terminator  2byte*/
    unsigned char flags; /* 3 lsb of type, 5 unused bits  1byte*/
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; /* used  4byte*/
    uint32_t alloc; /* excluding the header and null terminator  4byte*/
    unsigned char flags; /* 3 lsb of type, 5 unused bits  1byte*/
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* used  8byte*/
    uint64_t alloc; /* excluding the header and null terminator  8byte*/
    unsigned char flags; /* 3 lsb of type, 5 unused bits  1byte*/
    char buf[];
};

以上几种结构的布局很相似都是包含"len\alloc\flags\buf"不同的是记录“len\alloc”的字节数不相同。
其中flags的类型如下

#define SDS_TYPE_5  0
#define SDS_TYPE_8  1
#define SDS_TYPE_16 2
#define SDS_TYPE_32 3
#define SDS_TYPE_64 4

encoding

sds底层有三种编码方式
redis-7.2.2\src\server.h

#define OBJ_ENCODING_RAW 0     /* Raw representation */
#define OBJ_ENCODING_INT 1     /* Encoded as integer */
#define OBJ_ENCODING_EMBSTR 8  /* Embedded sds string encoding */

接下来依次看一下编码方式的选择

createStringObject

[redis-7.2.2\src\object.c]

#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
robj *createStringObject(const char *ptr, size_t len) {
    if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)
        return createEmbeddedStringObject(ptr,len);
    else
        return createRawStringObject(ptr,len);
}

从以上代码中可以看出,会根据字符串的长度选择合适的编码方式[embstr or raw]。长度的阈值为44,为什么是44呢,稍后再做解释。接下来看一下"createEmbeddedStringObject"和"createRawStringObject"

createEmbeddedStringObject

/* Create a string object with encoding OBJ_ENCODING_EMBSTR, that is
 * an object where the sds string is actually an unmodifiable string
 * allocated in the same chunk as the object itself. */
 /*
创建一个底层编码为"OBJ_ENCODING_EMBSTR"的string对象
实际上,"embstr"编码的字符串是不能修改的字符串
和"redisobject"分配在同一个内存块中
 */
robj *createEmbeddedStringObject(const char *ptr, size_t len) {
	//将redisObject和sdshdr8分配在同一个chunk中
    robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr8)+len+1);//这里加1指的是结尾的'\0'字符
    struct sdshdr8 *sh = (void*)(o+1);

    o->type = OBJ_STRING;
    o->encoding = OBJ_ENCODING_EMBSTR;
    o->ptr = sh+1;
    o->refcount = 1;
    o->lru = 0;

    sh->len = len;
    sh->alloc = len;//新创建的字符串的cap和len保持一致
    sh->flags = SDS_TYPE_8;
    //const char *SDS_NOINIT = "SDS_NOINIT";
    //将传入参数ptr指向内存的数据存储到buf中
    if (ptr == SDS_NOINIT)
        sh->buf[len] = '\0';
    else if (ptr) {
        memcpy(sh->buf,ptr,len);
        sh->buf[len] = '\0';
    } else {
        memset(sh->buf,0,len+1);
    }
    return o;
}

总结
  • 编码为embstr的字符串是无法被修改的字符串
  • 对于编码为embstr的字符串使用的sds结构为"sdshdr8"
  • 新创建的字符串的cap和len保持一致,并且字符的结尾含有’\0’结束符[猜想这里应该是为了兼容c中字符数组并且必要时候能够使用c的库函数]
  • 编码为embstr的字符串,redisObject和sds分配在同一个chunk中,如下图所示

在这里插入图片描述

createRawStringObject

/* Create a string object with encoding OBJ_ENCODING_RAW, that is a plain
 * string object where o->ptr points to a proper sds string. */
//创建一个底层编码为"raw"的字符串,可选择的sds的结构为
//[sdshdr5,sdshdr8,sdshdr16,sdshdr32,sdshdr64]
robj *createRawStringObject(const char *ptr, size_t len) {
    return createObject(OBJ_STRING, sdsnewlen(ptr,len));
    //createObject根据传入的参数"type、sds"创建新的"redisObject"
    //sdsnewlen根据传入的参数"char*、size_t"创建新的sds
}
sds sdsnewlen(const void *init, size_t initlen) {
    return _sdsnewlen(init, initlen, 0);
}

/*
根据"init指向的content"和initlen创建一个新的sds
如果init指向的内容是NULL,则将字符串初始化为zero bytes
如果init是SDS_NOINIT,buf将不会被初始化
*/
/*
The string is always null-terminated (all the sds strings are, always) 
so even if you create an sds string with:
mystring = sdsnewlen("abc",3);
You can print the string with printf() 
as there is an implicit \0 at the end of the string.
However the string is binary safe and can contain\0 
characters in the middle, 
as the length is stored in the sds header.

将上述一段英文翻译成中文:
所有的sds string都是以空字符即'\0'结尾;
用如下的方式创建一个新的sds string:
mystring = sdsnewlen("abc",3);
可以使用"printf()"函数打印mystring,因为mystring的结尾有个隐式的'\0'字符。
但是mystring是二进制安全的并且可以在在字符串的中间包含'\0'的字符,
因为字符串的长度会被记录在sds的header中。
*/
sds _sdsnewlen(const void *init, size_t initlen, int trymalloc) {
    void *sh;
    sds s;
    //根据initlen大小获取sds的结构类型
    char type = sdsReqType(initlen);
    /* Empty strings are usually created in order to append. Use type 8
     * since type 5 is not good at this. */
    if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
    //获取头部大小
    int hdrlen = sdsHdrSize(type);
    unsigned char *fp; /* flags pointer. */
    size_t usable;

    assert(initlen + hdrlen + 1 > initlen); /* Catch size_t overflow */
    /*
    	Allocate memory or panic.
    	usable is set to the usable size if non NULL. 
    	如果能够成功分配内存,那么usable=hdrlen+initlen+1
    */
    sh = trymalloc?
        s_trymalloc_usable(hdrlen+initlen+1, &usable) :
        s_malloc_usable(hdrlen+initlen+1, &usable);
    if (sh == NULL) return NULL;
    if (init==SDS_NOINIT)
        init = NULL;
    else if (!init)
        memset(sh, 0, hdrlen+initlen+1);
    s = (char*)sh+hdrlen;
    fp = ((unsigned char*)s)-1;
    //正常情况下,usable和initlen保持一致
    usable = usable-hdrlen-1;
    if (usable > sdsTypeMaxSize(type))
        usable = sdsTypeMaxSize(type);
    switch(type) {
        case SDS_TYPE_5: {
            *fp = type | (initlen << SDS_TYPE_BITS);
            break;
        }
        case SDS_TYPE_8: {
            SDS_HDR_VAR(8,s);
            sh->len = initlen;
            sh->alloc = usable;
            *fp = type;
            break;
        }
        case SDS_TYPE_16: {
            SDS_HDR_VAR(16,s);
            sh->len = initlen;
            sh->alloc = usable;
            *fp = type;
            break;
        }
        case SDS_TYPE_32: {
            SDS_HDR_VAR(32,s);
            sh->len = initlen;
            sh->alloc = usable;
            *fp = type;
            break;
        }
        case SDS_TYPE_64: {
            SDS_HDR_VAR(64,s);
            sh->len = initlen;
            sh->alloc = usable;
            *fp = type;
            break;
        }
    }
    //将init指向的内容复制到buf中
    if (initlen && init)
        memcpy(s, init, initlen);
    s[initlen] = '\0';
    return s;
}
robj *createObject(int type, void *ptr) {
//ptr指向创建好的sds
//为redisObject分配一块新的内存空间,由此也可以得出底层编码为"raw"的redisObject和sds分开存储属于两块不同的连续内存块
    robj *o = zmalloc(sizeof(*o));
    o->type = type;
    o->encoding = OBJ_ENCODING_RAW;
    o->ptr = ptr;
    o->refcount = 1;
    o->lru = 0;
    return o;
}
总结
  • 底层编码为"raw"的string是可以修改的,编码为"embstr"的string若想要修改需要先将其的编码变为"raw"才可以被修改。如下图所示
    在这里插入图片描述

  • 根据传入的字符串的initlen从[sdshdr5,sdshdr8,sdshdr16,sdshdr32,sdshdr64]选择合适的sds头部结构

  • 所有的字符串都以’\0’作为结束符,可以使用printf()函数进行打印;二进制安全的,字符串中间可以存储’\0’字符,长度信息存储在sds_header中

  • 编码为"raw"的字符串,redisObject和sds存储在不同的内存块中,如下图所示
    在这里插入图片描述

createStringObjectFromLongDouble

#define MAX_LONG_DOUBLE_CHARS 5*1024
 /*
 从长双精度类型创建字符串对象。如果humanfriendly为非零,则不使用指数
 格式并在末尾修剪尾随的零,但是这会导致精度的损失。否则使用exp格式,并
 且不修改snprintf()的输出。“humanfriendly”选项用于INCRBYFLOAT和
 HINCRBYFLOAT。
 */
robj *createStringObjectFromLongDouble(long double value, int humanfriendly) {
    char buf[MAX_LONG_DOUBLE_CHARS];
    int len = ld2string(buf,sizeof(buf),value,humanfriendly? LD_STR_HUMAN: LD_STR_AUTO);
    return createStringObject(buf,len);
}

在这里插入图片描述

总结

从以上代码可以看出“long double”类型的浮点数以“string”的形式存储,底层的编码方式为"embstr"或者“raw”

createStringObjectFromLongLongWithOptions

/* Create a string object from a long long value according to the specified flag. */
#define LL2STROBJ_AUTO 0       /* automatically create the optimal string object */
#define LL2STROBJ_NO_SHARED 1  /* disallow shared objects */
#define LL2STROBJ_NO_INT_ENC 2 /* disallow integer encoded objects. */
robj *createStringObjectFromLongLongWithOptions(long long value, int flag) {
    robj *o;
    //#define OBJ_SHARED_INTEGERS 10000
    if (value >= 0 && value < OBJ_SHARED_INTEGERS && flag == LL2STROBJ_AUTO) {
    //value值处于[0,10000)进行共享
        o = shared.integers[value];
    } else {
    //在long的范围内,用int进行编码
        if ((value >= LONG_MIN && value <= LONG_MAX) && flag != LL2STROBJ_NO_INT_ENC) {
            o = createObject(OBJ_STRING, NULL);
            o->encoding = OBJ_ENCODING_INT;
            //value值内嵌在在ptr中
            o->ptr = (void*)((long)value);
        } else {
        //超出long表示的范围则根据长度在"embstr or raw"中选择编码
            char buf[LONG_STR_SIZE];
            int len = ll2string(buf, sizeof(buf), value);
            o = createStringObject(buf, len);
        }
    }
    return o;
}

总结

  • value值在[0,10000)内共享redisobject
    在这里插入图片描述

  • value在long范围内,底层编码为int。并且value值内嵌在ptr中
    在这里插入图片描述

  • value不在long范围内,根据len的长度选择合适的编码,“embstr"或者"raw”

相关操作

redis-7.2.2\src\sds.c
通过以上源码的分析,我们发现不论字符串以何种编码方式创建。sds的cap都和len保持一致,这是为了节约内存,因为绝大多数情况下都不会使用"append"操作。
接下来就看一下"append"操作的流程,

sdscatlen


/*
将t指向的字符串拼接到sds指向的字符串的末尾,
拼接之后,传入的sds不再有效,需要使用返回的新sds代替旧的传入的sds
*/
sds sdscatlen(sds s, const void *t, size_t len) {
	//当前sds指向字符串的长度
    size_t curlen = sdslen(s);
    //通过sdsMakeRoomFor计算拼接之后新的字符串的长度并返回新的sds
    s = sdsMakeRoomFor(s,len);
    //没有足够的空间,拼接失败
    if (s == NULL) return NULL;
    //将t指向的字符串拼接到s的末尾,s+curlen存储的是'\0'结尾符,也即是t指向的字符串会覆盖sds指向字符串的'\0'结尾符
    memcpy(s+curlen, t, len);
    //设置新sds的长度
    sdssetlen(s, curlen+len);
    //加上'\0'结尾符
    s[curlen+len] = '\0';
    return s;
}

接下来看一下如何"扩容"的

/*
为sds分配更多的空间避免重复的进行append操作
*/
sds sdsMakeRoomFor(sds s, size_t addlen) {
    return _sdsMakeRoomFor(s, addlen, 1);
}
/* Enlarge the free space at the end of the sds string so that the caller
 * is sure that after calling this function can overwrite up to addlen
 * bytes after the end of the string, plus one more byte for null term.
 * If there's already sufficient free space, this function returns without any
 * action, if there isn't sufficient free space, it'll allocate what's missing,
 * and possibly more:
 * When greedy is 1, enlarge more than needed, to avoid need for future reallocs
 * on incremental growth.
 * When greedy is 0, enlarge just enough so that there's free space for 'addlen'.
 *
 * Note: this does not change the *length* of the sds string as returned
 * by sdslen(), but only the free buffer space we have. */
/*
扩大sds字符串末尾的可用空间,以便调用者在调用此函数后可以覆盖原字符串的addlen字节和'\0'字符
如果已经有足够的空闲空间,这个函数返回时不做任何操作,
如果没有足够的空闲空间,它将分配缺失的部分,甚至更多:
当greedy为1时,分配比需要的更多,以避免再次扩容时需要重新分配。
当greedy为0时,将其放大到足够大以便为addlen腾出空间即可。
注意:这不会改变sdslen()返回的sds字符串的长度,而只会改变我们拥有的空闲缓冲区空间也即是alloc。
*/
sds _sdsMakeRoomFor(sds s, size_t addlen, int greedy) {
    void *sh, *newsh;
    //获取旧sds的可用space,[s->alloc - s->len];
    size_t avail = sdsavail(s);
    size_t len, newlen, reqlen;
    char type, oldtype = s[-1] & SDS_TYPE_MASK;
    int hdrlen;
    size_t usable;

    //可用空闲空间满足要求直接返回,什么也不做
    if (avail >= addlen) return s;
    
    
	//获取旧sds字符串的长度len
    len = sdslen(s);
    
    sh = (char*)s-sdsHdrSize(oldtype);
    //拼接之后新字符串的长度
    reqlen = newlen = (len+addlen);
    
    assert(newlen > len);   /* Catch size_t overflow */
    
    //greedy=1表示需要预先多分配一些内存
    if (greedy == 1) {
    //#define SDS_MAX_PREALLOC (1024*1024) [1MB]
    //newlen小于1MB,翻倍扩容
        if (newlen < SDS_MAX_PREALLOC)
            newlen *= 2;
        else
        //需要的长度大于等于1MB,多扩容1MB
            newlen += SDS_MAX_PREALLOC;
    }
	//根据newlen计算需要使用的sds类型
    type = sdsReqType(newlen);

   
    if (type == SDS_TYPE_5) type = SDS_TYPE_8;

    hdrlen = sdsHdrSize(type);
    //多分配1byte是为了存储末尾的'\0'字符
    assert(hdrlen + newlen + 1 > reqlen);  /* Catch size_t overflow */
    //sds头部类型不变,在原地扩容内存
    if (oldtype==type) {
        newsh = s_realloc_usable(sh, hdrlen+newlen+1, &usable);
        if (newsh == NULL) return NULL;
        //获取新sds的buf
        s = (char*)newsh+hdrlen;
    } else {
    //sds头部类型改变,重新分配内存
        newsh = s_malloc_usable(hdrlen+newlen+1, &usable);
        if (newsh == NULL) return NULL;
        //拷贝旧sds中的内容到新的sds中
        memcpy((char*)newsh+hdrlen, s, len+1);
        s_free(sh);
        s = (char*)newsh+hdrlen;
        s[-1] = type;
        //设置len
        sdssetlen(s, len);
    }
    //计算空闲空间
    usable = usable-hdrlen-1;
    if (usable > sdsTypeMaxSize(type))
        usable = sdsTypeMaxSize(type);
        //设置alloc
    sdssetalloc(s, usable);
    return s;
}

总结

在这里插入图片描述

阈值44

接下来分析一下为什么编码"embstr"和编码"raw"的长度界限值为44
先来回顾一下"redisObject"的样子

struct redisObject {
    unsigned type:4;//表示数据结构的类型,占4bits
    unsigned encoding:4;//数据结构底层使用的编码,占4bits
    unsigned lru:LRU_BITS; //用于LRU或者LFU内存淘汰算法,占24bits
    int refcount;//引用计数,占4bytes,引用计数变为0对象被销毁,内存被回收
    void *ptr;//指向具体的数据,占8bytes
};

通过上述代码,可以计算出redisObject所占用的内存大小为:

4bits+4bits+24bits+4bytes+8bytes=16bytes

通过"createEmbeddedStringObject"的源码分析,发现"embstr"编码的字符串使用的sds结构为sdshdr8

struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; //1byte
    uint8_t alloc; //1byte
    unsigned char flags; //1byte
    char buf[];
};

通过上述代码,可以计算出"sds"中除了存储数据的buf,其余所占用的内存空间大小为:

1byte+1byte+1byte=3bytes

通过对"“和”"的源码分析发现,不论哪种编码[embstr or raw]的字符串都是以’\0’作为结束字符的,所以还需要占用额外的1byte存储结尾的标识符。

整体计算下来,一个完整的字符串除了存储的数据所占用的内存空间大小为:

16bytes+3bytes+1byte=20bytes

而,redis的内存分配器"jemalloc\tcmalloc"等分配内存大小的单位是"2\4\8\16\32\64"字节等,为了能容纳一个完整的"embstr"字符串,至少会分配32字节的内存,至多会分配64字节的内存。如果64字节不够存储的话,redis就将其视为一个大字符串,不再使用"embstr"进行编码,而是采用"raw"进行编码。

所以"embstr"和"raw"之间的阈值为44字节

64bytes-20bytes=44bytes

sds VS C字符串

redis规定字符串的长度为"512MB"。
sds的本质还是char *,但是相比与"C字符串"多了一些字段"len,alloc,flag",优势如下

  • 以O(1)的时间复杂度快速获取字符换的长度。因为字符串的长度会记录在sds的header中。
  • 二进制安全。不再以’\0’字符作为字符串的结束标志,而是记录了字符串的长度,并且可以在字符串的中间位置存储’\0’字符。同样可以使用c相关函数。
  • 不同的数据对应不同的底层编码,更加灵活,更加节省内存。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

HTTP 3xx状态码:重定向的场景与区别

HTTP 状态码是服务器响应请求时传递给客户端的重要信息。3xx 系列的状态码主要与重定向有关&#xff0c;用于指示请求的资源已被移动到不同的位置&#xff0c;需要采取不同的操作来访问。 一、301 Moved Permanently 定义&#xff1a; 服务器表明请求的资源已永久移动到一个新…

基于多反应堆的高并发服务器【C/C++/Reactor】(中)在TcpConnection 中接收并解析Http请求消息

一、在TcpConnection 中多添加和http协议相关的request和response struct TcpConnection {struct EventLoop* evLoop;struct Channel* channel;struct Buffer* readBuf;struct Buffer* writeBuf;char name[32];// http协议struct HttpRequest* request;struct HttpResponse* r…

Phoenix基本使用

1、Phoenix简介 1.1 Phoenix定义 Phoenix是HBase的开源SQL皮肤。可以使用标准JDBC API代替HBase客户端API来创建表&#xff0c;插入数据和查询HBase数据。 1.2 Phoenix特点 容易集成&#xff1a;如Spark&#xff0c;Hive&#xff0c;Pig&#xff0c;Flume和Map Reduce。性能…

Python处理字符串-正则提取遇到的第一个完整括号内容处理后替换

1.场景分析 该场景介绍如何用python语言&#xff0c;使用正则表达式处理字符串内第一个完整的括号内容&#xff0c;一个括号内可能会含有一个括号&#xff0c;多个括号自行扩展正则即可&#xff0c;处理完成后再替换到括号的内容 2.重难点 第一个括号内可能会还有另一个括号 …

Poi实现根据word模板导出-图表篇

往期系列传送门&#xff1a; Poi实现根据word模板导出-文本段落篇 &#xff08;需要完整代码的直接看最后位置&#xff01;&#xff01;&#xff01;&#xff09; 前言&#xff1a; 补充Word中图表的知识&#xff1a; 每个图表在word中都有一个内置的Excel&#xff0c;用于…

云原生Kubernetes: Kubeadm部署K8S 1.29版本 单Master架构

目录 一、实验 1.环境 2.K8S master节点环境准备 3.K8S master节点安装kubelet、kubeadm、kubectl 3.K8S node节点环境准备与软件安装 4.K8S master节点部署服务 5.K8S node节点部署 6.K8S master节点查看集群 7.容器网络&#xff08;CNI&#xff09;部署 8.K8S 集群…

使用Excel批量给数据添加单引号和逗号

表格制作过程如下&#xff1a; A2表格暂时为空&#xff0c;模板建立完成以后&#xff0c;用来放置原始数据&#xff1b; 在B2表格内输入公式&#xff1a; ""&A2&""&"," 敲击回车&#xff1b; 解释&#xff1a; B2表格的公式&q…

2023年全国职业院校技能大赛(高职组)“云计算应用”赛项赛卷③

2023年全国职业院校技能大赛&#xff08;高职组&#xff09; “云计算应用”赛项赛卷3 目录 需要竞赛软件包环境以及备赛资源可私信博主&#xff01;&#xff01;&#xff01; 2023年全国职业院校技能大赛&#xff08;高职组&#xff09; “云计算应用”赛项赛卷3 模块一 …

Deno 1.22 发布

目录 更新默认的类型检查模式 移除Deno.emit()Deno.formatDiagnostics()和Deno.applySourceMap() API 默认启用Deno命名空间 --no-config标识 Navigator.userAgent 更新 Deno.resolveDns() API 引入新的Response.json()静态方法 在 LSP 默认启用 Linting 对测试运行程…

springboot项目创建及采用本地tomcat打包发布

springboot项目发布 maven使用 解压maven安装包 修改配置文件settings.xml 更改镜像(使用maven添加依赖时&#xff0c;选择下载的地址&#xff0c;百度云已提供) <mirror><id>nexus-aliyun</id><mirrorOf>*</mirrorOf><name>Nexus aliyu…

2024PMP考试新考纲-【过程领域】近期典型真题和超详细解析

前面的文章&#xff0c;华研荟讲解了三十多道PMP新考纲下的【人员People领域】的近年真题&#xff0c;这篇文章开始为大家分享【过程Process领域】的新考纲下的真题&#xff0c;进一步帮助大家体会和理解新考纲下PMP的考试特点和如何应用知识来解题&#xff0c;并且举一反三&am…

Linux网络的命令和配置

目录 一、网络配置命令 1、配置和管理网络接口 1.1 ifconfig 1.2 ip 1.2.1 ip link 1.2.2 ip addr 1.3 修改网络接口名 1.3.1 临时修改网络接口名 1.3.2 永久修改网络接口名 1.4 永久配置单网卡 1.5 永久配置双网卡 1.6 ethtool 2、查看和设置主机中路由表信息…

【算法】链表-20240109

这里写目录标题 一、141. 环形链表二、876. 链表的中间结点三、面试题 02.01. 移除重复节点 一、141. 环形链表 简单 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中…

Vue3:Axios配置及使用

Axios官方 一、安装&#xff1a; //使用 npm: $ npm install axios//使用 bower: $ bower install axios//使用 yarn: $ yarn add axios 在package-lock.json文件可以查看axios版本 二、配置&#xff1a; milliaAxios.js 配置axios import axios from axios // 创建一个 ax…

qt打包完整详细过程 包你成功

找问题找了一个多小时&#xff0c;不停调试&#xff0c;还修改文件路径&#xff0c;配置路径&#xff0c;开机关机&#xff0c;最后终于做出来了&#xff0c;得出来了一个结论 我绝对是天才 首先 看这个视频 k14 打包发布_哔哩哔哩_bilibili 不出意外&#xff0c;你绝对会在…

FreeRTOS学习——任务通知

一、什么是任务通知 FreeRTOS 从版本 V8.2.0 开始提供任务通知这个功能&#xff0c;每个任务都有一个 32 位的通知值。按照 FreeRTOS 官方的说法&#xff0c;使用消息通知比通过二进制信号量方式解除阻塞任务快 45%&#xff0c; 并且更加省内存&#xff08;无需创建队 列&#…

555断线报警器电路图

电路的核心部分由NE555组成&#xff0c;R1、R2、C1和NE555组成一个频率越为3KHz左右的多谐振荡电路&#xff0c;当电路接通电源时&#xff0c;振荡器开始工作蜂鸣器LS1发出响声&#xff1b;当1和2被短接时&#xff0c;振荡器的工作条件被破坏&#xff0c;LS1停止工作。 电路分…

React ant table警告:Each child in a list should have a unique “key“ prop.

如下图&#xff1a; 原因 React Ant table表格每一行都需要一个唯一标识来确保不重复&#xff0c;如果不加该属性&#xff0c;就会出现这个警告。 修复 添加这一行&#xff1a; rowKey{(record) > record.id} # id为行idTable代码段&#xff1a; <TabledataSourc…

华为mux vlan+DHCP+单臂路由用法配置案例

最终效果&#xff1a; vlan 2模拟局域网服务器&#xff0c;手动配置地址&#xff0c;也能上公网 vlan 3、4用dhcp分配地址 vlan 4的用户之间不能互通&#xff0c;但可以和其它vlan通&#xff0c;也能上公网 vlan 3的用户不受任何限制可以和任何vlan通&#xff0c;也能上公网 交…

How can I be sure that I am pulling a trusted image from docker?

1、Error response from daemon: manifest for jenkins:latest not found: manifest unknown: manifest unknown 2、Error response from daemon: pull access denied for nacos, repository does not exist or may require ‘docker login’: denied: requested access to th…