javazx 发表于 2017-3-15 18:27:05

《大规模分布式存储系统》第11章 质量保证、运维及实践【11.4】

11.4 最佳实践
分布式存储系统从整体架构的角度看大同小异,实现起来却困难重重。自主研
发的分布式存储系统往往需要两到三年才能逐步成熟起来,其中的难点在于如何把
系统做稳定。系统开发过程中涉及架构设计、关键算法实现、质量控制、团队成员
成长、线上运维、应用合作等,任何一个环节出现问题都可能导致整个项目失败。
本节首先介绍通用分布式存储系统发展路径,接着分享个人在人员成长、架构
设计、系统实现、线上运维的一些经验,最后给出实践过程中发现的工程现象以及
经验法则。
11.4.1 系统发展路径
通用分布式存储系统不是设计出来的,而是随着应用需求不断发展起来的。它
来源于具体业务,又具有一定的通用性,能够解决一大类问题。通用分布式存储平
台的优势在于规模效应,等到平台的规模超过某个平衡点时,成本优势将会显现。
通用分布式存储平台主要有两种成长模式:
1)公司高层制定战略大力发展通用平台。这种模式前期发展会比较顺利,但是
往往会因为离业务太远而在中期暴露大量平台本身的问题。
2)来源于具体业务并将业务需求通用化。这种模式会面临更大的技术挑战,但
是团队成员反而能够在这个过程中得到更多的锻炼。
第2种发展模式相对更加曲折,大致需要经历如下几个阶段。
1)起步:解决特定问题
在起步阶段,需要解决业务提出的特殊需求,这些特殊需求是以前的系统无法
解决或者解决得不太好的。例如,OceanBase系统起步时需要解决淘宝收藏夹业务提
出的两张大表左连接问题。起步期的挑战主要在于技术挑战,团队成员能够在这个
阶段获得较大的技术成长。
2)求生存:应用为王
为了证明平台的通用性,需要接入大量的业务。如果没有公司战略支持,这个
阶段将面临“鸡生蛋还是蛋生鸡”的问题,没有业务就无法完善平台,平台不完善就
无法吸引更多业务接入。在这个阶段,优先级最高的事情是接入合适的应用并把应
用服务好,形成良好的口碑。求生存阶段还将面临一个来自团队内部的挑战,团队
成员缺乏起步期的新鲜感,部分成员工作热情会有所降低。这个阶段需要明确团队
的愿景,耐住寂寞,重视每个细节。
3)平台化:提升易用性、可运维性
当应用数量积累到一定程度后,就需要花大力气提升易用性和可运维性了。易
用性的关键在于采用标准的使用接口,兼容应用以前的使用方式,从而降低学习成
本和应用改造成本;提升可运维性要求将系统内部更多状态暴露给运维人员并开发
方便的部署、监控、运维工具。
4)成熟期:持续不断地优化
分布式存储系统步入成熟期后,应用推广将会比较顺利。开发团队在这个阶段
做的事情主要是持续不断地优化系统,并根据应用的需求补充一些功能支持。随着
平台规模不断增长以及优化工作不断深入,平台的规模效应将显现,平台取得成
功。
通用存储平台发展过程中困难重重,要求团队成员有强烈的信念和长远的理
想,能够耐得住寂寞。另外,系统发展过程中需要保持对技术细节的关注,每个实
现细节问题都可能导致用户抱怨,甚至引起线上故障。
从公司的角度看,是否发展通用分布式存储平台取决于公司的规模。对于小型
互联网公司(员工数小于100人),那么,应该更多地选择广泛使用的存储技术,例
如MySQL开源关系数据库;对于中型互联网公司(员工数在100到1000人之间),那
么,可以组合使用各种SQL或NoSQL存储技术,改进开源产品或者基于开源产品做二
次开发,例如基于MySQL数据库做二次开发,实现7.1节中的MySQL Sharding架构;
对于大型互联网公司(员工数超过1000人),那么,往往需要自主研发核心存储技
术,包括分布式架构、存储引擎等。通用分布式存储系统研发周期很长,系统架构
需要经过多次迭代,团队成员也需要通过研发过程来获得成长,因此,这种事情要
么不做,要做就务必坚持到底。
11.4.2 人员成长
1.师兄带师弟
分布式存储系统新人培养周期较长,新人的成长一方面需要靠自己的努力,另
一方面更需要有经验的师兄悉心的指导。
OceanBase团队新人加入时,会给每人分配一个具有三年以上大规模分布式存储
实践经验的师兄。师兄的主要职责包括:
1)对于新加入的师弟(无论应届生与否),提供各种技术文档,并解惑文档中
的问题;
2)与技术负责人协商安排师弟的工作;
3)与师弟沟通代码编写(包括功能实现、bug修复等)的思路;
4)审核师弟的代码并对代码质量负责,确保代码符合部门编码规范;
5)保持代码修改与文档更新的同步并审核师弟文档的正确性及质量。
OceanBase的各种技术文档包括:
1)技术框架文档:介绍OceanBase整体技术架构和各个模块的详细设计;
2)模块接口文档:各个模块之间的接口和一些约定;
3)数据结构文档:OceanBase系统中的核心数据结构,例如ChunkServer模块的
SSTable、UpdateServer模块的MemTable、RootServer模块的RootTable;
4)编码规范。
可以看出,师兄主要的职责就是帮助师弟把关设计和编码的质量,帮助师弟打
好基础。同时,师兄需要根据师弟的情况安排具有一定挑战性但又在师弟能力范围
之内的工作,并解答师弟提出的各种问题。当然,成长靠自己,师弟需要主动利用
业余时间学习分布式存储相关理论。
2.架构理论学习
基于互联网的开放性,我们能够很容易获取分布式存储架构相关资料,例如
Google File System、Bigtable、Spanner论文、Hadoop系统源代码等。然而,这些论文
或者系统仅仅给出一种整体方案,并不会明确给出方案的实现细节以及背后经历的
权衡。这就要求我们在架构学习的过程中主动挖掘整体架构背后的设计思想和关键
实现细节。
阅读GFS论文时,可以尝试思考如下问题:
1)为什么存储三个副本?而不是两个或者四个?
2)Chunk的大小为何选择64MB?这个选择主要基于哪些考虑?
3)GFS主要支持追加(append)、改写(overwrite)操作比较少。为什么这样
设计?如何基于一个仅支持追加操作的文件系统构建分布式表格系统Bigtable?
4)为什么要将数据流和控制流分开?如果不分开,如何实现追加流程?
5)GFS有时会出现重复记录或者补零记录(padding),为什么?
6)租约(Lease)是什么?在GFS起什么作用?它与心跳(heartbeat)有何区
别?
7)GFS追加操作过程中如果备副本(Secondary)出现故障,如何处理?如果主
副本(Primary)出现故障,如何处理?
8)GFS Master需要存储哪些信息?Master数据结构如何设计?
9)假设服务一千万个文件,每个文件1GB,Master中存储的元数据大概占用多少
内存?
10)Master如何实现高可用性?
11)负载的影响因素有哪些?如何计算一台机器的负载值?
12)Master新建chunk时如何选择ChunkServer?如果新机器上线,负载值特别
低,如何避免其他ChunkServer同时往这台机器迁移chunk?
13)如果某台ChunkServer报废,GFS如何处理?
14)如果ChunkServer下线后过一会重新上线,GFS如何处理?
15)如何实现分布式文件系统的快照操作?
16)ChunkServer数据结构如何设计?
17)磁盘可能出现“位翻转”错误,ChunkServer如何应对?
18)ChunkServer重启后可能有一些过期的chunk,Master如何能够发现?
阅读Bigtable论文时,可以尝试思考如下问题:
1)GFS可能出现重复记录或者补零记录(padding),Bigtable如何处理这种情况
使得对外提供强一致性模型?
2)为什么Bigtable设计成根表(RootTable)、元数据表(MetaTable)、用户表
(UserTable)三级结构,而不是两级或者四级结构?
3)读取某一行用户数据,最多需要几次请求?分别是什么?
4)如何保证同一个子表不会被多台机器同时服务?
5)子表在内存中的数据结构如何设计?
6)如何设计SSTable的存储格式?
7)minor、merging、major这三种compaction有什么区别?
8)TabletServer的缓存如何实现?
9)如果TabletServer出现故障,需要将服务迁移到其他机器,这个过程需要排序
操作日志。如何实现?
10)如何使得子表迁移过程停服务时间尽量短?
11)子表分裂的流程是怎样的?
12)子表合并的流程是怎样的?
总而言之,学习论文或者开源系统时,将自己想象为系统设计者,对每个设计
要点提出质疑,直到找到合理的解释。
当然,更加有效的学习方式是加入类似OceanBase这样的开发团队,通过参与周
围同事对每个细节问题的讨论,并应用到实际项目中,能够较快地理解分布式存储
理论。
11.4.3 系统设计
1.架构师职责
分布式存储系统架构师的工作不仅在于整体架构设计,还需要考虑清楚关键实
现细节,做到即使只有自己一人也可以把系统做出来,只是需要花费更多的时间而
已。
架构师的主要工作包括:
1)权衡架构,从多种设计方案中选择一种与当前团队能力最为匹配的方案。架
构设计的难点在于权衡,架构师需要能够在理解业务和业界其他方案的前提下提出
适合自己公司的架构。这样的架构既能很好地满足业务需求,复杂度也在开发团队
的掌控范围之内。另外,制定系统技术发展路线图,提前做好规划。
2)模块划分、接口设计、代码规范制定。系统如何分层,模块如何划分以及每
个模块的职责,模块的接口、客户端接口,这些问题都应该在设计阶段考虑清楚,
而不是遗留到编码阶段。另外,确保整个团队的编码风格一致。
3)思考清楚关键实现细节并写入设计文档。架构师需要在设计阶段和团队成员
讨论清楚关键数据结构、算法,并将这些内容文档化。如果架构师都不清楚关键实
现细节,那么,团队成员往往更不清楚,最终的结果就是实现的系统带有不确定
性。如果分布式存储系统存在多处缺陷,那么,系统集成测试或者试运行的时候一
定会出现进程Core Dump、数据不正确等问题。这些问题在分布式以及多线程环境下
非常难以定位。如果引发这些错误的原因比较低级,团队成员将无法从解决错误的
过程中收获成就感,团队士气下降,甚至形成恶性循环。
4)提前预知团队成员的问题并给予指导。划分模块以及安排工作时需要考虑团
队成员的能力,给每个成员安排适当超出其当前能力的任务,并给予一定的指导,
例如,帮助其完善设计方案,建议其参考业界的某个方案等。
总而言之,每个问题总会有多种技术方案,架构师要有能力在整体上从稳定
性、性能及工程复杂度明确一种设计方案,而且思考清楚实现细节,切忌模棱两
可。分布式存储系统的挑战不在于存储理论,而在于如何做出稳定运行且能够逐步
进化的系统。
2.设计原则
大规模分布式存储系统有一些可以参考的设计准则:
1)容错。服务器可能宕机,网络交换机可能发生故障,服务器时钟可能出错,
磁盘存储介质可能损坏等。设计分布式存储系统需要考虑这些因素,将他们看成系
统运行过程中必然发生的“正常情况”。这些错误发生时,要求系统能够自动处理,
而不是要求人工干预。
2)自动化。人总是会犯错的,加上互联网公司往往要求运维人员在凌晨执行系
统升级等操作,因此,运维人员操作失误的概率远远高于机器故障的概率。很多设
计方案是无法做到自动化的,例如MySQL数据库主备之间异步复制。如果主机出现
故障,那么有两种选择:一种选择是强制切换到备机,可能丢失最后一部分更新事
务;另外一种选择是停写服务。显然,这两种选择都无法让人接受,因此,只能在
主机出现故障时报警,运维人员介入根据实际情况采取不同的措施。另一方面,如
果主备之间实现强同步,那么,当主机出现故障时,只需要简单地将服务切换到备
机即可,很容易实现自动化。当集群规模较小时,是否自动化没有太大的分别;然
而,随着集群规模越来越大,自动化的优势也会变得越来越明显。
3)保持兼容。分布式存储面临的需求比较多样,系统最初设计,尤其是用户接
口设计时需要考虑到后续升级问题。如果没有兼容性问题,用户很乐意升级到最新
版本。这样,团队可以集中精力开发最新版本,而不是将精力分散到优化老版本性
能或者修复老版本的Bug上。
11.4.4 系统实现
分布式存储系统实现的关键在于可控性,包括代码复杂度、服务器资源、代码
质量等。开发基础系统时,一个优秀工程师发挥的作用会超过10个平庸的工程师,
常见的团队组建方式是有经验的优秀工程师加上有潜质的工程师,这些有潜质的工
程师往往是优秀的应届生,能够在开发过程中迅速成长起来。
1.重视服务器代码资源管理
内存,线程池,socket连接等都是服务器资源,设计的时候就需要确定资源的分
配和使用。比如,对于内存使用,设计的时候需要计算好服务器的服务能力,常驻
内存及临时内存的大小,系统能够自动发现内存使用异常。一般来说,可以设计一
个全局的内存池,管理内存分配和释放,并监控每个模块的内存使用情况。线程池
一般在服务器程序启动时静态创建,运行过程中不允许动态创建线程。
2.做好代码审核
代码中的一些bug,比如多线程bug,异常情况处理bug,后期发现并修复的成本
很高。我们经历过系统的数据规模达到10TB才会出现bug的情况,这样的bug需要系
统持续运行接近48小时,并且我们分析了大量的调试日志才发现了问题所在。前期
的代码审核很重要,我们没有必要担心代码审核带来的时间浪费,因为编码时间在
整个项目周期中只占很少一部分。
代码审核的难点在于执行,花时间理解其他人的代码看起来没什么“技术含
量”。OceanBase团队采取的措施是“师兄责任制”。每个进入团队的同学会安排一个师
兄,师兄最主要的工作就是审核师弟的代码和设计细节。另外,每个师兄只带一个
师弟,要求把工作做细,避免形式主义。
3.重视测试
分布式存储系统开发有一个经验:如果一个系统或者一个模块设计时没有想好
怎么测试,说明设计方案还没有想清楚。比如开发一个基于Paxos协议的分布式锁服
务,只有想好了怎么测试,才可以开始开发,否则所做的工作都将是无用功。系统
服务的数据规模越大,开发人员调试和测试人员测试的时间就越长。项目进展到后
期需要依靠测试人员推动,测试人员的素质直接决定项目成败。
另外,系统质量保证不只是测试人员的事情,开发人员也需要通过代码审核、
单元测试、小规模代码集成测试等方式保证系统质量。
11.4.5 使用与运维
稳定性和性能并不是分布式存储系统的全部,一个好的系统还必须具备较好的
可用性和可运维性。
1.吃自己的狗粮
开发人员和运维人员往往属于不同的团队,这就会使得运维人员的需求总是被
开发人员排成较低的优先级甚至忽略。一种比较有效的方式是让开发人员轮流运维
自己开发的系统,定期总结运维过程中的问题,这样,运维相关的需求能够更快地
得到解决。
2.标准客户端
标准客户端的好处在于客户端版本升级不至于太过频繁。通用系统的上游应用
往往会很多,推动应用升级到某个客户端版本是非常困难的。如果客户端频繁修
改,最后的结果往往是不同的应用使用了不同的客户端版本,以至于服务器端程序
需要考虑和很多不同版本客户端之间的兼容性问题。例如,OceanBase的客户端初期
采用专有API接口,两年之内线上客户端的版本数达到数十个之多。后来我们将客户
端和服务端之间的协议升级为标准MySQL访问协议,客户端的底层采用标准的
MySQL驱动程序,从而解决了客户端版本混乱的问题。
3.线上版本管理
存储系统发展过程中会产生很多版本,有的版本之间变化较大,有的版本之间
变化较小。如果线上维护太多不同版本,那么,每个Bug的修复代码都需要应用到多
个版本,维护代价很高。推荐的方式是保证版本之间的兼容性,定期将线上的低版
本升级到高版本。
4.自动化运维
在系统设计时,就需要考虑到自动化运维,如主备之间采用强同步从而实现故
障自动切换;又如,在系统内部实现自动下线一批机器的功能,确保下线过程中每
个子表至少有一个副本在提供服务。另外,可以开发常用的运维工具,如一键部
署、集群自动升级等。
11.4.6 工程现象
1.错误必然出现
只要是理论上有问题的设计或实现,实际运行时一定会出现,不管概率有多
低。如果没有出现问题,要么是稳定运行时间不够长,要么是压力不够大。系统开
发过程中要有洁癖,不要放过任何一个可能的错误,或者心存侥幸心理。
2.错误必然复现
实践表明,分布式系统测试中发现的错误等到数据规模增大以后必然会复现。
分布式系统中出现的分布式或者多线程问题可能很难排查,但是,没关系,根据现
象推测原因并补调试日志吧,加大数据规模,错误肯定会复现的。
3.两倍数据规模
分布式存储系统压力测试过程中,每次数据量或者压力翻倍,都会暴露一些新
的问题。这个原则当然是不完全准确的,不过可以用来指导我们的测试过程。例
如,OceanBase压测过程中往往会提一个目标:TB级别数据量的稳定性。假设线上真
实的数据量为1TB,那么我们会在线上测试过程中构造2TB~5TB的数据量,并且将
测试过程分为几个阶段:百GB级别压力测试、TB级别压力测试、5TB数据量测试、
真实数据线下模拟实验等。
4.怪异现象的背后总有一个愚蠢的初级bug
调试过程中有时候会发现一些特别怪异的错误,比如总线错误,core dump的堆
栈面目全非等,不用太担心,仔细审核代码,看看编译连接的库是否版本错误等,
特别怪异的现象背后一般是很初级的bug。
5.线上问题第一次出现后,第二次将很快重现
线上问题第一次出现往往是应用引入了一些新的业务逻辑,这些业务逻辑加大
了问题触发的概率。开发人员经常会认为线上的某个问题是小概率事件,例如多线
程问题,加上修复难度大,从而产生懈怠心理。然而,正确的做法是永远把线上问
题当成第一优先级,尽快找出错误根源并修复掉。
11.4.7 经验法则
1.简单性原则
简单就是美。系统开发过程中,如果某个方案很复杂,一般是实践者没有想清
楚。OceanBase开发过程中,我们会要求开发人员用一两句话描述清楚设计方案,如
果不能做到,说明还需要梳理其中的关键点。
2.精力投入原则
开发资源总是有限的,不可能把所有的事情都做得很完美。以性能优化为例,
我们需要把更多的时间花在优化在整体时间中占比例较大或者频繁调用的函数上。
另外,在系统设计时,如果某个事件出现概率高,我们应该选择复杂但更加完美的
方案;如果某个事件出现概率低,我们可以选择不完美但更加简单的方案。
3.先稳定再优化
系统整体性能的关键在于架构,架构上的问题需要在设计阶段解决,实现细节
的问题可以留到优化阶段。开发人员常犯的错误就是在系统还没有稳定的时候就做
性能优化,最后引入额外的Bug导致系统很难稳定下来。实践表明:把一个高效但有
Bug的系统做稳定的难度远远高于把一个稳定但效率不高的系统做高效。当然,前提
是系统的整体架构没有重大问题。
4.想清楚,再动手
无论是设计还是编码,都要求“想清楚,再动手”。对于数据结构或者算法类代
码,如果有大致的思路但是无法确定细节,可以尝试写出伪代码,通过伪代码把细
节梳理清楚。开发人员常犯的一个错误就是先写出一个半成品,然后再修复Bug。然
而,如果发现Bug太多或者整体思路出现


linuvzg 发表于 2022-7-8 13:18:06


路过!我是来打酱油的!
页: [1]
查看完整版本: 《大规模分布式存储系统》第11章 质量保证、运维及实践【11.4】