[DB]-Redis基础数据类型及数据结构

0、Redis中Key命令

// 1. 检查给定 key 是否存在
redis 127.0.0.1:6379> EXISTS runoob-new-key
(integer) 0
redis 127.0.0.1:6379> set runoob-new-key newkey
OK
redis 127.0.0.1:6379> EXISTS runoob-new-key
(integer) 1

// 2. 设置过期时间,以秒计算
// 基本语法
redis 127.0.0.1:6379> Expire KEY_NAME TIME_IN_SECONDS
// 实例 (一分钟后删除)
redis 127.0.0.1:6379> SET runooobkey redis
OK
redis 127.0.0.1:6379> EXPIRE runooobkey 60
(integer) 1

// 3. 设置过期时间,以毫秒计算
// 基本语法
PEXPIRE key milliseconds
// 实例
redis> SET mykey "Hello"
"OK"
redis> PEXPIRE mykey 1500
(integer) 1

// 4. 移除key的过期时间,使key永不过期
// 基本语法
redis 127.0.0.1:6379> PERSIST KEY_NAME
// 实例
redis> SET mykey "Hello"
OK

redis> EXPIRE mykey 10  # 为 key 设置生存时间
(integer) 1

redis> TTL mykey
(integer) 10

redis> PERSIST mykey    # 移除 key 的生存时间
(integer) 1

redis> TTL mykey
(integer) -1

// 5. 返回key的剩余过期时间 PTTL以毫秒为单位、TTL以秒为单位
// 基本语法
redis 127.0.0.1:6379> PTTL KEY_NAME
// 实例
# key 不存在,返回-2
redis> PTTL key
(integer) -2

# key 存在,但没有设置剩余生存时间,返回-1
redis> SET key value
OK
redis> PTTL key
(integer) -1

# 有剩余生存时间的 key,返回剩余生存时间
redis> PEXPIRE key 10086
(integer) 1
redis> PTTL key
(integer) 6179

1、数据类型

Redis中数据类型-《Redis In Action》

数据类型可以存储的值操作
STRING字符串、整数或浮点数对整个字符串或字符串其中一部分执行操作,对整数和浮点数执行自增自减
LIST列表从两端压入弹出元素;对单个或多个元素进行修剪;只保留一个范围内的元素
SET无序集合添加获取移出单个元素;检查元素是否存在;计算交并差集;从集合中随机获取元素
HASH键值对检查键是否存在
ZSET有序集合根据分值范围或成员获取元素;计算键的排名

STRING

String存储时使用的数据结构是简单的key-value类型
在这里插入图片描述
get key 通过key获取value

set key value 存入键值对

del key 删除key对应的键值对

LIST

Redis对于List的实现是双向链表,可以支持双向查找和遍历

  • 常用场景:分页查询
    在这里插入图片描述
// rpush: 将value压入list末尾,不会去重
redis 127.0.0.1:6379 > rpush list-key item
(integer) 1
redis 127.0.0.1:6379 > rpush list-key item2
(integer) 2
redis 127.0.0.1:6379 > rpush list-key item
(integer) 3

// lrange:获取list中给定范围的数据
redis 127.0.0.1:6379 > lrange list-key 0 -1
1) "item"
2) "item2"
3) "item"

// lindex:获取给定索引下的数据 0为首位 -1 末位
redis 127.0.0.1:6379 > lindex list-key 1
"item2"

// lpop:弹栈
redis 127.0.0.1:6379 > lpop list-key
"item"
redis 127.0.0.1:6379 > lrange list-key 0 -1
1) "item2"
2) "item"

SET

支持去重的列表
在这里插入图片描述

// sadd:向集合中添加元素,会去重,添加成功返回1,否则返回0证明该元素已存在
redis 127.0.0.1:6379 > sadd set-key item
(integer) 1
redis 127.0.0.1:6379 > sadd set-key item2
(integer) 1
redis 127.0.0.1:6379 > sadd set-key item3
(integer) 1
redis 127.0.0.1:6379 > sadd set-key item
(integer) 0

// smembers:返回整个集合中的元素
redis 127.0.0.1:6379 > smembers set-key
1) "item"
2) "item2"
3) "item3"

// sismember:判断元素是否在集合中 0表示不在,1表示在
redis 127.0.0.1:6379 > sismember set-key item4
(integer) 0
redis 127.0.0.1:6379 > sismember set-key item
(integer) 1

