Redis系列之——深入了解Redis的String,源码层级+极易理解图片解析!

Redis的数据结构

Redis最常用的数据类型有五种

  • String: 字符串
  • Hash: 散列
  • List: 列表
  • Set: 集合
  • Sorted Set: 有序集合

五种其实是Redis键值对中值存储的数据类型,而他们的底层数据结构一共有6种:分别是

  • 简单动态字符串
  • 双向链表
  • 压缩列表
  • 哈希表
  • 跳表
  • 整数数组

数据类型和数据结构的对应关系如下图:

这张图会在未来几篇文章中反复出现,帮大家彻底了解Redis的基础类型。

今天我们就来聊一聊其中的string

Redis是用C写的,那为什么不用C语言的String?

众所周知,Redis是用C语言写的,那Redis为什么没有使用C原生的字符串,而是自己创建了一个简单动态字符串?(SDS simple dynamic string)

  • C语言的字符串底层是用字符数组来实现的,在一片连续的空间中依次存放字符,为了判断字符的结束,他会在最后以'\0'作为识别,这样就会带来以下问题

    1. 无法存放任意的字符,至少'\0'是不可以的,这就会导致一些如图片,音频等出现了'\0'就会出现问题。
    2. 对字符串进行追加等操作的时候,必须遍历到'\0'才可以操作,会导致效率比较低,复杂度为o(n)
  • C语言的字符串是不记录字符串长度的,一旦我们调用了拼接函数等,而没有提前计算好内存,就会产生缓冲区溢出的情况,所以为了不出问题,会进行内存重分配,而这又多出了内存重分配的性能损耗

那么,Redis是怎么处理这些问题的呢?

简单动态字符串(Redis5.0版本)

Redis中的字符串数据是通过简单动态字符串(以下简称SDS)来存储数据的。

SDS到底是什么?

我们先来看看SDS的结构长什么样

  • len:表示buf的已用长度。(sdshdr8中占1个字节)
  • alloc:表示分配给buf的总长度,不包括结构体和'\0'结束字符。(sdshdr8中占1个字节)
  • flags: SDS类型。(sdshdr8中占一个字节)
  • buf:字节数组,保存实际数据。为了表示字节数组的结束,Redis 会自动在数组最后加一个“\0”,这就会额外占用 1 个字节的开销。(nycdf--> 你也才掉发是我在这里写的示例,代表存储的某个数据)

你丫才掉发小课堂--详解SDS类型

SDS 结构中的字段 flags,表示的是SDS的类型。在Redis中SDS一共设计了5种类型,分别是

  • sdshdr5(未使用)
  • sdshdr8
  • sdshdr16
  • sdshdr32
  • sdshdr64

这5种类型的主要区别就在于,它们数据结构中的len和alloc,这两个字段占据的大小不同,也就是这个结构体能存储的长度不同。下面他们是结构体的源码:

从源码我们可以看出来,其实最大的不同就是len和alloc所代表的字节数不同,那么比如sdshdr8中这2个字段的类型uint8_t,也就是这个类型结构最大存储的字符长度为256(2的8次方),其他的以此类推。

这样的设计目的:Redis是基于内存的,而内存永远都是珍贵的资源,每一个字节都很重要,所以针对不同大小的字符串使用不同的结构,也是为了节省内存资源。

在SDS中,除了上面解释过的flags,对比于C传统的char[],SDS新增了2个参数,实际占用长度len 和总分配长度alloc

SDS解决了什么问题呢?

1. 获取字符串长度的复杂度问题

上面我们说过,c语言遍历一下字符串的复杂度是o(n)

对于SDS来说,我们有了长度的参数,那么我们的复杂度就是o(1),这样在进行拼接等操作的时候不需要再次遍历了。

2. 存储二进制数据

C字符串不能存储二进制数据的原因是只能根据'\0'来判断数据是否结束,不能保证其完整性,但因为SDS的len属性,无论你数据里有多少'\0'都没关系,我是根据 len 属性值来判断数据长度的,必定是完整的,所以SDS可以安全地存储二进制数据,这样图片音频等二进制数据也可以存储。

你丫才掉发小课堂

Q:SDS你都有len了,那为啥还要跟C字符串一样在字符串后面加'\0',是不是多此一举了?

A:SDS如果保持和C字符串一样的特性,就不用专门编写多余的函数了,在一定的情况下可以直接用C语言的函数,避免了不必要的重写。

3. 分配内存空间问题

上面还在存在一个问题是在使用拼接等操作的时候C语言是需要重分配空间的,而这个又是非常的消耗资源。SDS提供的2个特性就是空间预分配惰性空间释放

空间预分配

空间预分配源码

