查看原文
其他

自动化运维产品的命门——元数据库

MQ4096 数据库技术闲谈 2022-08-27

概述


企业机器和数据库上规模后,运维不再单纯依赖个人的能力,而依赖各种运维工具平台。如针对主机的运维平台,能维护主机基础信息(机房信息、机柜信息、机器位置信息)、自动安装操作系统、更新软件、通过模板初始化主机、管理机器使用状态(待初始化/待使用/上线/下线等等),针对数据库的运维平台,能自动安装数据库实例、搭建高可用备库、自动主备切换、数据迁移、弹性扩容/缩容、实例下线、性能分析等等。善假于物是人的能力。

这个,做得粗糙一点称为工具,功能再丰富完善一点称为产品。产品在宣传上会突出管理多大规模的机器,节省多少人力,一键上线/扩容/迁移/切换,智能化运维,自治或自愈等等。博眼球也是人的本性。(本文标题也不能免俗~)。

本文首先是指出自动化运维产品架构设计的关键模块——元数据库的重要性和风险,以及常用的风险规避方法。这些产品在云计算业务里随处可见。然后阐述蚂蚁的分布式数据库OceanBase的关键模块——rootservice以及OceanBase运维平台OCP元数据库的特殊作用。最后以示例解释OceanBase集群不同安装方法的区别所在。

看完本文能够从另外一个角度理解为什么OceanBase数据库的可靠性可以说是金融级的(本文并不是重提RPORTO概念)。OceanBase的这个设计对其他分布式集群产品的架构设计也有一定参考意义。


自动化运维产品的可用性分析


自动化运维产品的功能大体有这么几类:

  • 主机、网络、数据库实例或者其他信息的生命周期的管理。主机和数据库实例好理解,网络信息有静态和动态的VIP或VLAN及其访问规则等信息。其他信息有如某个中间件集群(如LVS、业务服务、分布式数据库中间件等)。

  • 一些常用运维操作的自动化执行。这类操作可以规范化,多是重复的操作,通过程序自动化后可以提高运维效率和降低人为失误风险。如初始化、安装、扩容/缩容、迁移、下线等。

  • 数据库实例或者其他服务的高可用。提供服务的实例,出现故障后业务都要求能快速恢复服务。这类服务分为两类。一类是有状态的,即有数据的。以数据库为例。另外一类是无状态的。这类多是以集群形式提供服务,个别节点出现故障后就直接被踢出集群。如F5后面挂载的服务集群、LVS自身集群、业务服务集群(服务化的产物)。由于是无状态的集群,服务扩容也是很方便的。详情可以看看之前的文章《常见关系数据库的架构》.

  • 运维数据采集和监控告警,高级一点的还有自动化的性能分析。监控告警好做,自动化的性能分析就要在大量基础数据(信息)基础上活用规则和机器学习技术去做异常现象和基础信息(原因)的关联。


不同产品的能力特点或许有点不一样,但都有一个共同点,就是有自己的元数据库。作为一个自动化运维产品,必然要管理很多对象(主机、网络、数据库或者其他服务等)。这些对象的信息不能存在产品的内存中,一定会持久化到数据库中,产品的功能在使用过程中多少会依赖元数据库。所以元数据库的可用性对自动化运维产品的可用性就至关重要。

元数据库的正确性也很重要,在企业运维体系里,越是偏底层基础的产品的元数据库,越是重要。元数据如果错误,在自动化运维的作用下,其影响范围可能会非常大。如正在提供服务的机器被批量下线、新的网络访问规则被应用到不合适的网段导致大面积网络访问中断等等。所以自动化运维产品的元数据库的变更操作要规范、备份要频繁保证有效。

元数据库出现故障时,自动化运维产品的某些功能就会异常。所以元数据库本身也要有高可用。如果这个自动化运维产品就是数据库运维产品,它提供数据库高可用切换的前提就依赖这个元数据库,所以这个元数据库的高可用就不能靠自己来解决,必须使用另外一套产品去保证。而那个产品显然在功能上不如当前这个自动化运维产品(否则那个产品就有理由替换这个产品)。这是一个很有趣的现象。

