01—背景
一开始并无打算梳理redis的相关内容,由于在一篇文章中看到关于热点问题的处理,心中有一些疑惑,内容以下:java
缓存热点:程序员
对于特别热的数据,若是大部分甚至全部的业务都命中同一份缓存数据,则这份数据所在缓存服务器压力就很大,例如,某明星微博发布“咱们”来宣告恋爱了,则短期内有成千上万的用户都来围观。面试
缓存热点解决方案:redis
就是复制多份缓存,将请求分散到多个缓存服务器上,减轻缓存热点致使的单台缓存服务器压力,以新浪微博为例,对于粉丝超过100万的明星,每一条微博均可以生成100分缓存,缓存的数据都是同样的,经过缓存key里面加编号进行分区,每次读缓存都随机读取其中某份缓存。算法
解决思路是没有问题的,是经过备份相同的数据到多台缓存服务器中,缓解分散单台服务器的压力,个人疑惑是"经过缓存key里面加编号进行分区"这种方式是怎么确保使须要保存的数据都分散到不一样的服务器呢?redis集群根据key进行hash散列算法,最终映射的是槽位,不一样的缓存服务器分别管理的是一些列表槽位,怎么保证经过key计算出来的槽位值正好不在同一台机器上呢?数据库
带着这个疑问点,开始了redis复制,哨兵,集群内容的整理。设计模式
02—内容梳理
复制缓存
在分布式系统中,为了解决单点问题,一般会把数据复制多个副本部署到其余的机器,知足故障恢复和负载均衡的需求。哨兵和集群模式都是在复制的基础上实现的高可用。安全
redis复制拓扑极其应用服务器
拓扑结构分为一主一从,一主多从,树状多从结构。
1. 一主一从结构
应用场景:主节点出现宕机时,从节点提供故障转移支持。
当写命令并发量较高而且须要持久化时,能够在从节点开启AOF,这样既保证了数据的安全也避免了持久化对于主节点的影响。
注意:
当主节点关闭持久化功能,在从节点实现的时候,若是主节点脱机要避免主节点自动重启,由于主节点没有开启持久化功能,自动重启后数据集为空,这时若是从节点继续复制主节点会致使从节点数据也被清空。
解决方案:
在从节点执行slaveof no one断开与主节点的复制关系,再重启主节点避免这种状况。
2. 一主多从结构
利用多个节点实现读写分离,适用于读占比大的场景,把读命令发送到从节点来分担主节点压力。
平常开发中,执行一些比较耗时的读命令,能够在其中的一个从节点上进行,防止慢查询对主节点形成阻塞
3. 树状主从结构
从节点不但能够复制主节点的数据,同时能够做为其余从节点的主节点继续向下层复制。
有效的下降主节点的负载和须要传送给从节点的数据量,下降主节点压力。
复制模式的注意事项:
1. 复制的数据流是单向的,只能从主节点复制到从节点。
2. 从节点默认使用slave-read-only=yes配置为只读模式,对于从节点的任何修改主节点是无感知的,修改从节点会形成主从数据不一致,因此建议不要修改从节点的只读模式。
3. 读写分离面临的问题
对读占比高的场景,把一部分读流量分摊到从节点来减轻主节点压力,永远只对主节点执行写操做。
当使用从节点影响请求时,业务端可能面临的问题:
复制数据延迟
刚在主节点写入数据后马上在从节点读取可能读取不到,须要业务场景容许短期内数据的延迟。
当没法容忍延迟场景,能够编写外部监控程序,监听主从节点的复制偏移量,当延迟较大时触发报警或者通知客户端避免读取延迟太高的从节点。
监控程序按期检查主从节点偏移量,当延迟字节太高时,例如超过10MB,监控程序触发报警通知客户端从节点延迟太高,客户端接收到从节点太高延迟通知后,修改读命令路由到其余的从节点或者主节点上,当延迟恢复后,再次通知客户端,恢复从节点的读命令请求。
读到过时数据
Redis内部维护过时数据删除策略,删除策略一共有两种,惰性删除和按期删除。
惰性删除:主节点每次处理读请求命令时,都会检查键是否超时,若是超时,则执行del命令删除键对象,以后del命令也会异步发送给从节点,为了保证数据一致性,从节点自己永远不会主动删除超时数据。
若是在从节点读取数据,则获取到的数据就有多是已通过期的数据了,由于此时读请求没有发送到主节点,主节点不会检查键是否过时也不会发送del命令给从节点。
按期删除:Redis主节点内部定时任务会循环采样必定数量的键,当发现采样的键过时时执行del命令,以后同步给从节点。
若是此时数据大量超时,主节点采样的速度跟不上过时的速度且主节点没有读取过时键操做,那么从节点将没法收到del命令,这时从节点可能读取到已经超时的数据。
定时删除和惰性删除命令都会有读取到过时数据的问题,在Rdis3.2版本以后,从节点读取数据以前会检查键的过时时间来决定是否返回数据,规避了读取超时数据的问题。
从节点故障
对于从节点故障,须要在客户端维护可用从节点列表,当从节点故障时马上切换到其余从节点或主节点上,相似于延迟太高的监控处理。
哨兵
redis主从复制模式下,一旦主节点因为故障不能提供服务,须要人工将从节点晋升为主节点,同时还要通知应用方更新主节点地址,为了解决人工处理问题,Redis Sentinel(哨兵)架构解决了这个问题。
Sentinel是Redis的高可用的解决方案,由一个或多个Sentinel实例组成Sentinel系统能够监视任意多个主服务器以及这些主服务器下的全部从服务器,而且在被监视的主服务器进入下线状态时,自动将下线主服务器下的某个从服务器升级为新的主服务器,而后由新的主服务器代替已下线的主服务器继续处理命令请求。
当主节点出现故障时,Redis Sentinel能自动完成故障发现和故障转移,并通知应用方,从而实现真正的高可用。
高可用读写分离思路:
从节点的做用:
第一,当主节点出现故障时,做为主节点的后备"顶"上来,实现故障转移,Redis Sentinel实现了该功能的自动化,实现了真正的高可用。第二,扩展主节点的读能力,尤为是在读多写少的场景,很是适用。
上述的模型中,从节点不是高可用的,若是slave-1节点出现故障,首先客户端client-1将与其失联,其次Sentinel节点只会对该节点作主观下线,由于Redis Sentinel的故障转移时针对主节点的。
不少时候Redis Sentinel中的从节点仅仅是做为主节点的一个热备,不让它参与客户端的读操做,就是为了保证总体的高可用,但实际上这种使用方法仍是有一些浪费,尤为是在有不少从节点或者确实须要读写分离的场景,因此如何实现从节点的高可用是很是有必要的。
设计Redis Sentinel从节点的高可用,只要可以掌握全部的从节点的状态,把全部的从节点看作是一个资源池,不管是上线仍是下线一个从节点,客户端都能及时感知到(将其从资源池中添加或者删除),这样就实现了从节点的高可用
集群
Redis cluster是Redis的分布式解决方案,当遇到单机内存,并发,流量等瓶颈时,能够采用Cluster架构方案达到负载均衡的目的。
数据分布理论
分布式数据库首要解决的是把整个数据集按照分区规则映射到多个节点的问题。把数据集划分到多个节点,每一个节点负责整个数据的一个子集。
数据分区规则有两种:哈希分区和顺序分区两种。
哈希分区:离散度好,数据分布业务无关,没法顺序访问。
顺序分区:离散度易倾斜,数据分布业务相关,可顺序访问。
Redis Cluster使用的是哈希分区规则,哈希分区规则有如下几种:
节点取余分区
使用特定的数据,如Redis的键或用户ID,再根据节点数量N使用公式:
hash(key) % N 计算出哈希值,用来决定数据映射到哪个节点。
问题:
当节点数量发送变化时,如扩容或收缩节点,数据节点映射关系须要从新计算,会致使数据的从新迁移。
使用场景:
经常使用于数据库的分库分表规则,通常采用预分区的方式,提早根据数据量规划好分区数,好比划分512或1024张表,保证支撑将来一段时间的数据量,再根据负载状况将表迁移到其余的数据库中。
扩容时一般采用翻倍扩容,避免数据映射所有打乱致使全量迁移的状况。
一致性哈希分区
优势:相比于节点取余的好处在于加入和删除节点只影响哈希环中相邻的节点,对其余的节点没有影响。
问题:
加减节点会形成哈希环中部分数据没法命中,须要手动处理或忽略这部分数据,因此一致性哈希经常使用于缓存场景。
当使用少许节点时,节点变化将大范围影响哈希环中数据映射。一致性哈希不适合少许数据节点的分布式方案。
普通的一致性哈希分区在增减节点时,须要增长一倍或者减小一半的节点才可以保证数据和负载的均衡。
虚拟槽分区
虚拟槽分区巧妙的使用了哈希空间,使用分散度良好的哈希函数把全部的数据映射到一个固定范围的整数集合中,整数定义为槽(slot),整个范围通常远远大于节点数量,Redis Cluster槽范围是0-163843,槽是集群内数据管理和迁移的基本单位,采用大范围槽的主要目的是为了方便数据拆分和集群扩展。
每个节点会负责必定数量的槽。
Redis Cluster使用的虚拟槽分区,全部的键根据哈希函数映射到0-16383整数槽内,计算公式:slot=CRC16(key) & 16383,每个节点负责维护一部分槽以及槽所映射的键值数据。
Redis虚拟槽分区的特色:
解耦数据和节点之间的关系,简化了节点扩容和缩容的难度。
节点自身维护槽的映射关系,不须要客户端或代理服务器维护槽分区元数据。
支持节点,槽,键之间的映射查询,用于数据路由,在线伸缩等场景。
集群功能的限制:
key批量操做支持有限,如mset,mget目前只支持具备相同slot值的key执行批量操做(相同的槽位slot可能会存放多个key,根据公式可知:slot=CRC16(key) & 16383)。
key事务操做支持有限,只支持多key在同一个节点上的事务操做。
key做为数据分区的最小粒度,不能将一个大的键值对象如hash,list等映射到不一样的节点。
单机下Redis能够支持16个数据库,集群模式下只能使用一个数据库空间,即db0。
复制结构只支持一层,从节点只能复制主节点,不支持嵌套树状复制结构。
集群伸缩原理:
在不影响集群对外提供服务的状况下,能够为集群添加节点进行扩容,也能够下线部分节点进行缩容
Redis集群对节点灵活上下线控制,其原理可抽象为槽和对应的数据在不一样节点之间灵活的移动。
三个节点分别维护本身负责的槽和对应的数据,若是但愿加入一个节点实现集群扩容时,须要经过相关命令把一部分槽和数据迁移到新节点。
图中每个节点把槽和数据迁移到新节点6385上,每一个节点负责的槽和数据相比以前变少了,从而达到了集群扩容的目的。
集群水平伸缩的上层原理:集群伸缩=槽和数据在节点之间的移动。
03—解决方案
再回到开始本身的疑问,我理解我本身的想法是没有错的,不可以彻底保证能够经过key来进行分区,由于生成的key经过hash算法最终获取到的是一个槽位,并不能确保新生成的槽位分配在不一样的机器上,通常状况下每个节点对应的是多个槽位。
在已知热点key的状况下推荐使用内存存放热key来解决。
针对这种热key请求,会直接从jvm中获取,避免走redis层,假设此时有10万请求针对同一个key请求过来,若是没有本地缓存,这十万个请求直接到了redis,会将redis服务器弄挂,若是应用服务器有50台,同时热数据在jvm中已经存储了,这十万请求能够平均分散开来,每一个机器2000请求,直接从jvm中取值,而后返回数据
推荐阅读
为何阿里巴巴的程序员成长速度这么快,看完他们的内部资料我懂了
看完三件事❤️
若是你以为这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
点赞,转发,有大家的 『点赞和评论』,才是我创造的动力。
关注公众号 『 Java斗帝 』,不按期分享原创知识。
同时能够期待后续文章ing🚀