引言

我们在进行 ceph 的 osd 的增加和减少的维护的时候,会碰到迁移数据,但是我们平时会怎么去回答关于迁移数据量的问题,一般来说,都是说很多,或者说根据环境来看,有没有精确的一个说法,到底要迁移多少数据?这个我以前也有思考过这个问题,当时想是对比前后的pg的分布,然后进行计算,正好在翻一些资料的时候,看到有alram写的一篇博客,alram是Inktank的程序员,也就是sage所在的公司,程序是一个python脚本,本篇会分析下这个对比的思路,以及运行效果

计算迁移量只需要一个修改后的crushmap就可以了,这个是离线计算的,所以不会对集群有什么影响

运行效果

准备修改后的crushmap

获取当前crushmap

ceph osd getcrushmap -o crushmap

解码crushmap

crushtool -d crushmap -o crushmap.txt

修改crushmap.txt

这个根据自己需要,修改成自己想修改成的crushmap即可,可以是增加,也可以是删除

减少节点的计算

假如删除一个osd.5 我们需要迁移多少数据

将crushmap里面的osd.5的weight改成0

crushtool -c crushmap.txt -o crushmapnew

运行计算脚本

[root@lab8106 ceph]# python jisuan.py --crushmap-file crushmapnew
POOL REMAPPED OSDs BYTES REBALANCE OBJECTS REBALANCE
rbd 59 6157238296 1469
data 54 5918162968 1412
metadata 53 5825888280 1390

可以看到迁移的数据量

REMAPPED OSDs 下面就是有多少份的PG数据需要迁移,这里面计算的方式是比较前后的分布

[1,2] - > [1,2] 迁移0个


[1,2] - > [4,2] 迁移1个


[1,2] - > [4,3] 迁移2个

上面的统计的是这样的个数,所以不太好说是PG或者是OSD,可以理解为PG内数据的份数,因为单个PG可能需要迁移一份,也有可能迁移两份,或者多份

增加节点的计算

如果增加一个osd.6 我们需要迁移多少数据

直接运行脚本

[root@lab8106 ceph]# python jisuan.py --crushmap-file crushmapnew
POOL REMAPPED OSDs BYTES REBALANCE OBJECTS REBALANCE
rbd 0 0 0
data 0 0 0
metadata 0 0 0

可以看到没有输出,这个是因为计算的脚本里面有个地方报错了,ceph内部有个限制,在将crushmap import进osdmap的时候,ceph会验证osdmap里面的osd个数和crushmap里面的osd个数是不是相同

所以这个地方需要多做一步,将osd的个数设置成跟预估的一致,这个是唯一对现有集群做的修改操作,

[root@lab8106 ceph]# ceph osd setmaxosd 7
set new max_osd = 7

然后再次运行就可以了

[root@lab8106 ceph]# python jisuan.py --crushmap-file crushmapnew
POOL REMAPPED OSDs BYTES REBALANCE OBJECTS REBALANCE
rbd 31 3590324224 856
data 34 3372220416 804
metadata 41 4492099584 1071

上面就是运行的效果,下面我们对内部的逻辑进行分析

代码和代码分析

代码

#!/usr/bin/env python

