Redis相关梳理

 


 

 一:五种常用数据类型

简单动态字符串

redis做了一个用作字符串的SDS,除了一些不需要修改的场景,都是用SDS

C字符串的底层实现总是一 个N+1个字符长的数组

sds.h:

复制代码
struct sdshdr {
    
    // buf 中已占用空间的长度
    int len;

    // buf 中剩余可用空间的长度
    int free;

    // 数据空间
    char buf[];
};
复制代码

 

C字符串与SDS区别
1 C字符串 SDS
2 求长度,需要遍历O(n) 直接取len就可以O(1)
3 容易造成缓冲区溢出  
4  

减少了内存重新分配次数

(速度要求严苛,避免分配内存耗时多)

5   二进制安全

 

 

 

 

 

 

 

 

4:SDS有free,可以比C字符串多一些预留空间。空间优化策略主要有两种:

  • 空间预分配
    1. SDS进行修改时,会额外获得未使用空间。
    2. 修改后空间n
      1. n<1M; free分配n+1byte(空字符)
      2. n>=1M; free分配1M+1byte
  • 惰性空间释放
    1. SDS进行缩短时,不释放删除的空间,加到free里。

SDS的API

  • sdsnew 创建
  • sdsempty 创建空的SDS
  • sdsfree 释放
  • sdslen 获取len
  • sdsvail 获取free数量
  • sdsdup 创建一个sds副本
  • 。。

 

链表

 redis自己构建的链表:

复制代码
/*
 * 双端链表节点
 */
typedef struct listNode {

    // 前置节点
    struct listNode *prev;

    // 后置节点
    struct listNode *next;

    // 节点的值
    void *value;

} listNode;

复制代码
复制代码
/*
 * 双端链表迭代器
 */
typedef struct listIter {

    // 当前迭代到的节点
    listNode *next;

    // 迭代的方向
    int direction;

} listIter;
复制代码
复制代码
/*
 * 双端链表结构
 */
typedef struct list {

    // 表头节点
    listNode *head;

    // 表尾节点
    listNode *tail;

    // 节点值复制函数
    void *(*dup)(void *ptr);

    // 节点值释放函数
    void (*free)(void *ptr);

    // 节点值对比函数
    int (*match)(void *ptr, void *key);

    // 链表所包含的节点数量
    unsigned long len;

} list;
复制代码

字典

也叫:符号表、关联数组、映射,用于保存键值对;

复制代码
/*
 * 字典
 */
typedef struct dict {

    // 类型特定函数
    dictType *type;

    // 私有数据
    void *privdata;

    // 哈希表
    dictht ht[2];

    // rehash 索引
    // 当 rehash 不在进行时,值为 -1
    int rehashidx; /* rehashing not in progress if rehashidx == -1 */

    // 目前正在运行的安全迭代器的数量
    int iterators; /* number of iterators currently running */

} dict;
复制代码

 

 普通状态下的字典https://www.cnblogs.com/beiluowuzheng/p/9726741.html

 理解:

  • 字典dict;
    • 属性type是指向dictType的;dictType是保存特定类型键值对的函数;redis会为不同的字典设置不同的函数。
    • 属性privateData是存储对应类型的可选参数。
    • ht是指向dictht的引用;
      • 其中table是指向dictEntry的二维引用,有两级。
      • 第一级是hash后的值,到阈值就要rehash扩缩容
        • dictEntry是哈希节点
        • key是键
        • value是值
        • 还有指向下一个节点的引用next,用于成链。

hash算法

冲突解决

哈希表使用链地址法来解决键冲突:

哈希表节点dictEntry的指针构成一个链,hash相同的就排在当前dictEntry的next

rehash

1.为ht[1]分配空间,与ht[0]比较,扩容则分配

 // 新哈希表的大小至少是目前已使用节点数的两倍
 // T = O(N)
return dictExpand(d, d->ht[0].used*2);

收缩则分配大小等于ht[0]的?

