Redis核心知识整理
2021-11-06 09:35:14 2 举报
AI智能生成
Redis知识思维脑图,核心知识点整理
作者其他创作
大纲/内容
Redis高性能
线程模型
Redis基于<b><u>Reactor模式</u></b>开发了网络事件处理器,这个处理器被称为<u><b><font color="#f15a23">文件事件处理器</font></b></u>(file event handler)。它的组成结构为4部分:<b><u>多个套接字</u></b>、<b><font color="#f15a23">IO多路复用程序</font></b>、<b>文件事件分派器</b>、<b>事件处理器</b>。因为文件事件分派器队列的消费是单线程的,所以Redis才叫<b><font color="#f15a23">单线程模型</font></b>。<br>文件事件处理器使用 <b><font color="#f15a23">I/O 多路复用(multiplexing)</font></b>程序来同时监听多个套接字, 并根据套接字目前执行的任务来为套接字关联不同的事件处理器。当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关闭(close)等操作时, 与操作相对应的文件事件就会产生, 这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件虽然文件事件处理器以单线程方式运行, 但通过使用 I/O 多路复用程序来监听多个套接字, 文件事件处理器既实现了高性能的网络通信模型, 又可以很好地与redis 服务器中其他同样以单线程方式运行的模块进行对接, 这保持了 Redis 内部单线程设计的简单性。
非阻塞IO 内部实现采用epoll,采用了epoll+自己实现的事件框架。<br>epoll中的读、写、关闭、连接都转化成了事件,然后利用epoll的多路复用特性,绝不在io上浪费一点时间。<br>
Redis 服务器是一个事件驱动程序, 服务器处理的事件分为时间事件和文件事件两类。<br><br> 1、文件事件:Redis主进程中,主要处理客户端的连接请求与相应。<br> 2、时间事件:fork出的子进程中,处理如AOF持久化任务等。
<br>参考资料:<br>一文搞懂 Redis高性能之IO多路复用<br>https://mp.weixin.qq.com/s?__biz=MzA3MDg5MDkzOA==&mid=2448762668&idx=1&sn=b0678332189bd6c9654dd9c21c379de8&srcid=0313aeqyoUs5FUKXE7SSFkTc<br>深入理解 Linux 的 epoll 机制<br>https://mp.weixin.qq.com/s/YNgYwR3gYBICkAj_cesw2g<br>
单线程速度快的原因<br>
<b>纯内存</b>:<br>完全基于内存的,内存的读写速度非常<br>
<b>非阻塞的IO多路复用:</b><br>使用多路复用技术,可以处理并发的连接
采用<b>单线程</b>设计,避免线程切换和竞态消耗:<br>单线程的,省去了很多上下文切换线程的时间
<b><font color="#f15a23">新版有引入多线程操作,但仅是对一些删除操作采用使用多线程</font></b>:对于一些大键值对的删除操作,通过多线程非阻塞地释放内存空间也能减少对 Redis 主线程阻塞的时间,提高执行的效率<br>
<b>数据结构简单,操作也简单:</b><br>Redis全程使用hash结构,读取速度快,还有一些特殊的数据结构,<br>对数据存储进行了优化,如压缩表,对短数据进行压缩存储,再如,跳表,使用有序的数据结构加快读取的速度。<br>
使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,<br>Redis 直接<b><font color="#f15a23">自己构建了 VM 机制</font> </b>,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;<br>
Redis高可用
哈希槽(Hash Slot)<br>
<b>Redis集群有<font color="#c41230">16384</font>个哈希槽(Hash Slot)</b>,每个key通过<b>CRC16校验</b>后对16384取模来决定放置哪个槽,<b>集群的每个节点负责一部分hash槽</b>。
<font color="#c41230"><b>redis集群不保证数据的强一致性</b></font>,在实际中集群在特定的条件下可能会丢失写操作
主从架构
<b>主从复制</b>
简介
主要用来支撑读高并发,一主多从
1、为了读写分离,提高Redis性能;保证数据安全
2、主(master)redis以写操作为主,从(slave)redis以读为主,主从之间自动同步数据
3、每次从机联通后,都会给主机发送 sync 指令,主机立刻进行存盘操作,发送 RDB 文件,给从机<br>从机收到 RDB 文件后,进行全盘加载。之后每次主机的写操作,都会立刻发送给从机,从机执行相同的命令
主从建立
临时建立:执行slaveof ip:port命令(可以通过info replication命令查看当前redis的主从策略)
永久建立:配置master配置文件
salveof no one:执行该命令去除主从配置
工作原理
1. 当从库和主库建立MS关系后,会向主数据库发送SYNC命令
2. 主库接收到SYNC命令后会开始在后台保存快照(RDB持久化过程),并将期间接收到的写命令缓存起来
3. 当快照完成后,主Redis会将快照文件和所有缓存的写命令发送给从Redis
4. 从Redis接收到后,会载入快照文件并且执行收到的缓存的命令
5. 之后,主Redis每当接收到写命令时就会将命令发送从Redis,从而保证数据的一致
要点<br>
一个 master node 是可以配置多个 slave node 的
slave node 也可以连接其他的 slave node
slave node 做复制的时候,不会 block master node 的正常工作
slave node 在<b>做复制的时候</b>,也不会 block 对自己的查询操作,它会<b>用旧的数据集来提供服务</b>;<br>但是<b>复制完成的时候,<font color="#f15a23">需要删除旧数据集,加载新数据集,这个时候就会暂停对外服务了</font></b>;<br>
建议必须开启 master node 的持久化
如果slave持久化,可能在 master 宕机重启的时候数据是空的,然后可能一经过复制, slave node 的数据也丢了。
缺点
所有的slave节点数据的<font color="#f15a23"><b><u>复制和同步都由master节点来处理</u></b></font>,会造成master节点压力太大,使用集群结构来解决
<b>哨兵模式(sentinel)<br>(<i>主从复制的一种实现</i>)</b>
概述
作用:检测master状态,若异常则选取一个从机升为主机,原主机降为从机
下线
①主观下线:<br>Subjectively Down,简称 SDOWN,指的是当前 Sentinel 实例对某个 redis 服务器做出的<br>下线判断。
②客观下线:<br>Objectively Down, 简称 ODOWN,指的是多个 Sentinel 实例在对 Master Server 做出SDOWN 判断,<br>并且通过 SENTINEL is-master-down-by-addr 命令互相交流之后,得出的 Master Server 下线判断,然后开启 failover.
哨兵节点<br>
特殊的redis节点,不存数据
监控
sentinel会不断检测主节点和从节点是否正常
提醒
当检测某个节点时,可以通过API像管理员和其他应用发送通知<br>
自动故障转移
当一个主服务不能正常工作时会开始一次故障处理操作
数据节点
主节点和从节点都是数据节点<br>
主要功能<br>
监控<br>
监控master和slaver的进程是否正常
消息通知<br>
如果某个节点处故障,发消息通知管理员
故障转移
如果master挂了,自动转移到slave节点
工作原理<br>
本身也是分布式的,作为一个哨兵集群去运行,互相协同工作。
故障转移时,判断一个 master node 是否宕机了,需要大部分的哨兵都同意才行,涉及到了<b><font color="#f15a23">分布式选举</font></b>的问题。
即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的
具体流程
①每个 Sentinel 以每秒钟一次的频率向它所知的 Master,Slave 以及其他 Sentinel 实例发送一个 PING 命<br>令 ;
②如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所<br>指定的值, 则这个实例会被 Sentinel 标记为主观下线;
③如果一个 Master 被标记为主观下线,则正在监视这个 Master 的所有 Sentinel 要以每秒一次的频率确认<br>Master 的确进入了主观下线状态
④当有足够数量的 Sentinel(大于等于配置文件指定的值)在指定的时间范围内确认 Master 的确进入了主<br>观下线状态, 则 Master 会被标记为客观下线 ;
⑤在一般情况下, 每个 Sentinel 会以每 10 秒一次的频率向它已知的所有 Master,Slave 发送 INFO 命令
⑥当 Master 被 Sentinel 标记为客观下线时,Sentinel 向下线的 Master 的所有 Slave 发送 INFO 命令的<br>频率会从 10 秒一次改为每秒一次 ;
⑦若没有足够数量的 Sentinel 同意 Master 已经下线, Master 的客观下线状态就会被移除;<br>若 Master 重新向 Sentinel 的 PING 命令返回有效回复, Master 的主观下线状态就会被移除
<font color="#2196f3"><b>总结:挑选出新的master后,sentinel向原master的从服务发送slaveof新master的命令,<br>当下线的原master上线时,sentinel会发送slaveof命令让其成为新master的slaver</b></font><br>
配置
配置哨兵:sentinel monitor mymaster 127.0.0.1 6379 1
启动哨兵:redis-sentinel sentinel.conf
新master选取策略
1、依据优先级,配置文件中配置slave-priority属性
2、偏移量最大(获取原master数据最多的从机)
3、选择runid最小的(每个redis实例启动后都会随机生成一个40位的runid)
要点
<font color="#e65100"><b>至少需要3个实例</b></font>保障自身的健壮性<br>
哨兵集群+redis主从复制部署<b><font color="#c41230">不能保证数据零丢失,只能保证高可用</font></b><br>
集群方案
<b>Cluster分片集群方式</b><br>(服务器路由)<br>
简介
实现对Redis水平扩容
没有采用<b><font color="#f15a23">一致性哈希算法</font></b>,采用<font color="#e65100"><b>槽(slot)</b></font>的概念,一共分为<font color="#f15a23"><b>16384</b></font>个槽;<br>请求发送到任一节点,都会被正确路由(<b>在客户端的帮助下直接redirected到正确的redis节点</b>)到正确的节点<br>
哈希槽(slot)
1、一个Redis集群包含16384个hash槽(hash slot)
2、集群的每个节点负责一部分hash槽
3、集群使用公式CRC16(KEY)%16384来计算key属于哪个槽
4、每个slot可以存储一批键值对
集群相关数据操作
客户端重定向
1、不使用重定向:则操作数据的key不属于当前客户端插槽德华,redis会报错提示前往对应的redis实例客户端操作数据;
2、使用重定向:redis-cli -c -p 6379<br>其中 -c 使命令自动重定向
多键操作
1、不在一个slot下的键值,不能进行mget/mset等多键操作
2、通过{}来定义组的概念,从而使key中{}内相同内容的键值对放到同一个slot中去
<b>即HashTag机制可以影响key被分配到的slot</b><br>HashTag即是用{}包裹key的一个子串,如{user:}1, {user:}2。<br>在设置了HashTag的情况下,集群会根据HashTag决定key分配到的slot,两个key拥有相同的HashTag:{user:},<br>它们会被分配到同一个slot,允许我们使用MGET命令。<br>举例:mset {group}k1 v1 {group}k2 v2<br>
读取数据
cluster nodes:查看集群信息
cluster keyslot <key>:计算key应该被放在哪个槽上
cluster countkeysinslot <slot>:返回槽slot目前包含的键值对数量->cluster countkeysinslot 12539
cluster getkeysinslot <slot> <count>:返回count个slot槽中的键
常见问题
主节点下线,从节点自动升为主节点
主节点恢复后,变为从节点
redis.conf中属性cluster-require-full-coverage控制是否需要集群中16384slot都正常的时候才能对外提供服务<br>默认值为yes<br>
工作原理
具体流程
1. 通过哈希的方式,将数据分片,每个节点(主多从)均分存储一定哈希槽(哈希值)区间的数据,默认分配了16384 个槽位<br>
2. 每份数据分片会存储在多个互为主从的多节点上
3. 数据写入先写主节点,再同步到从节点(<font color="#f15a23">支持配置为阻塞同步</font>)
4. 同一分片多个节点间的数据不保持一致性
5. 读取数据时,当客户端操作的key没有分配在该节点上时,<b><u><font color="#f15a23">redis会返回客户端重定向指令,指向正确的节点</font></u></b>
6. 扩容时需要把旧节点的数据迁移一部分到新节点
节点间通讯
每个redis要开放两个端口,一个对外提供使用,一个用于节点间通讯<br>
节点间通讯采用cluster bus 的通信,用来进行故障检测、配置更新、故障转移授权。cluster bus 用了另外一<br>种二进制的协议, <font color="#f68b1f">gossip 协议</font>,用于节点间进行高效的数据交换(如元数据等),占用更少的网络带宽和处理时间
gossip协议
优点
去中心化架构,支持动态水平扩容<br>
整个集群的部分节点失败或者不可达的情况下能够继续处理命令
分摊压力
具有哨兵模式的监控和故障转移能力<br>
客户端不需要连接所有节点,连接任一节点即可<br>
高性能,直接连接服务,免去了中间代理层<br>
缺点<br>
不支持批量操作,多键key操作不支持
Redis集群并不支持处理多个keys的命令,因为这需要在不同的节点间移动数据,<br>从而达不到像Redis那样的性能,在高负载的情况下可能会导致不可预料的错误<br>
多键操作不被支持
多键redis事务不被支持,lua脚本不被支持
分布式逻辑和数据存储模块耦合<br>
运维复杂,数据迁移需要人工干预<br>
常见问题
主节点下线,从节点自动升为主节点
主节点恢复后,变为从节点
redis.conf中属性cluster-require-full-coverage控制当某一段插槽的主从节点都宕机以后,redis服务是否还继续。
基于客户端分片
采用哈希算法将Redis数据的key进行散列,通过hash函数,特定的key会映射到特定的Redis节点上
优点
服务端彼此独立无相互关联,每个redis像独立的服务一样<br>
非常容易线性扩展,系统灵活性强<br>
缺点<br>
sharding处理放到客户端,规模进一步扩大时给运维带来挑战
服务端Redis实例群拓扑结构有变化时,每个客户端都需要更新调整。<br>连接不能共享,当应用规模增大时,资源浪费制约优化<br>
基于代理服务器分片<br>
代理解析客户端的数据,并将请求转发至正确的节点,最后将结果回复给客户端
特征<br>
透明接入,业务程序不用关心后端Redis实例,切换成本低
Proxy 的逻辑和存储的逻辑是隔离的
代理层多了一次转发,性能有所损耗
业界开源方案
Twtter开源的Twemproxy
豌豆荚开源的Codis
事务
概述
通过<font color="#f15a23">multi\exec\watch</font>执行的一组命令集合;事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照<br>顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。<br>
Redis中的事务指单独的隔离操作;将一个事务的所有命令序列化,然后按顺序执行<br>
要点<br>
1、事务不支持回滚,跳过错误命令继续执行,不会被其他命令打断<br>2、主要作用是串联多个命令防止其他命令打断<br>3、一个事务中所有命令是非原子性的:出现运行错误,正确的命令会被执行<br>4、没有回滚机制,事务中错误的命令无法执行,正确的命令会全部执行
只支持一致性和隔离性,在AOF先开启aways备份时具有持久性,不具备原子性
Redis多数事务失败是由编译时语法错误或者运行时数据结构类型错误导致的,语法错误说明在命令入队前就进行检测的,而类型错误是在执行时检测的,两类错误具体如下所示;Redis为提升性能而采用这种简单的事务,这是不同于关系型数据库的,特别要注意区分。
1、编译错误<br>编译时报错,是因为队列中的命令本身有问题,导致在命令入队的时候就报错;有编译错误的时候,执行exec会提示失败,所有的命令都不能执行。<br>
2、运行错误<br>运行时错误,是入栈的命令本身没有错误,但是在出队执行的时候报错,比如对String做自增操作。<br>运行时报错了,但是事务不会回滚,而且,出错后不会影响后续的命令执行,只会有出错的那一条命令执行失败。所以,对于队列中的命令,是不存在原子性的。<br>
三阶段
<ol><li>开启事务<font color="#f15a23"><b>multi</b></font></li><li>命令入队列</li><li>事务执行<font color="#f15a23"><b>exec</b></font></li></ol><br>
事务<b>执行过程中</b>,如果服务端收到有EXEC、DISCARD、WATCH、MULTI之外的请求,将会把<font color="#f15a23"><u><b>其他请求放入队列中排队</b></u></font>
命令<br>
multi
开启一个事务
exec
执行事务
discard<br>
客户端可以放弃一个事务队列,并放弃执行事务,刷新一个事务中所有在排队等待的指令,客户端会从事务状态退出;<br>如果已使用 WATCH,DISCARD 将释放所有被 WATCH 的 key。<br>
watch
命令用于监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断<br>
unwatch<br>
其他实现方式<br>
基于LUA代码
基于中间标记量
锁
简介
<b>悲观锁(Redis不支持)</b>:假设当前操作很大几率会被打断
<b>乐观锁</b>:假设当前操作不会被打断,做操作前不会锁定资源,万一被打断,则操作被放弃
Redis中的锁策略
通过watch实现乐观锁,多读少写,unwatch命令可以取消枷锁
事务之前执行了watch(加锁),在exec/discard命令执行后锁会自动释放
参考资料:<br>彻底搞懂 Redis 事务<br>https://www.cnblogs.com/fengguozhong/p/12161363.html<br>
消息订阅
简介:消息订阅是进程间的一种消息通信方式,即发送者发布消息(pub),订阅者(sub)接收消息,Redis支持消息订阅机制。
命令
subscribe 频道:订阅频道
publish 频道 消息:向指定频道发布消息
应用场景
基于Redis分布式锁
基本实现
单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系Redis中可以使用SETNX命令实现分布式锁
SETNX命令
设置成功,返回 1 。设置失败,返回 0
流程<br>
使用SETNX命令获取锁,若返回0(key已存在,锁已存在)则获取失败,反之获取成功
为了防止获取锁后程序出现异常进入死锁状态,需要为该key设置一个“合理”的<font color="#f15a23">过期时间释放锁</font>,使用DEL命令将锁数据删除;<br>对应的如果处理耗时操作,也会涉及<font color="#f15a23">续命操作(在过期时间到达之前,修改过期时间,使得继续使用锁)</font><br>
存在的问题
有效期导致的安全问题
A拿到的锁已经失效,但是还在操作资源,此时B服务拿到锁。则存在并发数据安全问题(AB服务同时访问资源)
解决方案:续命
另外线程异步通知更改加锁时间
优雅实现阻塞加锁
解决方案:mq通知<br>
主从模式下,在master中拿到锁,还未同步至slave,master宕机导致的并发问题<br>
<b>不建议使用redis分布式锁,建议使用zookeeper实现</b><br>
基于zookeeper<font color="#f15a23">临时有序节点</font>可以实现的分布式锁。大致思想为:<br>每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 <br>判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。<br>同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。完成业务流程后,删除对应的子节点释放锁。<br>
redlok<br>
官方放提出的redis分布式锁方案<br>
集群环境下,<b>依次从各个节点获取锁,<font color="#f15a23">半数以上节点获取成功</font>才真正获取锁成功,否则释放已经获取的锁</b><br>
特征
安全特性
互斥访问,即永远只有一个 client 能拿到锁
避免死锁
最终 client 都可能拿到锁,不会出现死锁的情况,即使原本锁住某资源的 client crash 了或者出现了网络分区
容错性
只要大部分 Redis 节点存活就可以正常提供服务
Redis队列(发布订阅)
Redis缓存
缓存
<b>缓存异常</b>
缓存雪崩
缓存同一时间大量失效
缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。<br>
解决方案<br>
1、过期时间设置随机值,避免大量失效<br>
比如可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低
2、并发不大的情况可以使用加锁排队<br>
加锁或者队列的方式保证缓存的单线程(进程)写,从而避免失效时大量的并发请求落到底层存储系统上
3、使用快速失败的熔断或限流策略,减少DB 瞬间压力<br>
4、分多级缓存:如使用本地缓存
如果使用本地缓存,即使分布式缓存挂了,也可以将 DB 查询的结果缓存到本地,避免后续请求全部达到DB中。
缓存穿透
非法key,指缓存和数据库中都没有的数据<br>
缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击应用,这就是漏洞。<br><br>举例:如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。或如活动系统里面查询一个不存在的活动。
解决方案<br>
1、接口前置增加校验拦截无效请求<br>
2、缓存空对象,并设置短时间失效<br>
3、采用布隆过滤器(异常值的存在性检测)
1)根据 key 查询缓存,如果存在对应的值,直接返回;如果不存在则继续执行2)。<br> 2)根据 key 查询缓存在布隆过滤器的值,如果存在值,则说明该 key 不存在对应的值,直接返回空,如果不存在值,继续向下执行。<br> 3)查询 DB 对应的值,如果存在,则更新到缓存,并返回该值,如果不存在值,则更新缓存到布隆过滤器中,并返回空。<br>
ps:合法的请求在布隆过滤器中一定可以经过(即布隆过滤器判断元素不再集合,那肯定不在。如果判断元素存在集合中,有一定的概率判断错误),<br>但是布隆过滤器并不能完全拦截所有非法的请求。应用在缓存上,我们能够拦截绝大部分请求即可。<br>
缓存击穿
热点key过期失效或淘汰(缓存中没有,但数据库中有)<br>
某个key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个key 在失效的瞬间,大量的请求就击穿了缓存,<br>直接请求数据库,就像是在一道屏障上凿开了一个洞。这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是很多key。<br>
解决方案
1、“永远不过期”:即自动续期
(1) 从redis上看,确实没有设置过期时间,这就保证了,不会出现热点key过期问题,也就是“物理”不过期。<br>
(2) 从功能上看,如果不过期,那不就成静态的了吗?进行自动续期:所以我们把过期时间存在key对应的value里,<br>如果发现要过期了,通过一个后台的异步线程进行缓存的构建,也就是“逻辑”过期<br>
<b>2、加互斥锁</b>
setnx互斥锁 (mutex key),让一个线程构建缓存,其他线程等待构建缓存的线程执行完,重新从缓存获取数据就可以了
3、分多级缓存:如使用本地缓存
缓存预热
系统上线时,将相关数据直接缓存到缓存系统<br>
方案
管理界面手动管理
系统启动时直接加载(数据量不大的情况)<br>
定时任务刷新
缓存降级<br>
服务出现异常或非核心业务影响核心业务,仍然保证核心系统能使用,可以根据关键数据判断实现缓存降级或者手动降级<br>
目的<br>
保障核心业务可用
为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略
Redis+Lua实现分布式限流<br>
参考资料:<br>1、基于Redis和Lua的分布式限流<br>https://segmentfault.com/a/1190000018783729<br>
Lua脚本
Lua 脚本在 Redis 中是以原子方式执行的,在 Redis 服务器执行EVAL命令时,在命令执行完毕并向调用者返回结果之前,只会执行当前命令指定的 Lua 脚本包含的所有逻辑,其它客户端发送的命令将被阻塞,直到EVAL命令执行完毕为止。因此 LUA 脚本不宜编写一些过于复杂了逻辑,必须尽量保证 Lua 脚本的效率,否则会影响其它客户端。
分布式会话Session
运维部署
<b>生产环境中的 redis 是怎么部署的?(举例如下)</b><br><br>redis cluster,10 台机器,5 台机器部署了 redis 主实例,另外 5 台机器部署了 redis 的从实例,每个主实例挂了一个从实例,5 个节点对外提供读写服务,每个节点的读写高峰qps可能可以达到每秒 5 万,5 台机器最多是 25 万读写请求/s。<br><br>机器是什么配置?32G 内存+ 8核 CPU + 1T 磁盘,但是分配给 redis 进程的是10g内存,一般线上生产环境,redis 的内存尽量不要超过 10g,超过 10g 可能会有问题。<br><br>5台机器对外提供读写,一共有50G内存。<br><br>因为每个主实例都挂了一个从实例,所以是高可用的,任何一个主实例宕机,都会自动故障迁移,redis 从实例会自动变成主实例继续提供读写服务。<br><br>其实大型的公司,会有基础架构/中间件的团队负责缓存集群的运维。<br>
常见问题&最佳实践
<b><font color="#f15a23">redis为单线程应用,可以在一个节点部署多个redis提高CPU利用率</font></b><br>
一个数据至少读取两次才有缓存意义<br>
缓存与数据库再写去数据时的一致性问题<br>
1.非不必须情况,建议允许有偶尔的不一致情况<br>
2.串行化<br>
读请求和写请求串行化,串到一个内存队列里去,这样就可以保证一定不会出现不一致的情况<br>
缺点
吞吐量大幅降低<br>
3.<b>先更新数据库,再删除缓存</b>(建议使用)<br>
缺点
可能会暂时产生不一致的情况,但是发生的几率特别小
常见性能问题和解决方案<br>
1. Master最好不要做任何持久化工作,包括内存快照和AOF日志文件,特别是不要启用内存快照做持久化。
2. 如果数据比较关键,某个Slave开启AOF备份数据,策略为每秒同步一次(<b>可配置为每执行一个命令就持久化一次</b>)。
3. 为了主从复制的速度和连接的稳定性,Slave和Master最好在同一个局域网内。
4. 尽量避免在压力较大的主库上增加从库
5. Master调用BG<b>REWRITE</b>AOF<u>重写AOF文件</u>,AOF在重写的时候会占大量的CPU和内存资源,导致服务load过高,出现短暂服务暂停现象。
一个字符串类型的内存最大容量<br>
<b><font color="#f15a23">512M</font></b>
大量数据插入的处理<br>
Redis2.6开始redis-cli支持一种新的被称之为<b><u><font color="#f68b1f">pipe mode的新模式</font></u></b>用于执行大量数据插入工作。
背景
原有模式客户端发送的操作命令为“一问一答”中间存在<font color="#f15a23">多次网络往返(网络开销大,导致吞吐量低)</font>
工作模式<br>
一次性发送多条命名,并在执行完后一次性打包返回
通过减少客户端与 redis 的通信次数来实现降低往返延时时间;<br> Pipeline 实现的原理是<font color="#f15a23">队列</font>,而队列的原理是先进先出,这样就<font color="#f15a23">保证数据的顺序性</font>。 <br>Pipeline 的<u><b><font color="#f15a23">默认</font></b></u>的同步的个数为<u><b><font color="#c41230">53个</font></b></u>,也就是说 arges 中累加到53条数据时会把数据提交<br>
redis需要在所有命名执行完之前<font color="#f15a23">缓存所有命名的执行结果</font>
具体能够容忍的操作<font color="#f15a23">个数和socket-output 缓冲区大小/返回结果的数据尺寸都有很大的关系;</font><br>同时也意味着每个 redis-server 同时所能支撑的 pipeline <font color="#f15a23">链接的个数</font>,<font color="#f15a23">也是有限的</font>,这将受限于 server 的物理内存或网络接口的缓冲能力。
查找指定模式的key
keys指令
速度快<br>
由于redis单线程,数据量大时会导致服务阻塞
scan命令
速度慢
无阻塞获取指定模式Key,由于是非阻塞操作,因此会有不准确的情况(执行该命令期间会有别的命令还在执行)
延时队列的实现<br>
使用时间戳做score, 消息内容作为key,调用zadd来生产消息,消费者使用zrangbyscore获取n秒之前的数据做轮询处理。<br>
常用工具
redisson<br>
一个高级的分布式协调Redis客服端,能帮助用户在分布式环境中轻松实现一些Java的对象
功能简单,不支持字符串操作,不支持排序,事务,<b><font color="#c41230">管道</font></b>,分区等特性<br>
Java整合Redis
简述
Lettuce和Jedis的都是连接Redis Server的客户端程序
但是在SpringBoot2.0之后默认都是使用的Lettuce这个客户端连接Redis服务器
Jedis<br>
Jedis在实现上是直连redis server,多线程环境下非线程安全,除非使用连接池,为每个Jedis实例增加物理连接<br>
每个线程都去拿自己的 Jedis 实例,当连接数量增多时,资源消耗阶梯式增大,连接成本就较高了
lettuce
Lettuce的连接是基于Netty的,连接实例(StatefulRedisConnection)可以在多个线程间并发访问;<br>因为StatefulRedisConnection是线程安全的,所以一个连接实例(StatefulRedisConnection)就可以满足多线程环境下的并发访问;<br>当然这个也是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。<br>
Lettuce支持异步调用,可以避免将 CPU 浪费在等待网络 IO 和磁盘 IO 时上,实现提高资源使用率。
Lettuce主要提供三种API
同步(sync):RedisCommands。<br>异步(async):RedisAsyncCommands。<br>反应式(reactive):RedisReactiveCommands。<br>
Lua脚本
1、常用作嵌入式脚本语言
2、将复杂的redis操作,写为一个脚本,交于redis执行,减少连接redis的次数
3、具有一定的原子性,不被其他命令插队,可以完成事务操作
4、redis2.6以上的版本才可以使用
概述
Redis->Remote Dictionary Server(远程字典服务器)
优点
读写性能优异-读11万,写8万<br>
支持持久化数据
RDB
AOF<br>
支持事务<br>
所有命令操作都是原子性
支持<b>多个命令</b>合并打包的原子执行(打包顺序执行,<b>之间不执行别的命令,别的命令进入等待队列</b>)<br>
数据结构丰富<br>
支持主从复制<br>
<b>主机自动将数同步给从机</b><br>
缺点
<b>受物理内存限制</b>,不能作为海量数据高速读写<br>
<b>不具备自动容错和恢复功能</b>,主从机的宕机都会导致部分请求的失败<br>
主机宕机前,部分数据未同步从机,会<b>引起数据不一致和数据丢失</b>的问题<br>
<b>在线扩容较难</b><br>
集群容量达到上线时再次扩容难度较大<br>
高性能<br>
将用户访问的数据存在缓存中
高并发<br>
将数据库中部分数据移到缓存中
redis与Map/guava的优势<br>
map/guava
在多机实例下,各个实例单独拥有缓存,导致缓存不一致<br>
应用场景<br>
计数器<br>
如需要实时读写数据库时,例如评论数、点赞数;结合缓存使用
缓存(内容可以失效)
数据库缓存、会话缓存、全网页缓存(FPC)<br>
查找表(内容不能失效)<br>
消息队列(订阅/发布功能)<br>
分布式锁<br>
redis支持的<font color="#f15a23">setnx</font>实现分布式锁<br>
官方推荐的RedLock分布式锁<br>
分布式会话
数据类型<br>
基本数据类型
字符串、整数、浮点数(String)
对字符串或字符串部分进行操作;<br>对数据可以进行自增减操作<br>
列表(List)
从两端压入或弹出元素;对单个或多个进行修剪操作;保留一定范围的元素<br>
集合(Set)
添加、获取、移除单个元素,检查一个元素是否存在,计算<font color="#f68b1f">交、并、差集</font>计算,在集合里面随机获取元素<br>
散列表(Hash)
添加、获取、移除、单个键值对;获取所有键值对;检查某个键是否存在<br>
有序集合(ZSet)
添加、获取、删除元素;根据<b><font color="#f384ae">分值</font></b>范围或成员获取元素;计算一个键的排名<br>
特殊数据类型
地理位置(geospatial)<br>
Redis 在 3.2 推出 Geo 类型,该功能可以推算出地理位置信息,两地之间的距离。
HyperLogLog 是用来做基数统计的算法
Redis 2.8.9 版本更新了 hyperloglog 数据结构,是基于基数统计的算法。<br>基数:数学上集合的元素个数,是不能重复的。
hyperloglog 的优点是占用内存小,并且是固定的。存储 2^64 个不同元素的基数,只需要 12 KB 的空间。但是也可能有 0.81% 的错误率。
这个数据结构常用于统计网站的 UV。传统的方式是使用 set 保存用户的ID,然后统计 set 中元素的数量作为判断标准。但次方式保存了大量的用户 ID,ID 一般比较长,占空间,还很麻烦。而本质目的是计数,不是保存数据,所以这样做有弊端。但是如果使用 hyperloglog 就比较合适了。
位图(bitmaps)
bitmap就是通过最小的单位bit来进行0或者1的设置,表示某个元素对应的值或者状态。一个bit的值,或者是0,或者是1;也就是说一个bit能存储的最多信息是2。
bitmap 常用于统计用户信息比如活跃粉丝和不活跃粉丝、登录和未登录、是否打卡等。
pub/sub
stream
持久化的pub/sub
redis5.0新增
借鉴kafka的模式
参考资料:<br>Redis 的 8 大数据类型,写得非常好!<br><br>
数据结构(底层)
SDS动态字符串
多增加<b><font color="#f68b1f">len记录字符串长度</font></b>
自动扩展空间<br>
空间不够的话,SDS 会<b><font color="#f68b1f">自动扩展空间</font></b>,避免了像 C 字符串操作中的覆盖情况;
有效降低内存分配次数
空间<b><font color="#f68b1f">预分配</font></b><br>
空间<b><font color="#f68b1f">惰性释放</font></b>
二进制安全<br>
C 语言字符串只能保存 ascii 码,对于图片、音频等信息无法保存,SDS 是<b><font color="#f68b1f">二进制</font></b>安全的,写入什么读取就是什么,不做任何过滤和限制;
字典(相当于hash)<br>
扩容
当 hash 表中 元素的个<b>数等于第一维数组的长度</b>时,就会<b>开始扩容</b>,扩容的新数组是 原数组大小的 2 倍。<br>不过如果 Redis 正在做 bgsave(持久化命令),为了减少内存也得过多分离,Redis 尽量不去扩容,<br>但是如果 hash 表非常满了,达到了第一维数组长度的 <b>5 倍</b>了,这个时候就会<b> 强制扩容</b>。<br>
缩容
元素个数<b><font color="#f68b1f">低于数组长度的10%</font></b>会缩容<br><b>缩容操作不会考虑bgsave</b>
跳跃表
压缩表(ziplist)
zset,hash会在元素比较少的时候采用压缩存储
压缩表是一块连续的内存空间,元素之间数据紧挨着没有空隙<br>
快速列表(quicklist)
list+zipList
redis3.2引入用于替代ziplist 和 list
基本操作
数据库操作
redis database概述
Redis默认提供了16个数据库(database),每个数据库有一个id,从0到15,他们没有名字,只有id。<br>客户端登录Redis时默认登录的是id为0的数据库。<br>
Redis默认支持16个数据库,可以通过调整Redis的配置文件redis/redis.conf中的databases来修改这一个值
对于db正确的理解应为“命名空间”,多个应用程序不应使用同一个Redis不同库,而应一个应用程序对应一个Redis实例,<br>不同的数据库可用于存储不同环境的数据。<br>
注意:Redis集群下只有db0,不支持多db。
select <dbid>:切换数据库
flushdb:清空当前数据库
dbsize:查看数据库数据个数
flushall:清空所有数据库
move key index:将键值对从当前db移动到目标db
lastsave:获取最后一次持久化操作的时间
key操作
keys pattern:查看符合指定表达式的所有key,pattern可以为*,?等
type key:查看key对应值的类型
exists key:查看key是否存在
del key:删除key
randomkey:随意选取key
expire key seconds:为键值设置过期时间
TTL key:查看key还有多久过期,-1->永不过期,-2->已过期
rename key newkey:重命名key,强制执行
renamex key newkey:重命名key,非强制执行,如果newkey存在则语句不生效
数据操作
数据类型介绍
string操作
list操作
set操作
hash操作
zset操作
数据持久化
RDB(redis database缩写快照)
简介
1、在指定时间间隔内将内存中的数据集快照写入磁盘,恢复时将快照文件直接读到内存中;<br>
2、默认开启
3、Redis 会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束<br>了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何 IO 操作的,这就确保了极高<br>的性能如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那 RDB 方式要比 AOF 方式更加<br>的高效。
4、最后一次持久化的数据可能丢失
保存策略
save second count:second秒内如果至少有count个key值变化,则进行保存
save "":禁用RDB模式
属性配置
save:保存策略
dbfilename:RDB快照文件名
dir:指定RDB快照保存的目录
stop-writes-on-bgsave-error:备份出错时,是否继续接受写操作,默认会停止
rdbcompression:是否压缩存储快照,若压缩则会采用LZF算法进行压缩
rdbchecksum:是否进行数据校验,若校验则会采用CRC64算法来校验,会增加10%的性能消耗
触发机制
自动保存
执行save/bgsave命令,执行过程中redis会阻塞
执行flushdb,也会产生dump.rdb,但为空文件
执行shutdown命令
优缺点
优点
性能最大化,子进程完成数据备份,不影响主进程:<br>redis主进程调用fork()方法创建一个子进程来处理所有保存工作,主进程不需要进行任何磁盘IO操作。
容灾性好,一个文件可以保存在安全磁盘
RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。
只有一个dump.rdb文件,方便持久化<br>
缺点
数据安全性低,隔一段时间备份会<b>有数据丢失的问题</b><br>
AOF(append only file)<br>
简介
将redis执行的每次命令放到一个日志文件中记录;当开启两种方式备份时,优先使用AOF恢复数据<br>
保存策略
appendfsync always:每次执行写操作都保存
appendfsync everysec(默认):每秒保存一次,可能会丢失1秒的数据
appendfsync no:从不保存
属性配置
appendonly:是否开启AOF
appendfilename:AOF备份文件的名称
appendfsync:保存策略
<font color="#9c27b0" style="">no-appendfsync-on-rewrite</font>:重写时,是否执行保存策略
auto-aof-rewrite-percentage:指定重写与否的aof文件大小比例
auto-aof-rewrite-min-size:设置允许重写的最小aof文件大小
<font color="#9c27b0">aof-load-truncated:是否截断</font>
优缺点
优点
数据安全,可以做到记录备份每一条数据<br>
<b><font color="#f15a23">rewrite模式</font></b>会对过大的文件进行合并重写,删除其中的某些命令<br>
比RDB方式更安全<br>
缺点<br>
AOF文件比RDB文件大,数据恢复启动时慢
总结
最好两种策略配合使用
可以单独使用RDB,不建议单独使用AOF
若只是将Redis作为纯内存缓存,可以都不使用
数据分片机制<br>
目前官方redis cluster采用hash slot(哈希槽) 分片算法
原因<br>
数据分片可以让Redis管理更大的内存
没有分片,你最多只能使用一台机器的内存
分片方案<br>
基于客户端分片<br>
在客户端就已经决定数据会被存储到哪个redis节点或者从哪个redis节点读取
基于代理服务器分片<br>
代理根据分区规则决定请求哪些Redis实例
查询路由<br>
客户端随机地请求任意一个redis实例,然后由Redis将请求转发给正确的Redis节点
缺点
涉及多个key的操作通常不会被支持
例如你不能对两个集合求交集,因为他们可能被存储到不同的Redis实例(实际上这种情况也有办法,但是不能直接使用 交集指令)
同时操作多个key,则不能使用Redis事务.
分区使用的粒度是key,不能使用一个非常长的排序key存储一个数据集
当使用分区的时候,数据处理会非常复杂
例如为了备份你必须从不同的Redis实例和主机同时收集RDB / AOF文件
分区时动态扩容或缩容可能非常复杂
Redis集群在运行时增加或者删除Redis节点,能做到最大程度对用户透明地数据再平衡,<br>但其他一些客户端分区或者代理分区方法则不支持这种特性。然而,有一种预分片的技术也可以较好的解决这个问题<br>
补充:数据分片算法<br>
算法
hash slot(哈希槽) 算法(redis cluster采用)
hash 算法
比较适合固定分区或者分布式节点的集群架构
缺点
扩容或缩容会导致缓存重建<br>
一致性 hash 算法(自动缓存迁移)
缺点
数据倾斜问题,无法控制节点分布的均匀性
一致性 hash 算法(自动缓存迁移)+ 虚拟节点(自动负载均衡)
解决数据倾斜问题,为每一个设置多个虚拟节点(实际应用一般设置32甚至更大),使得数据分布均匀。<br>
应满足的条件<br>
平衡性<br>
单调性<br>
分散性<br>
负载
平滑性<br>
内存回收机制<br>
过期删除策略
策略方式
定时删除策略(主动策略)<br>
到了时间就立即移除(对内存友好,但会占用大量cpu)
对于每一个设置了过期时间的key都会创建一个定时器,一旦到达过期时间就立即删除。<br>该策略可以立即清除过期的数据,对内存较友好,<br>但是缺点是占用了大量的CPU资源去处理过期的数据,会影响Redis的吞吐量和响应时间。<br>
惰性删除策略(被动策略)
当访问一个key的时候再判断是否过期(对内存不够友好)
当访问一个key时,才判断该key是否过期,过期则删除,并且返回NULL。<br>该策略对CPU是友好的,能最大限度地节省CPU资源,但是对内存却十分不友好。<br>有一种极端的情况是可能出现大量的过期key很少被再次访问,因此不会被清除,导致占用了大量的内存。<br>
定期删除策略(主动策略)
每过一段扫描一次数据,清楚过过期的
每隔一段时间,扫描Redis中过期key字典,并清除部分过期的key。<br>该策略是前两者的一个折中方案,还可以通过调整定时扫描的时间间隔和每次扫描的限定耗时,<br>在不同情况下使得CPU和内存资源达到最优的平衡效果。<br>由于惰性删除策略无法保证冷数据被及时删掉,所以Redis会定期主动淘汰一批已过期的key。<br>
Redis会周期性的随机测试一批设置了过期时间的key并进行处理。<br>测试到的已过期的key将被删除。典型的方式为:Redis每秒做10次如下的步骤:<br>1. 随机测试100个设置了过期时间的key<br>2. 删除所有发现的已过期的key<br>3. 若删除的key超过25个则重复步骤1<br>
定时删除(即立即删除)会短时间内占用大量cpu,<br>惰性删除会在一段时间内浪费内存,所以定期删除是一个折中的办法。<br>
定期删除是通过每隔一段时间执行一次删除操作,<br>并通过限制删除操作执行的时长和频率,来减少删除操作对cpu的影响。<br>
删除达到过期时间的key;目前redis使用的过期键值删除策略是:惰性删除+定期删除,两者配合使用。
<b><font color="#f68b1f" style=""><u>redis采用惰性过期策略和定期过期策略</u></font></b>
<u>redis通过<font color="#f68b1f">expire</font>和<font color="#f68b1f">persist(移除可以的过期时间)</font>设置<font color="#f68b1f">过期时间</font>和<font color="#f68b1f">永久生效</font></u><br>
内存淘汰策略<br>
为什么会有淘汰?<br>
通过maxmemory指令配置Redis的数据集使用指定量的内存<br>
当内存使用达到maxmemory极限时,需要使用某种淘汰算法来决定清理掉哪些数据,以保证新数据的存入。属于一种主动策略
在配置文件中,通过maxmemory-policy可以配置要使用哪一个淘汰机制。
常见淘汰算法
FIFO:First In First Out,先进先出算法。<br>淘汰最早数据。判断被存储的时间,离目前最远的数据优先被淘汰。<br>
LRU:Least Recently Used,最近最少使用算法。<br>剔除最近最少使用,判断最近被使用的时间,离目前最远的数据优先被淘汰。<br>
LFU:Least Frequently Used,剔除使用频率最低。<br>在一段时间内,数据被使用次数最少的,优先被淘汰。<br>
策略方式(8种)
全局所有的键空间选择性移除<br>
allkeys-lru
最近最少使用<br>
allkeys-lfu
最近使用频次最少的数据<br>
allkeys-random
随机选择键进行删除<br>
<font color="#c41230">no-enviction</font>
<u style=""><font color="#f68b1f">禁止淘汰,</font><font color="#c41230">新写入会报错</font><font color="#f68b1f">,但保证数据不会丢失,</font><font color="#c41230">系统默认的方式,</font></u>但读操作都能正常进行<br>
设置过期时间的键空间选择性移除
volatile-lru
最近最少使用的数据<br>
volatile-lfu
最近使用频次最少的数据<br>
volatile-ttl
选择过期时间最早的(即存活时间最短)键值对清除<br>
volatile-random
随机选择键进行删除<br>
注意:前缀为 volatile- 和 allkeys- 的区别在于二者选择要清除的键时的字典不同<br>1、volatile- 前缀的策略代表从redisDb中的 expire 字典中选择键进行清除;<br>2、allkeys- 开头的策略代表从dict字典中选择键进行清除;
布隆过滤器Bloom Filter
异常值的存在性检测:判断一个元素是否在集合中存在
Bloom Filter判断元素不再集合,那肯定不在。<br>如果判断元素存在集合中,有一定的概率判断错误<br>
存在一定的误判率;<br>1)过滤器越长误判率越小;<br>2)哈希函数越多,效率越差,误判率越小<br>
<font color="#0076b3">参考</font>
详细解析Redis中的布隆过滤器及其应用
详解布隆过滤器的原理、使用场景和注意事项
收藏
0 条评论
下一页
为你推荐
查看更多