在机房容灾切换里,所有自动化运维产品的元数据库的切换一定是首先进行的,其次才是各个产品的容灾切换,最后才是这些产品的客户应用的容灾切换。这里面有个层层依赖的关系。理论上如果产品A的高可用还要依赖另外一个产品B的话,那么产品B的高可用就要先于产品A生效。以此往前推,总有一个产品的高可用是不依赖其他产品的,只能自己解决或靠运维人工介入才可以。而它能否切换成功则关系着整个容灾演练计划的成败。所以元数据库是自动化运维产品的命门这说法也不过。

有些运维产品意识到这个问题,会采用将元数据缓存起来的方法来降低对元数据库可用性的依赖。在元数据变更时它会同时更新缓存和元数据库,并能接受元数据库更新失败的情景。如果是产品服务宕机再启动了,缓存中数据就丢失了,这时候就需要去元数据库里再拉去一份最新的数据。只要元数据库跟产品不是同时出现故障,这个设计还是有效的。很多产品都采用了这个设计。

元数据库的高可用分析


自动化运维产品的元数据库可用性分析

既然上面提到元数据库的重要性,这里就多提一下元数据库的高可用技术。传统数据库(ORACLE、SQLServer和MySQL)使用主备架构时,其高可用需要依赖外部工具产品。但是最近几年流行的分布式数据库,使用了Paxos协议做多副本(至少三副本)同步时,可以在原主节点故障时自动选出新的主节点继续提供服务,这个提升了元数据库的可靠性。详情查看之前的文章《揭开数据库RPO为0的秘密(上)》和《揭开数据库RPO为0的秘密(下)》。这类产品的具体架构技术也不完全相同,所以在RPO和RTO上还是有细微区别的,这个要注意辨别。

蚂蚁的分布式数据库OceanBase也有自动化运维平台OCP,OCP也有一个元数据库。目前这个元数据库是一个独立的三节点的OceanBase集群,它的高可用是自身能力,不需要干预,并且故障切换后还保证数据绝对不丢。

OceanBase运维平台OCP的元数据库的作用

OceanBase自动化运维平台(又叫云平台OCP)的元数据库主要是服务于OCP的。OCP的功能就是管理大规模的OceanBase集群。会涉及到基础信息、自动化任务、监控告警等对象。这些信息都保存在OceanBase的元数据库里。这点跟其他自动化运维产品是一样的。

OCP的元数据库还有个特殊的作用就是也保存了所有OceanBase集群内部的元数据管理服务的关键信息。这个跟OceanBase的架构有关。

OceanBase集群可用性分析


OceanBase的宕机应对逻辑

