searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

Redis笔记

2024-11-08 09:21:13
1
0

Redis 数据结构

  • SDS 简单动态字符串

    struct sdshdr {
       // 字符串长度
       int len;
       // buf数组中未使用的字节数
       int free;
       // 字节数组,用于保存字符串
       char buf[];
    };
    • 与 c 相比,redis 通过 len 和 free 提升性能

      1. 获取长度时间复杂度O(1)

      2. 减少修改字符串时带来内存重分配次数

    • raw 和 embstr 编码的 SDS

      字符串长度小于39字节,编码类型 embstr;否则为 raw

      区别:embstr 的 redisObject 和 sdshdr 地址连续;raw 不连续

  • LinkedList 双向无环链表 【前后指针】

  • ZipList 压缩列表

    经过压缩编码的由连续内存块组成的顺序型数据结构

    zlbytes zltail zlen entryX1...N zlend
    列表占用字节数 尾节点距离起始地址字节数 节点个数 各个节点 列表尾标志
  • SkipList 跳跃表

    typedef struct zskiplistNode {
       // 后退指针
       struct zskiplistNode *backward;
       // 分值
       double score;
       // 成员对象
       robj *obj;
       // 层
       struct zskiplistLevel {
           // 前进指针
           struct zskiplistNode *forward;
           // 跨度
           unsigned int span;
      } level[];
    } zskiplistNode;
    • 跳表是链表与二分法的结合,上层节点个数是下层节点的一半(如何实现?随机层数)

    • 查找的时间复杂度 O(logN):19二层 前进 37二层,37二层 后退 到19一层,19一层 前进 22一层

  • HashTable 哈希表 【数组 + 链表 + Entry<K,V>】

    • (单线程)渐进式 rehash 提高扩容性能

      在对哈希表增删改查操作过程中逐步迁移数据,每次迁移一个桶上所有数据

      查: 先h[0] 没有则h[1],增: 直接h[1],删: 先[0] 没有则h[1]

  • IntSet 整数集合

    用于保存整数值,contents[] 不含重复项,按值从小到大排序

    typedef struct intset {
       // 编码方式
       uint32_t encoding;
       // 集合包含的元素数量
       uint32_t length;
       // 保存元素的数组
       int8_t contents[];
    } intset;

     

Redis 对象类型

对象类型redisObject结构体

typedef struct redisObject {
   // 类型
   unsigned type:4;
   // 编码
   unsigned encoding:4;
   // 底层数据结构的指针
   void *ptr;
} robj;
  • String :Int / SDS 【KV缓存、计数】

  • List :ZipList / LinkedList 【消息队列】

  • Hash(Map) : ZipList / HashTable 【关系型数据库表】

  • Set :IntSet / HashTable 【交集】

  • ZSet :ZipList / SkipList 【排行榜】

 

Redis 线程模型

  • 单线程:事件驱动 + IO多路复用

    Redis 的文件事件处理器 Event Loop 是基于事件驱动,是单线程模型 同时,socket上基于IO多路复用产生事件源,也是单线程

  • 单线程与多线程

    Redis核心是数据存在内存中,由于内存iops很高,故使用单线程的效率更高,省去线程切换的开销

    假设:CPU读取内存1MB数据,单线程耗时250us;一次线程上下文切换约1500ns,若1000个线程读...

    相反,当下层iops慢速时(如磁盘、网络)应使用多线程。

    Redis 的性能瓶颈在网络 IO上,故6.0采用多线程来处理网络数据读写(事件处理执行命令仍是单线程)

 

Redis 缓存问题

  • 缓存雪崩

    大量key同时失效(如缓存服务宕机),流量打到DB上

    解决:事前哨兵主从,事中熔断限流,事后持久化恢复

  • 缓存击穿

    单个热key过期瞬间,流量全部打到DB上。

    解决:缓存无需更新时,热点数据设为永不过期;更新不频繁时,缓存查不到则加锁更新;更新频繁时定时异步任务

  • 缓存穿透

    查询不存在的key(恶意攻击),流量每次打到DB上

    解决:做标记,如布隆过滤器。判断某个key是否在过滤器(位图),用k个hash函数计算出k个散列值,并查询数组中对应的比特位,如果所有的比特位都是1,则认为存在并过滤(可能误判好人)。

     

Redis 双写不一致

在同时使用数据库和缓存时,

读数据流程固定为:先读缓存,没有则查数据库,再放入缓存,

