redis之String字符串类型以及存储原理

文章目录

1.redis String类型的数据类型,是二进制安全的,那么我们如何理解这个二进制安全呢?

0.概述

0.1Redis数据类型 中文官网 (不推荐,更新不及时)

http://www.redis.cn/topics/data-types-intro

0.2.Redis数据类型 英文官网 (推荐)

https://redis.io/topics/data-types-intro

1.String类型:是最简单的一种数据类型,key-value的形式存储,key都是String类型

0.3.key的最大大小:512M

0.4.value的最大大小:512M

0.5.可以存储的数据类型:字符串,整数,浮点型

下面设置如下信息内容:
gaoxinfu年龄:18岁
gaoxinfu性别:男
gaoxinfu收入:10000.10
127.0.0.1:6379> mset gaoxinfu_age 18 gaoxinfu_sex man gaoxinfu_salary 10000.10
OK
127.0.0.1:6379> 

1.String 字符串类型的相关命令

1.2.简单的赋值:set key1 value1

1.2.1.格式

set key value [expiration EX seconds|PX milliseconds] [NX|XX]
127.0.0.1:6379> set key1 value1
OK
127.0.0.1:6379> get key1
"value1"
127.0.0.1:6379> 

1.2.2.批量操作

1.2.2.1.批量赋值:mset key2 value2 key3 value3

127.0.0.1:6379> mset key2 value2 key3 value3
OK
127.0.0.1:6379> get key2
"value2"
127.0.0.1:6379> get key3
"value3"
127.0.0.1:6379> 

1.2.2.3.批量取值:mget name2 name3

127.0.0.1:6379> keys *
 1) "name2"
 2) "salary"
 3) "name3"
 4) "rename1"
 5) "gaoxinfu_sex"
 6) "gaoxinfu_age"
 7) "key1"
 8) "gaoxinfu_salary"
 9) "frank_age"
10) "key3"
11) "key2"
127.0.0.1:6379> mget name2 name3 
1) "gaoxinfu2"
2) "gaoxinfu3"
127.0.0.1:6379> 

1.2.3.如果key不存在,则设置key的值为value:setnx key value

1.2.3.2.设置失败返回:0

由于key:gaoxinfu_age前面已经存在,所以设置的时候没成功,返回了0

127.0.0.1:6379> setnx gaoxinfu_age 19
(integer) 0
127.0.0.1:6379> 

1.2.3.2.设置成功返回:1

新创建的key:frank_age 设置成功

127.0.0.1:6379> setnx frank_age 19
(integer) 1
127.0.0.1:6379> 

1.2.3.3.总结:setnx可以用来作为分布式锁,进行资源的竞争,设置成功即为获得锁;

setnx可以用来作为分布式锁,进行资源的竞争,设置成功即为获得锁;
当然,获取完锁之后,如果要释放需要用del命令

1.2.4.删除key(这个可以作为释放锁的命令):del frank_age

127.0.0.1:6379> del frank_age
(integer) 1
127.0.0.1:6379> 

1.2.4.1.设置10s后过期:set key value ex 10

设置10s后过期

127.0.0.1:6379> set name10 gaoxinfu ex 10
OK
127.0.0.1:6379> 

10s后去查询,已经不存在

127.0.0.1:6379> get name10
(nil)
127.0.0.1:6379> 

1.2.4.2.单独设置过期时间10s:EXPIRE name11 10

127.0.0.1:6379> set name11 gaoxnfu11
OK
127.0.0.1:6379> EXPIRE name11 10
(integer) 1
127.0.0.1:6379> 

1.2.4.3.如果不存在的时候,设置key的值并且过期时间为10s:set name12 gaoxinfu12 ex 10 nx

127.0.0.1:6379> set name12 gaoxinfu12 ex 10 nx
OK
127.0.0.1:6379> 

10s后再次查询,已经不存在

127.0.0.1:6379> get name12
(nil)
127.0.0.1:6379> 

在这里插入图片描述

1.2.4.4.只有key存在的时候,设置key的值并且过期时间为10s:set name13 gaoxinfu13_new ex 10 xx

127.0.0.1:6379> set name13 gaoxinfu13
OK
127.0.0.1:6379> set name13 gaoxinfu13_new  ex 10 xx
OK
127.0.0.1:6379> 

在这里插入图片描述

1.2.5.value为数值类型的操作

1.2.5.1.设置值为数值:set frank_age 18

127.0.0.1:6379> set frank_age 18
OK
127.0.0.1:6379> type frank_age
string

