蜻蜓点水说说Redis的String的奥秘
如果面试官问你,单线程的Redis为什么那么快,你可能脱口而出,因为单线程,避免上下文切换;因为基于内存,比硬盘读写快很多;因为采用的是多路复用网络模型。不管你是否真的理解了,这个回答足以应付一半以上的面试官了,但是如果可以再进行补充就更好了:因为Redis对各种数据结构进行了精心的设计,比如String采用的是SDS,比如list采用的是ziplist,quicklist等等,可能这样的回答就比较出彩了,至少可以说出部分面试者不太清楚的事情。今天我们就来看看Redis中最常用的String数据结构的奥秘。
从位操作说起
bitmap的应用场景很多,比如大名鼎鼎的布隆过滤器(之前的博客有介绍过:《大白话布隆过滤器》),比如统计指定用户在一年内任意日期内的登录情况,统计任意日期内,所有用户的登录情况等等,都可以用bitmap来实现(之前的博客也有介绍过:《有点长的博客:Redis不是只有get set那么简单》),所以好好看看bitmap还是很有必要的,不过本篇博客不打算详细介绍bitmap,只是通过bitmap引出我们今天的话题,而bitmap的核心就是位操作。
如果我们要往Redis塞入一个value为“hello”的key,这个所有人都会:
test:1>set key hello
"OK"
test:1>get key
"hello"
如果我们要利用位操作实现这个需求呢?什么,我没听错把,位操作也可以实现这个需求吗?当然可以,因为在redis中,String就是用byte数组来存储的。
什么,你不信?那请继续看下去。
要用位操作实现这个需求,我们要获得“hello”的ascii码,接着计算出二进制:
比如,“h”的ascii码是104,二进制是1101000:

"e"的ascii码是65,二进制是101,二进制是1100101:

然后形成如下的位图:

下面就需要利用位操作来进行设置:
test:1>setbit s 1 1
"0"
test:1>setbit s 2 1
"0"
test:1>setbit s 4 1
"0"
test:1>setbit s 10 1
"0"
test:1>setbit s 13 1
"0"
test:1>setbit s 9 1
"0"
test:1>setbit s 15 1
"0"
setbit的顺序可以随意调整,只要最终得到的位图是如上形式的就OK了。(我这里就调整了下seitbit的顺序,好吧,我承认其实我是打错了,又懒得再去打一遍,反正最终形成的位图是一样的)。
然后我们get一下:
test:1>get s
"he"
很神奇,有木有,这也说明了在redis的底层,String就是一个数组,而且还是一个byte[]。
SDS
不管在什么编程语言、存储引擎中,String都是应用最广泛的,而在不同的编程语言、存储引擎中,String可能有不同的实现,在Redis中,String的底层就是SDS,它的全称是Simple Dynamic String。
Redis是C语言开发的,Redis为什么不直接利用C语言的字符串,而要“别出心裁”的自己构建SDS数据结构来实现字符串呢?
我们先来这个SDS是个什么鬼:
struct sdshdr {
int len;
int free;
char buf[];
};
SDS的定义比较简单,只有3个字段,而且从字面上就可以看出是什么意思:
- len:存储字符串的实际长度
- free:存储剩余(空闲)的空间
- buf[]:存储实际数据
下面我们就来看下SDS和C语言的字符串有什么区别:
- 求字符串长度
在C语言中,求字符串的长度只能遍历,时间复杂度是o(n),单线程的redis表示鸭梨山大,但是现在引入了一个字段来存储字符串的实际长度,时间复杂度瞬间降低成了o(1)。 - 二进制安全
在C语言中,读取字符串遵循的是“遇零则止”,即,读取字符串,当读取到“\0”,就认为已经读到了结尾,哪怕后面还有字符串也不会读取了,像图片、音频等二进制数据,经常会穿插“\0”在其中,好端端的图片、音频就毁了...但是现在有了一个字段来存储字符串的实际长度,读取字符串的时候,先看下这个字符串的长度是多少,然后往后读多少位就可以了。 - 缓冲区溢出
字符串拼接是开发中常见的操作,C语言的字符串是不记录字符串长度的,一旦我们调用了拼接函数,而没有提前计算好内存,就会产生缓冲区溢出的情况,但是现在引入了free字段,来记录剩余的空间,做拼接操作之前,先去看下还有多少剩余空间,如果够,那就放心的做拼接操作,不够,就进行扩容。 - 减少内存重分配次数
- 空间预分配:当对字符串进行拼接操作的时候,Redis会很贴心的分配一定的剩余空间,这块剩余空间现在看起来是有点浪费,但是我们如果继续拼接,这块剩余空间的作用就出来了。
- 惰性空间释放:当我们做了字符串缩减的操作,Redis并不会马上回收空间,因为你可能即将又要做字符串的拼接操作,如果你再次操作,还是没有用到这部分空间,Redis也会去回收这部分空间。
扩容策略
字符串小于1M,采用的是加倍扩容的策略,也就是多分配100%的剩余空间,当大于1M,每次扩容,只会多分配1M的剩余空间。
最大长度
Redis 规定字符串的长度不得超过 512M 字节。
embstr raw
Redis的字符串有两种存储方式,一种是embstr,一种是raw,当长度<=44,采用embstr 来存储:
set codebear abcdefghijklmnopqrstuvwxyz012345678912345678
"OK"
debug object codebear
"Value at:0x7f4050476880 refcount:1 encoding:embstr serializedlength:45 lru:1999016 lru_seconds_idle:36"
当长度>44,改用raw来存储:
set codebear abcdefghijklmnopqrstuvwxyz0123456789123456781
"OK"
debug object codebear
"Value at:0x7f404ac30100 refcount:1 encoding:raw serializedlength:46 lru:1999188 lru_seconds_idle:3"
网上也有一些博客说是以39为分界线,为什么会有两种答案呢?继续看下去就明白了。
我们先来看看Redis的对象头,查看:
#define LRU_BITS 24
typedef struct redisObject {
unsigned type:4;
unsigned encoding:4;
unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
* LFU data (least significant 8 bits frequency
* and most significant 16 bits access time). */
int refcount;
void *ptr;
} robj;
Redis对象头占 4bit+4bit+24bit+4byte+8byte(指针,在64 bit system下,占8byte)=32bit+12byte=4byte+12byte=16byte。
再来看看这两种存储形式有什么区别:

