深入理解Redis String数据类型

String

💡 最基本也是最常用的数据类型就是 String,他是一个二进制安全的字符串。set 和 get 命令就是 String 的操作命令。为什么叫 Binary-safe strings 呢?后面会给大家分析。

存储类型

  • string
  • int
  • float

操作命令

底层编码

  • int
  • embstr
  • row

下面存储原理会做相应的解释分析,查看key对应的存储编码命令:OBJECT ENCODING <YOUR_KEY>

存储原理

set name shouzhi 为例,因为 Redis 是 KV 的数据库,它是通过 hashtable 实现的(我
们把这个叫做外层的哈希)。所以每个键值对都会有一个 dictEntry(源码位置:dict.h),
里面指向了 key 和 value 的指针。next 指向下一个 dictEntry。

在这里插入图片描述

typedef struct dictEntry {
      void *key; /* key 关键字定义 */
      union {
               void *val; uint64_t u64; /* value 定义 */
               int64_t s64; double d;
      } v;
      struct dictEntry *next; /* 指向下一个键值对节点 */
} dictEntry;

key 是字符串,但是 Redis 没有直接使用 C 的字符数组,而是存储在自定义的 SDS
中。
value 既不是直接作为字符串存储,也不是直接存储在 SDS 中,而是存储在
redisObject 中。实际上五种常用的数据类型的任何一种,都是通过 redisObject 来存储
的。

redisObject

redisObject 定义在 src/server.h 文件中。

typedef struct redisObject {
     unsigned type:4; /* 对象的类型,包括:OBJ_STRING、OBJ_LIST、OBJ_HASH、OBJ_SET、OBJ_ZSET */
     unsigned encoding:4; /* 具体的数据结构 */
     unsigned lru:LRU_BITS; /* 24 位,对象最后一次被命令程序访问的时间,与内存回收有关 */
     int refcount; /* 引用计数。当 refcount 为 0 的时候,表示该对象已经不被任何对象引用,则可以进行垃圾回收了
*/
void *ptr; /* 指向对象实际的数据结构 */
} robj;

可以使用 type 命令来查看对外的类型。

在这里插入图片描述

内部编码

字符串类型的内部编码有三种:
1、int,存储 8 个字节的长整型(long,2^63-1)。
2、embstr, 代表 embstr 格式的 SDS(Simple Dynamic String 简单动态字符串),
存储小于 44 个字节的字符串。
3、raw,存储大于 44 个字节的字符串(3.2 版本之前是 39 字节)。

什么是SDS?

Redis 中字符串的实现。

在 3.2 以后的版本中,SDS 又有多种结构(sds.h):sdshdr5、sdshdr8、sdshdr16、

sdshdr32、sdshdr64,用于存储不同的长度的字符串,分别代表

2^5=32byte
2^8=256byte

2^16=65536byte=64KB

2^32byte=4GB

/* sds.h */
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; /* 当前字符数组的长度 */
uint8_t alloc; /*当前字符数组总共分配的内存大小 */
unsigned char flags; /* 当前字符数组的属性、用来标识到底是 sdshdr8 还是 sdshdr16 等 */
char buf[]; /* 字符串真正的值 */
};

什么Redis要用SDS实现字符串?

  • C 语言本身没有字符串类型(只能用字符数组 char[]实现)。
    1、使用字符数组必须先给目标变量分配足够的空间,否则可能会溢出。
    2、如果要获取字符长度,必须遍历字符数组,时间复杂度是 O(n)。
    3、C 字符串长度的变更会对字符数组做内存重分配。
    4、通过从字符串开始到结尾碰到的第一个’\0’来标记字符串的结束,因此不能保
    存图片、音频、视频、压缩文件等二进制(bytes)保存的内容,二进制不安全。

  • SDS 的特点:
    1、不用担心内存溢出问题,如果需要会对 SDS 进行扩容。
    2、获取字符串长度时间复杂度为 O(1),因为定义了 len 属性。
    3、通过“空间预分配”( sdsMakeRoomFor)和“惰性空间释放”,防止多
    次重分配内存。
    4、判断是否结束的标志是 len 属性(它同样以’\0’结尾是因为这样就可以使用 C语言中函数库操作字符串的函数了),可以包含’\0’。
    在这里插入图片描述

embstr 和 raw 的区别?

embstr 的使用只分配一次内存空间(因为 RedisObject 和 SDS 是连续的),而 raw
需要分配两次内存空间(分别为 RedisObject 和 SDS 分配空间)。
因此与 raw 相比,embstr 的好处在于创建时少分配一次空间,删除时少释放一次
空间,以及对象的所有数据连在一起,寻找方便。
而 embstr 的坏处也很明显,如果字符串的长度增加需要重新分配内存时,整个
RedisObject 和 SDS 都需要重新分配空间,因此 Redis 中的 embstr 实现为只读。

