1 、背景redis
Redis的出现确实大大地提升系统大并发能力支撑的可能性,转眼间Redis的最新版本已是3.X版本了,但咱们的系统依然继续跑着2.8,并很好地支撑着咱们当前天天5亿访问量的应用系统。想当年Redis的单点单线程特性没法知足咱们日益壮大的系统,只能硬着头皮把Redis“集群化”负载。且这套“集群化”方案良好地运行至今。虽难度不高,胜在简单和实用。不管简单仍是很简单,记录这种经历是一件很是有趣的事情。数据库
2 、问题缓存
系统访问量日益倍增,当前的Redis单点服务确实客观存在连续可用性以及支撑瓶颈风险,这种主/备模式在服务故障突发的状况下就会被动中止服务进行Redis节点切换。针对单点问题,咱们结合自身的业务应用场景对Redis“集群化”提出几个主要目标:性能优化
一、避免单点状况,确保服务高可用;bash
二、紧可能把数据分布式存储,下降故障影响范围,知足服务灵活伸缩;服务器
三、控制“集群化”的复杂度,从而控制边际成本;架构
3 、过程并发
以上目标1和2就是所谓的分布式集群方案,把大问题分而治之。但最难把控的是目标3的“简化”实现。基于当时开源社区的那几种Redis集群方案,对于咱们“简化”的要求来讲相对略显臃肿。因此仍是决定结合自身的业务应用等因素打造一个“合适”的Redis集群。框架
初始,咱们凭借本身对分布式集群的认识勾结合应用场景勾勒出一个咱们以为足够“简化”的设计图,而后在这个“简化”架构的基础上继续击破咱们各类应用场景所带来的缺陷。架构图以下:分布式
不难看出,咱们想尽可能经过一个RedisManager类和配置文件就能管理整个集群,不须要而外的软件支持。单例使用的时候RedisManager和配置文件就已经存在,RedisManager有各类单例操做的API重写(如get、set等),如今咱们仍是想保持这种模式对业务处理提供集群API,保持整个服务化应用框架(相似于今天所倡导的“微服务”)的轻量级特性。如上图所示,数据根据hash实现分红不一样块放在不一样的hash节点上,而每一个hash节点必须存在两个Redis实例作hash节点集群支撑。为何会是两个而不是三个或可扩展多个?咱们是这样考虑的:
一、任何可持续扩展或抽象是站在规范这个巨人的肩膀上,咱们秉承了整个系统架构“约定远远大于配置”的原则,适当地限制了边界范围换取控制性而又不失灵活。
二、对于咱们系统目前的服务器质量来讲,宕机的几率较小,双机(双实例)同时宕机的几率更小。就算这个几率出现,咱们众多的业务场景仍是容许这种部分间接性故障。这就是成本与质量之间的平衡和取舍。
三、因为咱们没有使用额外的软件辅助,这些额外的操做都依赖了线程额外性能去弥补,例如两个Redis实例负载之间的同步等,因此咱们是用性能换取部分一致性。负载节点越多性能消耗越多,因此两个实例作负载是咱们“适当约束”和衡量的决策。
在此我向你们推荐一个架构学习交流群。交流学习群号:575745314 里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多
4 、场景
4.1 场景 1
在RedisManager类中有两个最基本的API,那就是c_get和c_set,其中c表明cluster。这两个API的基本实现以下:
从这两个基本操做能够看出,咱们利用了遍历hash节点的全部负载实例来实现高可用性,并经过“同步写”来知足Redis数据“弱一致性”问题。而这个“同步写”就是额外的性能消耗,是依赖于双写过程当中只成功写入一个实例的几率。由于Redis的稳定性,这种几率不高,因此额外性能消耗的几率也不高。以上操做几乎适用全部缓存类集群支持。这类缓存数据的强一致性更多放在数据库。数据在库表变动后,只须要把缓存数据delete便可。具体场景以下:
一、数据的变动经过“后台”维护,变动后同步DELETE缓存的相互数据。
二、业务线程请求缓存数据为空(c_get),则查询书库并把数据同步至缓存(c_set)。
三、Redis的hash节点集群安装集群内部实现负责和数据同步问题(c_get和c_set的实现原来)。
咱们大部分业务数据缓存都是基于以上流程实现,这个流程会存在一个脏数据问题,例如当UPDATE库表成功但DELETE缓存数据不成功,就会存在脏数据。为了能尽量下降脏数据的可能性,咱们会在缓存设置缓存数据一个有效期(setex),就算脏数据出现,也只会影响seconds时间段。另外在后台变动过程若是DELETE缓存失败咱们会有适当的提示语提示,好让认为发现继续进一步处理(例如从新变动)。
4.1 场景 2
除了场景1的缓存用途外,还存在持久化场景。就是基于Redis作数据持久化集群,即全部操做都是基于Redis集群的。那么在数据一致性问题上就须要下点功夫了(c_set_sync),伪代码以下:
c_set_sync(key,value){
if(c_del_sync(key)){
c_set(key,value);
}
}
c_del_sync(key){
if(del(r1,key)&&del(r2,key)){
return true;
}
return false;
}
复制代码
经过以上伪代码能够看出,c_set_sync方法是先强制所有删除数据后再c_set,确保数据一致性。但这会出现一个数据丢失问题,就是c_del_sync后但set失败,那数据就会丢失,由于咱们的数据几乎都是从后台操做的,若是出现这种数据丢失,简单的咱们能够从新配置,复杂的咱们能够经过日志恢复。
5 伸缩
以上两个场景更多围绕C(一致性)和A(可用性)的特性进行讨论,那么接下来再介绍一下咱们“集群化”的P(分区容错性)特性。其实从我思考触发就能够看得出咱们对P的权重是轻于C和A的。为何这么说?由于咱们系统架构是服务化架构(那时我还没接触到“微服务”概念),也就是从问题角度把大问题(业务统称)拆分各类小问题(服务化)逐一独立解决。各类小问题的业务复杂度咱们紧可能控制到必定的轻量级程度(若是要量化解释的话那就是服务保持在10到20个API的规模,甚至小于10)。并且每一个服务的承载量增加率预估值也在可控范围,因此到目前为止,极少有对Redis集群进行伸缩的需求。但少数的伸缩仍是存在,但频率不高。对于一个完整的集群化方案,伸缩功能必须得有,只不过可能须要像以上两种场景那样针对不一样业务使用场景在“规范化(原架构基础上)”下定制出各类场景API。
5.1 场景 1
由于缓存场景相对简单,扩展或收缩hash节点后,若是在缓存中找不到数据,则会访问数据库从新Load数据到新的hash节点。伸缩期完成初期可能会对数据库带来必定的压力,这种压力的大小来源于设计hash数据的变化大小,这种数据变化大小取决于从新这个hash实现规则的变化的大小。因此,可根据具体状况来重写hash规则。
还有一个就是数据一致性问题(C和P),如何在动态伸缩过程当中,确保缓存数据一致性。为了解决这个问题,咱们在动态扩展过程当中,中止各类更新接口操做。由于咱们的数据变动都是经过管理员的,因此这个代价能够忽略不计。
5.2 场景 2
此场景2对应是却倒是4.2场景,若是用Redis集群作持久化工具,若是确保分区容错性(P)和数据一致性(C)。对于数据一致性问题,咱们一样选择了场景1的办法,在伸缩期间中止全部更新操做,只保留读。这就避免了数据一致性问题。对于分区容错性问题,那就是若是确保从新hash后,数据能流向各类的新hash节点呢。为了继续保持这种“简化性”框架,咱们继续选择了牺牲必定的性能来知足分区容错性问题,具体实现以下所示:
一、先尝试从New_Hash节点读取;
二、若不存在则继续寻找Old_Hash节点;
三、若仍是不存在,怎放回空;
四、若存在则c_set到New_Hash节点。
经过以上流程分析看到,咱们牺牲了部分线程性能(第一次访问的变动数据的线程)的性能,可能会多2到3此的redis请求(每一个Redis请求约5至10毫秒)。当伸缩完成后,从新放开数据更新API(咱们服务化框架全部业务API均可以经过控制台控制并发并设置相关提示语,无需重启应用)。除了读取须要遍历新旧hash节点外,为了确保数据一致性问题,咱们c_del_sync内置了一个判断是否存在旧hash节点。伪代码以下:
c_del_sync(key){
if(hash_old){
if(del(nr1,key)&&del(nr2,key) &&del(or1,key) &&del(or2,key)) {
true;
}
}else{
if(del(r1,key)&&del(r2,key)){
true;
}
}
return false;
}
复制代码
这种场景比较适用于set操做很少的场景,由于多set操做会多消耗约一倍的性能,若是以为资源充足,这固然能够考虑。
在此我向你们推荐一个架构学习交流群。交流学习群号:575745314 里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多
6 、总结
以上集群化方案已经运行约两年,系统日访问量约亿,70%归功于Redis的支撑。以上集群方案是基于咱们的行业业务场景和自身框架量身定作的,我更多地是想分享解决这个问题的思路和过程。世界上没有“绝对通用”,只有“相对通用”,通用范围越广,臃肿程度越高,可能带来的成本就会越大。咱们更多的是面对若是“很好地”解决问题,这个“很好地”隐藏着各类各样的考虑因素。抉择就是一个为了能达到最佳效果而去衡量、选择、放弃的过程。