import ast
import json
import os
import subprocess
import argparse
import sys FNULL = open(os.devnull, 'w') # assume the osdmap test output
# is the same lenght and order...
# if add support for PG increase
# that's gonna break
def diff_output(original, new, pools):
number_of_osd_remap = 0
osd_data_movement = 0 results = {} pg_data, pg_objects = get_pg_info() for i in range(len(original)):
orig_i = original[i]
new_i = new[i] if orig_i[0].isdigit():
pg_id = orig_i.split('\t')[0]
pool_id = pg_id[0]
pool_name = pools[pool_id]['pool_name'] if not pool_name in results:
results[pool_name] = {}
results[pool_name]['osd_remap_counter'] = 0
results[pool_name]['osd_bytes_movement'] = 0
results[pool_name]['osd_objects_movement'] = 0 original_mappings = ast.literal_eval(orig_i.split('\t')[1])
new_mappings = ast.literal_eval(new_i.split('\t')[1])
intersection = list(set(original_mappings).intersection(set(new_mappings))) osd_movement_for_this_pg = int(pools[pool_id]['pool_size']) - len(intersection)
osd_data_movement_for_this_pg = int(osd_movement_for_this_pg) * int(pg_data[pg_id])
osd_object_movement_for_this_pg = int(osd_movement_for_this_pg) * int(pg_objects[pg_id]) results[pool_name]['osd_remap_counter'] += osd_movement_for_this_pg
results[pool_name]['osd_bytes_movement'] += int(osd_data_movement_for_this_pg)
results[pool_name]['osd_objects_movement'] += int(osd_object_movement_for_this_pg) elif orig_i.startswith('#osd'):
break return results def get_pools_info(osdmap_path):
pools = {}
args = ['osdmaptool', '--print', osdmap_path]
osdmap_out = subprocess.check_output(args, stderr=FNULL).split('\n')
for line in osdmap_out:
if line.startswith('pool'):
pool_id = line.split()[1]
pool_size = line.split()[5]
pool_name = line.split()[2].replace("'","")
pools[pool_id] = {}
pools[pool_id]['pool_size'] = pool_size
pools[pool_id]['pool_name'] = pool_name
elif line.startswith('max_osd'):
break return pools def get_osd_map(osdmap_path):
args = ['sudo', 'ceph', 'osd', 'getmap', '-o', osdmap_path]
subprocess.call(args, stdout=FNULL, stderr=subprocess.STDOUT) def get_pg_info():
pg_data = {}
pg_objects = {}
args = ['sudo', 'ceph', 'pg', 'dump']
pgmap = subprocess.check_output(args, stderr=FNULL).split('\n') for line in pgmap:
if line[0].isdigit():
pg_id = line.split('\t')[0]
pg_bytes = line.split('\t')[6]
pg_obj = line.split('\t')[1]
pg_data[pg_id] = pg_bytes
pg_objects[pg_id] = pg_obj
elif line.startswith('pool'):
break return pg_data, pg_objects def osdmaptool_test_map_pgs_dump(original_osdmap_path, crushmap):
new_osdmap_path = original_osdmap_path + '.new'
get_osd_map(original_osdmap_path)
args = ['osdmaptool', '--test-map-pgs-dump', original_osdmap_path]
original_osdmaptool_output = subprocess.check_output(args, stderr=FNULL).split('\n') args = ['cp', original_osdmap_path, new_osdmap_path]
subprocess.call(args, stdout=FNULL, stderr=subprocess.STDOUT)
args = ['osdmaptool', '--import-crush', crushmap, new_osdmap_path]
subprocess.call(args, stdout=FNULL, stderr=subprocess.STDOUT)
args = ['osdmaptool', '--test-map-pgs-dump', new_osdmap_path]
new_osdmaptool_output = subprocess.check_output(args, stderr=FNULL).split('\n') pools = get_pools_info(original_osdmap_path)
results = diff_output(original_osdmaptool_output, new_osdmaptool_output, pools) return results def dump_plain_output(results):
sys.stdout.write("%-20s %-20s %-20s %-20s\n" % ("POOL", "REMAPPED OSDs", "BYTES REBALANCE", "OBJECTS REBALANCE")) for pool in results:
sys.stdout.write("%-20s %-20s %-20s %-20s\n" % (
pool,
results[pool]['osd_remap_counter'],
results[pool]['osd_bytes_movement'],
results[pool]['osd_objects_movement']
)) def cleanup(osdmap):
FNULL.close()
new_osdmap = osdmap + '.new'
os.remove(new_osdmap) def parse_args():
parser = argparse.ArgumentParser(description='Ceph CRUSH change data movement calculator.') parser.add_argument(
'--osdmap-file',
help="Where to save the original osdmap. Temp one will be <location>.new. Default: /tmp/osdmap",
default="/tmp/osdmap",
dest="osdmap_path"
)
parser.add_argument(
'--crushmap-file',
help="CRUSHmap to run the movement test against.",
required=True,
dest="new_crushmap"
) parser.add_argument(
'--format',
help="Output format. Default: plain",
choices=['json', 'plain'],
dest="format",
default="plain"
) args = parser.parse_args()
return args if __name__ == '__main__':
ctx = parse_args() results = osdmaptool_test_map_pgs_dump(ctx.osdmap_path, ctx.new_crushmap)
cleanup(ctx.osdmap_path) if ctx.format == 'json':
print json.dumps(results)
elif ctx.format == 'plain':
dump_plain_output(results)

直接放在这里方便拷贝,也可以去原作者的gist里面去获取

主要代码分析

首先获取osdmap

ceph osd getmap -o /tmp/osdmap

获取原始pg分布

使用osdmaptool  --test-map-pgs-dump /tmp/osdmap

获取新的crushmap

这个是自己编辑成需要的crushmap

将新的crushmap注入到osdmap里面得到新的osdmap

osdmaptool --import-crush  crushmap  /tmp/new_osdmap_path

根据新的osdmap进行计算新的分布

osdmaptool  --test-map-pgs-dump /tmp/new_osdmap_path

然后比较两个输入进行对比得到结果

相关链接

Calculate data migration when changing the CRUSHmap

alram/crush_data_movement_calculator.py

变更记录

Why Who When
创建 武汉-运维-磨渣 2017-02-08

