Featured image of post redis应用(基础)

redis应用(基础)

redis的数据类型string的概述、应用以及数据结构介绍

本文阅读量

String

概述

String类型是redis的最基础的数据结构,string类型的值最大能存储512MB

String类型可以是简单字符串、复杂的xml/json的字符串、二进制图像或者音频的字符串、以及可以是数字的字符串

使用语法

基本使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
127.0.0.1:6379[1]> set key1 hello # 如果key已经存在,会覆盖
OK
127.0.0.1:6379[1]> get key1 # key不存在,返回nil
"hello"
127.0.0.1:6379[1]> exists key1
(integer) 1
127.0.0.1:6379[1]> append key1 " word" #追加字符串,如果当前key不存在,就相当于setkey
(integer) 10
127.0.0.1:6379[1]> get key1
"hello word"
127.0.0.1:6379[1]> strlen key1  #获取字符串的长度!
(integer) 10

浏览量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
127.0.0.1:6379[1]> set views 0
OK
127.0.0.1:6379[1]> get views
"0"
127.0.0.1:6379[1]> incr views   #自增1  浏览量+1
(integer) 1
127.0.0.1:6379[1]> incr views
(integer) 2
127.0.0.1:6379[1]> get views
"2"
127.0.0.1:6379[1]> decr views  #自减1  浏览量减-1
(integer) 1
127.0.0.1:6379[1]> decr views
(integer) 0
127.0.0.1:6379[1]> get views
"0"
127.0.0.1:6379[1]> incrby views 10    # 可以设置步长,指定增量!
(integer) 10
127.0.0.1:6379[1]> get views
"10"
127.0.0.1:6379[1]> decrby views 10    # 可以设置步长,指定减量!
(integer) 0
127.0.0.1:6379[1]> get views
"0"

字符串范围

1
2
3
4
5
6
7
8
127.0.0.1:6379[1]> set key1 "hello world"
OK
127.0.0.1:6379[1]> get key1
"hello world"
127.0.0.1:6379[1]> getrange key1 0 3  #[0,3]
"hell"
127.0.0.1:6379[1]> getrange key1 0 -1  #获取全部的字符串 和get key是一样的
"hello world"

字符串替换

1
2
3
4
5
6
7
8
127.0.0.1:6379> set key2 abcdefg
OK
127.0.0.1:6379> get key2
"abcdefg"
127.0.0.1:6379> setrange key2 1 xx  #替换指定位置开始的字符串!
(integer) 7
127.0.0.1:6379> get key2
"axxdefg"

带有过期时间的

setex (set with expire) #设置过期时间

setnx (set if not exist) #不存在在设置(在分布式锁中会常常使用!)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
127.0.0.1:6379> setex key3 30 "hello"  # 30s过期
OK
127.0.0.1:6379> ttl key3
(integer) 26
127.0.0.1:6379> get key3
"hello"
127.0.0.1:6379> keys *   #过期后 key2不存在
1) "key2"


127.0.0.1:6379> setnx mykey "redis"  #如果mykey不存在,创建mykey
(integer) 1
127.0.0.1:6379> keys *
1) "key2"
2) "mykey"
127.0.0.1:6379> get mykey
"redis"
127.0.0.1:6379> setnx mykey "mongodb" #如果mykey存在,创建失败!
(integer) 0
127.0.0.1:6379> get mykey
"redis"

批量设置和获取值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3  #同时设置多个值
OK
127.0.0.1:6379> mget k1 k2 k3           #同时获取多个值
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k1 v1 k4 v4     #msetnx 是一个原子性的操作,要么一起成功,要么一起失败
(integer) 0
127.0.0.1:6379> get k4
(nil)

对象存储

1
2
3
4
5
6
7
set user:1 {name:snailsir,age:3} 
#同样可以这样实现
127.0.0.1:6379> mset user:1:name snailsir user:1:age 2
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "snailsir"
2) "2"

先get然后在set

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
127.0.0.1:6379> get db
(nil)
127.0.0.1:6379> getset db redis #如果不存在值,则返回nil
(nil)
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> getset db mongodb #如果存在值,则获取原来的值,并设置新的值
"redis"
127.0.0.1:6379> get db
"mongodb"

应用场景

session分散管理

session本质是存储在web服务器的文件,当我们使用nginx对多台web服务器做负载均衡时,会使用session分散管理(将所有的session都存储到redis中去),该技术已经淘汰,我们更推荐使用jwt技术记录我们的登录个人信息。

计数器-商品浏览

string类型的incrdecr命令的作用是将key中存储的数字值加一/减一,这两个操作具有原子性,总能安全的进行加减操作,因此可以用string类型进行计数,如微博的评论数、点赞数、分享数,商品的销量量、评论数等。

限速

避免一些恶意请求或为了减轻服务器压力,对用户访问进行限速,如:一分钟某个接口请求不超过5次