而在更新数据场景下,可能存在数据不一致问题,常见的流程如下:

  • 先删除缓存,再更新数据库

    存在问题:A线程 删除缓存后准备(还未)更新数据库;此时 B线程 查不到缓存,将数据库脏数据同步到缓存中。

    解决思路:延时双删策略,即 A线程 在更新数据库后休眠200ms(根据实际流程),再次删除缓存,保证中间产生的脏数据被删除。若Redis删除操作异常,则可提供 重试保障机制(同步 / 异步)。

  • 先更新数据库,再删除缓存【Cache Aside】

    存在问题:更新数据库后删除缓存失败,此时数据不一致产生脏数据

    解决思路:重试保障机制之异步消息队列,若再次删除失败,可重发消息多次尝试

    方法论:① Double Check(脏数据) ② 异步消息重试 (操作失败)

    缓存组件的设计模式

    • Cache Aside(旁路模式):缓存和数据库两个存储的操作同时对外暴露

    • Read / Write Through(读写穿透):数据库由缓存服务统一管理,对外提供单一存储视图

      • Read Through:查不到缓存时,缓存服务自己加载数据库(Cache Aside 是业务层加载)

      • Write Through:数据更新时,由缓存服务同步更新缓存和数据库

    • Write Behind Caching(异步缓存写入):缓存服务更新数据时,只更新缓存,再异步批量更新数据库(Page Cahce用该模式,其中 write back 思想可以合并同一数据的多次操作,提高性能)

 

Redis 过期策略

  • 惰性删除

    在查询key的时候对过期时间进行检测,过期则删除

  • 定期删除

    开启定时任务,随机获取一些key做检查和删除

  • 内存淘汰

    当惰性和定期没把某些key删除时,则key当内存满时走内存淘汰机制

     

Redis 事务机制

Redis 事务将一组命令集合按顺序添加到队列中,并按顺序执行。要么处理所有命令,要么不处理任何,是原子性,但不提供回滚。

  • multi:开启事务,之后输入的命令将加入队列中(不是原子),若 加入队列过程中 报错(如语法问题),则 Redis 撤销事务,不执行队列中的任何命令。

  • exec:执行事务,按顺序执行队列命令(原子操作)执行期间 若有命令报错(如变量类型问题),Redis 不会进行回滚继续执行后续命令

  • discard:撤销事务,位于 exec 前面,不执行队列中的任何命令。

  • watch:监视 redis key,若变量值在 exec 之前 被其它客户端修改,则 exec 返回 null,事务失败 撤销。是乐观锁 机制,默认数据不会冲突,不加锁,在数据更新时才进行检测。

0条评论
0 / 1000
邱****龙
3文章数
0粉丝数
邱****龙
3 文章 | 0 粉丝
邱****龙
3文章数
0粉丝数
邱****龙
3 文章 | 0 粉丝
原创

Redis笔记

2024-11-08 09:21:13
1
0

Redis 数据结构

  • SDS 简单动态字符串

    struct sdshdr {
       // 字符串长度
       int len;
       // buf数组中未使用的字节数
       int free;
       // 字节数组,用于保存字符串
       char buf[];
    };
    • 与 c 相比,redis 通过 len 和 free 提升性能

      1. 获取长度时间复杂度O(1)

      2. 减少修改字符串时带来内存重分配次数

    • raw 和 embstr 编码的 SDS

      字符串长度小于39字节,编码类型 embstr;否则为 raw

      区别:embstr 的 redisObject 和 sdshdr 地址连续;raw 不连续

  • LinkedList 双向无环链表 【前后指针】

  • ZipList 压缩列表

    经过压缩编码的由连续内存块组成的顺序型数据结构

    zlbytes zltail zlen entryX1...N zlend
    列表占用字节数 尾节点距离起始地址字节数 节点个数 各个节点 列表尾标志
  • SkipList 跳跃表

    typedef struct zskiplistNode {
       // 后退指针
       struct zskiplistNode *backward;
       // 分值
       double score;
       // 成员对象
       robj *obj;
       // 层
       struct zskiplistLevel {
           // 前进指针
           struct zskiplistNode *forward;
           // 跨度
           unsigned int span;
      } level[];
    } zskiplistNode;
    • 跳表是链表与二分法的结合,上层节点个数是下层节点的一半(如何实现?随机层数)

    • 查找的时间复杂度 O(logN):19二层 前进 37二层,37二层 后退 到19一层,19一层 前进 22一层

  • HashTable 哈希表 【数组 + 链表 + Entry<K,V>】

    • (单线程)渐进式 rehash 提高扩容性能

      在对哈希表增删改查操作过程中逐步迁移数据,每次迁移一个桶上所有数据

      查: 先h[0] 没有则h[1],增: 直接h[1],删: 先[0] 没有则h[1]

  • IntSet 整数集合

    用于保存整数值,contents[] 不含重复项,按值从小到大排序

    typedef struct intset {
       // 编码方式
       uint32_t encoding;
       // 集合包含的元素数量
       uint32_t length;
       // 保存元素的数组
       int8_t contents[];
    } intset;

     

Redis 对象类型

