蜻蜓点水说说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支持数据的持久 ...
随机推荐
- plsql截取字符串字段中的某个字符段
截取字符串 字符串s=" hello world ! (name) " 如果要截取括号中的字符串可以采取如下方法. select substr(s,instr(s,'(')+1, ...
- 硬刚 lodash 源码之路,compact & concat
前置 本篇随笔包含 _.compact 和 _.concat 及其依赖的工具函数. 你可能需要一些 JavaScript 基础知识才能看懂一些没有注释的细节. compact _.compact(ar ...
- P2882 Face The Right Way G 题解
题目 Farmer John has arranged his N \((1 ≤ N ≤ 5,000)\) cows in a row and many of them are facing forw ...
- Aspose下载图片
/// <summary> /// 把DataTable数据按照Excel模板导出到Excel /// </summary> /// <param name=" ...
- CSRF原理及防御
CSRF原理及防御 CSRF攻击原理 CSRF攻击利用网站对用户的信任,以用户的身份发送请求来执行攻击者所要的操作,比如:转账.发邮件.修改密码.添加用户等. CSRF和XSS一样危害都特别大,只不过 ...
- electron自定义最小化,最大化和关闭按钮
Electron ipcRenderer 模块 ipcRenderer 模块是一个 EventEmitter 类的实例. 它提供了有限的方法,你可以从渲染进程向主进程发送同步或异步消息. 也可以收到主 ...
- JVM 专题十七:垃圾回收(一)简述
1. 什么是垃圾 1.1 C++与Java 1.2 概述 垃圾收集,不是Java语言的伴生产物.早在1960年,第一门开始使用内存动态分配和垃圾收集技术的Lisp语言诞生. 关于垃圾收集有三个经典问题 ...
- 数据可视化之DAX篇(二十四)Power BI应用技巧:在总计行实现条件格式
https://zhuanlan.zhihu.com/p/98975646 如何将表格或者矩阵中值的条件格式也应用于总计行? 目前PowerBI并不支持这种功能,无法在总计行或者小计行上应用条件格式, ...
- redis(一):Redis 数据类型
Redis 数据类型 Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合). String(字符串) st ...
- bzoj3289Mato的文件管理
bzoj3289Mato的文件管理 题意: 一共有n份资料,每天随机选一个区间[l,r],Mato按文件从小到大的顺序看编号在此区间内的这些资料.他先把要看的文件按编号顺序依次拷贝出来,再用排序程序给 ...