在HA部署众,心跳无疑是最重要的组件,因此心跳也绝大部分你情况是冗余的,大部分都是用网络做心跳介质,因此一般认为是可靠的。但是,所有的事情都有意外,如果心跳出现了故障,到时HA环境中的节点互相直接之间无法通信,就会出现脑裂(Split Brain)现象,出现这种情况后,会导致HA监控的服务会在多个节点上同时运行,如果此时有共享存储,则会导致共享存储在多个节点上同时可写挂载,从而导致文件系统崩溃,数据丢失。这显然不是可以接受的情况。
开源HA解决方案–[Heartbeat]–解决这个问题的方案是使用成为[Fence]的设备,也就是电源管理设备,当发生脑裂情况时,Hearbeat通知电源设备关闭该节点。目前绝大部分服务器都自带了这样的设备。
虽然都自带了,但我必须承认,到目前为止,我真的还没有在[Heartbeat]上部署过。我也一直在找一个靠谱的纯软件解决方案。这几天测试了[Heartbeat]自带的SFEX 工具,发现基本上可以帮我们防止因脑裂带来的共享磁盘存在数据解构的风险。SFEX的原理是在一个独立的共享存储上的一个分区上写上锁信息,在一个分区上可以有多个锁信息(通过sfex_init -n 来指定),每一个锁信息可以对应一个数据分区。然后把Filesystem资源配置为依赖SFEX资源,这样每次需要挂载数据分区时,都需要预先去读取锁信息,如果能获得锁信息,则可以挂载,否则不能挂载。
基本概念
- SFEX,即Shared Disk File EXclusiveness Control Pogram,被定位一种OCF资源,用于控制共享磁盘的所有权。
- SFEX 在共享磁盘上使用一个特定的分区,用来维护下列数据:
- “status” 显示当前共享磁盘属于谁?
- “node” 显示所有权节点名称
- “count” 用于判断拥有者节点是否运行
- 通常把使用共享存储上的数据分区的资源(比如PostgreSQL)和SFEX配置在一个组资源里
- 所有权节点可以访问数据分区

- 什么时候可以获得所有权
- 无人有所有权
- 节点能够判断另外一个节点已经宕机
时序图
启动过程
SFEX可以在分值最高的节点上运行,此时,其他节点则无法同时访问共享存储(无法获得锁)
Node A
- SFEX 从共享存储上读取数据从而获得”status”,通常”status”是”NO_OWNED”,表示当前没有节点有所有权。
- 写数据到共享存储,内容包括”node=Node A” 和”status=OWNED”
- 重读数据,获得”node=Node A”
- 比较节点名是不是和当前节点一致,如果节点名没有改变,则Node A获得所有权
- SFEX 在监控共享存储的过程中增加”count”值,表明所有权节点在线

心跳通信失败
Node A
- SFEX 通过心跳监控进程更新所有权
Node B
- 当心跳通信失败时,待命(stand by)节点(Node B)开始启动资源
- SFEX 从共享存储上读取数据
- 等待一会儿,等待时间应该长于sfex监控间隔(interval),在等待期间,它定期等待Node A的更新,以确保Node A 还在维护所有权
- 重新读取数据
- 检查”count”的新值,当两个”count”有差别时,它会认为Node A 还在线
- SFEX 启动进程停止

活动节点失效
Node A
- Node A 失效
Node B
- 等待一会,等待Node A 定期更新,但此时发现Node A 没有更新
- SFEX 重新读取数据
- 检查”count”的新值,发现两个值相同,于是它认为Node A 已经失效了(down)
- 写数据到共享存储,内容包括”node=Node B” 和 “status=OWNED”
- 重读数据,获得”node=Node B”
- 比较节点名是不是和当前节点一致,如果节点名没有改变,则Node B获得所有权
- 然后启动后续资源

配置步骤
创建特定分区
首先,需要在共享存储上划出一个单独的小分区出来,不用多大,一个节点仅需要1Kb。因此100M足够了。假定是/dev/sdb1。
初始化
SFEX提供了sfex_init指令来初始化特定分区,使用方式如下
sfex_init -b 512 -n 2 /dev/sdb1
-b参数表示块大小,512是缺省值。-n表示能创建多少过锁。假定你有2个数据分区,那就需要2个锁。
注意,一旦初始化后,不能再增加锁的数量(暂时我没有看到如何增加)。因此要考虑数据分区的扩展性,所以建议多初始化几个锁。
测试分区
SFEX提供了sfex_state指令来测试分区信息是否有效,指令使用及输出结果如下:
# sfex_stat -i 1 /dev/sdb1
control data:
magic: 0x53, 0x46, 0x45, 0x58
version: 1
revision: 3
blocksize: 512
numlocks: 3
lock data #1:
status: lock
count: 393
nodename: tsd1
status is UNLOCKED
-i 1表示锁的位置,从1开始,以此类推。
配置HA
这个步骤就和平常没有什么区别了,每一个Filesystem资源,对应一个sfex资源,比如我测试的环境的crm configure show的部分结果如下:
primitive prmData ocf:heartbeat:Filesystem \
params device="/dev/sdb2" fstype="ext4" directory="/misc" \
op monitor interval="3" \
meta target-role="Started"
primitive prmEx ocf:heartbeat:sfex \
params collision_timeout="1" lock_timeout="70" monitor_interval="10" \
index="1" device="/dev/sdb1" monitor interval="10"
primitive sfex2 ocf:heartbeat:sfex \
params collision_timeout="5" lock_timeout="20" index="2" device="/dev/sdb1" \
op monitor interval="10"
primitive sfex2data ocf:heartbeat:Filesystem \
params device="/dev/sdb3" fstype="ext4" directory="/sfex2data" \
op monitor interval="10"
group grp1 prmEx prmData
group grp2 sfex2 sfex2data
prmEx 和 prmData 是两个sfex资源,使用的锁位置分别是1和2。
当前HA状态信息如下:
Last updated: Thu Jan 12 18:07:11 2012
Last change: Fri Jan 13 00:27:19 2012
Stack: Heartbeat
Current DC: tsd1 (424e04a8-0b7c-4d6a-85cb-f005cbd1702d) - partition with quorum
Version: 1.1.6-1.fc15-b379478e0a66af52708f56d0302f50b6f13322bd
2 Nodes configured, unknown expected votes
4 Resources configured.
============
Online: [ wgzhao-nb tsd1 ]
Resource Group: grp1
prmEx (ocf::heartbeat:sfex): Started tsd1
prmData (ocf::heartbeat:Filesystem): Started tsd1
Resource Group: grp2
sfex2 (ocf::heartbeat:sfex): Started wgzhao-nb
sfex2data (ocf::heartbeat:Filesystem): Started wgzhao-nb
心跳失效模拟测试
当前prmEx的锁所有权在tsd1节点上,sfex2在wgzhao-nb上。我们做一个手工模拟心跳失效测试,就是我们希望手工在wgzhao-nb上获得prmEx的锁。
首先导出OCF的相关环境变量
export OCF_ROOT=/usr/lib/ocf
export OCF_RESKEY_device=/dev/sdb1
export OCF_RESKEY_index=1
./sfex start
sfex[11849]: INFO: sfex_daemon: starting...
sfex[11849]: DEBUG: sfex_monitor: started...
sfex[11849]: DEBUG: sfex_monitor: complete. sfex_daemon is not running.
sfex[11849]: ERROR: sfex_daemon failed to start.
从上述结果可以看出,如果心跳出现了故障,尝试在两个节点同时启动时,sfex报错,返回值为1,表示请求锁超时,这表明当一个节点已经获得所有权后,另外一个节点无法获得锁。