redis 自增_坏了,Redis的字符串类型竟然被张三学明白了?

Redis简介

Redis(Remote dictionary server) 是一款高性能的开源非关系型缓存数据库,Redis使用C语言编写,支持多种类型的数据结构,如字符串,字典,列表,集合,有序集合与范围查询, bitmaps,hyperloglogs 和 地理空间(geospatial)索引半径查询。Redis 内置了 复制,LUA脚本,LRU驱动事件,事务和不同级别的磁盘持久化,并通过 Redis哨兵和自动分区提供高可用性。

String类型基本命令

新建字符串的格式为 SET key value,这是最简单的形式,还可以在后面添加一些其他的参数,我们后面再讲

14c8352055a8548ebb35f51e2f6d6045.png

查看字符串,直接GET key就可以啦

ecbbd214bf062d6c329d048ff0a24a50.png

对字符串进行追加,用APPEND key content

aec18d24053186a4af6ad7691634b24a.png

看到这里可能就会有小伙伴问了,如果我想一次性设置多个字符串应该怎么办呢?当然使用MSET命令了呀,这个命令相当于把多个SET命令合并到一起了

1078c2eba7083bcc6598d01c5c7faab6.png

有了批量设置,那批量读取呢?可能有的读者已经想到了,没错,批量读取的命令就是MGET

e049d5aad7dea162dfcc3276ba686988.png

除了基本的添加和查询,redis字符串还有很多高级的功能

为键值对设置过期时间,我们可以通过EXPIRE命令为一个已经存在的键设置过期时间

2f8081863505e263259c46a9adda09ce.png

在这里我先通过SET设置了一个键值对,然后通过EXPIRE操作为kkk这个键设置了三秒的过期时间,三秒之内可以查询到结果,但是三秒之后就查询不到了

还有一个命令是SETEX(set and expire),基本格式是SETEX key second value, 其中的second就是有效时间,过了这个有效时间这个key就被删除了(msetnx的作用就不用我说了吧)

e2edba955bc5ed705eee9019f282a1ee.png

可以看到我为kkk设置了三秒的过期时间,在设置完的三秒内是可以查询到相应的值,过了三秒之后就查不到了。

我们还可以通过PSETEX命令完成和SETEX命令一样的功能,只不过setex是以秒为单位,而PSETEX是以毫秒为单位。

设置锁,在redis中可以通过SETNX命令来设置锁,如果设置成功之后,再次执行这个命令则无法成功,SETNX(set if not exist),只有当不存在这个键的时候才会成功。

57fb97447b93f8e94a529719bcf09c0b.png

我们可以看到,我们第一次执行这个命令的时候返回了1(代表成功),第二次执行却返回了(0),代表失败,因为第二次执行的时候这个键已经存在了,则必然失败。

那有的小伙伴又会问了,如果已经通过SETNX设置了键值对之后,我还想重新设置这个键的值该怎么办呢?还记得我们刚刚说的过期时间吗,我们可以通过EXPIRE为他设定一个过期时间,当然还有另外一个做法

90288afefa6c68525279715a46737973.png

我们可以通过对SET操作加上其他命令行参数就可以啦, 上面图里的EX表示设置过期时间,5就表示五秒后过期,NX表示如果这个键不存在才生效,当然EX和NX这两个参数都是可以单独使用的

对一个字符串的一定范围内进行设置可以通过SETRANGE进行实现,

df1d9043592e4b4a6d42389a9c71d59d.png

这里需要注意的是不管我们想设置的字符串长度为多少,都是可以成功的,如果超出了原字符串的长度会自动扩容

de26b399c3116d7a7aefc97c53a84223.png

我们可以通过GETRANGE命令获取对应位置上的子串,这里可以把起始范围设置为负数,表示倒数第几个值

ef99293efa559ae242103ceeb66ebcbf.png

在Redis的字符串中,如果字符串中的字符全是整数,则可以对这个“字符串”(其实是个整数啦)进行简单的自增自减操作,自增自减操作的命令是INCR和DECR,我们可以看到下图中我们给test赋值为996,对他的自增自减操作可以成功

5a57ce82608012dfe81a0d16537c17ee.png

如果是小数类型就不可以了

fee9f4625dd48ad382c0590f43689038.png

那如果我想每次自增100该怎么办呢?执行incr执行100次?当然不会这么麻烦啦,我们以自增操作来演示一下,通过INCRBY命令 ,我们可以在后面设置一个步长就可以了,DECRBY操作也是一样的,大家可以自己去试一试。

b31074c3908062d18ff08880b715043f.png

但我如果我就想对小数类型进行自增操作怎么办?我们可以使用INCRBYFLOAT命令就可以了,这里需要注意的是并不存在DECRBYFLOAT

7b1390745fea27c337acbd9799db322b.png