// srem:移除集合中元素 存在该元素返回1,不存在返回0
redis 127.0.0.1:6379 > srem set-key item2
(integer) 1
redis 127.0.0.1:6379 > srem set-key item2
(integer) 0
redis 127.0.0.1:6379> smembers set-key
1) "item"
2) "item3"

HASH

string类型的field和value的映射表,适合用于存储对象。
在这里插入图片描述

// hset: 将key value成堆存储在hash表中,添加成功返回1,重复添加/更新value返回0
redis 127.0.0.1:6379 > hset hash-key sub-key1 value1
(integer) 1
redis 127.0.0.1:6379 > hset hash-key sub-key2 value2
(integer) 1
redis 127.0.0.1:6379 > hset hash-key sub-key1 value1
(integer) 0

// hgetall: 获取hash表中的全部键值对
redis 127.0.0.1:6379 > hgetall hash-key
1) "sub-key1"
2) "value1"
3) "sub-key2"
4) "value2"

// hdel: 删除给定key的键值对,删除成功返回1,不存在返回0
redis 127.0.0.1:6379> hdel hash-key sub-key2
(integer) 1
redis 127.0.0.1:6379> hdel hash-key sub-key2
(integer) 0

// hget: 根据key获取value
redis 127.0.0.1:6379> hget hash-key sub-key1
"value1"

ZSET-有序集合

相比set增加了权重参数score,将元素按照score进行有序排列。

  • 常用场景:直播中礼物排行榜…
    在这里插入图片描述
// zadd: 将元素和对应的score添加进ZSET
redis 127.0.0.1:6379 > zadd zset-key 728 member1
(integer) 1
redis 127.0.0.1:6379 > zadd zset-key 982 member0
(integer) 1
redis 127.0.0.1:6379 > zadd zset-key 982 member0
(integer) 0

// zrange: 获取按scores排序后的数据 withscores是否携带scores
redis 127.0.0.1:6379 > zrange zset-key 0 -1 withscores
1) "member1"
2) "728"
3) "member0"
4) "982"

// zrangebyscore: 获取某一scores范围的数据
redis 127.0.0.1:6379 > zrangebyscore zset-key 0 800 withscores
1) "member1"
2) "728"

// zrem: 移除元素
redis 127.0.0.1:6379 > zrem zset-key member1
(integer) 1
redis 127.0.0.1:6379 > zrem zset-key member1
(integer) 0
redis 127.0.0.1:6379 > zrange zset-key 0 -1 withscores
1) "member0"
2) "982"

2、数据结构

字典

dict字典,dictht是字典中的散列表结构,使用拉链法解决哈希冲突。

字典 dict 的实现:

/*
 * 字典
 *
 * 每个字典使用两个哈希表,用于实现渐进式 rehash
 */
typedef struct dict {

    // 特定于类型的处理函数
    dictType *type;

    // 类型处理函数的私有数据
    void *privdata;

    // 哈希表(2 个)
    dictht ht[2];

    // 记录 rehash 进度的标志,值为 -1 表示 rehash 未进行
    int rehashidx;

    // 当前正在运作的安全迭代器数量
    int iterators;

} dict;

哈希表 dictht 的实现:

/*
 * 哈希表
 */
typedef struct dictht {

    // 哈希表节点指针数组(俗称桶,bucket)
    dictEntry **table;

    // 指针数组的大小
    unsigned long size;

    // 指针数组的长度掩码,用于计算索引值
    unsigned long sizemask;

    // 哈希表现有的节点数量
    unsigned long used;

} dictht;

哈希表 dictEntry 中的节点:

/*
 * 哈希表节点
 */
typedef struct dictEntry {

    // 键
    void *key;

    // 值
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
    } v;

    // 链往后继节点
    struct dictEntry *next;

} dictEntry;

字典的实现图示:
在这里插入图片描述

扩容机制

dictAdd 在每次向字典添加新键值对之前, 都会对哈希表 ht[0] 进行检查, 对于 ht[0] 的 size 和 used 属性, 如果它们之间的比率 ratio = used / size 满足以下任何一个条件的话,rehash 过程就会被激活:

  1. 自然 rehash : ratio >= 1 ,且变量 dict_can_resize 为真。(没有执行 BGSAVE 或 BGREWRITEAOF时,即没有进行持久化时)
  2. 强制 rehash : ratio 大于变量 dict_force_resize_ratio (目前版本中, dict_force_resize_ratio 的值为 5 )。