1.2.5.2.减少1:dec frank_age

127.0.0.1:6379> dec frank_age
(error) ERR unknown command `dec`, with args beginning with: `frank_age`, 
127.0.0.1:6379> decr frank_age
(integer) 17

1.2.5.3.增加1:incr frank_age

127.0.0.1:6379> incr frank_age
(integer) 18
127.0.0.1:6379> 

1.2.5.4.增加n:incrby frank_age 10

127.0.0.1:6379> INCRBY frank_age 10
(integer) 29
127.0.0.1:6379> 

1.2.5.5.减少n:DECRBY frank_age 2

127.0.0.1:6379> DECRBY frank_age 2
(integer) 27
127.0.0.1:6379> 

1.2.6.value为浮点类型的操作

1.2.6.1.增加n:incrbyfloat salary 8.4

127.0.0.1:6379> set salary 1002.6
OK
127.0.0.1:6379> incrbyfloat salary 8.4
"1011"
127.0.0.1:6379> 

1.2.6.2.减少n:incrbyfloat salary 8.4

1.2.7.追加内容:APPEND name2 frank

127.0.0.1:6379> get name2
"gaoxinfu2"
127.0.0.1:6379> APPEND name2 frank
(integer) 14
127.0.0.1:6379> get name2
"gaoxinfu2frank"
127.0.0.1:6379> 

1.2.8.获取字符串指定位置内容:getrange name2 9 14

127.0.0.1:6379> get name2
"gaoxinfu2frank"
127.0.0.1:6379> getrange name2 9 14
"frank"
127.0.0.1:6379> 
其中 9是开始位置,14是结束位置

1.2.9.其他命令参考:http://redisdoc.com/string/index.html

http://redisdoc.com/string/index.html

2.源码

1.首先,redis的数据存储通过dictEntry去存储;

2.1.dictEntry数据结构-源码

typedef struct dictEntry {
    void *key; # 这个就是我们的key
    union {
        void *val; # 这个就是我们的value,但是这里的value是通过redisOject对象(见下面的分析)去存储,
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    struct dictEntry *next;# 这个dictEntry是指向下一个dictEntry的引用地址
} dictEntry;

在这里插入图片描述

2.2.redisObject数据结构-源码

在这里插入图片描述

#define OBJ_SHARED_REFCOUNT INT_MAX
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;

2.2.1.type :指的是我们客户端调用存储的数据类型

127.0.0.1:6379> get name1
(nil)
127.0.0.1:6379> get name2
"gaoxinfu2frank"
127.0.0.1:6379> type name2
string
127.0.0.1:6379> 

2.2.2.encoding:字符串在redis中的字符编码类型(主要是根据存储值的大小去划分)

1.关于字节的长度问题,我们这里不做过多介绍,根据不同的变成语言(C语言,Java语言)以及不同字符编码有关系
  比如在C语言中:
  UTF-8编码:一个英文字符等于一个字节,一个中文(含繁体)等于三个字节。中文标点占三个字节,英文标点占一个字节
2.2.2.1.int类型:存储 8 个字节的长整型(long,2^63-1)
127.0.0.1:6379> set gaoxinfu_age 10
OK
127.0.0.1:6379> type gaoxinfu_age
string
127.0.0.1:6379> object encoding gaoxinfu_age
"int"
127.0.0.1:6379> 
2.2.2.2.embstr类型:代表embstr 格式的SDS(Simple Dynamic String 简单动态字符串), 存储小于 44 个字节的字符串
127.0.0.1:6379> set name4 gaoxinfu4
OK
127.0.0.1:6379> type name4
string
127.0.0.1:6379> type name4
string
127.0.0.1:6379> object encoding name4
"embstr"
127.0.0.1:6379> 
2.2.2.3.raw类型:代表raw 格式的SDS,存储大于 44 个字节的字符串(3.2 版本之前是 39 字节)
127.0.0.1:6379> set name3 我叫高新富我来自山东临沂非常高兴认识大家,欢迎大家来我的博客学习,谢谢大家
OK
127.0.0.1:6379> type name3
string
127.0.0.1:6379> object encoding name3
"raw"
127.0.0.1:6379>

在这里插入图片描述

2.2.3.lru :垃圾回收的策略:

2.2.3.1.lru:Least Recently Used 最近最久未使用算法
1.如果一个数据在最近一段时间没有被访问到,那么可以认为在将来它被访问的可能性也很小。
因此,当空间满时,最久没有访问的数据最先被置换(淘汰)。
2.2.3.2.lfu Frequently Used :最小频率未使用算法
1.如果一个数据在最近一段时间很少被访问到,那么可以认为在将来它被访问的可能性也很小。
因此,当空间满时,最小频率访问的数据最先被淘汰

2.2.4.refcount:被引用次数(如果为0,标示没有对象在引用,可以被回收)

2.2.5.*ptr:真正的value数据存储结构的地址

2.3.SDS数据结构-源码:(sdshdr5,sdshdr8,sdshdr16,sdshdr32,sdshdr64)

在这里插入图片描述

1.sdshdr5,sdshdr8,sdshdr16,sdshdr32,sdshdr64为不同的SDS存储类型
  其中:sdshdr5 已经不用
  sdshdr5标示:0-2^5=32byte 
  sdshdr8表示:2^8=64byte 
  sdshdr16表示:2^16=64K        <---- 2^16=2
  sdshdr32表示:2^32=4G
  sdshdr64表示:2^64=16G

typedef char *sds;

/* Note: sdshdr5 is never used, we just access the flags byte directly.
 * However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* used */
    uint8_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; /* used */
    uint16_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; /* used */
    uint32_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* used */
    uint64_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};

