前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Redis架构简述

Redis架构简述

作者头像
搬砖俱乐部
发布2020-01-17 15:42:07
6780
发布2020-01-17 15:42:07
举报
文章被收录于专栏:BanzClubBanzClub

Redis是基于内存亦可持久化的日志型、Key-Value数据库。

Redis的使用场景

  • 抗峰值-读场景
  • 分布式锁
  • 热点数据缓存
  • 后端接口数据缓存
  • 异步队列
  • 通知

数据结构

  • String
    • Redis的字符串是动态字符串,可以修改的字符串,类似于ArrayList,采用分配冗余空间来减少频繁的内存分配,并且支持扩容;
    • Redis的数值型,可以进行自增操作,是由范围的,是signed long的最大值最小值;
    • 字符串底层原理
      • Redis 的字符串叫着「SDS」,也就是Simple Dynamic String。它的结构是一个带长度信息的字节数组
      • Redis 规定字符串的长度不得超过 512M 字节
  • List
    • ‍类似Java的链表-LinkedList,插入和删除都是O(1),查询是O(N);
    • 提供入队列、弹出等功能
    • 可以用在异步队列
    • List底层:ZipList和QuickList
  • Hash‍
    • 类似Java的HashMap,数组+链表,不过,rehash比较耗时,所以,采用的是渐进式rehash,即同时保留新老两个结构;
    • 对于存储一个对象而言,Hash结构可以单独对字段进行操作,字符串结构则需要整体取出;Hash结构比字符串结构更消耗Redis存储资源,字符串结构比Hash结构耗费网络资源;
  • Set
    • 类似于Java的HashSet,是无序唯一的,是特殊的字典,字典的value是Null;
  • ZSet
    • 类似于 Java 的 SortedSet 和 HashMap 的结合,一方面具有Set的唯一性,另一方面每个key对应value存储的是score(权重),用于排序;
    • 跳跃列表采取一个随机策略来决定新元素可以兼职到第几层:
      • 首先 L0 层肯定是 100% 了
      • L1 层只有 50% 的概率
      • L2 层只有 25% 的概率
      • L3 层只有 12.5% 的概率
      • 一直随机到最顶层 L31 层

底层结构

  • SDS:Simple Dynamic String。它的结构是一个带长度信息的字节数组,Redis用于实现键值、String

代码语言:javascript
复制
struct SDS<T> {
    T capacity; // 数组容量
    T len; // 数组长度
    byte flags; // 特殊标识位,不理睬它
    byte[] content; // 数组内容
}
代码语言:javascript
复制
typedef struct listNode {
    struct listNode *prev;//前置节点
    struct listNode *next;//后置节点
    void *value;//节点的值
} listNode;
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;
  • 字典:用于保存键值对的数据结构,Redis用于实现Hash、Set
    • 链地址法解决键冲突
    • 字典中的ht属性是一个包含两个项的数组,数组中的每个顶都是一个dictht哈希表,一般情况下只使用ht[0]哈希表,ht[1]哈希表只会在进行rehash时使用
    • 渐进式rehash
代码语言:javascript
复制
# 字典
typedef struct dict{
   //类型特定函数
    dictType *type;
    //私有数据
    void *privdata;
    //哈希表
    dictht ht[2];
    //rehash索引
    //当rehash不在进行时,值为-1
    int rehashidx;
}dict;
# 哈希表
typedef struct dictht{
     //哈希表数组
     dictEntry **table;
     //哈希表大小
     unsigned long size;
     //哈希表大小掩码,用于计算索引值
     //总是等于 size-1
     unsigned long sizemask;
     //该哈希表已有节点的数量
     unsigned long used;
}dictht;
# 哈希表节点
typedef struct dictEntry{
     //键
     void *key;
     //值
     union{
          void *val;
          uint64_tu64;
          int64_ts64;
     }v;
     //指向下一个哈希表节点,形成链表
     struct dictEntry *next;
}dictEntry;
  • 跳表:跳跃表是一种有序的数据结构,跳跃表查找平均复杂度O(logN)、最坏时是O(N),可以和平衡树媲美
