• Redis 高可用架构设计(转载)


     

    转载自:https://mp.weixin.qq.com/s?__biz=MzA3NDcyMTQyNQ==&mid=2649263292&idx=1&sn=b1703906840e177f854f543ca68e0f00&chksm=87675d42b010d454fed8ddcaa27a2f0a925e6d1db90596f25bef4d10dbe481e60af8d0390907&scene=0&xtrack=1&key=64880cbd3f974b3bfca947a6bbd0dc7de4371c1404947282463667d1fb05469c07e0e306f26cdd5919b8cdf7104c035d81f210dbd76cd38eaa5a7706365c7e6dfaf00d85f90df438dd0f27ce4c15fa34&ascene=1&uin=MTIxNjI3MjUwMg%3D%3D&devicetype=Windows+10&version=62070158&lang=zh_CN&pass_ticket=SfUM%2F5LwQwAMP7uwpn87NnU4UNBVUjA718uL3PfcgjaDszq3%2FyH5JCrCFuqbdfLG

    前言

    本文主要介绍了 Qunar Redis 高可用架构设计原理、安全机制及集群自动化运维方面的内容。

    Qunar Redis 高可用架构设计原理

    概述

    Qunar Redis 集群是一个分布式的高可用架构,整个架构主要由以下几个重要部分组成:

    • Redis Server 节点:每个节点有一主一从两个实例,多个节点组成一份完整的集群数据,其中每个节点只有主库对外提供服务,从库仅仅用于节点高可用、数据持久化及定时备份。

    • Zookeeper 集群:由五个 zk 节点组成,Redis 集群配置变更后,通知客户端进行重连。

    • Redis Sentinel 集群:由五个 Sentinel 节点组成,用于 Reids Server 节点的高可用,主从切换、故障转移、配置更新等。

    • 配置中心集群:由五个 MySQL 节点组成的 PXC 集群,用于存储 Redis 集群的分片信息,即每个节点的 Master 实例信息及分配key的一致性 hash 值范围。

    • 应用程序客户端:监听 zk 变化,在配置中心获取 Redis 实例信息进行连接。

    架构原理图

    客户端实现

    1.当客户端根据 Redis 集群的 namespace 建立连接时,会先从 zk 中查找/config_addr 节点, 该节点下存放的是配置中心集群的实例信息,从中随机选择一个数据库实例进行连接。

    2.在配置中心的特定库表中,根据 Redis 的 namespace 查询集群的节点的连接配置,然后建立 Redis 连接。

    3.客户端建立 Redis 连接后,会启动了两个线程:

    • 一个用于监听 zk 的地址的变化。每个 Redis 集群在 zk 中都会有一个/redis/namespace 的节点 ,如果集群配置发生变化,哨兵会通知 zk 更新此节点的值,客户端感知到 zk 配置变化, 将会去配置中心获取新的连接配置,重新建立连接。

    • 一个用于轮询配置中心的连接配置。为了防止 zk 通知失败,客户端会通过这个线程,每隔 10s 去轮询配置中心的配置信息,如果发现配置中心的配置和本地缓存的不一样,就会使用配置中心的配置建立新的连接。

    客户端与其他组件的关系示意图如下:

    数据分片方法

    开发人员提交 Redis 集群申请工单信息后,DBA 会依据工单中的内存大小、QPS 大小等几项主要的数据,规划集群分片节点数量为 N,所有节点平均分配 0~4294967295 范围内的值,即共有 2 的 32 次方个 key 的值,某一个 key 使用 murmurhash2 算法计算哈希值后,只会落在集群的一个节点上。

    分片节点示意图如下:

    分片节点信息在配置中心的存储信息如下:

    架构特点

    Quanr Redis 高可用架构具有以下特点:

    • 实现自己的 Redis 客户端,客户端不再访问 Sentinel, Sentinel 只负责高可用。

    • 通过 ZK 集群和配置中心来实现配置的集中管理。

    • 将端口视作一种资源,即集群的一个节点的主从实例使用一个端口,下线的集群端口可复用。

    • 弱化了哨兵机器的地位, 降低了哨兵和集群之间直接的耦合度。

    • 减少了哨兵机器的使用量, 目前只使用了 5 台哨兵机器组成集群。

    • 客户端使用 namespace 访问集群, 将端口和 namespace 对应,namespace 和业务部门对应,方便 DBA 管理和运维,对应用透明。

    架构局限性

    Quanr Redis 高可用架构具有以下局限性:

    • 支持的客户端比较少。目前客户端仅支持 Java 和 Python。

    • 不支持快速水平扩容。当集群内存不足时可以快速扩大各个节点实例的内存大小,以此来增加整个集群大小,但单个实例的内存大小也有一定的限度,不能无限扩展。当需要增加集群节点个数时,由于各个节点的一致性哈希范围发生了变化,所以的 key 需要重新分配,对于比较大的集群,过程比较繁琐和耗时。

    • 整个架构依赖的组件比较多。虽然架构中的 zookeeper、配置中心、Sentinel 等都是多节点的高可用集群,但依赖的组件越多,发生故障的可能性也越大,运维难度和工作量也会随着增加,无疑对运维人员有更高的要求。

    • 部分 Redis 原生功能无法使用。由于客户端的限制,部分 Redis 原生功能无法使用,如不支持事务、Lua 脚本等。

    Qunar Redis 安全机制

    Redis 被设计成仅供可信环境下的可信用户才可以访问,并没有最大化的去优化安全方面,而是尽量可能的去优化高性能和易用性,因此 Redis 没有类似关系型数据库那样严格的权限控制,因此将 Redis 实例直接暴露在网络上或者让不可信的用户直接访问 Redis 的 TCP 端口,是非常危险的行为。

    为了提高 Redis 使用的安全性,去哪儿网使用的 Redis Server 是在官方 Redis4.0.14 版本上进行了部分的源代码改造,增加了一个白名单参数 trustedip,屏蔽了部分高危指令,除了 trustedip 中配置的 IP 之外,任何其他客户端连接都无法执行这些高危指令,同时为了提高 Redis 的性能,对主从实例进行了差异性配置。

    客户端使用 clientcipher 和 IP 白名单机制

    Qunar Redis 客户端并没有直接通过 TCP 方式去连接 Redis 实例,而是首先要通过集群 namespace 和该集群唯一的 clientcipher 的验证,然后从配置中心获取真正的连接信息后,才可以连接 Redis 实例。同时白名单机制对客户端请求中的高危指令进行过滤,避免对线上 Redis 执行不合理的操作,进一步加强了其安全性。

    • 客户端使用 namespace 和 clientcipher 方式访问集群。

    • 不同 namespace 对应的 clientcipher 不同,在创建集群时通过随机生成的密码再次加密生成 clientcipher。

    • 即使知道密码,也无法使用屏蔽的危险命令,除非 IP 地址在白名单中。

    • 本地登陆和 IP 白名单登陆,命令不受限制,方便 DBA 管理和兼容各种监控统计脚本。

    • IP 白名单可以动态配置,最大支持 32 个 IP 白名单。

    IP 白名单功能涉及修改代码的地方:

    • 在 config.c 文件的 configGetCommand 方法中增加参数 trustedip。

    1. void configGetCommand(client *c) {

    2. robj *o = c->argv[2];

    3. void *replylen = addDeferredMultiBulkLength(c);

    4. char *pattern = o->ptr;

    5. char buf[128];

    6. int matches = 0;

    7. serverAssertWithInfo(c,o,sdsEncodedObject(o));

    8. ...

    9. /* 增加trustedip参数 */

    10. if (stringmatch(pattern,"trustedip",0)) {

    11. sds buf = sdsempty();

    12. int j;

    13. int numips;

    14. numips = server.trusted_ips.numips;

    15. for (j = 0; j < numips; j++) {

    16. buf = sdscat(buf, server.trusted_ips.ips[j]);

    17. if (j != numips - 1)

    18. buf = sdscatlen(buf," ",1);

    19. }

    20. addReplyBulkCString(c,"trustedip");

    21. addReplyBulkCString(c,buf);

    22. sdsfree(buf);

    23. matches++;

    24. }

    25. setDeferredMultiBulkLength(c,replylen,matches*2);

    26. }

    • 在 server.h 文件中增加 trustedIPArray 结构体定义。

    1. typedef struct trustedIPArray {

    2. int numips;

    3. sds* ips;

    4. } trustedIPArray;

    • 在 networking.c 文件中增加 isTrustedIP 方法。

    1. /* 判断客户端IP是否在IP白名单中 */

    2. int isTrustedIP(int fd) {

    3. char ip[128];

    4. int i, port;

    5. anetPeerToString(fd,ip,128,&port);

    6. if (strcmp(ip, "127.0.0.1") == 0) {

    7. return 1;

    8. }

    9. for (i = 0; i < server.trusted_ips.numips; i++) {

    10. if (strcmp(ip, server.trusted_ips.ips[i]) == 0) {

    11. return 1;

    12. }

    13. }

    14. return 0;

    15. }

    • 在 networking.c 文件的 createClient 方法中增加 issuperclient 的设置。

    1. client *createClient(int fd) {

    2. client *c = zmalloc(sizeof(client));

    3. /* passing -1 as fd it is possible to create a non connected client.

    4. * This is useful since all the commands needs to be executed

    5. * in the context of a client. When commands are executed in other

    6. * contexts (for instance a Lua script) we need a non connected client. */

    7. if (fd != -1) {

    8. anetNonBlock(NULL,fd);

    9. anetEnableTcpNoDelay(NULL,fd);

    10. if (server.tcpkeepalive)

    11. anetKeepAlive(NULL,fd,server.tcpkeepalive);

    12. if (aeCreateFileEvent(server.el,fd,AE_READABLE,

    13. readQueryFromClient, c) == AE_ERR)

    14. {

    15. close(fd);

    16. zfree(c);

    17. return NULL;

    18. }

    19. }

    20. ...

    21. /* 设置is_super_client */

    22. if (isTrustedIP(fd)) {

    23. c->is_super_client = 1;

    24. } else {

    25. c->is_super_client = 0;

    26. }

    27. ...

    28. return c;

    29. }

    • 在 server.c 文件的 processCommand 方法中增加对 issuperclient 的认证。

    1. int processCommand(client *c) {

    2. /* The QUIT command is handled separately. Normal command procs will

    3. * go through checking for replication and QUIT will cause trouble

    4. * when FORCE_REPLICATION is enabled and would be implemented in

    5. * a regular command proc. */

    6. if (!strcasecmp(c->argv[0]->ptr,"quit")) {

    7. addReply(c,shared.ok);

    8. c->flags |= CLIENT_CLOSE_AFTER_REPLY;

    9. return C_ERR;

    10. }

    11. ...

    12. /* Check if the user is authenticated */

    13. /* 增加is_super_client认证 */

    14. if (!c->is_super_client && server.requirepass && !c->authenticated && c->cmd->proc != authCommand)

    15. ...

    16. return C_OK;

    17. }

    • 在 db.c 文件中增加 checkCommandBeforeExec 方法。

    1. /* 如果是super client或者是master,返回1,否则返回0

    2. * 因为在master-slave下,master(client)需要向slave执行危险命令*/

    3. int checkCommandBeforeExec(client *c) {

    4. if (c->is_super_client || (server.masterhost && (c->flags & CLIENT_MASTER))) {

    5. return 1;

    6. }

    7. addReplyError(c,"No permission to execute this command");

    8. return 0;

    9. }

    屏蔽高危指令

    通过修改 Redis 源代码,在 Server 端屏蔽部分危险指令,规定只有通过白名单检查的客户端连接才可以执行这些指令。在执行高危指令前进行检查,如需对 save 指令进行屏蔽,可对 rdb.c 文件的 saveCommand 方法的第一行增加 checkCommandBeforeExec 检查。

    1. void saveCommand(client *c) {

    2. if (!checkCommandBeforeExec(c)) return; /* 执行指令之前进行检查,如不通过直接返回 */

    3. if (server.rdb_child_pid != -1) {

    4. addReplyError(c,"Background save already in progress");

    5. return;

    6. }

    7. rdbSaveInfo rsi, *rsiptr;

    8. rsiptr = rdbPopulateSaveInfo(&rsi);

    9. if (rdbSave(server.rdb_filename,rsiptr) == C_OK) {

    10. addReply(c,shared.ok);

    11. } else {

    12. addReply(c,shared.err);

    13. }

    14. }

    屏蔽的高危指令有:

    • 比较耗时类指令:info、keys *

    • 清空数据类指令:shutdown、flushdb、 flushall

    • 数据持久化类指令:save、bgsave、bgrewriteaof

    • 配置类指令:config get、config set、config rewrite

    • 运维管理类指令:slaveof、monitor、client list、client kill

    在 Redis 源代码涉及这些指令的地方,都需要加上 checkCommandBeforeExec 方法进行检查。

    配置优化

    针对集群各个节点的主从实例进行差异化配置,由于每个节点只有主库对外提供服务,为了最大限度的提高主库的并发能力,一些比较耗时的操作可以放到从库去执行。

    几项主要的配置如下:

    • 主库关闭 bgsave、bgrewriteaof 功能。

    • 从库开启 aof 功能,定时调度重写 aof 文件,释放服务器磁盘空间。

    • 从库定时执行 bgsave 操作,备份 rdb 文件。

    • 从库开启 slave-read-only 参数,只读。

    当 Redis 集群部署完之后,会有定时任务去检查服务器上各个 Redis 实例的角色,根据角色的不同修改相关的配置参数,同时将修改后的持久化到配置文件。

    Qunar Redis 自动化运维

    初始化系统环境

    在 Redis 服务器上部署集群之前,首先需要初始化系统环境,将这些环境配置添加到 Redis 的 rpm 打包程序的 spec 文件中,安装 Redis 软件包时会自动更改相关配置,主要的系统环境参数有以下几个:

    1. sed -i -r '/vm.overcommit_memory.*/d' /etc/sysctl.conf

    2. sed -i -r '/vm.swappiness.*/d' /etc/sysctl.conf

    3. sed -i -r '/vm.dirty_bytes.*/d' /etc/sysctl.conf

    4. echo "vm.overcommit_memory = 1" >> /etc/sysctl.conf

    5. echo "vm.swappiness = 0" >> /etc/sysctl.conf

    6. echo "vm.dirty_bytes = 33554432" >> /etc/sysctl.conf

    7. /sbin/sysctl -q -p /etc/sysctl.conf

    8. groupadd redis >/dev/null 2>&1 || true

    9. useradd -M -g redis redis -s /sbin/nologin >/dev/null 2>&1 || true

    10. sed -i -r '/redis soft nofile.*/d' /etc/security/limits.conf

    11. sed -i -r '/redis hard nofile.*/d' /etc/security/limits.conf

    12. echo "redis soft nofile 288000" >> /etc/security/limits.conf

    13. echo "redis hard nofile 288000" >> /etc/security/limits.conf

    14. sed -i -r '/redis soft nproc.*/d' /etc/security/limits.conf

    15. sed -i -r '/redis hard nproc.*/d' /etc/security/limits.conf

    16. echo "redis soft nproc unlimited" >> /etc/security/limits.conf

    17. echo "redis hard nproc unlimited" >> /etc/security/limits.conf

    18. echo never > /sys/kernel/mm/transparent_hugepage/enabled

    统一运维管理工具

    Qunar Redis 集群的统一管理套件,封装了系统环境初始化、实例安装、实例启动、实例关闭、监控报警、定时任务等脚本,实现了监控、统计、注册等自动化操作。

    1. /etc/cron.d/appendonly_switch

    2. /etc/cron.d/auto_upgrade_toolkit

    3. /etc/cron.d/bgrewriteaof

    4. /etc/cron.d/check_maxmemory

    5. /etc/cron.d/dump_rdb_keys

    6. /etc/cron.d/rdb_backup

    7. /etc/profile.d/q_redis_path.sh

    8. /xxx/collectd/etc/collectd.d/collect_redis.conf

    9. /xxx/collectd/lib/collectd/collect_redis.py

    10. /xxx/collectd/share/collectd/types_redis.db

    11. /xxx/nrpe/libexec/q-check-redis-cpu-usage

    12. /xxx/nrpe/libexec/q-check-redis-latency

    13. /xxx/nrpe/libexec/q-check-redis-memory-usage

    14. /xxx/nrpe/libexec/q-check-zookeeper-ruok

    15. /xxx/redis/tools/cron_appendonly_switch.sh

    16. /xxx/redis/tools/cron_bgrewrite_aof.sh

    17. /xxx/redis/tools/cron_check_maxmemory.sh

    18. /xxx/redis/tools/cron_dump_rdb_keys.sh

    19. /xxx/redis/tools/cron_rdb_backup.sh

    20. /xxx/redis/tools/dump_rdb_keys.py

    21. /xxx/redis/tools/redis-cli5

    22. /xxx/redis/tools/redis-latency

    23. /xxx/redis/tools/redis_install.sh

    24. /xxx/redis/tools/redis_start.sh

    25. /xxx/redis/tools/redis_stop.sh

    单机多实例多版本部署

    Qunar Redis 的安装工具包支持单机多实例安装,安装脚本提供选项和配置文件模板,可以自定义安装不同版本的 Redis,目前支持的 Redis Server 版本有 2.8.6、3.0.7 以及 4.0.14。

    1. /* 安装包及Redis实例目录结构 */

    2. .

    3. ├── multi

    4. │ ├── server_2800 /* Redis2.8.6软件包 */

    5. │ │ ├── bin

    6. │ │ └── utils

    7. │ ├── server_3000 /* Redis3.0.7软件包 */

    8. │ │ ├── bin

    9. │ │ └── utils

    10. │ └── server_4000 /* Redis4.0.14软件包 */

    11. │ ├── bin

    12. │ └── utils

    13. ├── redis10088 /* 端口为10088的Redis实例数据目录,用于存放该实例的配置文件、日志、AOF文件及RDB文件 */

    14. │ ├── bin

    15. │ └── utils

    16. ├── redis10803 /* 端口为10803的Redis实例数据目录,用于存放该实例的配置文件、日志、AOF文件及RDB文件 */

    17. │ ├── bin

    18. │ └── utils

    19. ├── redis11459 /* 端口为11459的Redis实例数据目录,用于存放该实例的配置文件、日志、AOF文件及RDB文件 */

    20. │ ├── bin

    21. │ └── utils

    22. /* Redis实例安装程序用法 */

    23. Usage: redis_install.sh -P <port> -v [2.8|3.0|4.0] -p <password> -m <size>

    24. 必选参数:

    25. -P redis端口

    26. -p redis密码

    27. -v 将要安装的redis版本,强烈推荐4.0版本

    28. -m redis实例允许的最大内存大小,单位是G

    29. 可选参数:

    30. --cluster 集群模式,version>=3.0

    31. --testenv 测试环境

    32. example:

    33. sudo redis_install.sh -P 6379 -v 4.0 -m 20 -p 1qaz2wsx

    使用 git 管理 Redis 哨兵

    使用 git 集中管理所有的哨兵配置,一个地方发生变更,哨兵集群的所有服务器同时拉取进行同步更新。同时详细的 commit log,方便跟踪配置文件修改历史。Qunar Redis 哨兵具有以下特点:

    • 一套哨兵只管理一个节点,即只对端口号相同的一组 Redis(一主一从或一主多从)实例进行监控和故障转移。

    • 哨兵只负责节点的高可用,客户端不需要通过哨兵来访问 Redis 实例。

    • 哨兵配置文件使用 git 统一管理,配置文件以[节点端口号+20000]集群 namespace.conf 方式统一命名,例如 30708redisdelaytest.conf,通过集群任意一个节点的端口号或者 namespace 可以获取集群全部节点的信息。

    • 当哨兵监控的节点发生切换时,会更新配置中心对应节点的主库配置和 zookeeper 中对应节点的 dataVersion,客户端检测到 zookeeper 的变化会去配置中心获取节点最新的信息进行重连,同时哨兵会将切换信息发送至 DBA 和运维事件平台。

    • 哨兵服务器的 IP 默认都添加到 Redis 实例的白名单中,即通过哨兵服务器可以访问任何一个 Redis 实例进行所有的操作,所以哨兵服务器的权限必须严格控制,只有 DBA 才有权限登陆。

    运维操作平台化

    以上几项规范统一的标准化流程,为 Qunar Redis 的整个运维平台化提供了有力的支撑,目前 Qunar Redis 的 90% 以上的运维操作都实现了平台自动化,包括工单申请及审核、集群部署、实例迁移、集群垂直伸缩、不同维度(服务器、集群、实例)的信息查看等,下面主要介绍下 Qunar Redis 集群部署和实例迁移的实现过程。

    集群部署

    Qunar Redis 集群部署时主要有以下步骤:

    • 开发人员通过平台提交集群申请工单发起流程,TL 审核完成后流程扭转到 DBA。

    • DBA 根据申请工单的信息规划集群规模,如节点个数、内存大小、部署机房、Redis 版本等。

    • 根据集群规划在 Redis 集群部署页面填写部署信息。

    • 提交部署信息后平台会自动筛选资源空闲的服务器进行集群部署。

    • 集群部署完成后会在在 Qtalk 上通知 DBA,集群的 clientcipher 会通过邮件方式通知开发人员,同时会将集群部署情况推送到公司运维事件平台,保留操作记录。

    实例迁移

    运维过程中实例迁移主要分为两大类:

    • 部分实例迁移。当某台服务器的可用资源不足时,将这台机器上的部分实例迁移到其他资源比较空闲的服务器上。在页面输入实例的源主机和目前主机,提交后会自动生成迁移任务。

    • 整机实例迁移。主要是替换过保服务器或者服务器需要停机维护时,将该机器上的所有实例自动迁移到其他资源比较空间的服务器上。在页面输入需要迁移的主机名,提交后会自动生成迁移任务。

    迁移任务开始后,整个迁移过程无须人工介入,会自动更新执行进度并输出日志。

    转载自:https://mp.weixin.qq.com/s?__biz=MzA3NDcyMTQyNQ==&mid=2649263292&idx=1&sn=b1703906840e177f854f543ca68e0f00&chksm=87675d42b010d454fed8ddcaa27a2f0a925e6d1db90596f25bef4d10dbe481e60af8d0390907&scene=0&xtrack=1&key=64880cbd3f974b3bfca947a6bbd0dc7de4371c1404947282463667d1fb05469c07e0e306f26cdd5919b8cdf7104c035d81f210dbd76cd38eaa5a7706365c7e6dfaf00d85f90df438dd0f27ce4c15fa34&ascene=1&uin=MTIxNjI3MjUwMg%3D%3D&devicetype=Windows+10&version=62070158&lang=zh_CN&pass_ticket=SfUM%2F5LwQwAMP7uwpn87NnU4UNBVUjA718uL3PfcgjaDszq3%2FyH5JCrCFuqbdfLG

  • 相关阅读:
    内存中的堆和栈
    数据库——关系型数据库
    如何创建一个进程,如何进程调用进程
    浅谈C++之冒泡排序、希尔排序、快速排序、插入排序、堆排序、基数排序性能对比分析(好戏在后面,有图有真相)
    Moon.Orm 5.0(MQL版)使用指南
    初学 快速幂 的理解
    2016 杭电
    2015 偶数求和 AC 杭电
    2014 青年歌手大赛 AC
    C语言初学 数组 打印菱形
  • 原文地址:https://www.cnblogs.com/xibuhaohao/p/11949747.html
Copyright © 2020-2023  润新知