对象类型redisObject结构体

typedef struct redisObject {
   // 类型
   unsigned type:4;
   // 编码
   unsigned encoding:4;
   // 底层数据结构的指针
   void *ptr;
} robj;
  • String :Int / SDS 【KV缓存、计数】

  • List :ZipList / LinkedList 【消息队列】

  • Hash(Map) : ZipList / HashTable 【关系型数据库表】

  • Set :IntSet / HashTable 【交集】

  • ZSet :ZipList / SkipList 【排行榜】

 

Redis 线程模型

  • 单线程:事件驱动 + IO多路复用

    Redis 的文件事件处理器 Event Loop 是基于事件驱动,是单线程模型 同时,socket上基于IO多路复用产生事件源,也是单线程

  • 单线程与多线程

    Redis核心是数据存在内存中,由于内存iops很高,故使用单线程的效率更高,省去线程切换的开销

    假设:CPU读取内存1MB数据,单线程耗时250us;一次线程上下文切换约1500ns,若1000个线程读...

    相反,当下层iops慢速时(如磁盘、网络)应使用多线程。

    Redis 的性能瓶颈在网络 IO上,故6.0采用多线程来处理网络数据读写(事件处理执行命令仍是单线程)

 

Redis 缓存问题

  • 缓存雪崩

    大量key同时失效(如缓存服务宕机),流量打到DB上

    解决:事前哨兵主从,事中熔断限流,事后持久化恢复

  • 缓存击穿

    单个热key过期瞬间,流量全部打到DB上。

    解决:缓存无需更新时,热点数据设为永不过期;更新不频繁时,缓存查不到则加锁更新;更新频繁时定时异步任务

  • 缓存穿透

    查询不存在的key(恶意攻击),流量每次打到DB上

    解决:做标记,如布隆过滤器。判断某个key是否在过滤器(位图),用k个hash函数计算出k个散列值,并查询数组中对应的比特位,如果所有的比特位都是1,则认为存在并过滤(可能误判好人)。

     

Redis 双写不一致

在同时使用数据库和缓存时,

读数据流程固定为:先读缓存,没有则查数据库,再放入缓存,

而在更新数据场景下,可能存在数据不一致问题,常见的流程如下:

  • 先删除缓存,再更新数据库

    存在问题:A线程 删除缓存后准备(还未)更新数据库;此时 B线程 查不到缓存,将数据库脏数据同步到缓存中。

    解决思路:延时双删策略,即 A线程 在更新数据库后休眠200ms(根据实际流程),再次删除缓存,保证中间产生的脏数据被删除。若Redis删除操作异常,则可提供 重试保障机制(同步 / 异步)。

  • 先更新数据库,再删除缓存【Cache Aside】

    存在问题:更新数据库后删除缓存失败,此时数据不一致产生脏数据

    解决思路:重试保障机制之异步消息队列,若再次删除失败,可重发消息多次尝试

    方法论:① Double Check(脏数据) ② 异步消息重试 (操作失败)

    缓存组件的设计模式

    • Cache Aside(旁路模式):缓存和数据库两个存储的操作同时对外暴露

    • Read / Write Through(读写穿透):数据库由缓存服务统一管理,对外提供单一存储视图

      • Read Through:查不到缓存时,缓存服务自己加载数据库(Cache Aside 是业务层加载)

      • Write Through:数据更新时,由缓存服务同步更新缓存和数据库

    • Write Behind Caching(异步缓存写入):缓存服务更新数据时,只更新缓存,再异步批量更新数据库(Page Cahce用该模式,其中 write back 思想可以合并同一数据的多次操作,提高性能)

 

Redis 过期策略

  • 惰性删除

    在查询key的时候对过期时间进行检测,过期则删除

  • 定期删除

    开启定时任务,随机获取一些key做检查和删除

  • 内存淘汰

    当惰性和定期没把某些key删除时,则key当内存满时走内存淘汰机制

     

Redis 事务机制

Redis 事务将一组命令集合按顺序添加到队列中,并按顺序执行。要么处理所有命令,要么不处理任何,是原子性,但不提供回滚。

  • multi:开启事务,之后输入的命令将加入队列中(不是原子),若 加入队列过程中 报错(如语法问题),则 Redis 撤销事务,不执行队列中的任何命令。

  • exec:执行事务,按顺序执行队列命令(原子操作)执行期间 若有命令报错(如变量类型问题),Redis 不会进行回滚继续执行后续命令

  • discard:撤销事务,位于 exec 前面,不执行队列中的任何命令。

  • watch:监视 redis key,若变量值在 exec 之前 被其它客户端修改,则 exec 返回 null,事务失败 撤销。是乐观锁 机制,默认数据不会冲突,不加锁,在数据更新时才进行检测。

文章来自个人专栏
随手记
3 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0