本节引言
redis String类型的数据类型,是二进制安全的,那么我们如何理解这个二进制安全呢?
概述
1、Redis数据类型 中文官网 (不推荐,更新不及时)
http://www.redis.cn/topics/data-types-intro
2、Redis数据类型 英文官网 (推荐)
https://redis.io/topics/data-types-intro
String类型:是最简单的一种数据类型,key-value的形式存储,key都是String类型
3、key的最大大小:512M
4、value的最大大小:512M
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>
String 字符串类型的相关命令
简单的赋值:set key1 value1
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>
2、批量操作
批量赋值: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>
批量取值: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>
3、如果key不存在,则设置key的值为value:setnx key value
设置失败返回:0
由于key:gaoxinfu_age前面已经存在,所以设置的时候没成功,返回了0
127.0.0.1:6379> setnx gaoxinfu_age 19
(integer) 0
127.0.0.1:6379>
设置成功返回:1
新创建的key:frank_age 设置成功
127.0.0.1:6379> setnx frank_age 19
(integer) 1
127.0.0.1:6379>
总结:setnx可以用来作为分布式锁,进行资源的竞争,设置成功即为获得锁;
setnx可以用来作为分布式锁,进行资源的竞争,设置成功即为获得锁;
4、删除key(这个可以作为释放锁的命令):del frank_age
127.0.0.1:6379> del frank_age
(integer) 1
127.0.0.1:6379>
设置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>
单独设置过期时间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>
如果不存在的时候,设置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>
只有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>
5、value为数值类型的操作
设置值为数值:set frank_age 18
127.0.0.1:6379> set frank_age 18
OK
127.0.0.1:6379>
减少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:incr frank_age
127.0.0.1:6379> incr frank_age
(integer) 18
127.0.0.1:6379>
增加n:incrby frank_age 10
127.0.0.1:6379> INCRBY frank_age 10
(integer) 29
127.0.0.1:6379>
减少n:DECRBY frank_age 2
127.0.0.1:6379> DECRBY frank_age 2
(integer) 27
127.0.0.1:6379>
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>
减少n:incrbyfloat salary 8.4
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>
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是结束位置
9、其他命令参考:http://redisdoc.com/string/index.html
http://redisdoc.com/string/index.html
源码
首先,redis的数据存储通过dictEntry去存储;
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、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;
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>
encoding:字符串在redis中的字符编码类型(主要是根据存储值的大小去划分)
关于字节的长度问题,我们这里不做过多介绍,根据不同的变成语言(C语言,Java语言)以及不同字符编码有关系
比如在C语言中:
UTF-8编码:一个英文字符等于一个字节,一个中文(含繁体)等于三个字节,中文标点占三个字节,英文标点占一个字节
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>
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>
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>
lru :垃圾回收的策略
lru:Least Recently Used 最近最久未使用算法
如果一个数据在最近一段时间没有被访问到,那么可以认为在将来它被访问的可能性也很小。
因此,当空间满时,最久没有访问的数据最先被置换(淘汰)。
lfu Frequently Used :最小频率未使用算法
如果一个数据在最近一段时间很少被访问到,那么可以认为在将来它被访问的可能性也很小。
因此,当空间满时,最小频率访问的数据最先被淘汰
refcount:被引用次数(如果为0,标示没有对象在引用,可以被回收)
*ptr:真正的value数据存储结构的地址
3、SDS数据结构-源码:(sdshdr5,sdshdr8,sdshdr16,sdshdr32,sdshdr64)
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[];
};
len:字符串长度
alloc:分配的内存空间
flags:sds类型(sdshdr5,sdshdr8,sdshdr16,sdshdr32,sdshdr64)
buf:数据内容(C语言中使用字符数组)
比如我们的value是“gaoxinfu”,那么buf[] 存储的就是如下的字符:'g','a','o','x','i','n','f','u'
为什么我们说redis String类型是二进制安全的?
1、SDS 即遵循了C语言的部分功能,又规避了 C语言字符串常见的一些问题,比如:二进制安全的问题。
2、SDS 为了保证读写速度,尽管做了很多节省内存的操作(比如:sdshdr8/16/32/64,int/embstr/raw),但是,还在是一定程度上采用空间换时间。
3、通过 SDS 的设计,我们可以看出:在程序的世界里没有“银弹”,每种数据结构似乎总有其擅长的场景以及不足之处,这也正是各种数据结构百花齐放的原因。
为什么Redis用SDS实现字符串的存储?为啥不直接用C语言中的Char[]数组呢?
1、首先,C语言本身是没有字符串这种数据类型,而是通过Char[]数组存储,但是Char[]存储存在下面问题?
1、C语言Char[]类型必须去提前分配数组的大小,意味着必须存储足够大的空间,否则会溢出;
2、如果获取数组长度,必须遍历字符数据,时间复杂度O(n)
3、C语言 对于字符串的变更会对字符数组进行内存的重新分配
4、通过从字符串开始到结尾碰到的第一个’\0’来标记字符串的结束,因此不能保存图片、音频、视频、压缩文件等二进制(bytes)保存的内容,二进制不安全
2、使用SDS存储的优势
1、不用担心内存溢出问题,如果需要会对SDS 进行扩容
2、获取字符串长度时间复杂度为O(1),因为定义了len 属性
3、通过“空间预分配”( sdsMakeRoomFor)和“惰性空间释放”,防止多次重分配内存
4、判断是否结束的标志是len 属性(它同样以’\0’结尾是因为这样就可以使用C语言函数库操作字符串的函数)
String字符串类型应用场景
1、热点数据的存储
直接存储的redis中,后面访问的时候会更快
比如像一些前端界面的缓存数据等等
比如:对象缓存,全页数据缓存
2、数据共享缓存<----分布式
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
因为redis是分布式部署的,所以不同的应用服务是可以调用同一个redis请求的,
所以不同的应用服务器可以进行数据共享;
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"
4、全局唯一的Id(原子性):分库分表主键
incrby orderid 20
5、计数器:如微博点赞数量,或者抽奖数量,文章的阅读数量
微博点赞数量,或者抽奖数量,文章的阅读数量,都是可以先写写入redis,再写入数据库的
6、限流:客户访问次数限制等 incr
可以使用客户访问的ip或者其他信息作为key,存储访问的次数,一旦超了次数,则直接返回不允许访问