- content {:toc}
基础知识
1 什么是 Redis
- redis是内存数据库、key-value 数据库、以及 数据结构数据库
- Redis 单线程指的是「接收客户端请求 -> 解析请求 -> 进行数据读写等操作 -> 发送数据给客户端」, 但是 redis 程序不是单线程的, redis 在启动的时候,是会启动后台线程
Redis 是一种基于内存的数据库,对数据的读写操作都是在内存中完成,因此读写速度非常快,常用于缓存,消息队列、分布式锁等场景。
Redis 提供了多种数据类型来支持不同的业务场景,比如 String (字符串)、Hash (哈希)、 List (列表)、Set (集合)、Zset (有序集合)、Bitmaps(位图)、HyperLogLog(基数统计)、GEO(地理信息)、Stream(流),并且对数据类型的操作都是原子性的,因为执行命令由单线程负责的,不存在并发竞争的问题。
除此之外,Redis 还支持事务 、持久化、Lua 脚本、多种集群方案(主从复制模式、哨兵模式、切片机群模式)、发布 / 订阅模式,内存淘汰机制、过期删除机制等等
- redis内存中的数据与redis磁盘一一对应
- mysql有少部分热数据以及索引在mysql缓冲池
结构类型 | 结构存储的值 | 结构的读写能力 |
---|---|---|
String字符串 | 可以是字符串、整数或浮点数 | 对整个字符串或字符串的一部分进行操作:对整数或浮点数进行自增或自减操作 |
List列表 | 一个链表,链表上的每个节点都包含一个字符串 | 对链表的两端进行push和pop操作,读取单个或多个元素:根据值查找或除元素 |
Set集合 | 包含宇符串的无序集合 | 字符串的集合,包含基础的方法有看是否存在添加、获取、删除:还包合计算交集、并集、差集等 |
Hash散列 | 包含键值对的无序散列表 | 包含方法有添加、获取、删除单个元素 |
Zset有序集合 | 和散列一样,用于存储键值对 | 字符串成员与浮点数分数之间的有序映射:元素的排列顺序由分数的大小决定:包含方法有添加、获取、删除单个元素以及根据分值范围或成员来获取元素 |
2 安装 Redis
3 什么是 QPS
- QPS(Queries Per Second)是一种衡量系统处理能力和性能的度量单位,表示每秒钟能够处理的查询请求数量。
- QPS 通常用于衡量数据库、网络服务器、Web 服务、缓存系统等对查询请求的处理速度。它表示在每秒的时间间隔内,系统能够处理的平均请求数量。QPS 的值越大,说明系统的处理能力越强。
- QPS 的计算方式是根据一段时间内的请求数量来进行统计。例如,如果一个系统在 1 秒钟内处理了 100 个查询请求,那么它的 QPS 就是 100。
- 需要注意的是,QPS 仅仅是衡量系统在某一时刻或时间段内的瞬时处理能力,它并不能全面反映系统的性能。还有其他指标如响应时间、并发数等也需要综合考虑,以全面评估系统的性能。
- QPS 的具体值会受到多种因素的影响,包括硬件配置、网络带宽、查询复杂度、系统负载等。因此,针对不同的应用场景和需求,对 QPS 的要求也不同。
- 总之,QPS 是衡量系统查询处理能力的重要指标,用于评估系统的性能和稳定性。
redis基本工作原理
- 数据从内存中读写
- 数据保存到硬盘上防止重启数据丢失
- 增量数据保存到AOF文件
- 全量数据RDB文件
- 单线程处理所有操作命令 ( 顺序执行 )
应用场景
连续签到
- 设计一个 包含日期的
key
, value 为 天数, 然后设置过期时间
消息通知
- 例如当文章更新时,将更新后的文章推送到ES, 用户就能搜索到最新的文章数据
排行榜
- 积分要变化时, 排名要实时变更
mysql 中
userid
+ 积分, 对积分进行排序
限流
- 要求1秒内放行的请求为N, 超过N则禁止访问
key中有时间戳, 对这个Key调用incr,超过限制N则禁止访问
分布式锁
- 并发场景,要求一次只能有一个协程执行.
- 执行完成后, 其它等待中的协程才能执行.
可以使用redis的setnx实现,利用了两个特性
- Redis是单线程执行命令
- setnx只有未设置过才能执行成功
不是高可用的分布式锁实现, 该实现存在的问题:
- (1)业务超时解锁, 导致并发问题. => 业务执行时间超过锁超时时间
- (2)redis主备切换临界点问题.主备切换后,A持有的锁还未同步到新的主节点时, B可在新主节点获取锁, 导致并发问题,
- (3)redis集群脑裂,导致出现多个主节点
pipeline
- 一次设置多个
key
, 减少网络传输
rehash:rehash操作是将ht[0]中的数据,全部迁移到ht[1]中.数据量小的场景下直接将数据从ht[0]拷贝到ht[1]速度是较快的.数据量大的场景,例如存有上百万的KV时,迁移过程将会明显阻塞用户请求
渐进式rehash: 为避免出现这种情况,使用了rehash方案.基本原理就是,每次用户访问时都会迁移少量数据.将整个迁移过程, 平摊到所有的访问用不请求过程中.
2.2 list
- List 列表是简单的字符串列表,按照插入顺序排序,(这不是就是队列的特性) 可以从头部或尾部向 List 列表添加元素。列表的最大长度为
2^32 - 1
,也即每个列表支持超过40 亿
个元素。 - List 类型的底层数据结构是由双向链表或压缩列表实现的:
- 但是在 Redis 3.2 版本之后,List 数据类型底层数据结构就只由 quicklist 实现了,替代了双向链表和压缩列表
quicklist
每个节点都有很多数据, 结点内每个数据的大小相等的
问题
大 key
大Key的危害
- 读取成本高
- 容易导致慢查询(过期、删除)
- 主从复制异常,服务阻塞无法正常响应请求
业务侧使用大Key的表现
- 请求Redis超时报错
消除 大 key方法
- 拆分, 将大key拆分为小key. 例如一个String拆分成多个String\
- 压缩, 将value压缩后写入redis, 读取时解压后再使用.
- 集合类结构hash、list、set、set (1)拆分:可以用hash取余、位掩码的方式决定放在哪个key中 (2)区分冷热: 如榜单列表场景使用zset, 只缓存前10页数据, 后续数据走db, ( 只关心第一名, 后面的人不关心 )
热Key的定义
- 用户访问一个Key的QPS特别高,导致Server实例出现CPU负载突增或者不均的情况.
- 热key没有明确的标准, QPS超过500 就有可能被识别为热Key
设置Localcache
- 在访问Redis前,在业务服务侧设置Localcache, 降低访问 Redis 的 QPS. LocalCache中缓存过期或未命中,则从Redis中将数据更新到LocalCache.Java的Guava、Golang的Bigcache就是这类LocalCache
拆分
- 将key:value这一个热Key复制写入多份, 例如key1:value,key2:value, 访问的时候访问多个key, 但value是同一个以此将 qps 分散到不同实例上, 降低负载 .代价是, 更新时需要更新多个key, 存在数据短暂不一致的风险
解决热Key的方法2.使用Redis代理的热Key承载能力字节跳动的Redis访问代理就具备热Key承载能力.本质上是结合了"热Key发现"、“LocalCache"两个功能
慢查询
导致慢查询的操作
- 批量操作一次性传入过多的key/value,如mset/hmset/sadd/zadd等O(n)操作建议单批次不要超过100,超过100之后性能下降明显.
- zset大部分命令都是O(log(n)),当大小超过5k以上时,简单的zadd/zrem也可能导致慢查询
- 操作的单个value过大,超过10KB.也即,避免使用大Key
- 对大key的delete/expire操作也可能导致慢查询,Redis4.0之前不支持异步删除unlink,大key删除会阻塞Redis
缓存穿透、缓存雪崩
缓存穿透:热点数据查询绕过缓存,直接查询数据库
缓存雪崩:大量缓存同时过期
缓存穿透
- 查询一个一定不存在的数据通常不会缓存不存在的数据,这类查询请求都会直接打到db,如果有系统bug或人为攻击,那么容易导致db响应慢甚至宕机
如何减少缓存穿透
- (1)缓存空值 如一个不存在的userlD.这个id在缓存和数据库中都不存在.则可以缓存一个空值,下次再查缓存直接反空值.
- (2)布隆过滤器通过 bloom filter 算法来存储合法 Key, 得益于该算法超高的压缩率, 只需占用极小的空间就能存储大量 key 值
如何避免缓存雪崩
- 1)缓存空值将缓存失效时间分散开,比如在原有的失效时间基础上增加一个随机值, 例如不同Key过期时间可以设置为10分1秒过期,10分23秒过期,10分8秒过期.单位秒部分就是随机时间,这样过期时间就分散了对于热点数据,过期时间尽量设置得长一些,冷门的数据可以相对设置过期时间短一些.
- 2)使用缓存集群,避免单机宕机造成的缓存雪崩.