复制代码
/*
 * 缩小给定字典
 * 让它的已用节点数和字典大小之间的比率接近 1:1
 * 返回 DICT_ERR 表示字典已经在 rehash ,或者 dict_can_resize 为假。
 * 成功创建体积更小的 ht[1] ,可以开始 resize 时,返回 DICT_OK。
 */
int dictResize(dict *d)
{
    int minimal;
    // 不能在关闭 rehash 或者正在 rehash 的时候调用
    if (!dict_can_resize || dictIsRehashing(d)) return DICT_ERR;
    // 计算让比率接近 1:1 所需要的最少节点数量
    minimal = d->ht[0].used;
    if (minimal < DICT_HT_INITIAL_SIZE)
        minimal = DICT_HT_INITIAL_SIZE;
    // 调整字典的大小
    // T = O(N)
    return dictExpand(d, minimal);
}
复制代码

2.把ht[0]复制并重新hash计算到ht[1]上

3.把ht[0]释放,ht[1]设置为ht[0]。

 

hash表的扩容与收缩

哈希表会自动在表的大小的二次方之间进行调整。

在没有bgSave或bgRewriteAOF命令时,负载因子大于1;或者有bgSave或bgRewriteAOF命令时,负载因子大于5;时执行

负载因子= 已保存/哈希表大小

渐进式rehash

 

跳跃表

skipList 

命令:

ZRANGE

ZCARD

有序,按value值排序?

平均O(logN)复杂度

适用于有序集合元素较多或集合中元素是较长字符串等场景。

具体应用:

  • 实现有序集合键
  • 在集群节点中用作内部数据结构
复制代码
/*
 * 跳跃表
 */
typedef struct zskiplist {

    // 表头节点和表尾节点
    struct zskiplistNode *header, *tail;

    // 表中节点的数量
    unsigned long length;

    // 表中层数最大的节点的层数
    int level;

} zskiplist;
复制代码

复制代码
/*
 * 跳跃表节点
 */
typedef struct zskiplistNode {
    // 成员对象
    robj *obj;
    // 分值
    double score;
    // 后退指针
    struct zskiplistNode *backward;
    //
    struct zskiplistLevel {
        // 前进指针
        struct zskiplistNode *forward;
        // 跨度
        unsigned int span;
    } level[];
} zskiplistNode;
复制代码

 

https://img2020.cnblogs.com/blog/2175154/202011/2175154-20201126162055269-725613871.png

 理解:

  • 跳表zskipList;
    • 属性header和tail分别指向zskiplistNode的头尾指针。
      • zskiplistNode
        • 后退
        • 分支
        • 成员对象
    • level记录层数最大的层数

整数集合

命令:

SADD numbers 1 3 5 7 9

 

复制代码
typedef struct intset {
    
    // 编码方式
    uint32_t encoding;

    // 集合包含的元素数量
    uint32_t length;

    // 保存元素的数组
    int8_t contents[];

} intset;
复制代码
uint32_t  取值范围 0 ~ 4,294,967,295
int8_t   取值范围 -128 ~ 127

https://img2018.cnblogs.com/blog/1334023/201810/1334023-20181001103542550-120639155.png 

contents中的值从小到大排列,并且没有重复元素。

整数集合升级过程:

  • 根据新元素的类型,扩展数组空间,为新元素分配空间。
  • 将已有的数据转换成相同类型,保持排序。
  • 将新元素加到新数组里。

升级之后不支持降级,即使没有当前等级的元素

 

 

压缩表

 zipList 是列表键和hash键的底层实现之一。

RPUSH lst 1 3 5 10086 "hello" "world"

 

对象

  • 字符串对象
  • 列表对象
  • 哈希对象
  • 集合对象
  • 有序集合对象

 每种对象都最少对应上述一种数据类型

不同的对象有不同的特性~(省略)。

 

 

 

二:单机

数据库

复制代码
struct redisServer{
    
    // ~
    // 一个数组保存,保存服务器中所有数据库
    redisDb *db;
   // ~
  //根据此数量决定在初始化时创建数据库个数
  int dbnum;
} intset;
复制代码

https://img2018.cnblogs.com/blog/1301290/201908/1301290-20190819211007379-2091925315.png

通过SELECT n 可以切换到不同的库

 

 

 

