跳转至

Redis

学习后台技术,简单记录一下

redis是位于内存上的数据库,对于在硬盘上的mysql来说,速度是非常快的,一般是作为mysql的缓存,放到内存上

数据类型

  • String:字符串
    • 底层实现:这里的string其实就是一个包含char*的结构体,只不过记录了一个长度,跟std::string没啥区别
    • API是安全的,拼接字符串不会造成缓冲区溢出
  • List: 双向列表,可以从头传入和从尾插入
    • 底层实现:是一个quicklist实现的,
  • Hash: 哈希,KV存储的映射表,
    • 底层实现:listpack数据结构进行实现,不过感觉也可以是用Hash表的形式进行实现
  • Set: 无序的唯一集合 -> std::set
    • 底层实现:哈希表或者整数集合进行实现
  • ZSet: 有序的唯一集合 -> std::unordered_set
    • 每一个成员都有一个评分(score),按照从低到高的方式排序集合中的成员,集合的成员是唯一的,但是评分确实可以重复的
    • key (score, name),这样的组成,根据key来找到对应的数据,然后数据按照score来排序
    • 底层实现:ListpPack数据结构

数据结构

  • quick_list: 快速列表
  • listpack: 紧凑列表
  • 跳表
  • 哈希表
  • 整数集合

线程模型

  • 对于命令的执行,依旧是单线程的模式,包括:【接受客户端请求 -> 解析请求 -> 进行数据读写 -> 发送数据给客户端】 主线程完成的。
  • 但是对于整个程序来说,不是单线程的,
    • 主线程:处理任务的执行
    • 副线程:关闭文件
    • 副线程:AOF刷盘
    • 新加的线程:异步释放Redis内存,lazyfree线程,

对于这些线程,本质上也是一个任务队列,将任务放到对应的队列中,然后副线程去处理这些任务,有一股异步的味道,这里只要不堵塞主线程就行了,

Linux上,是用了epoll这样的结构来处理读写,这里其实跟我在前公司做的http请求库一样,也是一个epoll监听读入和写入,然后处理对应的事件,

  • 在6.0之后,为了增加网络的并发量,对网络的处理也开启了多线程进行处理,但是命令的执行,依旧是单线程

持久化

redis是放在内存上的,如果要持久化,就要将其放到硬盘上,这里redis是自带缓存了

AOF日志

每次写操作的时候,将写操作的日志记录一下,如果断电,那么就重新执行这些操作就可以了,

AOF重写机制

  • 如果日志的数量非常大,那么就会将这些日志进行压缩,这里感觉跟mysql,C++优化的差不多,用不到的数据可以直接舍弃掉,
  • 比如说两条设置name的数值,很显然后者才是有效的,所以说这里应该保留后者就行了。

RDB快照

  • RDB快照是记录二进制数据,记录这一瞬间的二进制数据,所以数据恢复的时候要更快一点。RDB文件直接读入内存中即可。
  • 每隔一段时间,都会RDB快照执行一次,这里是全量快照,也就是全部数据
    • 感觉一般都不是堵塞在主线程上执行,而是直接在子线程上执行的

还有混合持久化,

缓存功能

redis主要是在内存上,做mysql的缓存的,所以这里数据可能有缓存时间

  • 缓存雪崩:大量缓存都在同一时间都到期了,或者Redis Server 寄了,那么请求就会直接请求到数据库中,压力就会变大
    • 大量时间同时过期
      • 均匀设置过期时间,过期时间加一个随机数值
      • 互斥锁:没有命中的情况下,对mysql数据锁一下,当然,锁也要TTL,防止请求数据寄了
  • Redis Server 寄了
    • 服务熔断或者请求限流
    • 分布式
  • 缓存击穿:热点数据,如果出现过期现象,那么大量的请求请求到数据库上,就会出现很多请求到数据库,压力就会变大
  • 缓存穿透:数据不在数据库,也不再redis上,那么大量的请求请求到数据库上,寄
    • 非法请求限制:
    • 在请求之前,都做一下Check,防止key不存在
    • 布隆过滤器:

缓存一致性

redis是存在内存上的,mysql是在硬盘上的,redis是mysql的缓存,这里就设计到缓存一致性的问题,怎么保证数据是相同的

这个怎么解决呢?如果是查询,那么其实没有什么事情,修改的时候会出现缓存不一致的情况

  1. 缓存旁路更新政策:读操作的时候,先从缓存中读相关数据,如果未命中,那么就从数据库中读取,然后写入缓存
  2. 写操作的时候:先更新数据库,然后删除缓存,但是这样在多并发的情况下会出现问题
    1. 延迟双删政策:在更新数据库之后,先删除缓存,然后在一百毫秒内再删除缓存,避免并发读导致缓存脏数据 1. 强一致性,因为可能有另一个线程查到没有更新之前的数据库了。
    2. 分布式锁,直接锁上去,只有一个线程来操作数据和更新缓存
  3. 消息队列:可以将其变成一个消息放到队列中,保证执行顺序一致性,(原来这里也有消息队列)

其他问题

惰性删除和异步删除

删除一个key的时候,肯定是不能堵塞当前线程,他会标记以下,然后将这个任务放到一个任务队列中,然后任务队列中执行这个任务,相当于交给另一个线程中的任务。

Redis的TTL是如何实现的?

  • 内部维护一个堆,表示一个键要摧毁的时间,服务器在空闲的时候轮询这个堆,如果堆顶的事件早于当前的时间,那么就会被销毁,
  • 为了防止一段时间内有多个键同时销毁,每次轮询会有次数上限,每一次轮询会有很多键销毁,但是这里只会标记,而不是真的销毁,(销毁的时候会堵塞),销毁过程也是另一个线程去做(从线程队列中取出,)