预估ceph的迁移数据量的更多相关文章

  1. 从SQL Server到MySQL,近百亿数据量迁移实战

    从SQL Server到MySQL,近百亿数据量迁移实战 狄敬超(3D) 2018-05-29 10:52:48 212 沪江成立于 2001 年,作为较早期的教育学习网站,当时技术选型范围并不大:J ...

  2. 大数据量下的集合过滤—Bloom Filter

    算法背景 如果想判断一个元素是不是在一个集合里,一般想到的是将集合中所有元素保存起来,然后通过比较确定.链表.树.散列表(又叫哈希表,Hash table)等等数据结构都是这种思路,存储位置要么是磁盘 ...

  3. elasticsearch5.0集群大数据量迁移方法及注意事项

    当es集群的数据量较小的情况下elasticdump这个工具比较方便,但是当数据量达到一定级别比如上百G的时候,elasticdump速度就很慢了,此时我们可以使用快照的方法进行备份 elasticd ...

  4. mysql迁移之巨大数据量快速迁移方案

    mysql迁移之巨大数据量快速迁移方案-增量备份及恢复 --chenjianwen 一.前言: 当mysql库的大小达到几十个G或者上百G,迁移起来是一件非常费事的事情,业务中断,导出导入耗费大量的时 ...

  5. MYSQL千万级别数据量迁移Elasticsearch5.6.1实战

    从关系型库中迁移数据算是比较常见的场景,这里借助两个工具来完成本次的数据迁移,考虑到数据量并不大(不足两千万),未采用snapshot快照的形式进行. Elasticsearch-jdbc,Githu ...

  6. 针对数据量较大的表,需要进行跨库复制,采用navcat 实现sqlite数据库跨数据库的数据表迁移 [转载]

    2014年12月13日 14:36 新浪博客 (转自http://www.cnblogs.com/nmj1986/archive/2012/09/17/2688827.html) 需求: 有两个不同的 ...

  7. (转)预估大数据量下UV的方法

    在实际应用中,我们经常碰到这种情况,即要统计某个对象或者事件独立出现的次数.对于较小的数据量,这很容易解决,我们可以首先在内存中对序列进行排序,然后扫描有序序列统计独立元素数目.其中排序时间复杂度为O ...

  8. 日均数据量千万级,MySQL、TiDB两种存储方案的落地对比

    http://mp.weixin.qq.com/s?__biz=MzIzNjUxMzk2NQ==&mid=2247484743&idx=1&sn=04337e020d268a9 ...

  9. mysql分库分表,做到永不迁移数据和避免热点

    作者:老顾聊技术   搜云库技术团队  来源:https://www.toutiao.com/i6677459303055491597 一.前言 中大型项目中,一旦遇到数据量比较大,小伙伴应该都知道就 ...

随机推荐

  1. windows.h头文件中改变光标位置的函数——SetConsoleCursorPosition

    COORD 具体为 typedef struct COORD{ short X; short Y; } COORD,*PCOORD;     可以用来记录坐标. #include <iostre ...

  2. 带你了解 MySQL Binlog 不为人知的秘密

    MySQL 的 Binlog 日志是一种二进制格式的日志,Binlog 记录所有的 DDL 和 DML 语句(除了数据查询语句SELECT.SHOW等),以 Event 的形式记录,同时记录语句执行时 ...

  3. 修改apt,pip,npm为国内镜像源

    apt 原文件备份 sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak 编辑源列表文件 sudo vim /etc/apt/sources. ...

  4. 链接WPA2-企业WIFI时出现无法链接到该网络,可以链接个人WIFI时的问题和解决方案

    因在一个问题上掉两次坑所以还是决定记录下来,方便以后查阅. 第一次因为要部署.net 应用程序要求使用TLS1.2,所以修改了操作系统的默认启用的安全协议类型,导致好多应用程序出问题. 第二次因为vs ...

  5. Java基础系列-Lambda

    原创文章,转载请标注出处:https://www.cnblogs.com/V1haoge/p/10755338.html 一.概述 JDK1.8引入了函数式编程,重点包括函数式接口.lambda表达式 ...

  6. Java nio Server端示例

    public class ServerNio { public static void main(String[] args) throws IOException, InterruptedExcep ...

  7. 关于天线长度及LC值的计算

    一.天线长度与波长 1.天线最佳长度计算 理论和实践证明,当天线的长度为无线电信号波长的1/4时,天线的发射和接收转换效率最高.因此,天线的长度将根据所发射和接收信号的频率即波长来决定.只要知道对应发 ...

  8. JDK---00Linux上编译openjdk8

    Centos 7 编译自定义jdk8 1. 安装所需的依赖 yum install alsa-lib-devel cups-devel libX* gcc gcc-c++ freetype-devel ...

  9. 回顾C#各版本特性

    C# 6.0 Read-only auto-properties(只读自动属性) 以前版本,声明只读属性时,示例: public string FirstName { get; private set ...

  10. 【源码】spring生命周期

    一.spring生命周期 1. 实例化Bean 对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用crea ...