Rehash执行过程
  1. 创建一个比 ht[0] -> table 更大的 ht[1] -> table;
  2. 将 ht[0] -> table 中的所有键值对迁移到 ht[1] -> table;
  3. 将原有 ht[0] 的数据清空,并将 ht[1] 替换为新的 ht[0];

为了保证redis的响应速度,rehash过程会被均摊到每个操作中,称为渐进式Rehash

渐进式Rehash

具体步骤:

  1. 为ht[1]分配空间,同时持有两个哈希表(一个空表,一个有数据)
  2. 维持一个计数器rehashidx(记录当前rehash进行到 ht[0] 哪个索引位置上),初始值为0
  3. 每次对字典增删改查,会顺带将ht[0]中的第一个不为空的索引上的数据迁移到ht[1],rehashidx++
  4. 直到rehash操作完成,rehashidx值设为-1

其他措施:

  • rehash过程中字典会同时使用两个哈希表,所有的查找、删除等操作,需要同时在 ht[0] ht[1]上进行
  • 添加时,直接添加到ht[1],保证ht[0]的节点数量在整个rehash过程中只减不增。

跳表

有序集合的底层实现之一
基于多指针有序链表实现。
查找时从上层指针开始查找,找到对应区间后再到下一层寻找。

在这里插入图片描述
与红黑树相比的优点:

  • 插入速度快,不需要旋转等维护平衡性
  • 实现简单
  • 支持无锁操作

3、过期时间与内存淘汰机制

token,短信验证码等需要进行过期时间设置
在设置存储元素时设置其过期时间,到达过期时间数据会失效,将这部分数据删除有两种方式:

定期删除

redis默认是每隔 100ms 就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。注意这里是随机抽取的。为什么要随机呢?你想一想假如 redis 存了几十万个 key ,每隔100ms就遍历所有的设置过期时间的 key 的话,就会给 CPU 带来很大的负载!

惰性删除

定期删除可能会导致很多过期 key 到了时间并没有被删除掉。所以就有了惰性删除。假如你的过期 key,靠定期删除没有被删除掉,还停留在内存里,除非你的系统去查一下那个 key,才会被redis给删除掉。

内存淘汰

若存在大量过期数据存储在Redis中,可能会导致内存耗尽,因此Redis会根据内存淘汰机制对内存进行清理。
目前Redis中存在以下几种内存淘汰策略:

策略描述
volatile-lru从已设置过期时间的数据集中挑选最近最少使用的数据淘汰
volatile-ttl从已设置过期时间的数据集中挑选将要过期的数据淘汰
volatile-random从已设置过期时间的数据集中任意选择数据淘汰
allkeys-lru从所有数据集中挑选最近最少使用的数据淘汰
allkeys-random从所有数据集中任意选择数据进行淘汰
noeviction禁止驱逐数据

4、持久化

RDB持久化

将某个时间点的所有数据都存放到硬盘上。

AOF持久化

将写命令添加到AOF文件(Append Only File)末尾,以保证文件能够记录对Redis的所有更改操作。
AOF持久化需要设置同步选项,确保写命令同步到磁盘文件上的时机。
同步选项:

  • always:同步每个写命令(对性能影响较大)
  • everysec:每秒同步
  • no:操作系统决定何时同步(对性能提升不大,但会增加数据丢失风险)

5、分片

分片是将数据划分为多个部分的方法,可以将数据存储到多台机器里面,这种方法在解决某些问题时可以获得线性级别的性能提升。
假设有 4 个 Redis 实例 R0,R1,R2,R3,还有很多表示用户的键 user:1,user:2,… ,有不同的方式来选择一个指定的键存储在哪个实例中。

  • 最简单的方式是范围分片,例如用户 id 从 0~1000 的存储到实例 R0 中,用户 id 从 1001~2000 的存储到实例R1 中,等等。但是这样需要维护一张映射范围表,维护操作代价很高。
  • 还有一种方式是哈希分片,使用 CRC32 哈希函数将键转换为一个数字,再对实例数量求模就能知道应该存储的实例。

根据执行分片的位置,可以分为三种分片方式:

  • 客户端分片:客户端使用一致性哈希等算法决定键应当分布到哪个节点。
  • 代理分片:将客户端请求发送到代理上,由代理转发请求到正确的节点上。
  • 服务器分片:Redis Cluster。

参考资料:
JavaGuide的Redis讲解
Redis中数据类型-《Redis In Action》
Redis设计与实现
CS-Notes
Redis中文网

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值