embstr的存储形式比较紧凑,Redis的对象头和SDS对象存在一起(连续)。

一般来说,在raw的存储形式下,Redis的对象头和SDS对象不存在一起(不连续)。
我们可以简单的理解为,一块内存的大小为64byte。
好了,前置内容介绍完毕了,我们来看看Redis3.0版本的SDS的定义,查看:
struct sdshdr {
unsigned int len;
unsigned int free;
char buf[];
};
Redis对象头占了16byte,SDS对象的len和free又占了8byte,64-16-8=40,同时保存的字符串会以\0结尾,又占用了1byte,所以实际存储的字符串只能<=39位,所以在低版本的Redis下,embstr、raw的分界线为39。
再来看看Redis5.0版本的SDS的定义,查看:

可以看到变化很大,为什么要做那么大的改变?更节省内存,当字符串长度比较小的时候,会用
sdshdr8来存储,len和alloc共占用2byte,flags占用1byte,\0结尾占用1byte,一共是4byte,64byte-16byte(对象头)-4byte=44byte,所以在高版本的Redis下,embstr、raw的分界线为44。
怎么样,没想到吧,我们Redis经常使用的String竟然牵扯到那么多东西,而这些东西就可以区分平庸开发和优秀开发,成为一个优秀的开发,要学习的东西还有很多很多。
蜻蜓点水说说Redis的String的奥秘的更多相关文章
- 蜻蜓点水说说Redis的ziplist的奥秘
本篇博客参考: Redis 深度历险:核心原理与应用实践 Redis内部数据结构详解(4)--ziplist Redis的压缩列表ZipList 上篇博客中,我给大家蜻蜓点水般的介绍了Redis中SD ...
- 二、Redis基本操作——String(实战篇)
小喵万万没想到,上一篇博客,居然已经被阅读600次了!!!让小喵感觉压力颇大.万一有写错的地方,岂不是会误导很多筒子们.所以,恳请大家,如果看到小喵的博客有什么不对的地方,请尽快指正!谢谢! 小喵的唠 ...
- 一、Redis基本操作——String(原理篇)
小喵的唠叨话:最近京东图书大减价,小喵手痒了就买了本<Redis设计与实现>[1]来看看.这里权当小喵看书的笔记啦.这一系列的模式,主要是先介绍Redis的实现原理(可能很大一部分会直接照 ...
- Redis之String
一.Redis之String简介 1. String是redis最基本的数据类型,一个key对应一个value. 2. String是二进制安全的,可以包含任何数据,例如图片或序列化的对象. 3. S ...
- redis对string进行的相关操作
redis对string类型操作的相关命令以及如何在python使用这些命令 redis对string类型操作的命令: 命令 语法 概述 返回值 Redis SET 命令 set key value ...
- Redis操作string
Redis简介: ''' redis: 缓存,例如两个个程序A,B之间要进行数据共享,A可以把数据存在redis(内存里),其他程序都可以访问redis里的数据, 这样通过中间商redis就实现了两个 ...
- Redis学习-string数据类型
Redis 是一个开源的使用 ANSI C 语言编写.支持网络.可基于内存亦可持久化的日志 型.Key-Value 数据库. redis提供五种数据类型string,hash,list,set及sor ...
- 使用Redis数据库(String类型)
一 String类型 首先使用启动服务器进程 : redis-server.exe 1. Set 设置Key对应的值为String 类型的value. 例子:向 Redis数据库中插入一条数据类型为S ...
- PHP操作redis之String(字符串)、List(列表)(一)
Redis 简介 Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库. Redis 与其他 key – value 缓存产品有以下三个特点: Redis支持数据的持久 ...
随机推荐
- 字符串String和list集合判空验证
1`字符串判断处理: 结论: 当if判断条件为两个,并且它们两个为或的关系,如果第一个条件为false,则继续第二个条件的判断:如果第一个条件为true,该例子不足以说明是否判断第二个条件, 最终可以 ...
- 一张PDF了解JDK9 GC调优秘籍-附PDF下载
目录 简介 Oracle中的文档 JDK9中JVM参数的变化 废弃的JVM选项 不推荐(Deprecated)的JVM选项 被删除的JVM参数 JDK9的新特性Application Class Da ...
- JavaScript基础-即时函数(Immediate Functions)(017)
1.即时函数的声明方法 即时函数(Immediate Functions)是一种特殊的JavaScript语法,可以使函数在定义后立即执行:(function () { alert('watch ...
- Bootstrap 3.3
https://jeesite.gitee.io/front/bootstrap/3.3/v3.bootcss.com/index.htm
- 看源码,重新审视Spring Security中的角色(roles)是怎么回事
在网上看见不少的博客.技术文章,发现大家对于Spring Security中的角色(roles)存在较大的误解,最大的误解就是没有搞清楚其中角色和权限的差别(好多人在学习Spring Security ...
- Python中的错误和异常
前言 错误是程序中的问题,由于这些问题而导致程序停止执行.另一方面,当某些内部事件发生时,会引发异常,从而改变程序的正常流程. python中会发生两种类型的错误. 语法错误 逻辑错误(异常) 语法错 ...
- 每日一题 - 剑指 Offer 30. 包含min函数的栈
题目信息 时间: 2019-06-24 题目链接:Leetcode tag:栈 难易程度:简单 题目描述: 定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 m ...
- Mysql查询语句执行过程
Mysql查询语句执行过程 Mysql分为server层和存储引擎两部分,或许可以再加一层连接层 连接层(器) Mysql使用的是典型的C/S架构.连接器通过典型的TCP握手完成连接. 需要注 ...
- BUUCTF-Misc-No.3
比赛信息 比赛地址:Buuctf靶场 内心os(蛮重要的) 我只想出手把手教程,希望大家能学会然后自己也成为ctf大佬,再来带带我QWQ 文件中的秘密 | SOLVED | 打开文件,winhex照妖 ...
- 006.Nginx访问控制
一 Nginx连接限制 1.1 HTTP协议的连接与请求 HTTP是建立在TCP, 一次HTTP请求需要先建立TCP三次握手(称为TCP连接),在连接的基础上再进行HTTP请求. HTTP请求建立在一 ...