代码语言:javascript
复制
# 跳跃表节点
typedef struct zskiplistNode {
    //成员对象
    robj *obj;
    //分值
    double score;
    //后退指针
    struct zskiplistNode *backward;
    //层—每个层元素都包含一个指向其他节点的指针,可以通过指针快速访问其他节点
    struct zskiplistLevel {
        struct zskiplistNode *forward;//前进指针
        unsigned int span;//跨度
    } level[];
} zskiplistNode;
# 跳跃表:zskiplist 持有 跳跃表节点,可以更方便的对整个跳跃表进行处理,表头、表尾
typedef struct zskiplist {
    //表头节点和表尾节点
    struct zskiplistNode *header, *tail;
    //表中节点的的数量
    unsigned long length;
    //表中层数最大的节点层数int level;
} zskiplist;
  • 整数集合:是Redis用于保存整数值的集合抽象数据结构
    • intset是一个数组,元素具有唯一性、有序性
代码语言:javascript
复制
typedef struct intset {
    // 编码方式
    uint32_t encoding;
    // 集合包含的元素数量
    uint32_t length;
    // 保存元素的数组
    int8_t contents[];
} intset;

分布式锁

  • 原理:
    • setnx——缓存中不存在则进行设置value,否则设置失败;
    • lua脚本保证多个指令的原子性
  • ‍问题1:超时问题——增加超时时间,避免持有锁的线程长时间不释放,导致其他线程一直拿不到锁;setnx px
  • 问题2:锁持有问题——对于锁的排斥的判断,需要增加锁的值的唯一性,一般采用随机值或者线程ID等来证明是某个线程的持有;
  • 问题3:锁可重入性——应该记录同一个线程持有锁的计数,可以使用hreadLocal实现,Redis使用Hash结构记录某个线程请求锁次数(Redission);
  • 问题4:锁续约问题——如果某个线程执行时间超过锁设置的超时时间的话,会导致逻辑没有执行完成,锁已经释放问题,需要有持续加锁的逻辑;
  • 问题5:锁信息同步问题——在分布式环境下,当存有锁信息的Redis节点宕机了,而锁信息还没有同步到从节点,将导致锁失效;‍
    • 解决方案:RedLock算法,在超时时间内,向集群大部分机器加锁,如果失败,则依次删除锁
  • 优缺点:
    • 锁的询问需要线程不断地询问,Zookeeper分布式锁基于监听实现,不需要线程不断地询问;
    • 线程死掉,其他线程只能在到了超时时间才能获得锁,无法在线程失败时直接去获得锁;

Redis的单线程

  • 多路复用:单线程实现,通过epoll实现I/O的多路复用;
  • 指令队列:为每个socket连接关联一个指令队列,客户端的指令通过指令队列排队进行顺序处理,先到先处理;
  • 响应队列:为每个socket连接关联一个响应队列,Redis 服务器通过响应队列来将指令的返回结果回复给客户端。

Redis的持久化

  • AOF
    • AOF 日志是连续的增量备份,AOF 日志记录的是内存数据修改的指令记录文本;
    • AOF 日志存储的是 Redis 服务器的顺序指令序列,AOF 日志只记录对内存进行修改的指令记录
  • RDB:
    • 快照备份,快照是一次全量备份,快照是内存数据的二进制序列化形式,在存储上非常紧凑;
    • Redis 在持久化时会调用 glibc 的函数 fork 产生一个子进程,快照持久化完全交给子进程来处理,父进程继续处理客户端请求;
    • 子进程做数据持久化,它不会修改现有的内存数据结构,它只是对数据结构进行遍历读取,然后序列化写到磁盘中;
    • 会使用操作系统的 COW 机制来进行数据段页面的分离
      • 数据段是由很多操作系统的页面组合而成,当父进程对其中一个页面的数据进行修改时,会将被共享的页面复制一份分离出来,然后对这个复制的页面进行修改
      • 子进程相应的页面是没有变化的,还是进程产生时那一瞬间的数据
  • Redis 4.0 混合持久化
    • 将 rdb 文件的内容和增量的 AOF 日志文件存在一起。这里的 AOF 日志不再是全量的日志,而是自持久化开始到持久化结束的这段时间发生的增量 AOF 日志

