Redis|青训营笔记

91 阅读5分钟

这是我参与「第五届青训营 」笔记创作活动的第14天

Redis是什么

Redis属于一种NoSQL数据库,基于内存运行,性能高,支持分布式的key/value存储系统。

  • 数据从单表演进出了分库分表 ----> MySQL从单机演进出了集群

    • 数据量增长
    • 读写数据压力不断增大
  • 数据分冷热

    • 热数据:经常被访问到的数据
  • 将热数据存储到内存中加快访问速度,降低mysql的负载。

Redis基本工作原理

  • 数据从内存读写
  • 数据保存到硬盘上防止重启护具丢失
    • 增量数据保存到AOF文件
    • 全量数据RDB文件。rdb是一个二进制文件
  • 单线程处理所有操作命令
    • 根据收到的请求,按照收到顺序进行排序,单线程处理执行命令

Redis应用案例

连续签到

用户每日有一次签到机会,如果断签,连续签到的计数将归0。使用简单的string结构

连续签到的定义:每天必须在23:59:59前签到,给当前值进行++操作incr.

设置器过期时间 expireAt:后天的0点.不签到就过期

string数据结构

sds:

  • 可以存储字符串、数字、二进制数据
  • 通常配合expire配合使用
  • 场景:存储计数,session

image.png len存储了当前字符串长度,alloc存储分配的空间大小,flags前5位保留位,后三位表示存储的类型,buf存储数据的value

消息通知

用list作为消息队列

  • 使用场景:消息通知,例如当文章更新时,将更新的文章推送到ES,用户就能搜索到最新的文章数据

QuickList由一个双向链表个一个listpack实现

计数

一个用户有多项计数需求,可通过hash结构存储(类似于一个struct,每个字段对用一个值) 一次需要设置多个key时,可以使用pipeline,减少网络传输,一次传输多个数据。

Hash数据结构dict

根据hash值去确定槽位,每个槽位对应一个单向列表拉链。当槽位有一个元素,直接返回,当一个槽位有多个数据时,需要顺着链表遍历。

image.png

  • rehash:是为了扩容,将原空间的数据ht[0]拷贝到一个新的空间中ht[1]。数据小的情况下直接拷贝,数据大时,例如上百万kv,迁移过程将会明显阻塞用户请求
  • 渐进式hash:为了避免出现阻塞用户请求的情况,使用reshash方法。基本原理就是每次用户访问时,都会迁移少量数据。将整个迁移过程,平摊到所有的访问用户请求过程

排行榜

积分变化,排名要实时更新,使用Zset。

zset数据结构zskiplist

结合dict后,可实现通过key操作跳表的功能

image.png

限流

QPS每秒的请求书,要求1秒内放行的请求为N,超过N则禁止访问

key,对这个key调用incr,超过限制N则禁止访问。

分布式锁

并发场景下,要求一次只能有一个协程执行。执行完成后,其他等待中的协程才能执行。

可以使用redis的setnx实现,利用了两个特性

  • redis是单线程执行命令
  • setnx只有未设置过才能执行成功。设置的时候返回true,否则返回false

image.png 存在问题

  • 业务超时,导致并发问题,业务执行时间朝服哦了锁超时时间
  • redis主备切换临界点问题。主备切换后,A持有的锁还未同步到新的主节点,B即可在新主节点获取锁
  • redis集群脑崩,导致出现多个主节点

Redis使用注意事项

  • 大Key,热Key
    • 大key的危险
      • 读取成本高
      • 容易导致慢查询(过期,删除)
      • 主从复制异常,服务阻塞无法正常响应请求
    • 业务侧使用大Key的表现
      • 请求redis超时报错
  • 消除大Key
    • 拆分 :例如将一个string差分为多个string
    • 压缩:将value压缩后写入redis,读取时再解压缩。压缩算法可以是gzip,snappy,lz4等
    • 集合类结构hash,list,set,zset等
      • 差分:可以用hash取余、位掩码的方式决定放在哪个key中
      • 区分冷热,例如榜单场景使用zset,只缓存前10也数据,后续数据走db

热key 用户访问一个key的QPS特别高,导致server实例出现CPU负载徒增或者不均的情况。热key没有明确标准,QPS超过500就是热key

解决方案

  • 设置localcache,再访问redis前,在业务服务侧设置localcache,降低访问redis的QPS,localcache中缓存过期或未命中,再将redis中的数据更新到Localcache.JAVA的Guava,Golang的BigCache
  • 拆分:将key:value复制写入多份,分散到不同的实例上,代价是更新的时候需要更新多个key,存在数据短暂不一致的风险。
  • 使用redis代理的热ey承载能力。字节的redis访问代理具备承载热key能力,本质是结合了热key发现,LocalCache两个功能

image.png

慢查询场景

  • 一次性传入过多的key/value,如 mset/hmset/sadd/zadd等O(n)操作,建单批次不要超过100,超过100之后性能下降明显
  • zset大部分命令都是O(log(n)),当大小超过5k以上,简单的zadd/zrem可能导致慢查询
  • 操作的单个value过大,超过10kB.要避免使用大key
  • 对大key的delete、expire操作。

缓存穿透:热点数据查询绕过缓存,直接查询数据库

  • 查询一个一定不存在的值,请求直接打到DB上,导致db响应慢,甚至宕机
  • 缓存过期时,一个热key过期,大量请求直接击穿db 缓存雪崩:大量缓存同时过期,相当于缓存失效。退化为原始的直接请求数据库的架构。

解决方案:

  • 缓存空值

  • 布隆过滤器:通过bloom filter算法来存储合法的key.告诉我们当前key是否存在。存在的话走后续逻辑,不存在返回提示

  • 缓存空值:将缓存失效时间分散开。

  • 使用缓存集群,避免单机宕机造成的缓存雪崩