redis

150 阅读8分钟

redis remote dictionary service 远程字典服务

  1. 项目中redis用来做什么?

    1. 存储session,用户登录信息
    2. 计数器 比如限制ip、设备号登录,获取验证码等
    3. 存储一定过期时间的数据(重点在时效性、比如审批有一定的时效性这种)
    4. 用作缓存热点的数据
    5. 暂时存储一些不太重要的数据,一定的条件触发持久化
  2. redis存储缓存如何解决和mysql的数据一致性问题

    1. cache-aside
      1. 缓存命中,直接读取缓存数据
      2. 缓存未命中,查询数据库把数据放到缓存中
      3. 写请求:先更新数据库,再删除缓存
    2. 为什么是删除缓存,而不是更新
      1. 删除简单,更新可能涉及到其他计算
      2. 写操作很多时,缓存可能还没用到就被更新了
      3. 两个写请求同时发起的时候可能会有并发问题(a写db,b写db,b写cache,a写cache)
    3. 基于binlog管理缓存
      1. go里面使用的go-mysql-org/go-mysql这个库
      2. update和delete时删除缓存
      3. 业务解耦。
  3. redis 数据过期策略和

    • Redis并没有简单粗暴的使用定时删除方式,而是采用了“定期清理+惰性删除策略”
    • Redis默认每隔100ms检查,是否有过期的key,有过期key则删除 不是检查所有 而是随机抽取进行检查
    • 当我们获取某个key的时候,Redis会检查一下这个key如果设置了过期时间是否过期了,如果过期了此时就会删除。
    • 如果没有检查到 而且没有请求到 则会造成占用内存越来越高 需要内存淘汰机制
  4. 内存淘汰机制

    volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
    volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
    volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
    allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
    allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
    no-enviction(驱逐):禁止驱逐数据,新写入操作会报错
  1. 事务
    1. 单独的隔离操作,命令串行化,不会被其他客户端的命令请求打断
    2. multi 开启一个事务
    3. discard 清空事务队列,放弃事务
    4. exec 执行这个事务
    5. watch 可以监控一个或者多个键,如果有键被修改了,之后的事务就不会执行
    6. 事务的失败
      1. 入队的命令错误:拒绝执行并放弃这个事务
      2. 执行指令时候的错误:其他指令继续执行,不会回滚
      3. 为什么不支持回滚
        1. redis命令只会因为错误的语法而失败,失败是因为编程出现了错误或者命令用在了错误的键上,错误应该是发生在开发过程中就被排除掉的。
        2. 不支持回滚可以使redis内部更加简单和快速

6 redis为什么是单线程

  • redis是基于内存的操作,不涉及大量的cpu计算,大部分的数据结构读取都是o(1)的,单线程可以满足需求,而且不会有上下文切换的时间。
  • 模型简单,便于维护
  1. redis 为什么这么快?
  • 数据存储在内存中
  • 底层的数据结构操作简单 时间复杂度在O(1)
  • 单线程 保证了操作的原子性 没有上下文切换和竞争的开销
  • 使用非阻塞多路复用I/O技术,I/O请求处理高效 说Redis是单线程并不严谨,所谓单线程只是Redis在处理网络请求时使用了单线程。由于使用了非阻塞、多路复用的I/O技术,几乎纯内存无延迟的操作,使用单个线程足以满足使用需求。但是,这并不是说Redis内只有一个线程,它还有其他线程来完成辅助工作,比如:数据过期策略、内存淘汰机制、主从复制等都有相关职责的线程
  1. redis 数据持久化 参考 参考 详细 juejin.cn/post/714760…
    • RDB 快照模式 snapshot 全量模式

      • Redis采用多线程COW(Copy On write)机制来实现快照持久化
      • 写入二进制文件dump.rdb
      • redis 调用 fork,现在有了子进程和父进程。
      • 父进程继续处理 client 请求,子进程负责将内存内容写入到临时文件。由于 os 的实时复制机制(copy on write)父子进程会共享相同的物理页面,当父进程处理写请求时 os 会为父进程要修改的页面创建副本,而不是写共享的页面。所以子进程地址空间内的数据是 fork时刻整个数据库的一个快照。
      • 当子进程将快照写入临时文件完毕后,用临时文件替换原来的快照文件,然后子进程退出。client 也可以使用 save 或者 bgsave 做一次快照持久化。 save 操作是在主线程中保存快照的,会阻塞所有client 请求。每次快照持久化都是将内存数据完整写入到磁盘一次,并不是增量的只同步变更数据。如果数据量大的话,而且写操作比较多,必然会引起大量的磁盘 io 操作,可能会严重影响性能。
    • AOF日志模式 增量模式

      • AOF日志文件存储的是Redis服务器的顺序指令序列,AOF日志只记录内存改变的指令。
      • Redis的增量持久化,存在于每次处理完写命令之后,通过propagate函数触发。
      • Redis的AOF包含三种同步策略:
      • always 每次执行完命令,直接同步触发fsync方法,强制数据落地磁盘。会降低redis的吞吐量,每次当落地成功,才响应给客户端,因此此种方式,很大程度的有很好的容错能力
      • every second 每秒异步触发一次fsync方法。
      • no 不显式调用fsync方法,由操作系统决定什么时候落地。
      • 对于Redis数据的回放,即对数据的重写,全量和增量,触发时机一致。
    • 优缺点

