源码|redis7.2.2|sds

news2024/11/20 1:52:08

文章目录

  • 前言
  • 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相关函数。
  • 不同的数据对应不同的底层编码,更加灵活,更加节省内存。

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

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

相关文章

微信这一功能全面升级,万万没想到

大家好&#xff0c;我是小悟 兄弟们&#xff0c;今天要说的是&#xff0c;复用已认证公众号资料快速注册已认证小程序的时代一去不复还了。 再从公众号快速注册小程序入口去注册小程序&#xff0c;注册后也是未认证的状态&#xff0c;需要单独付费认证。通过该方式注册的小程序…

案例:自定义下拉刷新动画(ArkTS)

文章目录 介绍相关概念完整示例代码结构解读 自定义下拉组件自定义刷新动画总结 介绍 本篇Codelab主要介绍组件动画animation属性设置。当组件的某些通用属性变化时&#xff0c;可以通过属性动画实现渐变效果&#xff0c;提升用户体验。 本Codelab使用的display接口处于mock阶…

私域新手必看:这可能是你最快速的入门指南!

一、认知层&#xff1a;为什么必须做私域&#xff1f; 了解这个问题&#xff0c;必须得观察一下中国现状。根据中国互联网络信息中心&#xff08;CNNIC&#xff09;发布第48次《中国互联网络发展状况统计报告》显示&#xff0c;截至 2021 年 6 月&#xff1a; 而典型电商平台的…

Transformers 2023年度回顾 :从BERT到GPT4

人工智能已成为近年来最受关注的话题之一&#xff0c;由于神经网络的发展&#xff0c;曾经被认为纯粹是科幻小说中的服务现在正在成为现实。从对话代理到媒体内容生成&#xff0c;人工智能正在改变我们与技术互动的方式。特别是机器学习 (ML) 模型在自然语言处理 (NLP) 领域取得…

小美的蛋糕切割(美团2024届秋招笔试第一场编程真题)

题目分析&#xff1a;切蛋糕问题切记可以横着切&#xff0c;也可以竖着切。本题目需要快速求得两个矩形的和&#xff0c;可以用二维前缀和数组解决。然后枚举一刀切的位置。 #include <iostream> #include <algorithm> using namespace std; int a[1005][1005]; l…

Unity中URP下抓屏的 开启 和 使用

文章目录 前言一、抓屏开启1、Unity下开启抓屏2、Shader中开启抓屏 二、抓屏使用1、设置为半透明渲染队列&#xff0c;关闭深度写入2、申明纹理和采样器3、在片元着色器使用请添加图片描述 三、测试代码 前言 我们在这篇文章中看一下&#xff0c;URP下怎么开启抓屏。 一、抓屏…

DAY3 -Learning English

一、积累 1.谁在乎啊&#xff08;who gives a shit&#xff09; 例句&#xff1a;who gives a shit about what he think. 谁在乎他怎么想的? 2. 往事如烟|过去的都过去了(water under the bridge) The argument we had is now water under the bridge . 我们之前的争论已…

爬取国家法律法规数据库法律条文

代码展现&#xff1a; import requests import os filename 法律条文\\ if not os.path.exists(filename):os.mkdir(filename) url https://flk.npc.gov.cn/api/? data {type: flfg,searchType: title;vague,sortTr: f_bbrq_s;desc,gbrqStart: ,gbrqEnd: ,sxrqStart: ,sx…

【案例实战】业务稳定性运行之全链路混合压测

1.全链路压测开展步骤 &#xff08;1&#xff09;什么是全链路压测 全链路压测是指基于真实业务场景&#xff0c;通过模拟海量的用户请求&#xff0c;对整个后台服务进行压力测试&#xff0c;从而评估整个系统的性能水平。 对应用程序的整个技术栈进行完整的压力和性能测试&a…

Eureka切换Nacos时发现两个注册中心的解决方法

报错信息如下&#xff0c;意思是发现了两个注册中心 Field autoServiceRegistration in org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration required a single bean, but 2 were found: - nacosAutoServiceRegistration: defined…

