基于 AI 的Ceph 参数调优
Ceph
Ceph 的介绍
Ceph是一个统一的分布式存储系统,设计初衷是提供较好的性能、可靠性和可扩展性。Ceph项目最早起源于Sage就读博士期间的工作(最早的成果于2004年发表),并随后贡献给开源社区。在经过了数年的发展之后,目前已得到众多云计算厂商的支持并被广泛应用。RedHat及OpenStack都可与Ceph整合以支持虚拟机镜像的后端存储。
- Object
Ceph最底层的存储单元,每个Object包含元数据和原始数据。
- PG
全称Placement Groups,是一个逻辑的概念,一个PG包含多个OSD。引入PG这一层其实是为了更好的分配数据和定位数据。
- RADOS
全称Reliable Autonomic Distributed Object Store,是Ceph集群的精华,用户实现数据分配、Failover等集群操作。
- Librados
Librados是RADOS提供的库。因为RADOS很难直接访问,因此上层的RBD、RGW和CephFS都是通过Librados访问的。
- CRUSH
是Ceph使用的数据分布算法,让数据分配到预期的地方。
- RBD
全称RADOS block device,是Ceph对外提供的块设备服务。
- RGW
全称RADOS GateWay,是Ceph对外提供的对象存储服务,接口与S3、Swift兼容。
- CephFS
全称Ceph File System,是Ceph对外提供的文件系统服务
核心组件
Ceph的核心组件包括Ceph OSD、Ceph Monitor和Ceph MSD。
存储过程
无论使用哪种存储方式(对象、块、文件系统),存储的数据都会被切分成对象(Objects)。Objects size大小可以由管理员调整,通常为2M或者4M。每个对象都会有一个唯一的OID,有ino和ono生成:ino是文件的File ID,用于全局唯一标示每一个文件,而ono是分片的编号。例如,一个文件FileID是A,它被切成两个对象,一个编号为0,另一个编号为1,那么这两个对象的OID则为A0和A1。OID的好处是可以唯一标示每一个不同的对象,并且存储了对象与文件的从属关系。由于Ceph的所有数据都虚拟成了整齐划一的对象,所以在读写时效率都会比较高。
但是对象并不会直接存储进OSD中
- 对象的size很小,在一个大规模的集群中可能有几百到几千万个对象。如此多的对象光是遍历寻址速度都会很缓慢;
- 如果将对象直接通过某种固定映射的哈希算法映射到osd上,当这个osd损坏时,对象无法自动迁移到其他osd上面
为了解决这些问题,Ceph引入了归置组的概念,即PG
PG是一个逻辑概念,Linux系统中可以直接看到对象,但是无法直接看到PG。它在数据寻址时类似于数据库中的索引:
- 每个对象都会固定映射进一个PG中,所以当我们寻找一个对象时,只需要先找到对象所属的PG,然后遍历这个PG就可以了,无需遍历所有对象。
- 而且在数据迁移时,也是一PG作为基本单位进行迁移,Ceph不会直接操作对象。
对象是如何映射进PG的?首先使用静态hash函数对OID做hash取出特征码,用特征码与PG的数量取模,得到的序号就是PGID。由于这种设计,PG的数量多寡直接决定了数据分布的均匀性,所以合理设置的PG数量可以很好地提升Ceph集群的性能并使数据均匀分布。
最后PG会根据管理员设置的副本数量进行复制,然后通过CRUSH算法存储到不同的OSD节点上(其实是把PG中的所有对象存储到节点上),第一个osd节点即为主节点,其余均为从节点。
# 获取pgid
locator = object_name
obj_hash = hash(locator)
pg = obj_hash % num_pg
# crush 映射到不同 osd
osds_for_pg = crush(pg) # return a list of osds
primary = osds_for_pg[0]
replicas = osds_for_og[1:]
Pool是管理员自定义的命名空间,像其他的命名空间一样,用来隔离对象与PG。
读写流程
Ceph的读写操作采用主从模型,客户端要读写数据时,只能向对象所对应的的主OSD节点发起请求。主节点在接受写请求时,会同步的向从OSD中写入数据。当所有的OSD节点都写入完成后,主节点才会向客户端报告写入完成的信息,因此保证了主从节点数据的高度一致性。而读取的时候,客户端也只会向主OSD节点发送读请求,并不会有类似数据库中读写分离的情况出现,这也是处于强一致性的考虑。
由于所有写操作都要交给主OSD节点来处理,所以在数据量很大的时候,性能可能会比较慢,为了克服这个问题以及让Ceph能支持事务,每个OSD节点都包含一个journal文件。默认大小为5G,也就是说创建一个OSD节点,还没使用就要被journal占用5G的空间。这个值是可以调整的,具体大小要依据OSD的总大小而定。
Journal的作用类似于MySQL innodb引擎中的事务日志系统。当有突发的大量写入操作时,Ceph可以先把一些零散的,随机的IO请求保存到缓存中进行合并,然后在同一向内核发起IO请求。
- 这样做效率会比较高,
- 但是一旦OSD节点崩溃,缓存中的数据就会丢失,所以数据在还未写进硬盘时,都会记录到journal中,当OSD崩溃后重新启时,会自动尝试从journal恢复因崩溃而丢失的缓存数据。
因此journal的IO是非常密集的,而且由于一个数据IO两次,很大程度上也损耗了硬件的IO性能,所以通常在生产环境中,使用ssd来单独存储journal文件以提高Ceph的读写性能
monitor节点
Mon节点监控着整个Ceph集群的状态信息,监听于tcp的6789端口。每个Ceph集群中至少要有一个Mon节点,官方推荐每个集群至少部署三台。Mon节点中保存了最新的版本集群数据分布图(Cluster Map)的主副本。
客户端在使用时,需要挂载Mon节点的6789端口,下载最新的Cluster Map,通过CRUSH算法获得集群中各OSD的IP地址,然后再与OSD节点直接建立连接来传输数据。
Mon节点之间使用Paxos算法来保持各节点Cluster Map的一致性;
如果主Mon节点损坏,其他Mon存活节点超过半数时,集群还可以正常运行。当故障Mon节点恢复时,会主动从其他Mon节点拉取最新的Cluster Map。
Mon节点并不会主动轮询各个OSD的当前状态,相反,OSD只有在一些特殊情况下才会上报自己的信息,平常只会简单的发送心跳。特殊情况包括:
- **新的OSD**被加入集群;
- 某个OSD发现自身或其他OSD**发生异常**。Mon节点在收到这些上报信息时,则会更新Cluster Map信息并加以扩缩。
MDS
Mds是Ceph集群中的元数据服务器,而通常情况它都不是必须的,因为只有在使用CephFS的时候才需要它,而目前云计算中用到的更广泛的是另外两种存储方式。
MDs虽然是元数据服务器,但是它不负责存储元数据,元数据也是被切成对象存在各个OSD节点中。
在创建CephFS时,要至少创建两个Pool,一个用于存储数据,另一个用于存放元数据
Mds只是负责接收用户的元数据查询请求,然后从OSD中把数据取出来映射进自己的内存中供客户访问。所以Mds其实类似一个代理缓存服务器,替OSD分担了用户的访问压力。
CRUSH 算法
-
数据分布和负载均衡
- a.数据分布均衡,使数据能均匀地分布到各个节点上
- b.负载均衡,使数据访问读写操作的负载在各个节点和磁盘的负载均衡
- 灵活应对集群伸缩
- a.系统可以方便的增加或者删除节点设备,并且对节点失效进行处理
- b.增加或者删除节点设备后,能自动实现数据的均衡,并且尽可能少的迁移数据
- 支持大规模集群
- 要求数据分布算法维护的元数据相对较小,并且计算量不能太大。随着集群规模的增加,数据分布算法的开销相对较小
总体技术方案
方法调研
针对业内学术界的一些常用的调参算法,进行了调研:网格搜索(Grid Search)、 随机搜索(Random Search)、爬山算法(Hill Climbing)、模拟退火(SA,Simulated Annealing)、遗传算法(GA , Genetic Algorithm)、贝叶斯优化(BO, Bayesian Optimization)。
开始的方案:DDPG
强化学习中的DDPG,强化学习的通用思想是:一个智能体,输入当前系统环境的状态,智能体基于自己的判断,输出一个action,我们把这个action附加给环境,环境会给出一个反馈,发生状态的变化,我们基于自己的价值观,主观给出这个变化的好坏以及程度,计算出一个reward,再反馈给智能体,智能体就会学习到这次经验,如此循环下去,一直训练智能体,最终,智能体就会训练成我们期望的样子,类似于教育小孩。
对应到我们的项目中,环境就是Ceph文件系统,状态就是针对系统的一些内存、CPU指标进行构造,action就是一组参数配置,反馈的话,我们基于iops和时延的变化,设计了reward函数。
实验过程中的问题
技术方案上的问题
庞大的参数
Ceph有1800+的参数,其中类型:90%的参数为数值型、布尔型;60%的参数属于文件场景;osd/mds/Lib占68%;12%对IO性能有影响;
如此多的参数,加上很多参数的取值范围很大,参数空间巨大,不可能一次性输入给模型,模型hold不住。所以必须进行参数筛选。
参数筛选
首先,我们拉了公司负责不同模块的核心开发工程师,历时好几个月,不断开会讨论,结合他们的人工经验,最终获取了一份60个有效参数的表格。
这已经可以给模型进行训练了,但是实验发现,训练效果并不好,很缓慢。因此,我们参考了fast2020的一篇论文,利用一种基于方差的参数重要性度量方法,对参数进行重要性排序。这个是有依据的,因为我们在测试参数时,也能明显体会到,在特定场景下,某些参数,就是比其他参数,对性能的影响更大。因此,我们对它们进行排名,挑选前15个参数,最终输入给模型。
工程上的问题
如何将Ceph 文件系统建模成可以交互环境
我们参考了gym上的开源代码风格,gym是由微软的Openai团体开发的平台,是强化学习研究者常用的试验场。模仿里面环境的代码风格,对Ceph进行了建模,这里面代码量、工程量都挺大的,代码行大概1500+,需要处理很多问题。
参数设置的不稳定性
一开始我们对参数的设置,是采用静态文件配置方式,但是这种方式需要重启集群,这使得集群偶尔会变得不稳定,因此我们最终采用动态在线调参方式,尽可能不影响集群的运行。(其中有一个小问题,一直阻碍了我们很久,就是我们的client是通过nfs挂载到集群的,为了避免配置参数,影响到挂载,我们在配置参数之前都umount,配置好之后,再mount,但是也正是这个操作,使得我们性能测试变得很不稳定,同一组参数,测试两次,性能结果差异很大,因为上的mount和umount只是一行代码的操作,所以排查到这个问题很不容易,后来是我手工去测试,发现手工测试性能波动问题消失了,进而发现了这个问题)。
方案的改进
上面的方案,通过运行360步,历时16小时,最终相比于默认配置的性能,提升了50%
在实验其他ai算法,做对比实验时,我们发现,ga遗传算法,虽然达不到ddpg的最佳效果,但是ga可以很快到达一个次高点。而ddpg由于依赖前期的经验积累,前期所作出的选择都不是很好,得到的样本也比较差,换句话说,就是负面教材比较多,所以需要一定的步数去慢慢学习,也就是存在“冷启动”问题。
因此,我们后面用ga去快速生成一些好样本(可以达到次优点附近的样本),然后将这些样本输入给ddpg,使ddpg跳过前期的经验积累过程,更快的去训练。也就是用ga热启动ddpg
最终实验表明,通过ga热启动,ddpg可以在10小时内,达到之前的训练效果。