OceanBase是一个分布式数据库,是一个集群。集群的元数据信息在自己内部保存了一份。比如说集群有哪些节点,各个节点的运行状态等。OceanBase集群在机器上的存在形式就是一个OBServer进程。每个节点上的OBServer进程地位基本相同,除了内部租户sys所在的三个节点稍有不同。这个租户下会运行一个rootservice服务,管理集群内部节点状态、参数、内部任务(冻结合并转储、负载均衡)调度等。(rootservice并不负责实际选举,选举是由一个election模块负责。不过rootservice可以通过规则指导选举)。各个节点组成为一个分布式集群,通过启动时都指定了相同的rootservice地址来找到集群并注册。 (详情参见此前的文章《OceanBase实践入门——OceanBase集群手动搭建》。这是一个shared-nothing架构的集群。

OceanBase里所有节点跟这个rootservice保持联系(有心跳检测机制)。如果节点故障,rootservice很快就会发现节点(OBServer)心跳丢失,rootservice做节点成员变更(状态)管理。同时election模块会对节点上的分区判断是否要选举(切换),选主策略会受rootservice里规则影响。 rootservice依托于于sys租户内部的一个系统表 __all_core_table,服务存在于三个节点的OBServer进程内部,只有其中一个提供服务(该系统表的leader角色所在节点)。如果节点故障导致rootservice也出现故障,election模块会对__all_core_table选举出新的leader,然后启动新的rootservice。所以rootservice的可用性对OceanBase集群至关重要。
如果rootservice所在sys租户的节点出现故障并没有及时恢复(默认等2小时),rootservice会将该节点永久下线(注意只是下线,节点信息还存在集群元数据里),并从集群中其他节点里选一个节点(集群规模至少是
2-2-2布局的)来补齐sys租户数据的三副本,相应的数据都会迁移到那个节点。普通节点故障同理。


OceanBase的宕机恢复逻辑

上面说了OceanBase即使有节点宕机了,集群对外服务的可用性是自动恢复。接下来就说宕机的节点起来后如何恢复?

通常分布式集群产品的节点跟集群成员关系是通过配置或者启动参数指定的,或者集群端记录了节点的信息。即使节点宕机后集群会自动将节点踢出,在节点恢复服务后,节点自身可能有记录集群的连接信息或者集群端有该节点的信息。好一点的产品做到了集群发现之前故障节点恢复后,又重新把该节点加入到集群当中提供服务。比如说F5或者LVS都是这样管理后端节点的。稍微差一点的产品可能要在节点故障时手动将节点从集群中剔除,在节点恢复时手动将节点加入集群服务中。

所以在分析之前,再回顾一下OceanBase各个节点是怎么跟集群联系的,关键点就在于节点上OBServer首次启动时的参数rootservice_list里。下面是启动示例:

cd /home/admin/node1/oceanbase && /home/admin/node1/oceanbase/bin/observer -i bond0 -P 2882 -p 2881 -z zone1 -d /home/admin/node1/oceanbase/store/obdemo -r '11.***.84.78:2882:2881;11.***.84.79:2882:2881;11.***.84.83:2882:2881' -c 20190423 -n obdemo -o "memory_limit=51200M,datafile_size=100G,config_additional_dir=/data/data/1/obdemo/etc3;/data/log/log1/obdemo/etc2"

其中 -r参数就是指定了rootservice_list地址,节点就跟集群有了联系(当然第一次初始化时,节点还要命令加入到集群中)。这个参数会记录到OBServer进程的参数文件里。假设这个OBServer进程宕机或关闭,再启动的时候就不需要指定这些参数了。如下。OBServer进程会自动读取软件目录下面etc目录下的参数文件。

cd /home/admin/node1/oceanbase && bin/observer

所以OB节点恢复的时候通常是可以自己跟集群恢复联系。但是,运维经验丰富的人很快就会意识到这个启动参数的rootservice地址是写死的。如果rootservice所在节点发生变化了呢?

首先,如果sys租户所在节点出现宕机之后,rootservice选举后的leader信息会发生变化,它会自动通知所有活着的节点修改参数rootservice_list信息,并且立即持久化到各个活着的节点本地的参数文件中。rootservice成员节点变化或者角色变化都会反映在活着的节点内存以及配置文件里。
如果这个宕机的节点很久才恢复,它还使用老的
rootservice_list参数启动,并且如果这个参数里的多数派成员不可访问或者没有运行rootservice服务,则这个节点是无法跟集群取得联系,节点也就无法启动。

这个时候修复办法就是在启动OBServer的时候再次指定最新的rootservice参数。但这样就有点不完美,会给运维人员带来一点复杂性。实际上OceanBase生产环境选择了一个更灵活的办法。生产环境的节点上OBServer是通过OCP自动安装的,OBServer启动时不指定rootservice_list参数,而是指定一个obconfig_url参数。这个参数值是OCP暴露出来的一个API,可以返回最新的rootservice_list信息。它是存在OCP的元数据库里。

[admin@h07d17162.sqa.eu95 /home/admin/oceanbase]
$strings
etc/observer.config.bin|grep obconfig
obconfig_url=http://10.***.167.20:8082/services?Action=ObRootServiceInfo&User_ID=alibaba&UID=admin&ObRegion=oms_2x

[admin@h07d17162.sqa.eu95 /home/admin/oceanbase]
$curl -L 'http://10.***.167.20:8082/services?Action=ObRootServiceInfo&User_ID=alibaba&UID=admin&ObRegion=oms_2x'
{"Code":200,"Cost":5,"Data":{"ObRegionId":2100006,"RsList":[{"sql_port":2881,"address":"11.***.84.83:2882","role":"LEADER"},{"sql_port":2881,"address":"11.***.84.78:2882","role":"FOLLOWER"},{"sql_port":2881,"address":"11.***.84.84:2882","role":"FOLLOWER"}],"ReadonlyRsList":[],"ObRegion":"oms_2x"},"Message":"successful","Success":true,"Trace":"9.9.9.9:10.***.167.20:1556447216807"}
[admin@h07d17162.sqa.eu95 /home/admin/oceanbase]
$curl -L 'http://10.***.167.20:8082/services?Action=ObRootServiceInfo&User_ID=alibaba&UID=admin&ObRegion=oms_2x' | python -m json.tool
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 382 100 382 0 0 23226 0 --:--:-- --:--:-- --:--:-- 23875
{
"Code": 200,
"Cost": 3,
"Data": {
"ObRegion": "oms_2x",
"ObRegionId": 2100006,
"ReadonlyRsList": [],
"RsList": [
{
"address": "11.***.84.83:2882",
"role": "LEADER",
"sql_port": 2881
},
{
"address": "11.***.84.78:2882",
"role": "FOLLOWER",
"sql_port": 2881
},
{
"address": "11.***.84.84:2882",
"role": "FOLLOWER",
"sql_port": 2881
}
]
},
"Message": "successful",
"Success": true,
"Trace": "9.9.9.9:10.***.167.20:1556447216868"
}

当OceanBase集群的rootservice成员信息发生变动时,会通过这个API更新OCP的元数据库里该集群的rootservcie_list信息。那么其他新增节点启动OBServer时就可以通过这个API获取到最新的rootservice信息。那么此前说的那个宕机很久的节点恢复的时候,就能恢复跟OceanBase集群的联系了(如果被下线了,状态也会恢复为在线)。不过OceanBase对这个API的依赖是弱依赖,没有这个API并不影响OceanBase集群的可用性,只是极端情况下部分节点不能自动恢复。

OceanBase集群参数文件的重要性

每个节点的OBServer进程的参数文件内容大体相同,不同的就是相应本地特殊的地方。OBServer的参数文件是二进制文件,不建议直接编辑。修改参数有两种方式。一是启动OBServer的时候指定参数  -o "xxx=yyyy"; 二是在集群sys租户里通过 alter system set xxx=yyy [scope='xxx.xxx.xxx.xxx:2882'] ; 修改。这个跟ORACLE修改参数方法大体相同。

alter system set obconfig_url='http://10.***.167.20:8082/services?Action=ObRootServiceInfo&User_ID=alibaba&UID=admin&ObRegion=oms_2x' server = '11.***.84.78:2882';

OceanBase参数文件对OBServer的重要性前面已经说过,参数不对OBServer有可能就跟集群失去联系。这个重要性就跟ORACLE的控制文件重要性一样,OBServer会建议把参数文件保存在多个独立的目录(文件系统)下。参数config_additional_dir 就是用来指定额外的保存路径。同时每次observer.config.bin内容的变更都会先保存一下上一次的值在备份文件observer.config.bin.history里,方便参数修改不当后再恢复老的设置。

$strings etc/observer.config.bin|grep config_additional_dir
config_additional_dir=/data/log1/oms_2x/etc2;/data/1/oms_2x/etc3

$find /data/ |grep "observer.conf"
/data/1/oms_2x/etc3/observer.conf.bin.history
/data/1/oms_2x/etc3/observer.conf.bin
/data/log1/oms_2x/etc2/observer.conf.bin.history
/data/log1/oms_2x/etc2/observer.conf.bin

$find /home/admin |grep "observer.config"
/home/admin/oceanbase/etc/observer.config.bin
/home/admin/oceanbase/etc/observer.config.bin.history

OceanBase集群手动部署方案补遗

在前文《OceanBase实践入门——OceanBase集群手动搭建》里,为了降低网友安装OB学习环境的机器资源不足问题,方法里没有推荐用OCP安装,也没有采用obconfig_url参数(因为没有OCPAPI)。不过我们可以利用python搭建一个简单的HTTPServer 来模拟这个API。 方法如下:

注:下面的***是出于安全考虑估计屏蔽显示的。

1. 写一个 rs.json文件模拟api的结果
$cat /home/admin/test/rs.json
{"Code":200,"Cost":3,"Data":{"ObRegionId":2100006,"RsList":[{"sql_port":2881,"address":"11.***.84.83:2882","role":"LEADER"},{"sql_port":2881,"address":"11.***.84.78:2882","role":"FOLLOWER"},{"sql_port":2881,"address":"11.***.84.84:2882","role":"FOLLOWER"}],"ReadonlyRsList":[],"ObRegion":"oms_2x"},"Message":"successful","Success":true,"Trace":"9.9.9.9:10.***.167.20:1556446480671"}

2. 本地用python起一个httpserver
[admin@h07d17162.sqa.eu95 /home/admin/test]
$python -m SimpleHTTPServer 8080
Serving HTTP on 0.0.0.0 port 8080 ...

3. 验证api
[admin@h07d17162.sqa.eu95 /home/admin/oceanbase]
$curl -L 'http://11.***.84.78:8080/rs.json'
{"Code":200,"Cost":3,"Data":{"ObRegionId":2100006,"RsList":[{"sql_port":2881,"address":"11.***.84.83:2882","role":"LEADER"},{"sql_port":2881,"address":"11.***.84.78:2882","role":"FOLLOWER"},{"sql_port":2881,"address":"11.***.84.84:2882","role":"FOLLOWER"}],"ReadonlyRsList":[],"ObRegion":"oms_2x"},"Message":"successful","Success":true,"Trace":"9.9.9.9:10.***.167.20:1556446480671"}


4. 以新的参数启动observer
[admin@h07d17162.sqa.eu95 /home/admin/oceanbase]
$bin/observer -o "obconfig_url='http://11.***.84.78:8080/rs.json'"
bin/observer -o obconfig_url='http://11.***.84.78:8080/rs.json'
optstr: obconfig_url='http://11.***.84.78:8080/rs.json'
[2019-05-09 17:27:18.094254] ERROR [LIB] pidfile_test (utility.cpp:1152) [71476][0][Y0-0000000000000000] [lt=0] fid file doesn't exist(pidfile="run/observer.pid") BACKTRACE:0x7bfc359 0x7b400ab 0x6f4419 0x7c00a96 0x413266 0x7f7a35604445 0x43c025

不过模拟的API终究是假的,不支持POST方法,所以rootservice服务在成员变更后是没办法通过POST方法去调用API更新的。这个时候可以直接修改文件 rs.json里rootservice_list信息。那么故障节点OBServer启动时就不需要做任何修改了。

再次重申,这个方法只适合机器很少时搭建OB学习验证环境使用(这里特地举例是为了加深理解)。在生产环境,还是要用OCP来搭建和管理OceanBase集群。


总结


元数据库是自动化运维产品的重要模块,需要有有效机制保证元数据库的高可用和可恢复。

OceanBase云平台(OCP)是用来自动化运维多个OceanBase集群,OCP的元数据库是一个独立的OceanBase集群。每个OceanBase集群在自己内部租户sys里保存有元数据,并通过rootservice服务提供集群级别管理,rootservice的地址即保存在sys租户系统表里,也通过OCP API保存到OCP的元数据库里。OceanBase集群部分节点故障后再启动的时候通过OCP的API获取最新的rootservice_list信息。rootservice的机制比这里说的还要复杂,此外还有election模块的机制也很复杂,以后有机会再详细说明。


推荐阅读



其他


  • 个人理解,难免有误。我会在月底集中对当月文章里的错误进行修正。敬请关注。

  • 更多分享请查阅公众号


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存