int 和 embstr 什么时候转化为 raw?

当 int 数 据 不 再 是 整 数 , 或 大 小 超 过 了 long 的 范 围(2^63-1=9223372036854775807)时,自动转化为 embstr。

为什么没有超过阈值,也会变成 raw 了?

在这里插入图片描述

对于 embstr,由于其实现是只读的,因此在对 embstr 对象进行修改时,都会先
转化为 raw 再进行修改。
因此,只要是修改 embstr 对象,修改后的对象一定是 raw 的,无论是否达到了 44
个字节。

当长度小于阈值时,会还原吗?
关于 Redis 内部编码的转换,都符合以下规律:编码转换在 Redis 写入数据时完
成,且转换过程不可逆,只能从小内存编码向大内存编码转换(但是不包括重新 set)。

为什么要对底层的数据结构区分int、embstr、raw?
通过不同编码,可以根据对象的类型动态地选择存储结构和可以使用的命令,实现节省
空间和优化查询速度。

应用场景

缓存

String 类型
例如:热点数据缓存(例如报表,明星出轨),对象缓存,全页缓存。
可以提升热点数据的访问速度

分布式锁🔒

STRING 类型 setnx 方法,只有不存在时才能添加成功,返回 true。
http://redisdoc.com/string/set.html 建议用参数的形式

public Boolean getLock(Object lockObject){
			jedisUtil = getJedisConnetion();
			boolean flag = jedisUtil.setNX(lockObj, 1);
			if(flag){
			expire(locakObj,10);
			}
			return flag;
}

public void releaseLock(Object lockObject){
			del(lockObj);
}

分布式session

<dependency>
	<groupId>org.springframework.session</groupId>
	<artifactId>spring-session-data-redis</artifactId>
</dependency>

分布式ID

INT 类型,INCRBY,利用原子性

incrby userid 1000

计数器

INT 类型,INCR 方法
例如:文章的阅读量,微博点赞数,允许一定的延迟,先写入 Redis 再定时同步到数据库。

限流

INT 类型,INCR 方法以访问者的 IP 和其他信息作为 key,访问一次增加一次计数,超过次数则返回 false。

位统计

String 类型的 BITCOUNT(bitmap 数据结构)。字符是以 8 位二进制存储的。

set k1 a
setbit k1 6 1
setbit k1 7 0
get k1

a 对应的 ASCII 码是 97,转换为二进制数据是 01100001
b 对应的 ASCII 码是 98,转换为二进制数据是 01100010
因为 bit 非常节省空间(1 MB=8388608 bit),可以用来做大数据量的统计。
例如:在线用户统计,留存用户统计

setbit onlineusers 0 1
setbit onlineusers 1 1
setbit onlineusers 2 0

支持按位与、按位或等等操作。

BITOP AND destkey key [key ...] ,对一个或多个 key 求逻辑并,并将结果保存到 destkey 。
BITOP OR destkey key [key ...] ,对一个或多个 key 求逻辑或,并将结果保存到 destkey 。
BITOP XOR destkey key [key ...] ,对一个或多个 key 求逻辑异或,并将结果保存到 destkey 。
BITOP NOT destkey key ,对给定 key 求逻辑非,并将结果保存到 destkey 。

位操作简单使用实例

  • 需求
    如何使用redis实现记录每个员工上班是否打卡记录。并且推导出某个员工当前时候连续7天打卡。
  • key设计
    setbit user:${userId} ${offset} ${flag}
    userId:用户ID
    offset:位偏移,用于记录天数
    flag:0或者1,0表示未打卡,1表示当天一打卡
  • 操作
    userId=1的1周内打卡记录
SETBIT user:1 0 1  #第1天打卡标识
SETBIT user:1 1 1  #第2天打卡标识
SETBIT user:1 2 1  #第3天打卡标识
SETBIT user:1 3 1  #第4天打卡标识
SETBIT user:1 4 1  #第5天打卡标识
SETBIT user:1 5 1  #第6天打卡标识
SETBIT user:1 6 1  #第7天打卡标识
  • 查看当前打卡结果
    在这里插入图片描述
    通过上图可以看到user:1用户已经连续7个1了。很简单,这种如果要判断当前用户是否连续7天可以找到当天的offset往后推算就可以了。
  • 该方案优点
    1、节省存储空间。
    2、对用户量体系大的操作友好。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值