原理 

 

保存键值对的方法

 

过期键删除策略

  1. 定时删除
    1. 设置过期时间的同时,创建一个定时器,定时器到时间执行对键的删除操作。 --- 定时器是怎么实现的??
  2. 惰性删除
    1. 放任过期时间不管,但是每次从键空间中获取键时,都检查取得的键是否过期。过期删除,没过期返回。--这个存在空间的浪费
  3. 定期删除
    1. 每隔一段时间,对数据库进行一次检查,删除过期key。检查力度由算法决定。

1.定时删除 对内存最友好 对CPU时间不友好

CPU紧张时,影响服务器的响应时间和吞吐量。

实现定时器,需要用到redis服务器中的时间事件,当前时间事件的实现方式是:无序链表,查找效率为O(N),效率较低。

现阶段来说不实用。

2.惰性删除 对CPU时间友好 对内存不友好

浪费内存,由内存泄漏的危险

3.定期删除

前两种的整合

每个一段时间执行一次删除过期键,减少内存浪费

删除太频繁或执行时间长,会退化成定时删除,节约CPU

所以这个要合理配置。

 

 

 

redis的过期键删除策略

惰性删除 + 定期删除

 

 

持久化机制

aof 和 rdb 

 

事件、redis初始化过程等其他

 

三:分布式

https://www.cnblogs.com/jiangym/p/15881521.html#_label6

Sentinel

主从同步

 

 

 

复制

 

集群

sentinel

监控

主从选举

 

 

四:独立功能

 

发布订阅

 

事务

 

Lua脚本

redis2.6引入Lua脚本

在redis客户端可以直接使用Lua脚本

redis> EVAL "return 'hello world'" 0

"hello world"

 

Lua环境初始化(对Lua环境进行修改产生的影响)

 

执行Lua脚本中包含redis命令的伪客户端

 

Lua脚本的脚本字典

 

管理脚本的命令SCRIPT FLUSH、SCRIPT EXISTS、SCRIPT LOAD、SCRIPT KILL

 

EVAL命令和EVALSHA命令

 

 

 

 

 

排序 SORT

Sort可以对int或者字符等进行排序

还可以使用 SORT ~ BY ~ 以什么字段为权重排序

 

sort命令的最简单的执行形式为 SORT <key>

redis> SORT numbsers

1)"1"

2)"2"

3)"3"

 命令的详细步骤:

1.创建一个和numbsers列表长度相同的数组,数组的每一项都是redisSortObject结构

2.遍历数组,将Obj指针和列表之间一一对应

3.遍历数组,将obj指向的列表转换成double浮点数,放到u.score里

4.根据u.score属性从小到大排列

5.遍历数组,返回排序结果。

 

慢查询

 

 

redis分布式锁

https://www.cnblogs.com/jiangym/p/15877382.html#_label1_1 锁 和一部分lua脚本

 

 

 

redisson

看门狗 锁续期

https://www.cnblogs.com/jiangym/p/15877382.html#_label1_1

 

使用

https://www.cnblogs.com/jiangym/p/16017462.html

 

 

存储海量数据 100亿

 

Redis 数据结构 核心数据结构, 部署方式 容灾备份

 

持久化

RDB

每隔多长时间存储一次 dump.rdb

rdb是默认的持久化方式

快照:

  • 产生快照有几种情况:
  • 手动bgsave;
  • 手动save;
  • 根据配置时间自动执行;
  • 主从时,从节点发送sync,主节点会bgsave

AOF

aof默认关闭

根据配置方式可以同步

  • 1s一次
  • 不主动存

aof易读

 

怎么选择持久化方式

  1. 同时开启
    1. 主从模式下,从节点做持久化也行,可以把aof关了
  2. 混合模式
    1. redis4.0支持
    2. rdb可以写到aof中

容灾备份

rdb和aof备份,定时脚本,定时备份这个文件。

混合模式就可以了。

redis优化方案

独立部署

cpu密集型操作,解决子进程开销问题。

硬盘优化

大量写入请求:ssd磁盘

缓存禁用持久化

