文档章节

codis的Rebalance算法

sweeeeeet
 sweeeeeet
发布于 2016/06/06 13:41
字数 1357
阅读 151
收藏 0

Codis 由四部分组成:

  • Codis Proxy (codis-proxy)
  • Codis Dashboard (codis-config)
  • Codis Redis (codis-server)
  • ZooKeeper/Etcd

 

codis-proxy 是客户端连接的 Redis 代理服务, codis-proxy 本身实现了 Redis 协议, 表现得和一个原生的 Redis 没什么区别 (就像 Twemproxy), 对于一个业务来说, 可以部署多个 codis-proxy, codis-proxy 本身是无状态的.

 

codis-config 是 Codis 的管理工具, 支持包括, 添加/删除 Redis 节点, 添加/删除 Proxy 节点, 发起数据迁移等操作. codis-config 本身还自带了一个 http server, 会启动一个 dashboard, 用户可以直接在浏览器上观察 Codis 集群的运行状态.

codis-server 是 Codis 项目维护的一个 Redis 分支, 基于 2.8.21 开发, 加入了 slot 的支持和原子的数据迁移指令. Codis 上层的 codis-proxy 和 codis-config 只能和这个版本的 Redis 交互才能正常运行.

Codis 依赖 ZooKeeper 来存放数据路由表和 codis-proxy 节点的元信息, codis-config 发起的命令都会通过 ZooKeeper 同步到各个存活的 codis-proxy.

 

上面这张图的意思是,client可以直接访问codis-proxy,也可以通过访问HAProxy。

因为codis的proxy是无状态的,可以比较容易的搭多个proxy来实现高可用性并横向扩容。 对Java用户来说,可以使用经过我们修改过的Jedis,Jodis ,来实现proxy层的HA。它会通过监控zk上的注册信息来实时获得当前可用的proxy列表,既可以保证高可用性,也可以通过轮流请求所有的proxy实现负载均衡。如果需要异步请求,可以使用我们基于Netty开发的Nedis

 

codis的Rebalance

codis的存储层可以实现扩容,下面说明一下codis增加group的策略:

codis是用golang编写,使用了c的一些库,Rebalance的源码如下,仔细看过源码后我们能清楚理解它的策略

func Rebalance() error {

    targetQuota, err := getQuotaMap(safeZkConn)

    if err != nil {

        return errors.Trace(err)

    }

    livingNodes, err := getLivingNodeInfos(safeZkConn)

    if err != nil {

        return errors.Trace(err)

    }

    log.Infof("start rebalance")

    for _, node := range livingNodes {

        for len(node.CurSlots) > targetQuota[node.GroupId] {

            for _, dest := range livingNodes {

                if dest.GroupId != node.GroupId && len(dest.CurSlots) < targetQuota[dest.GroupId] && len(node.CurSlots) > targetQuota[node.GroupId] {

                    slot := node.CurSlots[len(node.CurSlots)-1]

                    // create a migration task

                    info := &MigrateTaskInfo{

                        Delay:      0,

                        SlotId:     slot,

                        NewGroupId: dest.GroupId,

                        Status:     MIGRATE_TASK_PENDING,

                        CreateAt:   strconv.FormatInt(time.Now().Unix(), 10),

                    }

                    globalMigrateManager.PostTask(info)



                    node.CurSlots = node.CurSlots[0 : len(node.CurSlots)-1]

                    dest.CurSlots = append(dest.CurSlots, slot)

                }

            }

        }

    }

    log.Infof("rebalance tasks submit finish")

    return nil

}

首先getQuotaMap(safeZkConn)获取并计算出存活的group的slot配额,数据结构为map<group, slot_num>。如果新增了group,那么新的配额会在这个函数中计算完成。getLivingNodeInfos(safeZkConn),这个函数获取存活的group列表。其实getQuotaMap也调用了这个方法,从ZK上获取元数据。下面看一下getQuotaMap(safeZkConn):

func getQuotaMap(zkConn zkhelper.Conn) (map[int]int, error) {

    nodes, err := getLivingNodeInfos(zkConn)

    if err != nil {

        return nil, errors.Trace(err)

    }

    ret := make(map[int]int)

    var totalMem int64

    totalQuota := 0

    for _, node := range nodes {

        totalMem += node.MaxMemory

    }

    for _, node := range nodes {

        quota := int(models.DEFAULT_SLOT_NUM * node.MaxMemory * 1.0 / totalMem)

        ret[node.GroupId] = quota

        totalQuota += quota

    }

    // round up

    if totalQuota < models.DEFAULT_SLOT_NUM {

        for k, _ := range ret {

            ret[k] += models.DEFAULT_SLOT_NUM - totalQuota

            break

        }

    }

    return ret, nil

}

先遍历nodes,计算最大内存总和,再遍历nodes,根据该节点占总内存大小的比例,分配slot数量,存入返回结果中。如果最后totalQuota数量小于1024,把剩余的solt分配给第一个group。下面是getLivingNodeInfos(safeZkConn):