在真正执行拼接等操作之前,会先计算下空间是否满足

  1. 如果计算后的SDS的长度(源码中的newLen)小于1MB,那么就会分配和len属性同样大的未使用空间,相当于将原数组长度乘以二

  1. 如果计算后的SDS的长度(源码中的newLen)大于或等于1MB,那么就会分配固定的1MB未使用空间

所以当有N次拼接操作时候,一定需要N次的内存重分配操作,现在最多只需要N次

比如你第一次拼接后,剩余的长度(alloc - len)就大于后续要拼接的字符串长度之和了,那其实就只有1次内存重分配操作,就可以进行至少2次拼接操作,所以说最多N次,这也就可以省下很多分配空间的消耗。

惰性空间释放

当有缩短SDS字符串操作时,程序并不立即把空闲出来的字节释放掉,而是留给到惰性删除策略等机制释放。

具体的表现就是:

在做删除操作的时候,当删掉N个字符后,alloc-len(也就是剩余空间)就增加N,换个说法,就是你删除的字符并没有立即被系统回收,这样当你短时间内再次拼接的时候,就不会申请空间分配了。

只有到了对应策略或者手动去删除的时候才会执行以下的代码:

代码的逻辑就是删除多余的空间直到没有浪费为止。

有了SDS,就万事大吉了么?

前面我们聊完了SDS,已经大概的知道了SDS是什么样的,解决了什么问题,这里给大家提出一些更深层的问题:

  1. 如果我的string是数字,Redis也按照字符串存储么?
  2. string在Redis中就是只有SDS就可以落地了么,有没有别的结构支撑?
  3. SDS最大支持存储长度为2的64次方的数组,这些超长的字符串他们是怎么存储的?

初识RedisObject

在我们真实使用Redis中string的时候,除了SDS的存储空间,还存在一个Redis本身的结构体,也就是RedisObject。

RedisObject存储空间

以上是RedisObject源码,

  • type:redisObject的数据类型,4个bits
  • encoding:redisObject的编码类型,4个bits
  • lru:LRU_BITS:redisObject的LRU时间,LRU_BITS为24个bits
  • refcount:redisObject的引用计数,4个字节
  • *ptr:指向值的指针,8个字节

为了形象的展示占据的大小:

RedisObject和String

这里暂时不对RedisObject展开讲,我们来看看当RedisObject和SDS在一起的时候大概是个什么样子?

上图表示的就是普通情况下RedisObject和SDS的关系。

作为Redis最常用的结构之一,针对String也做了很多的优化

1. 数字类型的字符串

对于数字类型的字符串,Redis在RedisObjec中的指针就直接赋值为整数数据了,这么指针的开销就省去了。

2. embstr编码方式(embed:嵌入式)

在保存的是字符串数据,如果字符串小于等于44字节时(为什么是44呢?读到这里可以思考一下),Redis就会将RedisObjec和SDS构建成一块连续的内存区域,既可以提升查询的速度(少了一次指针的跳转),也可以防止内存碎片的产生。

我们先来看下这里判断的源码:

  • 当字节数小于44,调用createEmbeddedStringObject
  • 当字节数大于44,调用createRawStringObject

这里我们来看下createEmbeddedStringObject的部分源码