数据结构

SDS动态字符串

SDS(Simple Dynamic Strings, 简单动态字符串)是 Redis 的一种基本数据结构,主要是用于存储字符串和整数。

SDS数据结构实现(Redis3):

1
2
3
4
5
struct sdshdr {
    unsigned int len;
    unsigned int free;
    char buf[];
};

其中,buf 表示数据空间,用于存储字符串;len 表示 buf 中已占用的字节数,也即字符串长度;free 表示 buf 中剩余可用字节数。

好处

  • 用单独的变量 len 和 free,可以方便地获取字符串长度和剩余空间;
  • 内容存储在动态数组 buf 中,SDS 对上层暴露的指针指向 buf,而不是指向结构体 SDS。因此,上层可以像读取 C 字符串一样读取 SDS 的内容,兼容 C 语言处理字符串的各种函数,同时也能通过 buf 地址的偏移,方便地获取其他变量;
  • 读写字符串不依赖于 \0,保证二进制安全。

坏处

对于不同长度的字符串,没有必要使用 len 和 free 这 2 个 4 字节的变量?

4 字节的 len,可表示的字符串长度为 2^32,而在实际应用中,存放于 Redis 中的字符串往往没有这么长,因此,空间的使用上能否进一步压缩?

新的SDS结构

新的Redis是如何根据字符串的长度,使用不同的数据结构进行存储的?

5种SDS类型:Sdshdr5Sdshdr8Sdshdr16Sdshdr32Sdshdr64

Redis 增加了一个 flags 字段来标识类型,用一个字节(8 位)来存储。

其中:前 3 位表示字符串的类型;剩余 5 位,可以用来存储长度小于 32 的短字符串。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 前3位存储类型,后5位存储长度 */
    char buf[]; /* 动态数组,存放字符串 */
};

struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len;
    uint8_t alloc;
  	unsigned char flags;
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len;
    uint16_t alloc;
  	unsigned char flags;
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len;
    uint32_t alloc;
  	unsigned char flags;
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len;
    uint64_t alloc;
  	unsigned char flags;
    char buf[];
};

而对于长度大于 31 的字符串,仅仅靠 flags 的后 5 位来存储长度明显是不够的,需要用另外的变量来存储。sdshdr8、sdshdr16、sdshdr32、sdshdr64 的数据结构定义如下,其中 :

  • len 表示已使用的长度
  • alloc 表示总长度
  • buf 存储实际内容
  • flags 的前 3 位依然存储类型,后 5 位则预留。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* 已使用长度,1字节 */
    uint8_t alloc; /* 总长度,1字节 */
    unsigned char flags; /* 前3位存储类型,后5位预留 */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; /* 已使用长度,2字节 */
    uint16_t alloc; /* 总长度,2字节 */
    unsigned char flags; /* 前3位存储类型,后5位预留 */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; /* 已使用长度,4字节 */
    uint32_t alloc; /* 总长度,4字节 */
    unsigned char flags; /* 前3位存储类型,后5位预留 */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* 已使用长度,8字节 */
    uint64_t alloc; /* 总长度,8字节 */
    unsigned char flags; /* 前3位存储类型,后5位预留 */
    char buf[];
};

Redis创建字符串流程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
sds sdsnewlen(const void *init, size_t initlen) {
    void *sh;
    sds s;
    // 根据字符串长度计算相应类型
    char type = sdsReqType(initlen);
    // 如果创建的是""字符串,强转为SDS_TYPE_8
    if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
    // 根据类型计算头部所需长度(头部包含 len、alloc、flags)
    int hdrlen = sdsHdrSize(type);
    // 指向flags的指针
    unsigned char *fp;
    // 创建字符串,+1是因为 `\0` 结束符
    sh = s_malloc(hdrlen+initlen+1);
    if (sh == NULL) return NULL;
    if (init==SDS_NOINIT)
        init = NULL;
    else if (!init)
        memset(sh, 0, hdrlen+initlen+1);
    // s指向buf
    s = (char*)sh+hdrlen;
    // s减1得到flags
    fp = ((unsigned char*)s)-1;
    ...
    // 在s末尾添加\0结束符 
    s[initlen] = '\0';
    // 返回指向buf的指针s
    return s;
}

创建 SDS 的大致流程是这样的:首先根据字符串长度计算得到 type,根据 type 计算头部所需长度,然后动态分配内存空间。

注意:

  1. 创建空字符串时,SDS_TYPE_5 被强制转换为 SDS_TYPE_8(原因是创建空字符串后,内容可能会频繁更新而引发扩容操作,故直接创建为 sdshdr8)
  2. 长度计算有 +1 操作,因为结束符 \0 会占用一个长度的空间。
  3. 返回的是指向 buf 的指针 s。
使用 Hugo 构建
主题 StackJimmy 设计