Bootstrap

20250104面试鸭特训营第12天

更多特训营笔记详见个人主页【面试鸭特训营】专栏

250104

1. Redis 中如何保证缓存与数据库的数据一致性?

旁路缓存

  • 流程
    • 先删除缓存,再写数据库
  • 存在的问题
    • 可能会发生脏读现象
    • 若某读线程 恰好 在写线程的 删除缓存写数据库 之间发生了读操作,则:
      • 缓存未命中,该读线程会从数据库中查询到脏数据,并回写到缓存中。
      • 随后导致在该缓存数据过期时间内,后续的全部查询请求都直接从缓存中得到了脏数据。
      • 可以发现,一个 恰好 就会导致很多脏数据的出现,数据一致性最差。

双删缓存

  • 双删缓存策略就是在旁路缓存的基础上,在写完数据库后,立刻再删除一遍缓存,减少脏数据,提升了一些数据一致性,但也不是最优解。
  • 存在的问题
    • 可能会发生脏读现象,相较于旁路缓存,脏读现象发生的概率更低
    • 若某读线程若恰好在写线程的 删除缓存写数据库 之间发生了读操作,则:
    • 缓存未命中,该读线程会从数据库中查询到脏数据。
    • 又恰好该读线程因为某些原因发生了延迟,在写线程的第二次删除缓存操作后才完成缓存回写。
    • 则仍然会导致在该缓存数据过期时间内,后续的全部查询请求都直接从缓存中得到了脏数据。
    • 可以发现,两个 恰好 才会导致很多脏数据的出现,数据一致性比旁路缓存策略强一点。

延迟双删

  • 延时双删策略就是在立即双删的基础上,将第二次的删除操作修改为通过MQ延时删除,一般延迟1到2秒即可,以保证读线程回写的脏数据最终可以被删除,数据一致性最好。

2. Redis 中的缓存击穿、缓存穿透和缓存雪崩是什么?

Redis并发现象简述解决方案
缓存击穿缓存突然过期1. 临时设置缓存永不过期
2. 自动续期
缓存穿透请求访问不存在的数据1. 在缓存中存储null值
2. 使用布隆过滤器
缓存雪崩大量数据同时过期1. 临时设置缓存永不过期
2. 为过期时间随机盐
3. 多地缓存

3. Redis String 类型的底层实现是什么?(SDS)

  • Redis 中的 String 的底层实现是基于简单动态字符串 (SDS),并结合 int 、embstr 、raw 等不同的编码方式进行存储优化

SDS 底层设计

  • lenallocflagsbuf[] 四个字段
  • len:记录字符串的实际长度
  • alloc:表示为字符串预分配的空间大小
    • 剩余空间大小 == alloc - len
  • flags:表示 SDS 的类型
    • SDS 共有五种类型,分别是 sdshdr 5、 sdshdr 8、 sdshdr 16、 sdshdr 32、 sdshdr 64
  • buf[]:存储数据,如字符串和二进制数据

int 、embstr 、raw编码

  • int编码

    • 可以解析成整数的字符串,且整数较小,直接放在 redisObject 的 ptr 指针字段中

    • struct redisObject {
          // 数据类型(字符串、哈希等)
          unsigned type:4;
          // 编码类型(int、embstr、raw等),这里是int
          unsigned encoding:4;
          // 实际的数据指针,这里直接存储整数值
          int64_t ptr;
      }
      
  • embstr编码

    • 字符串长度小于 44 时使用,redisObjectsdshdr 内存连续存储,减少内存分配和管理的开销,适用于小字符串

    • struct redisObject {
          // 数据类型(字符串、哈希等)
          unsigned type:4;
          // 编码类型(int、embstr、raw等),这里是embstr
          unsigned encoding:4;
          // 实际的数据指针,这里直接存储整数值
          void *ptr;
      }
      
      struct sdshdr {
          // 当前字符串长度
          uint32_t len;
          // 已分配的内存大小
          uint32_t alloc;
          // 编码类型
          unsigned char flags;
          // 实际字符串数据
          char buf[];
      }
      

    在这里插入图片描述

  • raw编码

    • 字符串长度大于 44 时使用,redisObjectsdshdr 内存分开存储,redisObject 中的 ptr 指针指向 sdshdr

    • struct redisObject {
          // 数据类型(字符串、哈希等)
          unsigned type:4;
          // 编码类型(int、embstr、raw等),这里是embstr
          unsigned encoding:4;
          // 实际的数据指针,这里直接存储整数值
          void *ptr;
      }
      
      struct sdshdr {
          // 当前字符串长度
          uint32_t len;
          // 已分配的内存大小
          uint32_t alloc;
          // 编码类型
          unsigned char flags;
          // 实际字符串数据
          char buf[];
      }
      

    在这里插入图片描述

SDS优点

  • 可以直接获取字符串长度
  • 二进制安全
  • 支持预分配字符串空间
  • 支持动态空间大小
;