Redis管道

  • I/O耗时主要消耗在缓冲区,比如:
    • write耗时在写缓冲区时,写满了,然后等待缓冲区重新变空,这个等待过程就是一次整个缓冲区的网络传输过程;
    • read耗时在读缓存区时,缓冲区还为有数据,等待缓冲区有数据,这个等待过程也是一次整个缓冲区的网络传输过程;
    • 而管道是基于客户端实现的,批量进行操作,使读写请求更加连续,而节省了单个读写请求的等待时长

Redis事务

  • 事务通常包括begin、commit 和 rollback,Redis是multi、exec、discard;
  • 服务端在接收到 exec 之前,会将指令缓存中事务队列,执行完成后一次性返回结果;
  • 并不能真正的保证原子性,因为某个指令失败,后续的指令还会继续执行;
  • 通常会配合管道使用,来节省网络开销;

Redis集群

  • 主从同步:当网络发生分区时,Redis保证可用性
    • 增量同步
    • 快照同步
    • 无盘同步
  • Sentinel
    • 通过使用一组哨兵,来对外提供服务,可以看成是一组Zookeeper节点
    • 客户端连接到哨兵上,而不是直接连接集群上,这样当集群的主节点挂掉,哨兵会重新选取新的主,并在客户端向哨兵询问时,返回新主给客户端;
    • 哨兵无法保证数据完全不丢失,不过提供两个参数尽可能的少丢失数据
      • min-slaves-to-write:表示主节点必须至少有一个从节点在进行正常复制,否则就停止对外写服务,丧失可用性
      • min-slaves-max-lag:它的单位是秒,表示如果 10s 没有收到从节点的反馈,就意味着从节点同步不正常,要么网络断开了,要么一直没有给反馈

  • Codis
    • 采用数据分片机制,将所有的key划分为1024个slot,对传进来的key进行运算,对于计算之后的整数值进行对1024取模得到对应的槽位,每个槽位都会映射到后面的实例上,
    • Codis会维护槽位和实例的映射关系,当客户端进行请求时,可以通过Codis转发到Redis节点上
    • 由于数据分片,所以牺牲了很多redis的功能,比如事务等
    • 映射关系可以使用Zookeeper进行存取,解决了数据同步问题
    • 支持扩容
  • Cluster
    • 是Redis官方的集群方案,去中心化的
    • 没有代理层,访问每个节点都可以知道要访问的数据在哪个节点上
    • RedisCluster集群内部实现了一套分布式协议,来保证元数据同步、主从切换

Redis监控

  • Info
    • Server 服务器运行的环境参数
    • Clients 客户端相关信息
    • Memory 服务器运行内存统计数据
    • Persistence 持久化信息
    • Stats 通用统计数据
    • Replication 主从复制相关信息
    • CPU CPU 使用情况
    • Cluster 集群信息
    • KeySpace 键值对统计数量信息

实战问题

  • 单线程的Redis性能为何这么高?
    • 基于内存
    • 使用单线程,避免上下文切换
    • 数据结构在内存使用上进行了极致的优化
    • I/O多路复用
  • 如何保证操作的原子性?
    • 事务
    • lua脚本


1、《Redis深度历险》

2、https://www.cnblogs.com/enochzzg/p/11294773.html

3、https://www.cnblogs.com/wanglijun/p/8797069.html

4、《Redis 设计与实现》

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-01-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 BanzClub 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档