Redis简介
Redis(Remote dictionary server) 是一款高性能的开源非关系型缓存数据库,Redis使用C语言编写,支持多种类型的数据结构,如字符串,字典,列表,集合,有序集合与范围查询, bitmaps,hyperloglogs 和 地理空间(geospatial)索引半径查询。Redis 内置了 复制,LUA脚本,LRU驱动事件,事务和不同级别的磁盘持久化,并通过 Redis哨兵和自动分区提供高可用性。
String类型基本命令
新建字符串的格式为 SET key value,这是最简单的形式,还可以在后面添加一些其他的参数,我们后面再讲
查看字符串,直接GET key就可以啦
对字符串进行追加,用APPEND key content
看到这里可能就会有小伙伴问了,如果我想一次性设置多个字符串应该怎么办呢?当然使用MSET命令了呀,这个命令相当于把多个SET命令合并到一起了
有了批量设置,那批量读取呢?可能有的读者已经想到了,没错,批量读取的命令就是MGET
除了基本的添加和查询,redis字符串还有很多高级的功能
为键值对设置过期时间,我们可以通过EXPIRE命令为一个已经存在的键设置过期时间
在这里我先通过SET设置了一个键值对,然后通过EXPIRE操作为kkk这个键设置了三秒的过期时间,三秒之内可以查询到结果,但是三秒之后就查询不到了
还有一个命令是SETEX(set and expire),基本格式是SETEX key second value, 其中的second就是有效时间,过了这个有效时间这个key就被删除了(msetnx的作用就不用我说了吧)
可以看到我为kkk设置了三秒的过期时间,在设置完的三秒内是可以查询到相应的值,过了三秒之后就查不到了。
我们还可以通过PSETEX命令完成和SETEX命令一样的功能,只不过setex是以秒为单位,而PSETEX是以毫秒为单位。
设置锁,在redis中可以通过SETNX命令来设置锁,如果设置成功之后,再次执行这个命令则无法成功,SETNX(set if not exist),只有当不存在这个键的时候才会成功。
我们可以看到,我们第一次执行这个命令的时候返回了1(代表成功),第二次执行却返回了(0),代表失败,因为第二次执行的时候这个键已经存在了,则必然失败。
那有的小伙伴又会问了,如果已经通过SETNX设置了键值对之后,我还想重新设置这个键的值该怎么办呢?还记得我们刚刚说的过期时间吗,我们可以通过EXPIRE为他设定一个过期时间,当然还有另外一个做法
我们可以通过对SET操作加上其他命令行参数就可以啦, 上面图里的EX表示设置过期时间,5就表示五秒后过期,NX表示如果这个键不存在才生效,当然EX和NX这两个参数都是可以单独使用的
对一个字符串的一定范围内进行设置可以通过SETRANGE进行实现,
这里需要注意的是不管我们想设置的字符串长度为多少,都是可以成功的,如果超出了原字符串的长度会自动扩容
我们可以通过GETRANGE命令获取对应位置上的子串,这里可以把起始范围设置为负数,表示倒数第几个值
在Redis的字符串中,如果字符串中的字符全是整数,则可以对这个“字符串”(其实是个整数啦)进行简单的自增自减操作,自增自减操作的命令是INCR和DECR,我们可以看到下图中我们给test赋值为996,对他的自增自减操作可以成功
如果是小数类型就不可以了
那如果我想每次自增100该怎么办呢?执行incr执行100次?当然不会这么麻烦啦,我们以自增操作来演示一下,通过INCRBY命令 ,我们可以在后面设置一个步长就可以了,DECRBY操作也是一样的,大家可以自己去试一试。
但我如果我就想对小数类型进行自增操作怎么办?我们可以使用INCRBYFLOAT命令就可以了,这里需要注意的是并不存在DECRBYFLOAT
一些不太常见的命令
BITCOUNT命令可以用来统计字符串中字符二进制表示中1的个数
redis这个字符串的二进制表示为01110010 01100101 01100100 01101001 01110011,一共有20个1,通过BITCOUNT就可以将其统计出来
GETBIT命令可以获取对应偏移量上的比特值,a的二进制表示是01100001
SETBIT可以设置对应偏移量上的比特值,改变这个比特之后原来的值也会发生相应变化
STRALOG,对字符串执行某些特定算法,暂时支持获取最长公共子序列,注意是子序列而不是子串,子串是在原串中连续存在的而子序列不需要连续存在
SETLEN命令获取字符串长度
以上包括了绝大多数的字符串类型的命令了,别问我为什么不是全部,问就是有几个我自己也没搞懂。。
如果想更全面的了解,可以去下面两个网址了解
英文官方文档https://redis.io/commands#string
中文文档http://www.redis.cn/commands.html#string
String底层结构
Redis中最常用的数据类型非String莫属,网传大部分程序员也只会用Redis的String类型。我们上面说到Redis是用C语言写的,但是Redis中的String却不是直接用我们常见的C语言字符类型数组写的,这是为什么呢?我们就一起深入源码看一下String类型,首先先看String类型结构的示意图
Redis中String类型是实现依赖于一种名为sds的自定义结构,sds中包含了free(当前可用空间大小),len(当前存储字符串长度),buf[] (存储的字符串内容),下面是sds的源码实现
源码中sds结构中共分为五个细分类型。之所以有5种,是为了能让不同长度的字符串可以使用不同大小的header。我们可以看到不同sds中len和alloc(free)的类型是不一样的,他们也对应着不同的长度,而结构体中的flags字段就记录着header的类型。
通过对不同长度的字符串分配范围不同的长度,从而最大化的节省空间,可见Redis团队对性能的极致追求。
使用sds有什么好处?
1.减少内存分配次数
我们知道Redis是一种直接使用内存的数据库,数据保存在内存当中,当我们对一个字符串类型进行追加的时候,可能会发生两种情况:①当前剩余空间(free)足够容纳追加内容时,我们就不需要再去分配内存空间,这样可以减少内存分配次数。②当前剩余空间不足以容纳追加内容,我们需要重新为其申请内存空间。
2.惰性释放内存空间
当我们截断字符串时,Redis会把截断部分置空,只保留剩余部分,且不立即释放截断部分的内存空间,这样做的好处就是当下次再对这个字符串追加内容的时候,如果当前剩余空间足以容纳追加内容时,就不需要再去重新申请空间,避免了频繁的内存申请。暂时用不上的空间可以被Redis定时删除或者惰性删除。
3.防止缓冲区溢出
在C语言中如果我们对一个字符串数组进行拼接时,如果没有把握'\0'的位置,则肯有可能造成缓冲区溢出的问题,但是在redis中我们通过len的长度来进行控制,很好地避免了这一点
4.二进制安全
在C语言中通过判断当前字符是否为'\0'来确定字符串是否结束,而在sds结构中,只要遍历长度没有达到len,即使遇到'\0',也不会认为字符串结束。根据这一点,我们就可以使用Redis缓存诸如图像、音频、压缩文件的二进制形式。
深挖Redis字符串
上面的SDS只是字符串类型中存储字符串内容的结构,Redis中的字符串分为两种存储方式,分别是embstr和raw,当字符串长度特别短的时候,Redis使用embstr来存储字符串,而当字符串长度超过44的时候,就需要用raw来存储,下面是他们的字符串完整结构的示意图
所有redis对象都会存在一种对象头的结构,这个对象头记录了数据的type(类型),encoding(编码方式),lru(用于lru相关缓存淘汰策略机制的时间戳),refcount(引用次数)和ptr(指向sds的指针)
embstr的存储方式是将RedisObject对象头和SDS结构放在内存中连续的空间位置,使用malloc方法一次分配,而raw需要两次malloc,分别分配对象头和SDS的空间。
为了能分配容纳一个完整的embstr对象,jemalloc(facebook提出的内存分配方案)最少会分配32字节,如果字符串再长一点,那就是64字节的空间,而embstr中会有一些固定字段使用19字节的空间,embstr是以NULL结尾的,占用一个字节,所以embstr只能存储44字节长度的字符串,如果超过44字节,Redis会使用raw进行存储。
字符串扩容策略
如果当前字符串结构小于1MB,扩容会采取加倍策略,若当前字符串长度超过1MB,为了避免加背后的空余空间造成空间浪费,每次至多分配1MB
参考链接
https://mp.weixin.qq.com/s/vXBFscXqDcXS_VaIERplMQ
https://github.com/AobingJava/JavaFamily
https://blog.csdn.net/codejas/article/details/88582831
《Redis深度历险》
好了,以上就是本期关于Redis里面字符串类型的内容了,基本也算是挺全的了,下个周打算写一篇volatile关键字的和一篇RedisHash类型的文章,大家有什么想看的可以后台告诉我,我会尽力给大家做。
我是星海,因为我不会,所以我才会,我们下次再见
扫码关注我们
微信号|码外狂徒