这里做的事情核心

  1. 先分配一段连续的内存空间(RedisObject+SDS+1的长度,这里的1是为了字符串最后的`'\0'),并创建RedisObject和SDS的机构。
  2. 将RedisObject的ptr指针指向字符数组的开始。(细节!!!!)
  3. 拷贝字符串到SDS的buf[]中。

这个时候内存块包含如下几部分:

  • 16个字节的robj结构。
  • 3个字节的sdshdr8头。
  • 最多44个字节的sds字符数组。
  • 1个'\0'结束符。

3. raw编码模式

在保存的是字符串数据,字符串大于44字节时,Redis就不像embstr编码方式一样把RedisObject和SDS存放在一起,这时RedisObject中的*ptr就会发挥真正的作用,Redis在内存中重新分配一块空间给SDS,并用*ptr指向这个SDS。

这里我们来看下createRawStringObject的部分源码

 

这里就比较简单了,一共做了这2件事:

  1. 先使用sdsnewlen返回SDS结构的指针
  2. 将SDS指针赋值给RedisObject中的指针

最后再给大家把所有的编码格式放一起汇总一下:👇

<think>好的,用户想了解如何设计和实现一个基于Spring Boot的服饰穿搭系统。首先,我需要整理Spring Boot的基础知识,以及如何应用到实际系统中。根据引用中的信息,类似系统如服装设计系统和个人服装定制系统已有实现,可以参考架构设计和技术选型。 接下来,系统模块划分是关键。用户可能希望系统具备用户管理、服饰管理、搭配推荐、社区互动等功能。需要明确每个模块的具体功能,比如用户模块需要注册登录和个人信息管理,引用[1]和[2]提到使用MySQL和JSP技术,可以借鉴数据库设计和前后端交互的实现。 技术选型方面,后端用Spring Boot,前端可能用Vue或React,数据库用MySQL,这些在引用[2]中也有提到。需要确保技术栈的可行性和开发效率,比如MyBatis或JPA来处理数据持久化,Spring Security做权限控制。 核心功能实现部分,要详细说明如何实现搭配推荐算法,可能基于标签、用户偏好或机器学习模型。引用[1]中的系统可能涉及类似功能,但需要具体到服饰属性的处理,比如颜色、季节等。此外,社区模块需要图片上传和动态发布,可能需要集成文件存储服务如OSS。 性能优化和安全性也不能忽视,引用[2]提到B/S模式和Java技术,需考虑缓存机制和SQL优化,确保系统响应速度。权限管理方面,Spring Security可以控制不同角色的访问权限,保护用户数据。 测试部署阶段,分模块测试和自动化测试保证稳定性,Docker容器化部署方便运维,引用[4]提到源码在Bilibili,可能涉及部署文档或脚本。 最后,扩展方向如3D试衣或智能推荐,可以提升系统竞争力。需要确保引用正确,比如引用[1]和[2]提供相关实现参考,引用[3]和[4]帮助用户获取源码。整个过程中,要确保LaTeX格式正确,问题相关且具体,帮助用户深入理解开发流程。</think>### 基于Spring Boot的服饰穿搭系统设计与实现方案 #### 一、系统架构设计 采用分层架构模式,分为表现层、业务逻辑层、数据持久层和数据库层。核心模块包括: 1. **用户模块**:注册登录(OAuth2.0集成)、个人资料管理、偏好设置 2. **服饰管理**:支持多维度分类(季节/风格/场合)的CRUD操作,采用$ER$模型设计数据库[^2] 3. **智能搭配模块**:基于协同过滤算法的推荐系统,计算公式: $$S(u,i) = \sum_{v \in N(u)} w_{uv}(r_{vi} - \bar{r_v})$$ 其中$w_{uv}$表示用户相似度,$r_{vi}$为用户v对物品i的评分 4. **社区互动模块**:用户穿搭分享、点赞收藏、评论交流 #### 二、技术选型 | 层级 | 技术栈 | |------------|-------------------------| | 前端 | Vue.js + ElementUI | | 后端 | Spring Boot 2.7 + Security | | 数据库 | MySQL 8.0 + Redis缓存 | | 文件存储 | 阿里云OSS | | 部署 | Docker + Nginx | #### 三、核心功能实现 1. **服饰特征提取** ```java // 基于OpenCV的服饰特征分析 public class ClothingAnalysis { public Map<String, Object> analyze(Mat image) { // 颜色直方图分析 Mat hist = new Mat(); Imgproc.calcHist(Arrays.asList(image), new MatOfInt(0), new Mat(), hist, new MatOfInt(256), new MatOfFloat(0,256)); // 纹理特征提取 Mat gray = new Mat(); Imgproc.cvtColor(image, gray, Imgproc.COLOR_BGR2GRAY); MatOfFloat descriptors = new MatOfFloat(); FeatureDetector.create(FeatureDetector.ORB).detect(gray, descriptors); return Map.of("color", hist.dump(), "texture", descriptors.dump()); } } ``` 2. **搭配推荐算法** - 基于标签匹配:$MatchScore = \sum_{i=1}^n w_i \cdot sim(t_{a_i}, t_{b_j})$ - 协同过滤:采用Spark MLlib实现分布式计算 - 深度学习:使用TensorFlow构建CNN搭配预测模型 #### 四、关键技术实现 1. **安全架构设计** ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/api/**").authenticated() .and() .oauth2Login() .userInfoEndpoint() .userService(customOAuth2UserService); } } ``` 2. **性能优化方案** - 二级缓存设计:Guava Cache + Redis Cluster - SQL优化:通过Explain分析查询计划,建立复合索引 $$CREATE INDEX idx_style_season ON clothing(style_tag, season_tag)$$ #### 五、测试与部署 1. 采用JUnit5 + Mockito进行单元测试 2. 使用JMeter进行并发压力测试 3. 部署流程: ```bash # 构建Docker镜像 docker build -t fashion-system:v1.0 . # 启动容器集群 docker-compose up -d mysql redis docker-compose up --scale app=3 -d ``` #### 六、扩展方向 1. 3D虚拟试衣间(Three.js集成) 2. 智能衣橱管理(RFID硬件对接) 3. 时尚趋势预测(LSTM时间序列分析)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值