2.3.1.len:字符串长度

2.3.2.alloc:分配的内存空间

2.3.3.flags:sds类型(sdshdr5,sdshdr8,sdshdr16,sdshdr32,sdshdr64)

2.3.4.buf:数据内容(C语言中使用字符数组)

比如我们的value是“gaoxinfu”,那么buf[] 存储的就是如下的字符:'g','a','o','x','i','n','f','u'

3.为什么我们说redis String类型是二进制安全的?

4.为什么Redis用SDS实现字符串的存储?为啥不直接用C语言中的Char[]数组呢?

4.1.首先,C语言本身是没有字符串这种数据类型,而是通过Char[]数组存储,但是Char[]存储存在下面问题?

4.1.1.C语言Char[]类型必须去提前分配数组的大小,意味着必须存储足够大的空间,否则会溢出;

4.1.2.如果获取数组长度,必须遍历字符数据,时间复杂度O(n)

4.1.3.C语言 对于字符串的变更会对字符数组进行内存的重新分配

4.1.4.通过从字符串开始到结尾碰到的第一个’\0’来标记字符串的结束,因此不能保存图片、音频、视频、压缩文件等二进制(bytes)保存的内容,二进制不安全

4.2.使用SDS存储的优势

4.2.1.不用担心内存溢出问题,如果需要会对SDS 进行扩容

4.2.2.获取字符串长度时间复杂度为O(1),因为定义了len 属性

4.2.3.通过“空间预分配”( sdsMakeRoomFor)和“惰性空间释放”,防止多次重分配内存

4.2.4.判断是否结束的标志是len 属性(它同样以’\0’结尾是因为这样就可以使用C语言函数库操作字符串的函数)

5.String字符串类型应用场景

5.1.热点数据的存储

1.直接存储的redis中,后面访问的时候会更快
  比如像一些前端界面的缓存数据等等
  比如:对象缓存,全页数据缓存

5.2.数据共享缓存<----分布式

<dependency>
	<groupId>org.springframework.session</groupId>
	<artifactId>spring-session-data-redis</artifactId>
</dependency>
1.因为redis是分布式部署的,所以不同的应用服务是可以调用同一个redis请求的,
  所以不同的应用服务器可以进行数据共享;

5.3.分布式锁<----setnx方法:大家可以设置失效时间

http://redisdoc.com/string/set.html

redis> EXISTS job                # job 不存在
(integer) 0

redis> SETNX job "programmer"    # job 设置成功
(integer) 1

redis> SETNX job "code-farmer"   # 尝试覆盖 job ,失败
(integer) 0

redis> GET job                   # 没有被覆盖
"programmer"

5.4.全局唯一的Id(原子性):分库分表主键

incrby orderid 20

5.5.计数器:如微博点赞数量,或者抽奖数量,文章的阅读数量

1.微博点赞数量,或者抽奖数量,文章的阅读数量,都是可以先写写入redis,再写入数据库的

5.6.限流:客户访问次数限制等 incr

1.可以使用客户访问的ip或者其他信息作为key,存储访问的次数,一旦超了次数,则直接返回不允许访问

6.关于SDS的概念的学习,可以参考下面的地址

https://www.cnblogs.com/break-python/p/5506606.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

东山富哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值