6.OpenResty系列之深入理解(二)

1. 日志输出 vim /usr/local/openresty/nginx/conf/nginx.conf默认配置如下 #error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info;#pid logs/nginx.pid;http {#log_format main $remote_addr - $remote_user [$time…

使用Nonebot编写QQ机器人

使用 NoneBot 这个工具&#xff0c;来编写 QQ 机器人。 安装基础软件 一、安装 NoneBot 库 直接使用 pip 安装即可 pip install nonebot二、安装酷Q 软件和 HTTP API 插件 酷Q 软件可以直接到官网下载&#xff0c;https://cqp.cc/b/news&#xff0c;或者可以到网盘下载&am…

TinyLog iOS v3.0接入文档

1.背景 为在线教育部提供高效、安全、易用的日志组件。 2.功能介绍 2.1 日志格式化 目前输出的日志格式如下&#xff1a; 日志级别/[YYYY-MM-DD HH:MM:SS MS] TinyLog-Tag: |线程| 代码文件名:行数|函数名|日志输出内容触发flush到文件的时机&#xff1a; 每15分钟定时触发…

SpringMVC 获取参数

文章目录 1、通过ServletAPI获取2、通过控制器方法的形参获取请求参数3、[RequestParam ](/RequestParam )4、[RequestHeader ](/RequestHeader )5、[CookieValue ](/CookieValue )6、通过POJO获取请求参数7、解决获取请求参数的乱码问题 1、通过ServletAPI获取 将HttpServlet…

HarmonyOS中的@ohos.promptAction 模块中弹框

在各种APP中会根据不同的业务场景显示不同的弹框情况&#xff0c;针对这些场景API中提示了那些弹框呢&#xff1f;今天就看下&#xff1a; 首先弹框分为模态弹框和非模态弹框&#xff0c;模态弹框必须用户点击反馈后进行下一步操作&#xff0c;非模态弹框只是告知用户信息&…

LeetCode 225.用队列实现栈(详解) ૮꒰ ˶• ༝ •˶꒱ა

题目详情&#xff1a; 思路&#xff1a;1.定义两个队列用于存储栈的数据&#xff0c;其中一个为空。 2.对我们定义的栈进行入数据&#xff0c;就相当于对不为空的队列进行入数据。 3.对我们定义的栈进行删除&#xff0c;相当于取出不为空的队列中的数据放到为空的队列中&#x…

什么是云安全?如何保护云资源

云计算允许组织通过互联网按需向其客户、合作伙伴或员工提供关键业务应用程序、服务和资源。换句话说&#xff0c;不再需要物理维护资源。每当您通过 Internet 从计算机访问文件或服务时&#xff0c;您都是在访问云。 迁移到云可以帮助企业增强安全性、简化运营并降低成本。企…

Linux 常用进阶指令

我是南城余&#xff01;阿里云开发者平台专家博士证书获得者&#xff01; 欢迎关注我的博客&#xff01;一同成长&#xff01; 一名从事运维开发的worker&#xff0c;记录分享学习。 专注于AI&#xff0c;运维开发&#xff0c;windows Linux 系统领域的分享&#xff01; 其他…

php 的运算符

目录 1.算数运算符 2.自增自减 3.比较运算符 4.赋值运算 5.逻辑运算符 6.三元运算 1.算数运算符 运算符名称描述a b加和a - b减差a * b乘积a/b除a和b的商a % b模&#xff08;除法的余数&#xff09;a 除以 b的余数-a取负数a 的负数a.b并置连接两个字符串 <?php he…

国内呼叫中心产业的发展趋势和其如何进入国际市场

最近跟一些行内的朋友聊天&#xff0c;能感受到大家的悲观情绪&#xff0c;明年如何&#xff0c;不可知&#xff0c;似乎都没什么信心会更好。 作为OKCC呼叫中心系统厂商&#xff0c;大环境如此&#xff0c;我们也面临同样的问题&#xff0c;同时也从网上和朋友那了解我们这个行…