redis学习笔记
2023-12-06 15:23:32 0 举报
AI智能生成
redis核心技术与实战
作者其他创作
大纲/内容
数据结构
String
简单动态字符串
List
双向链表
压缩列表
Set
整数数组
哈希表
Sorted Set
跳表
压缩列表
Hash
哈希表
压缩列表
高性能
单线程快的原因
内存操作
IO多路复用
高可用
日志
AOF
写日志策略
Always命令执行完立马写日志到磁盘
EverySec每表写回,会丢数据
No由操作系统决定什么时候写回磁盘
日志文件过大
AOF重写机制,后台子进程bgrewriteaof不阻塞主线程
RDB
命令
save主线程执行,阻塞
bgsave,子进程不阻塞,默认
主从同步
读写分离
主库和从库都可以读
主库先写,然后同步给从库
全量复制
第一次同步用RDB日志
主从级联分担全量复制的主库压力
主->从1->从2,从2通过从1 来复制数据
基于长链接的复制
全量复制之后,主从直接会有一个长链接来将日常主库的命令同步给从库
增量复制
假如主从同步的时候网络抖动,可以增量复制网络抖动期间主库收到的命令
哨兵机制
主要任务
监控
周期性给所有主从库发ping命令检测运行状态
选主
如果没有收到ping返回,主库下线之后自动切换主库
主观下线
ping命令超时,就会判断主观返回
客观下线
N/2 + 1个哨兵都判断下线,则升级为客观下线
通知
选出新主库后,通知从库和客户端
哨兵集群高可用
基于pub/sub机制组建哨兵集群
主库上有名为“__sentinel__:hello”的频道,不同哨兵就是通过它来相互发现,实现互相通信
识别从库的IP和端口
哨兵向主库发送INFO命令,主库返回从库列表
哨兵选择流程
选举获取N/2+1个投票才可以执行具体的选主
切片集群
切片集群有16384个哈希槽slot
根据键值对的key,CRC16算法得到16bit的值,然后和16384取模
实践案例
海量数据统计选型
聚合统计
统计小程序每天新增用户数和第二天的留存用户
set的差集、并集和交集
排序统计
最新列表或者排行榜
List按照先进先出排序
sorted set根据元素的权重来排序
二值状态统计
连续签到打卡
BitMap
基数统计
网页不重复的UV
HyperLogLog 有误差率0.81%
面向LBS的GEO
底层是sorted set
按照经纬度对二维地图区间划分并编码,即为权重分数
时间序列数据
查询特点
点查询,根据一个时间戳查询对应时间的数据
范围查询,根据开始时间和截止时间查询
聚合计算
时间范围内求最大或最小值
方案一
hash && sorted set
缺点
内存保存两份数据
聚合时要把数据读取到客户端
方案二
RedisTimeSeries模块
优点
直接在Redis实例上进行数据聚合,避免数据在客户端传输
缺点
底层数据结构是链表,查询复杂度是O(N)
只能返回最新的数据,无法返回任意时间点的数据
消息队列
基于List
生产者自己实现全局唯一ID
基于Streams
自动生成全局唯一ID
使用PENDING List自动留存消息,使用XPENDING查询,使用XACK确认消息
单线程模型性能阻塞点
集合全量查询和聚合操作
bigkey删除
在应用程序释放内存时,操作系统需要把释放掉的内存块插入一个空闲内存块的链表,以便后续进行管理和再分配。这个过程本身需要一定时间,而且会阻塞当前释放内存的应用程序,所以,如果一下子释放了大量内存,空闲内存块链表操作时间就会增加,相应地就会造成 Redis 主线程的阻塞。
异步删除 unlink
清空数据库
AOF日志同步写
从库加载RDB文件
redis变慢
方法
响应延迟
–intrinsic-latency 选项,可以用来监测和统计测试期间内的最大延迟
基线性能
系统在低压力、无干扰下的基本性能
可能原因
慢查询命令
用其他高效命令代替。比如说,如果你需要返回一个 SET 中的所有成员时,不要使用 SMEMBERS 命令,而是要使用 SSCAN 多次迭代返回,避免一次返回大量数据,造成线程阻塞。
当你需要执行排序、交集、并集操作时,可以在客户端完成,而不要用 SORT、SUNION、SINTER 这些命令,以免拖慢 Redis 实例。
KEYS 命令需要遍历存储的键值对,所以操作延时高。所以,KEYS 命令一般不被建议用于生产环境中。
过期key操作
排查过期 key 的时间设置,并根据实际使用需求,设置不同的过期时间。
Redis 键值对的 key 可以设置过期时间。默认情况下,Redis 每 100 毫秒会删除一些过期 key,具体的算法如下:采样 ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 个数的 key,并将其中过期的 key 全部删除;如果超过 25% 的 key 过期了,则重复删除的过程,直到过期 key 的比例降至 25% 以下。
AOF重写日志
AOF 重写会对磁盘进行大量 IO 操作,同时,fsync 又需要等到数据写到磁盘后才能返回,所以,当 AOF 重写的压力比较大时,就会导致 fsync 被阻塞。虽然 fsync 是由后台子线程负责执行的,但是,主线程会监控 fsync 的执行进度。
操作系统swap
产生的原因
触发 swap 的原因主要是物理机器内存不足,对于 Redis 而言,有两种常见的情况:Redis 实例自身使用了大量的内存,导致物理机器的可用内存不足;和 Redis 实例在同一台机器上运行的其他进程,在进行大量的文件读写操作。文件读写本身会占用系统内存,这会导致分配给 Redis 实例的内存量变少,进而触发 Redis 发生 swap。
解决方案
增加机器的内存或者使用 Redis 集群。
内存大页
Linux 内核从 2.6.38 开始支持内存大页机制,该机制支持 2MB 大小的内存页分配,而常规的内存页分配是按 4KB 的粒度来执行的。
虽然内存大页可以给 Redis 带来内存分配方面的收益,但是,不要忘了,Redis 为了提供数据可靠性保证,需要将数据做持久化保存。这个写入过程由额外的线程执行,所以,此时,Redis 主线程仍然可以接收客户端写请求。客户端的写请求可能会修改正在进行持久化的数据。在这一过程中,Redis 就会采用写时复制机制,也就是说,一旦有数据要被修改,Redis 并不会直接修改内存中的数据,而是将这些数据拷贝一份,然后再进行修改。如果采用了内存大页,那么,即使客户端请求只修改 100B 的数据,Redis 也需要拷贝 2MB 的大页。相反,如果是常规内存页机制,只用拷贝 4KB。两者相比,你可以看到,当客户端请求修改或新写入数据较多时,内存大页机制将导致大量的拷贝,这就会影响 Redis 正常的访存操作,最终导致性能变慢。
缓存删除后内存占用率高
内存碎片
info memory可以查看碎片率
mem_fragmentation_ratio 大于 1 但小于 1.5是合理的。这是因为,刚才我介绍的那些因素是难以避免的。毕竟,内因的内存分配器是一定要使用的,分配策略都是通用的,不会轻易修改;而外因由 Redis 负载决定,也无法限制。所以,存在内存碎片也是正常的。
mem_fragmentation_ratio 大于 1.5 。这表明内存碎片率已经超过了 50%。一般情况下,这个时候,我们就需要采取一些措施来降低内存碎片率了。
mem_fragmentation_ratio 大于 1.5 。这表明内存碎片率已经超过了 50%。一般情况下,这个时候,我们就需要采取一些措施来降低内存碎片率了。
清理碎片
Redis 需要启用自动内存碎片清理,可以把 activedefrag 配置项设置为 yes
active-defrag-ignore-bytes 100mb:表示内存碎片的字节数达到 100MB 时,开始清理;
active-defrag-threshold-lower 10:表示内存碎片空间占操作系统分配给 Redis 的总空间比例达到 10% 时,开始清理。
active-defrag-threshold-lower 10:表示内存碎片空间占操作系统分配给 Redis 的总空间比例达到 10% 时,开始清理。
active-defrag-cycle-min 25: 表示自动清理过程所用 CPU 时间的比例不低于 25%,保证清理能正常开展;
active-defrag-cycle-max 75:表示自动清理过程所用 CPU 时间的比例不高于 75%,一旦超过,就停止清理,从而避免在清理时,大量的内存拷贝阻塞 Redis,导致响应延迟升高。
active-defrag-cycle-max 75:表示自动清理过程所用 CPU 时间的比例不高于 75%,一旦超过,就停止清理,从而避免在清理时,大量的内存拷贝阻塞 Redis,导致响应延迟升高。
缓冲区溢出
客户端的输入和输出缓冲区
主从集群中主节点上的复制缓冲区和复制积压缓冲区
子主题
影响
缓冲区溢出导致网络连接关闭:普通客户端、订阅客户端,以及从节点客户端,它们使用的缓冲区,本质上都是 Redis 客户端和服务器端之间,或是主从节点之间为了传输命令数据而维护的。这些缓冲区一旦发生溢出,处理机制都是直接把客户端和服务器端的连接,或是主从节点间的连接关闭。网络连接关闭造成的直接影响,就是业务程序无法读写 Redis,或者是主从节点全量同步失败,需要重新执行。
缓冲区溢出导致命令数据丢失:主节点上的复制积压缓冲区属于环形缓冲区,一旦发生溢出,新写入的命令数据就会覆盖旧的命令数据,导致旧命令数据的丢失,进而导致主从节点重新进行全量复制。
解决方案
针对命令数据发送过快过大的问题,对于普通客户端来说可以避免 bigkey,而对于复制缓冲区来说,就是避免过大的 RDB 文件。
针对命令数据处理较慢的问题,解决方案就是减少 Redis 主线程上的阻塞操作,例如使用异步的删除操作。
针对缓冲区空间过小的问题,解决方案就是使用 client-output-buffer-limit 配置项设置合理的输出缓冲区、复制缓冲区和复制积压缓冲区大小。当然,我们不要忘了,输入缓冲区的大小默认是固定的,我们无法通过配置来修改它,除非直接去修改 Redis 源码。
旁路缓存
只读缓存
读写缓存
同步直写
保证数据可靠性
异步写回
提供低延迟访问
缓存淘汰
noevction(不淘汰)
进行淘汰
在设置过期时间的数据中淘汰
volatile-lru
voilatile-random
voilatile-ttl
voilatile-lfu
在所有数据中进行淘汰
allkeys-lru
allkeys-random
allkeys-lfu
最佳时间
优先使用 allkeys-lru 策略。这样,可以充分利用 LRU 这一经典缓存算法的优势,把最近最常访问的数据留在缓存中,提升应用的访问性能。如果你的业务数据中有明显的冷热数据区分,我建议你使用 allkeys-lru 策略。
如果业务应用中的数据访问频率相差不大,没有明显的冷热数据区分,建议使用 allkeys-random 策略,随机选择淘汰的数据就行。
如果你的业务中有置顶的需求,比如置顶新闻、置顶视频,那么,可以使用 volatile-lru 策略,同时不给这些置顶数据设置过期时间。这样一来,这些需要置顶的数据一直不会被删除,而其他数据会在过期时根据 LRU 规则进行筛选。
缓存和数据库不一致
先删除缓存值,再更新数据库
缓存删除成功之后,但数据库更新失败
应用从数据库读取到旧数据
重试数据库更新
缓存删除后,尚未更新数据库,有并发读请求
并发请求从数据库读到旧值,并且更新到缓存,导致后续请求都读区旧值
延迟双删
先更新数据库,再删除缓存
数据库更新成功,但是缓存删除失败
应用从缓存读到旧数据
重试缓存删除
数据库更新成功后,尚未删除缓存,有并发读请求
并发请求从缓存中读到旧值
等待缓存删除完成,期间会有不一致数据短暂存在
cache aside
类似只读模式,读操作命中缓存直接返回,否则从后端数据库加载到缓存再返回。写操作直接更新数据库,然后删除缓存。这种策略的优点是一切以后端数据库为准,可以保证缓存和数据库的一致性。缺点是写操作会让缓存失效,再次读取时需要从数据库中加载。这种策略是我们在开发软件时最常用的,在使用Memcached或Redis时一般都采用这种方案。
read/write through
应用层读写只需要操作缓存,不需要关心后端数据库。应用层在操作缓存时,缓存层会自动从数据库中加载或写回到数据库中,这种策略的优点是,对于应用层的使用非常友好,只需要操作缓存即可,缺点是需要缓存层支持和后端数据库的联动。
Write Back
类似于读写缓存模式+异步写回策略。写操作只写缓存,比较简单。而读操作如果命中缓存则直接返回,否则需要从数据库中加载到缓存中,在加载之前,如果缓存已满,则先把需要淘汰的缓存数据写回到后端数据库中,再把对应的数据放入到缓存中。这种策略的优点是,写操作飞快(只写缓存),缺点是如果数据还未来得及写入后端数据库,系统发生异常会导致缓存和数据库的不一致。这种策略经常使用在操作系统Page Cache中,或者应对大量写操作的数据库引擎中。
缓存雪崩
合理地设置数据过期时间
搭建高可靠缓存集群
缓存击穿
在缓存访问非常频繁的热点数据时,不要设置过期时间;
缓存穿透
提前在入口前端实现恶意请求检测,或者规范数据库的数据删除操作,避免误删除。
使用布隆过滤器快速判断数据是否存在,避免从数据库中查询数据是否存在,减轻数据库压力。
分布式锁
set nx px
redisson
redlock
事务机制
ACID
只能保证一致性和隔离性
因为日志可能丢失,无法保证持久性
因为当事务中有语法错误时,会继续执行其他正常的命令,故无法保证原子性
命令
MULTI
开启一个事务
EXEC
提交事务,从命令队列取出操作命令执行
DISCARD
放弃事务,清空队列
WATCH
检测一个或者多个键的值在事务执行期间是否发生变化,如果发生变化,那么当前事务放弃执行
主从同步不一致
主从数据不一致
原因:主从数据异步复制
方案:使用外部监控程序对比主从库的复制进度,不让客户端从落后的从库中读数据
读取到过期的数据
原因:过期数据删除策略
方案:使用expireat pexireat命令给数据设置过期时间点
不合理配置导致服务挂掉
原因:protected-mode cluster-node-timeout配置不合理
方案:protected-mode设置为no, 调大cluster-node-timeout
脑裂
和主库部署在同一台服务器上的其他程序临时占用了大量资源(例如 CPU 资源),导致主库资源使用受限,短时间内无法响应心跳。其它程序不再使用资源时,主库又恢复正常。
主库自身遇到了阻塞的情况,例如,处理 bigkey 或是发生内存 swap(你可以复习下第 19 讲中总结的导致实例阻塞的原因),短时间内无法响应心跳,等主库阻塞解除后,又恢复正常的请求处理了。
codis&&redis cluster
codis proxy 和 codis server 负责处理数据读写请求,其中,codis proxy 和客户端连接,接收请求,并转发请求给 codis server,而 codis server 负责具体处理请求。
codis dashboard 和 codis fe 负责集群管理,其中,codis dashboard 执行管理操作,而 codis fe 提供 Web 管理界面。
Zookeeper 集群负责保存集群的所有元数据信息,包括路由表、proxy 实例信息等。这里,有个地方需要你注意,除了使用 Zookeeper,Codis 还可以使用 etcd 或本地文件系统保存元数据信息。
数据倾斜
数据量倾斜
存在bigkey
业务层避免创建bigkey
把集合类型的bigkey拆分为多个小集群,分散保存
slot手工分配不匀
制定运维规范,避免把过多slot分配到一个实例上
根据hash tag,导致大量数据集中到一个slot
如果hash tag会造成数据倾斜,优先避免数据倾斜,不使用hash tag
数据访问倾斜
存在热点数据
采用带有不同key前缀的多副本方法
redis cluster通信
gossip协议通信
Redis Cluster 运行时,各实例间需要通过 PING、PONG 消息进行信息交换,这些心跳消息包含了当前实例和部分其它实例的状态信息,以及 Slot 分配信息。这种通信机制有助于 Redis Cluster 中的所有实例都拥有完整的集群状态信息。
集群中大规模的实例间心跳消息会挤占集群处理正常请求的带宽。而且,有些实例可能因为网络拥塞导致无法及时收到 PONG 消息,每个实例在运行时会周期性地(每秒 10 次)检测是否有这种情况发生,一旦发生,就会立即给这些 PONG 消息超时的实例发送心跳消息。集群规模越大,网络拥塞的概率就越高,相应的,PONG 消息超时的发生概率就越高,这就会导致集群中有大量的心跳消息,影响集群服务正常请求。
Redis Cluster 的规模控制在 400~500 个实例。
cluster-node-timeout 配置项减少心跳消息的占用带宽
0 条评论
下一页