业务运营活动中排行榜的使用很广泛,因此在三年前组内已经将排行榜服务组件化。整个服务是基于Redis的zset数据结构实现的。
限于当时Redis的发展,这套服务在高可用性方面有所欠缺。近年来,公司内外团队在实现高可用Redis方面做了很多工作,也有很多部门提供现成的接入方案。但由于我们的Redis是定制过的(修改过源码),一方面是现有方案不支持接入,开源项目往往也需要修改源码进行适配,即便如此也不完全如意;另一方面我们自己也希望在升级迭代上能灵活掌控,所以权衡后还是决定自行部署和运维。
在此之前,排行榜服务部署情况如下图所示。此时,无状态的接入层通过内部协议对外提供服务,各业务无需关注Redis的连接和调用方式。但是当redis master停止服务时,需要人工将slave提升为master,并发布配置将接入层的指向切换到slave。此方案虽然能在大多数情况下做到数据安全,但是在出现故障后人工介入之前,服务处于不可用状态。
早期的架构
关于Redis的高可用部署方案,常见的有twemproxy、codis等,这些第三方方案往往需要引入额外组件,增加了运维成本。Redis本身提供了哨兵做HA,官方文档就哨兵的不同部署方案做了详细介绍。本着低成本的原则,本文亦采用哨兵作高可用保障。改进的排行榜服务架构如下图所示。
高可用架构
整个服务由以下部分组成:
Redis存活监控与master自动切换。由哨兵负责。当实例进程终止或不可达时,哨兵会检测到该实例的sdown
和odown
状态(主观下线与客观下线),并在客观下线后自动切换主实例。通过配置哨兵的notification-script
脚本,在状态变化时哨兵可以调用运维接口发送短信和邮件通知。而配置reconfig-script
,能在新的master切换成功后触发通知。
同时,接入层需要根据哨兵sentinel slaves
命令返回的数据自动发现slave,并根据状态识别slave是否可用。几种情况下的master和slave可用性如下表所示。
情形 | master状态 | slave状态 |
---|---|---|
master与slave正常 | 正常 | 正常。flags为slave;master-link-status为ok |
master不可达 | 不可读写,等待恢复或切换 | 正常。flags为slave;master-link-status为ok |
master切换后slave同步中 | 新master正常。 | 暂不可用。与新master成功建立连接前flags包含disconnected;与新master连接后完成同步前master-link-status不为ok |
新slave上线同步中 | 正常 | 暂不可用。与新master成功建立连接前flags包含disconnected;与新master连接后完成同步前master-link-status不为ok |
slave不可达 | 正常 | 不可读。flags包含sdown或odown |
Redis指标监控。在Redis实例机器上部署监控脚本,通过crontab定时采样,数据上报到监控平台,方便查看实时和历史数据。监控的指标项可包含CPU使用率、内存使用率、key数量等,根据业务需要确定。
哨兵间相互监控。同样由哨兵负责。但需要注意的是,当某个哨兵不可达时,其他哨兵只会触发sdown
状态变化,并发送通知告警。
功能可扩展性。除了现有的黑名单、周期榜的能力,接入层还可以扩展丰富的业务功能,对业务屏蔽复杂的redis调用逻辑。比如,借助LUA实现一个定时消息队列(参考此文)、接口流控等。
标准组件兼容。接入层同时支持切换到其他符合Redis协议的组件,比如360 pika等。对于上层业务来说,不管是调用标准redis实例,还是其他组件,都没有差别。
业务的线上Redis使用相同的配置,统一使用内部服务包发布系统管理Redis的安装包和配置,以后台server方式打包和部署。好处是:
业务同样使用内部包发布系统管理哨兵安装包。但在配置管理方面与redis包有些不同,因为哨兵的配置文件,同时也是哨兵的状态存储,故每个哨兵的线上配置是有差异的。我们将哨兵配置项区分为以下几类:
分类 | 内容 | 特征 |
---|---|---|
每实例独有 | 哨兵ID | 配置变更后要保持不变,否则其他哨兵会认为有新哨兵加入且原哨兵不可用,将影响到故障恢复时多数派选举leader过程。哨兵之所以不清除不可用的哨兵状态,是为了防止当网络故障导致脑裂时错误的选举局部leader。 |
公共静态配置 | 端口、工作目录、日志文件等 | 每个实例一样,且日常运维中无需变更。 |
公共动态配置 | 监控的Redis实例及纪元信息。 | 这部分配置随着监控实例的增删会变化。值得注意的是,如果某个哨兵的纪元信息被重置,其可以从别的哨兵处恢复。 |
故可以采用如下策略管理哨兵配置:
sentinel-base.conf
文件中,随哨兵安装包发布。sentinel.conf
启动哨兵,首次部署启动后会在配置sentinel.conf
中生成哨兵唯一idsentinel myid
,利用包部署服务等启动后脚本将其备份到myid.conf
中。脚本如下:cd $install_path/conf grep -E '^sentinel myid' sentinel.conf > myid.confsentinel.conf
,在配置文件开头用include方式包含sentinel-base.conf
和myid.conf
,且实例监控配置不需要包括epoch信息。这样,就能在每次下发新的配置后,保留哨兵ID和每个被监控实例的epoch信息了。_不过需要注意的是,相比起使用配置中心动态存储配置的方式,当线上故障导致master变更后,需要手动修改配置。_include "/usr/local/services/redis_sentinel_qzmall-1.0/conf/sentinel-base.conf" include "/usr/local/services/redis_sentinel_qzmall-1.0/conf/myid.conf"# 实例1
sentinel monitor my_redis 100.66.82.X 6479 2
sentinel down-after-milliseconds my_redis 10000
sentinel auth-pass my_redis mypasswd
sentinel parallel-syncs my_redis 1
sentinel notification-script my_redis /usr/local/services/redis_sentinel_qzmall-1.0/bin/notification-script.sh
sentinel client-reconfig-script my_redis /usr/local/services/redis_sentinel_qzmall-1.0/bin/reconfig-script.sh
与大多数公共组建一样,本文的Redis部署方案也由接入层、哨兵(名服务)、Redis实例(核心服务)、监控服务等部分组成。除哨兵与Redis是官方开源组件外,其他部分均选择公司已有技术和运维平台完成。方案整体既能满足日常业务需求,也能满足日常运维与监控要求,在可用性与维护成本上取得了一个平衡。