func getLivingNodeInfos(zkConn zkhelper.Conn) ([]*NodeInfo, error) {

    groups, err := models.ServerGroups(zkConn, globalEnv.ProductName())

    if err != nil {

        return nil, errors.Trace(err)

    }

    slots, err := models.Slots(zkConn, globalEnv.ProductName())

    slotMap := make(map[int][]int)

    for _, slot := range slots {

        if slot.State.Status == models.SLOT_STATUS_ONLINE {

            slotMap[slot.GroupId] = append(slotMap[slot.GroupId], slot.Id)

        }

    }

    var ret []*NodeInfo

    for _, g := range groups {

        master, err := g.Master(zkConn)

        if err != nil {

            return nil, errors.Trace(err)

        }

        if master == nil {

            return nil, errors.Errorf("group %d has no master", g.Id)

        }

        out, err := utils.GetRedisConfig(master.Addr, globalEnv.Password(), "maxmemory")

        if err != nil {

            return nil, errors.Trace(err)

        }

        maxMem, err := strconv.ParseInt(out, 10, 64)

        if err != nil {

            return nil, errors.Trace(err)

        }

        if maxMem <= 0 {

            return nil, errors.Errorf("redis %s should set maxmemory", master.Addr)

        }

        node := &NodeInfo{

            GroupId:   g.Id,

            CurSlots:  slotMap[g.Id],

            MaxMemory: maxMem,

        }

        ret = append(ret, node)

    }

    cnt := 0

    for _, info := range ret {

        cnt += len(info.CurSlots)

    }

    if cnt != models.DEFAULT_SLOT_NUM {

        return nil, errors.Errorf("not all slots are online")

    }

    return ret, nil

}

这段代码中先获取group和slot信息列表,遍历slots,要求slot是在线状态,如果有任何一个不在线,在最后判断slot数量的时候都会报错。遍历groups,要求任何一个group都有master、配置了maxMem。

继续分析Rebalance。三层遍历,遍历存活节点,如果当前配额大于rebalance之后的配额的话,第三个for循环中标红的判断的结果一定是新增的group。下面的逻辑就是将node中多出来的slot(当前旧的group)迁移到dest(当前新的group)。起一个task,完成迁移。

综上所述,codis的Rebalance算法简单、清晰,符合常规思路。另外go语言的可读性也很强,之前没有涉及过go,但适应一下可以看懂。

如果group中master宕机,需要注意,codis将其中一个slave升级为master时,该组内其他slave实例是不会自动改变状态的,这些slave仍将试图从旧的master上同步数据,因而会导致组内新的master和其他slave之间的数据不一致。因为redis的slave of命令切换master时会丢弃slave上的全部数据,从新master完整同步,会消耗master资源。因此建议在知情的情况下手动操作。使用 codis-config server add <group_id> <redis_addr> slave 命令刷新这些节点的状态即可。codis-ha不会自动刷新其他slave的状态。

 

 

© 著作权归作者所有

sweeeeeet
粉丝 2
博文 19
码字总数 24105
作品 0
上海
后端工程师
私信 提问
Redis实践系列丨Codis数据迁移原理与优化

Codis介绍 Codis 是一种Redis集群的实现方案,与Redis社区的Redis cluster类似,基于slot的分片机制构建一个更大的Redis节点集群,对于连接到codis的Redis客户端来说, 除了部分不支持的命令外...

中间件小哥
2018/11/16
151
0
Elasticell-缘起

故事 小白是一个创业公司的技术负责人,创业初期,用户量很少,小白非常愉快的使用Redis来做缓存,系统上线,运行良好,系统响应快速。 过了两天潇洒日子,小白在睡梦中被一震手机短信提示音...

IT民工-南京
2018/01/17
83
0
Redis集群技术及Codis实践

“高效运维最佳实践”是InfoQ在2015年推出的精品专栏,由触控科技运维总监萧田国撰写,InfoQ总编辑崔康策划。 前言 如开篇文章所言,高效运维包括管理的专业化和技术的专业化。前两篇我们主要...

mac_zhao
2015/06/16
384
0
Codis集群扩容方法

在dashboard添加实例方法如下 1、添加group 对应按钮[NewServer Group] 2、均衡数据 对应按钮[AutoRebalance] 如下图:

lnucel
2015/12/29
233
0
这可能是最全的 Redis 集群方案介绍了

这可能是最全的 Redis 集群方案介绍了 原创 2016-06-01 曾健生 运维帮 由于Redis出众的性能,其在众多的移动互联网企业中得到广泛的应用。Redis在3.0版本前只支持单实例模式,虽然现在的服务...

fdhay
2016/06/02
260
0

没有更多内容

加载失败,请刷新页面

加载更多

Java 文件类操作API与IO编程基础知识

阅读目录: https://www.w3cschool.cn/java/java-io-file.html Java 文件 Java 文件 Java 文件操作 Java 输入流 Java 输入流 Java 文件输入流 Java 缓冲输入流 Java 推回输入流 Java 数据输入...

boonya
23分钟前
2
0
SDKMAN推荐一个好

是在大多数基于Unix的系统上管理多个软件开发工具包的并行版本的工具。它提供了一个方便的命令行界面(CLI)和API来安装,切换,删除和列出sdk相关信息。以下是一些特性: By Developers, fo...

hotsmile
47分钟前
8
0
什么是 HDFS

是什么? HDFS 是基于 Java 的分布式文件系统,允许您在 Hadoop 集群中的多个节点上存储大量数据。 起源: 单机容量往往无法存储大量数据,需要跨机器存储。统一管理分布在集群上的文件系统称...

Garphy
50分钟前
5
0
一起来学Java8(四)——复合Lambda

在一起来学Java8(二)——Lambda表达式中我们学习了Lambda表达式的基本用法,现在来了解下复合Lambda。 Lambda表达式的的书写离不开函数式接口,复合Lambda的意思是在使用Lambda表达式实现函...

猿敲月下码
今天
10
0
debian10使用putty配置交换机console口

前言:Linux的推广普及,需要配合解决实际应用方能有成效! 最近强迫自己用linux进行实际工作,过程很痛苦,还好通过网络一一解决,感谢各位无私网友博客的帮助! 系统:debian10 桌面:xfc...

W_Lu
今天
12
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部