a缓存失效,B缓存顶上去。对于数据丢失问题,不考虑。则把持久化砍掉。

主从模式,从持久化

正常流程:从节点sync给主节点-》主bgsave-》fork子进程做RDB快照

 

既然用了主从,关闭主节点持久化。从节点15分钟。save 900 -1 

甚至可以禁用aof,主从了已经是高可用,某个节点挂了数据还是有,可以省一大部分资源

(新浪这么做的)

优化fork处理

 

Redis主从

 

过期策略

allkeys

volitle

ttl 过期时间

lfu 频率访问低, 引用计数,排序

lru 最近最少 链表 用过之后放头,删除从队尾删

random 随机

不过期

 

缓存穿透、缓存及穿、缓存雪崩

 

主从

一般座位缓存,读多写少。

目标:读压力释放出去。

从节点用于读操作。

并发读,压力大,就加从节点。无休止扩展从节点。内部数据冗余。

 

缺点

  • 数据冗余
    • 从节点重启,全量复制主节点,pingpang心跳。
    • 存储压力大。(集群解决了这个问题,分片存)
  • master写压力大
  • 单点故障问题(哨兵解决这个问题。)

主从复制原理

从节点增加配置 slaveof 192.168.1.1 6379 

设置好主节点的IP就可以主从复制了。

  • 全量复制
  • 增量复制

复制流程

全量: slave-》sync命令-》maste -》basave -》 生成rdb-》从加载rdb-》init

异步性

bgsave时,主节点还能有新数据。

会有脏读的问题

从节点也是。

过期key处理

从节点是没有删除的,要接受主节点的del命令

故障及解决方案

数据一致性问题

主从网络延时,主多从少,进行部分重同步。通过指令触发。人为断开也可以。

数据延迟问题

从服务器有偏移量。监听这个偏移量报警。

脏数据

1.忽略,查询场景可以容错。

2.强制读主节点。

3.从节点查询时要判断过期时间。

数据安全性

主挂了又重启了,从节点复制过来一个空。

解决办法: 主节点不自动重启。

性能优化

  • 规避全量复制
    • 低峰时段挂在从节点
    • 选举slave为主节点
    • 增大缓冲区
  • 规避复制风暴(从节点全去主从同步)
    • 选举从节点为主节点
    • 或树状复制结构

 

哨兵

redis2.8开始,引入哨兵,自动化恢复。

sentinel是分布式系统,降低误报率。

至少3个sentinel实例,默认端口26379。(奇数) 健壮

指定配置文件。

缺点:

  1. 主节点写入压力。
  2. 写能力。存储能力收到单机的限制。
  3. 动态扩容复杂。

 

 

集群(高可用)

集群概念

一组计算机作为一个整体,提供服务。

  1. 可扩展
  2. 高可用
  3. 负载均衡
  4. 错误恢复

集群主要分三类

HA 高可用

LBC 负载均衡 nginx

gosip协议连接 

 

架构

10wqps,单机挂了-》主从复制

主从稳定-》sentinel,自动恢复

单节点存储-》集群解决

 

cluster 无中心结构

每个节点都保存数据,和其他节点连接。

最少3主3从。集群结构。

插槽16384个。

 

主从,就是高可用性。挂了有备份。

 

 缺点:

  无法数据强一致性

  搭建复杂

 

数据分区

分布式存储

增强可用性,维护方便,均衡IO,改善查询性能。

分区算法

一致性hash 

扩容和取余,会影响相邻节点。适合规模大的节点。

虚拟槽分区 232 

分区力度更小,

哈希槽

16384个槽位

 

集群高可用

新增节点

新节点的插槽怎么分配?

重新分配上一个环中的数据。

节点宕机

存下一个节点里。 

数据倾斜

引入虚拟节点。主机名加编号。node1#1,node1#2,node1#3

 

 

 相关文件

《redis设计与实现》

 

 

 

 

 

posted @   CodingOneTheWay  阅读(135)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· winform 绘制太阳,地球,月球 运作规律
· 上周热点回顾(3.3-3.9)
回到顶部
点击右上角即可分享
微信分享提示