什么是Redis
Redis(Remote Dictionary Server)是一个开源的内存数据存储系统,一个NOSQL数据库,它提供了高性能、持久化、键值对存储和多种数据结构的支持。Redis通常被用作缓存、消息队列、实时数据分析、计数器等场景。
Redis运行原理
- 内存存储:Redis将数据存储在内存中,这使得它能够提供快速的读写操作。内存存储也是Redis高性能的关键之一。
- 单线程模型:Redis采用单线程模型,通过事件驱动的方式处理客户端请求。这意味着Redis在任意时刻只能处理一个请求,但通过非阻塞I/O和异步操作,可以实现高并发的请求处理。
- 响应式协议:Redis使用RESP(REdis Serialization Protocol)作为与客户端通信的协议。RESP是一种简单、高效的文本协议,使得Redis能够快速解析和处理客户端请求。
- 持久化:Redis支持两种持久化方式,即RDB快照和AOF日志。RDB快照是将内存中的数据定期保存到磁盘上的快照文件中,而AOF日志则是将每个写操作追加到日志文件中。这样可以在重启后恢复数据。
- 多种数据结构:Redis支持多种数据结构,如字符串、哈希、列表、集合、有序集合等。每种数据结构都有对应的操作命令,使得Redis可以灵活地存储和处理不同类型的数据。
- 高可用性:Redis提供了主从复制和哨兵机制来实现高可用性。主从复制通过将主节点的数据复制到从节点上,实现数据的冗余和故障恢复。哨兵机制用于监控和管理Redis实例,自动进行主从切换和故障转移。
结合图片绘制的理解就是,客户端将数据通过RESP协议发送给Redis-server,服务进程将数据读写进入内存,然后持续的追加AOF日志文件,当宕机时保证数据不会丢失。
、
Resdis应用案例
连续签到:
- 可以存储 字符串、数字、二进制数据
- 通常和expire配合使用
- 场景:存储计数、Session
redis可以设置消息的到期时间,可以将签到信息设置为0点到期,要是用户在签到就将用户签到天数加1要是没有签到就将之前的签到时间清零。
消息通知
使用list
使用场景: 消息通知。 例如当文章更新时,将更新 后的文章推送到ES,用户就 能搜索到最新的文章数据
List数据结构Quicklist
Quicklist 的实现基于一个双向链表,每个节点都包含一个或多个列表节点。每个列表节点都是一个有序列表,可以存储多个元素。Quicklist 的主要特点如下:
- 分层结构:Quicklist 采用分层结构,将多个有序列表分为多个节点,每个节点可以包含多个元素。这种分层结构可以减少内存占用,并提高操作的效率。
- 压缩列表:Quicklist 中的每个列表节点都是使用压缩列表(ziplist)实现的,它是一种紧凑的、高效的列表数据结构。压缩列表可以在存储较小的列表时节省内存空间。
- 快速插入和删除:由于 Quicklist 使用双向链表实现,它可以在列表的两端进行快速的插入和删除操作,而不需要对整个列表进行重排。
- 跳跃表索引:为了提高查找效率,Quicklist 使用跳跃表(skiplist)作为索引结构。跳跃表可以快速定位到指定位置的节点,从而加速元素的查找。
Listpack数据结构
Listpack 的设计目标是在保持高性能的同时,尽可能地减少内存占用。它通过将多个列表元素紧密地打包在一起,减少了元素之间的空间浪费,并提供了高效的操作。
Listpack 的主要特点如下:
- 紧凑的存储:Listpack 使用紧凑的二进制格式存储列表元素,减少了元素之间的空间浪费。它采用变长编码来存储整数和字符串,根据元素的实际长度来分配存储空间。
- 快速访问:Listpack 使用连续的内存块存储列表元素,可以通过偏移量快速访问和遍历元素。这种连续存储的特性使得 Listpack 在读取和写入操作上具有高性能。
- 支持多种数据类型:Listpack 可以存储多种数据类型的元素,包括整数、字符串和浮点数等。它使用标记位来标识元素的类型,并根据类型进行相应的解析和处理。
- 可变长度编码:Listpack 使用可变长度编码来存储整数类型的元素。这种编码方式可以根据整数的大小来选择合适的存储空间,减少了整数元素的存储空间。
计数
一个用户有多项计数需求,可通过hash结构存储
Hash数据结构dict
- ehash:rehash操作是将ht[0]中的数据,全部迁移到ht[1]中。数据量小的场景下,直接将数据从ht[0]拷贝到ht[1]速度是较快的。数据量大的场景,例如存有上百万的KV时,迁移过程将会明显阻塞用户请求。
- 渐进式rehash:为避免出现这种情况,使用了rehash方案。基本原理就是,每次用户访问时都会迁移少量数据。将整个迁移过程,平摊到所有的访问用不请求过程中。
排行榜
积分变化时,排名要实时变更 结合dict后,可实现通过key操作跳表的功能 ZINCRBY myzset 2 "Alex" ZSCORE myzset "Alex"
zset数据结构 zskiplist
Zskiplist(Ziplist Skip List)是 Redis 中有序集合(Sorted Set)数据结构的一种实现方式。它是一种基于跳跃表(Skip List)的紧凑、高效的有序集合数据结构。
Zskiplist 的设计目标是在保持高性能的同时,尽可能地减少内存占用。它通过使用跳跃表的结构和压缩列表(Ziplist)的存储方式,实现了高效的有序集合操作。
Zskiplist 的主要特点如下:
- 跳跃表结构:Zskiplist 使用跳跃表作为索引结构,用于快速定位和查找有序集合中的元素。跳跃表是一种有序链表的扩展结构,通过多级索引层次,可以快速定位到指定位置的节点,从而加速元素的查找。
- 压缩列表存储:Zskiplist 使用压缩列表(Ziplist)作为节点的存储方式。压缩列表是一种紧凑的、高效的列表数据结构,可以在有限的内存空间中存储多个元素。通过使用压缩列表,Zskiplist 可以减少节点的存储空间,从而节省内存。
- 高效的有序集合操作:Zskiplist 提供了高效的有序集合操作,包括插入、删除和范围查询等。通过跳跃表的索引结构,可以在对数时间复杂度内执行这些操作。
- 支持多种数据类型:Zskiplist 可以存储多种数据类型的元素,包括整数和字符串等。它使用标记位来标识元素的类型,并根据类型进行相应的解析和处理。
限流
当一个用户key在1秒内多次请求数量超过一定量时,将对这个进行限流
分布式锁
并发场景,要求一次只能有一个协程执行。执行完成后,其它等待中的协程才能执行。
可以使用redis的setnx实现,利用了两个特性 Redis是单线程执行命令 setnx只有未设置过才能执行成功
Redis使用注意事项
大Key,热Key
大key指的是在String类型下value的字节数大于10KB即为大key,Hash/Set/Zset/list等复杂数据结构类型 元素个数大于5000个或总value字节数大于10MB即为大key。
消除大key:
- 拆分 将大key拆分为小key。例如一个String拆分成多个String
- 压缩 将value压缩后写入redis,读取时解压后再使用。压缩算法可以是gzip、snappy、lz4等。通常情况下, 一个压缩算法压缩率高、则解压耗时就长。需要对实际数据进行测试后,选择一个合适的算法。 如果存储的是JSON字符串,可以考虑使用MessagePack进行序列化。
- 集合类结构hash、list、set、set (1)拆分:可以用hash取余、位掩码的方式决定放在哪个key中 (2)区分冷热:如榜单列表场景使用zset,只缓存前10页数据,后续数据走db
热key指的是用户访问一个Key的QPS特别高,导致Server实例出现CPU负载突增或者不均的情况。 热key没有明确的标准,QPS 超过500就有可能被识别为热Key
解决热Key:
1.设置Localcache 在访问Redis前,在业务服务侧设置Localcache,降低访问Redis的QPS。LocalCache中缓存过期或未命中, 则从Redis中将数据更新到LocalCache。Java的Guava、Golang的Bigcache就是这类LocalCache
2. 拆分 将key:value这一个热Key复制写入多份,例如key1:value,key2:value,访问的时候访问多个key,但value是同一个, 以此将qps分散到不同实例上,降低负载。代价是,更新时需要更新多个key,存在数据短暂不一致的风险
3.使用Redis代理的热Key承载能力 字节跳动的Redis访问代理就具备热Key承载能力。本质上是结合了“热Key发现”、“LocalCache”两个功能
慢查询场景
下面操作会导致Redis慢查询:
- 批量操作一次性传入过多的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响应慢甚至宕机
- 缓存过期时 在高并发场景下,一个热key如果过期,会有大量请求同时击穿至db,容易影响db性能和稳定。 同一时间有大量key集中过期时,也会导致大量请求落到db上,导致查询变慢,甚至出现db无法响应新的查询
如何减少缓存穿透
- 缓存空值 如一个不存在的userID。这个id在缓存和数据库中都不存在。则可以缓存一个空值,下次再查缓存直接反空值。
- 布隆过滤器 通过bloom filter算法来存储合法Key,得益于该算法超高的压缩率,只需占用极小的空间就能存储大量key值
如何避免缓存雪崩
- 缓存空值 将缓存失效时间分散开,比如在原有的失效时间基础上增加一个随机值,例如不同Key过期时间, 可以设置为 10分1秒过期,10分23秒过期,10分8秒过期。单位秒部分就是随机时间,这样过期时间就分散了。 对于热点数据,过期时间尽量设置得长一些,冷门的数据可以相对设置过期时间短一些。
- 使用缓存集群,避免单机宕机造成的缓存雪崩。