redis.png

 - rdb 无法做到实时/秒级持久化
 - rdb 每次都要fork操作创建一个进程 频繁操作成本较高
 - rdb 恢复数据较快
 - aof文件较大
 - aof恢复数据较慢
 

redis 的数据结构 参考

  • string 字符串 二进制安全 "\0" 最长512mb
    • 使用场景 存储简单的键值类型 incr decr操作 原子性的 可以用于库存加减等场景
    • set get mget incr decr
  • hash
    • key field 都是string类型
    • hget hset
    • 两层key 删除时删除所有内容 过期时间只能设置所有
    • 扩容与收缩 扩容 负载因子>=1 缩容 负载因子>=5 采用渐进式rehash实现
      • 优点是把rehash操作分散到每一个字典操作和定时函数上,避免了一次性集中式rehash带来的服务器压力。
      • 缺点是在rehash期间需要使用两个hash表,占用内存稍大
  • list 插入顺序排序的字符串元素集合 有序可重复 底层双向链表
    • lpush lpop 头 rpush rpop 尾
    • 当作轻量级的队列使用
  • set 无序去重的集合 元素唯一 提供了求交集并集的方法
    • 编码格式 intset hashtable
    • 保存一些不重复的字符且顺序可以是无序的
    • 如果value可以转成整数值,并且长度不超过512的话就使用intset存储,否则采用hashtable。
  • zset有序集合 内部维护了一个score参数来实现,适用于排行榜和带权重的消息队列等场景。
    • zset的编码有两种,分别是:ziplist、skiplist。当zset的长度小于 128,并且所有元素的长度都小于 64 字节时,使用ziplist存储;否则使用 skiplist 存储。
    • 可以在使用积分排行榜等场景下使用

redis主从复制

//启动Redis实例作为主数据库
redis-server  
//启动另一个实例作为从数据库
redis-server --port 6380 --slaveof  127.0.0.1 6379   
  1. 从节点初次连接到主节点,触发一次全量复制,主节点生成一份rdb文件,再缓存新收到的写命令
  2. 从节点先把rdb文件持久化存储,之后再从磁盘读取到内存
  3. 主节点把缓存的写命令发送到从节点,从节点同步数据

缓存穿透

查询一个不存在的数据,db查不到数据就不会写缓存,会打到db
解决

  • 缓存空值数据
  • 使用布隆过滤器 查询不存在的会被过滤掉,避免对db的查询压力

缓存雪崩

缓存具有相同的过期时间,某一个时刻所有缓存失效,请求都到db
解决:

  • 在原有的失效时间上加一个随机值,让不同数据的过期时间分散一些

缓存击穿

大量请求同时查询一个存在的key,key失效,大量请求打到db
解决:

  • 第一个请求的数据可以访问数据库,拿到数据放到缓存之后,其他请求再去访问缓存