一些不太常见的命令

BITCOUNT命令可以用来统计字符串中字符二进制表示中1的个数

6617e6e3ead00965b9a12d5efa58c07a.png

redis这个字符串的二进制表示为01110010 01100101 01100100 01101001 01110011,一共有20个1,通过BITCOUNT就可以将其统计出来

GETBIT命令可以获取对应偏移量上的比特值,a的二进制表示是01100001

5200e7752cd69feac6713e9496dd292a.png

SETBIT可以设置对应偏移量上的比特值,改变这个比特之后原来的值也会发生相应变化

dc1fb19053d7fa33a23ed4289ceeb26e.png

STRALOG,对字符串执行某些特定算法,暂时支持获取最长公共子序列,注意是子序列而不是子串,子串是在原串中连续存在的而子序列不需要连续存在

27c67f1d2d5bfeb7bccfd264c4f072b0.png

SETLEN命令获取字符串长度

b85d59af8491318419f3fe7b46f7e3f6.png

以上包括了绝大多数的字符串类型的命令了,别问我为什么不是全部,问就是有几个我自己也没搞懂。。

如果想更全面的了解,可以去下面两个网址了解

英文官方文档https://redis.io/commands#string

中文文档http://www.redis.cn/commands.html#string

String底层结构

Redis中最常用的数据类型非String莫属,网传大部分程序员也只会用Redis的String类型。我们上面说到Redis是用C语言写的,但是Redis中的String却不是直接用我们常见的C语言字符类型数组写的,这是为什么呢?我们就一起深入源码看一下String类型,首先先看String类型结构的示意图

cdc2ab9af3d6a6512f80f9f2ee8b9801.png

Redis中String类型是实现依赖于一种名为sds的自定义结构,sds中包含了free(当前可用空间大小),len(当前存储字符串长度),buf[] (存储的字符串内容),下面是sds的源码实现

b3bf62a534aa00ffa0411f4e2aead0b4.png

源码中sds结构中共分为五个细分类型。之所以有5种,是为了能让不同长度的字符串可以使用不同大小的header。我们可以看到不同sds中len和alloc(free)的类型是不一样的,他们也对应着不同的长度,而结构体中的flags字段就记录着header的类型。

通过对不同长度的字符串分配范围不同的长度,从而最大化的节省空间,可见Redis团队对性能的极致追求。

使用sds有什么好处?

1.减少内存分配次数

我们知道Redis是一种直接使用内存的数据库,数据保存在内存当中,当我们对一个字符串类型进行追加的时候,可能会发生两种情况:①当前剩余空间(free)足够容纳追加内容时,我们就不需要再去分配内存空间,这样可以减少内存分配次数。②当前剩余空间不足以容纳追加内容,我们需要重新为其申请内存空间。

417a24b36cf5154de1415525422b92e8.png

2.惰性释放内存空间

当我们截断字符串时,Redis会把截断部分置空,只保留剩余部分,且不立即释放截断部分的内存空间,这样做的好处就是当下次再对这个字符串追加内容的时候,如果当前剩余空间足以容纳追加内容时,就不需要再去重新申请空间,避免了频繁的内存申请。暂时用不上的空间可以被Redis定时删除或者惰性删除。

3.防止缓冲区溢出

在C语言中如果我们对一个字符串数组进行拼接时,如果没有把握'\0'的位置,则肯有可能造成缓冲区溢出的问题,但是在redis中我们通过len的长度来进行控制,很好地避免了这一点

4.二进制安全

在C语言中通过判断当前字符是否为'\0'来确定字符串是否结束,而在sds结构中,只要遍历长度没有达到len,即使遇到'\0',也不会认为字符串结束。根据这一点,我们就可以使用Redis缓存诸如图像、音频、压缩文件的二进制形式。

2d73486d0be36f62e0e754d7659131fc.png

深挖Redis字符串

上面的SDS只是字符串类型中存储字符串内容的结构,Redis中的字符串分为两种存储方式,分别是embstr和raw,当字符串长度特别短的时候,Redis使用embstr来存储字符串,而当字符串长度超过44的时候,就需要用raw来存储,下面是他们的字符串完整结构的示意图

72f91d56d8c353bf13f0be61a4713603.png

所有redis对象都会存在一种对象头的结构,这个对象头记录了数据的type(类型),encoding(编码方式),lru(用于lru相关缓存淘汰策略机制的时间戳),refcount(引用次数)和ptr(指向sds的指针)

c4b68978a980913a6864dc1aa129f99d.png

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类型的文章,大家有什么想看的可以后台告诉我,我会尽力给大家做。

我是星海,因为我不会,所以我才会,我们下次再见

4acb63547c31f6fa8f35de33a10afbee.png

扫码关注我们

微信号|码外狂徒

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值