Sanlock原理与基本使用

2020-06-26 14:02:32 浏览数 (1)

Sanlock是一个基于共享存储的分布式锁管理器,集群中的每个节点都各自运行sanlock服务,锁的状态都被写到了共享存储上,所有节点都能访问共享存储,共同维护锁的状态。

Sanlock的锁是名义锁,并非强制锁,被sanlock保护的对象,即使不上锁也能直接访问,完全由程序员自由决定sanlock的锁保护的是何种资源,以及何时上锁和释放。sanlock不适用于对性能要求高的场景,它主要用于实现主机高可用的场景,比如实现虚拟机高可用。

sanlock原理

参考资料

  • 基本原理: https://www.ibm.com/developerworks/cn/linux/1404_zhouzs_sanlock
  • Disk Paxos算法: https://www.microsoft.com/en-us/research/uploads/prod/2016/12/Disk-Paxos-1.pdf
  • Delta Lease算法: http://publications.csail.mit.edu/lcs/pubs/pdf/MIT-LCS-TR-934.pdf
  • Libvirt使用sanlock参考: https://libvirt.org/locking-sanlock.html

Sanlock使用的Disk Paxos算法和Delta Lease算法理解起来比较困难,但对于使用sanlock而言,只需要记住下面几个点即可,无需对这两个算法进行深入的研究

  • 集群中的所有主机都需要挂载同一个共享存储
  • 集群中的每个主机都需要一个唯一的编号,编号需要人为分配(编号范围为1到2000)
  • sanlock通过对共享存储上的2个文件的读写来实现分布式锁的管理
  • 其中一个文件用于管理加入到sanlock集群中的主机,确保集群中没有编号重复的主机,我们将用于管理主机的文件命名为hosts_lease
  • 另外一个文件用于管理分布式锁,确保在任意时刻只能有一个进程能够得到锁,获取到对资源的操作权限。在sanlock中,一个进程只能对锁占用一段时间,超时后会自动释放,如果要继续持有锁,则必须续租(sanlock自动续租),在sanlock中称为租期,所以后续将该文件命名为resource_lease

sanlock安装

在centos7上安装sanlock

代码语言:txt复制
yum install -y sanlock sanlock-python

关闭firewall和selinux

代码语言:txt复制
# 关闭防火墙
systemctl stop firewalld
systemctl disable firewalld
# 关闭selinux
setenforce 0
sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config

启动sanlock和wdmd服务

代码语言:txt复制
# 启动sanlock
systemctl enable sanlock
systemctl start sanlock
# 启动wdmd
systemctl enable wdmd
systemctl start wdmd

sanlock命令行使用

参考资料: https://blog.didiyun.com/index.php/2018/12/27/sanlock-kvm

通过sanlock命令行来学习sanlock的使用,此处采用两个主机进行验证,两个主机需要挂载共享存储,共享存储的搭建此处不再赘述,网上已经有很多的搭建教程,这里采用NFS来搭建共享存储,共享存储路径为/data。

sanlock初始化

在任意一个节点上执行下面的步骤即可。

创建sanlock所需要的两个文件,两个文件的大小均为1MB

代码语言:txt复制
[root@sanlock01 ~]# dd if=/dev/zero bs=1048576 count=1 of=/data/hosts_lease
1 0 records in
1 0 records out
1048576 bytes (1.0 MB) copied, 0.00145441 s, 721 MB/s

[root@sanlock01 ~]# dd if=/dev/zero bs=1048576 count=1 of=/data/resource_lease
1 0 records in
1 0 records out
1048576 bytes (1.0 MB) copied, 0.00121022 s, 866 MB/s

初始化用于主机管理的文件 hosts_lease

代码语言:txt复制
# 命令格式为
#    sanlock direct init -s <name>:<host_id>:<path>:<offset>
# <name>:    sanlock的lockspace名字,任意字符串即可
# <host_id>: 主机id,前面提到的必须唯一的主机编号,初始化时用0替代
# <path>:    主机管理文件的具体路径,这里是/data/hosts_lease
# <offset>:  偏移量,始终提供0即可

[root@sanlock01 ~]# sanlock direct init -s test:0:/data/hosts_lease:0
init done 0

[root@sanlock01 ~]# chown sanlock:sanlock /data/hosts_lease

初始化用于分布式锁管理的文件 resource_lease

代码语言:txt复制
# 命令格式为
#    sanlock direct init -r <name>:<res_name>:<path>:<offset>
# <name>:    sanlock的lockspace名字,任意字符串即可
# <res_name>:要管理的资源名称,任意字符串即可
# <path>:    分布式锁管理文件的具体路径,这里是/data/resource_lease
# <offset>:  偏移量,始终提供0即可

[root@sanlock01 ~]# sanlock direct init -r test:testres:/data/resource_lease:0
init done 0

[root@sanlock01 ~]# chown sanlock:sanlock /data/resource_lease

将主机加入到sanlock集群中

前面提到sanlock有一个用于主机管理的文件,即我们前面创建的/data/hosts_lease,将主机加入到sanlock集群,也就是将主机写入到/data/hosts_lease文件中。

将主机1加入到sanlock集群中,我们为主机1编号为1

代码语言:txt复制
# 命令格式为
#    sanlock client add_lockspace -s <name>:<host_id>:<path>:<offset>
# <name>:    sanlock的lockspace名字,前面初始化步骤中名为test
# <host_id>: 主机id,前面提到的必须唯一的主机编号,对于主机1,我们编号为1
# <path>:    主机管理文件的具体路径,这里是/data/hosts_lease
# <offset>:  偏移量,始终提供0即可

[root@sanlock01 ~]# sanlock client add_lockspace -s test:1:/data/hosts_lease:0
add_lockspace
add_lockspace done 0

将主机2加入到sanlock集群中,我们为主机2编号为2

代码语言:txt复制
[root@sanlock02 ~]# sanlock client add_lockspace -s test:2:/data/hosts_lease:0
add_lockspace
add_lockspace done 0

查看 hosts_lease 文件中记录的注册到sanlock集群的主机

代码语言:txt复制
# 下面命令输出的结果中
#   offset 为自动计算的偏移量,主机1的数据记录在第1个512字节的磁盘中,主机2的数据记录在第2个512字节的磁盘中
#   owner 0001即对应主机sanlock01,owner 0002即对应主机sanlock02
[root@sanlock01 ~]# sanlock direct dump /data/hosts_lease
  offset                            lockspace                                         resource  timestamp  own  gen lver
00000000                                 test  00f18893-078c-49bc-a36c-c9d780576ae9.centos-san 0000000162 0001 0001
00000512                                 test  8ad72230-d8fd-48eb-ba99-0c245bc39d56.centos-san 0000023015 0002 0001

注册服务

将主机加入到sanlock集群后,就可以利用sanlock来管理主机上的服务了。如果两个主机上有相同的服务,这两个主机上的服务会对共享存储上的同一个文件执行写操作,如果不对其加以控制,就会导致数据损坏,sanlock存在的目的就是限制在任意时刻,都只会有一个服务能够对共享资源进行操作。

服务(对应的就是进程)必须要注册到sanlock服务后,sanlock才能为其提供锁机制。下面采用sleep命令来模拟服务的运行。

在主机1上执行下面的命令,创建一个sleep进程,进程运行3000秒后退出,并将该进程的pid注册到sanlock中

代码语言:txt复制
# 创建sleep进程,并将其注册到sanlock,从输出可以知道,pid号为11404
[root@sanlock01 ~]# sanlock client command -c /bin/sleep 3000 &
[1] 11404
...

# 查看本机上的sanlock状态
[root@sanlock01 ~]# sanlock client status
daemon 00f18893-078c-49bc-a36c-c9d780576ae9.centos-san
p -1 helper
p -1 listener
p 11404
p -1 status
s test:1:/data/hosts_lease:0

在主机2上执行下面的命令,创建一个sleep进程,进程运行3000秒后退出,并将该进程的pid注册到sanlock中

代码语言:txt复制
[root@sanlock01 ~]# sanlock client command -c /bin/sleep 3000 &
[1] 4481
...

[root@sanlock02 ~]# sanlock client status
daemon 8ad72230-d8fd-48eb-ba99-0c245bc39d56.centos-san
p -1 helper
p -1 listener
p 4481
p -1 status
s test:2:/data/hosts_lease:0

获取sanlock租期(即分布式锁)

主机加入集群,并将主机上的进程注册到sanlock后,就可以通过sanlock为注册的进程分配锁了。

前面提到sanlock通过共享存储上的一个文件来管理锁,即前面创建的/data/resource_lease文件,首先看一下该文件中的内容,在任意主机上执行下面的命令即可

代码语言:txt复制
# 命令显示own为0000,即此时没有主机获得锁
[root@sanlock01 ~]# sanlock direct dump /data/resource_lease
  offset                            lockspace                                         resource  timestamp  own  gen lver
00000000                                 test                                          testres 0000000000 0000 0000 0

在主机1上执行下面的命令尝试获取sanlock租期,如果获取租期成功,则表示此时共享资源只能由当前获得租期的进程访问,如果获取租期失败,则不能访问共享资源

代码语言:txt复制
# -p参数指定进程的pid,表示为进程11404获取sanlock租期,返回状态码为0表示获取成功
[root@sanlock01 ~]# sanlock client acquire -r test:testres:/data/resource_lease:0 -p 11404
acquire pid 11404
acquire done 0

# 再次查看resource_lease中的内容,可以看到此时own为0001,且timestamp不为0,即主机1获取到了租期
[root@sanlock01 ~]# sanlock direct dump /data/resource_lease
  offset                            lockspace                                         resource  timestamp  own  gen lver
00000000                                 test                                          testres 0000049743 0001 0001 1

在主机2上执行下面的命令尝试获取sanlock租期,由于上一步主机1已经获取到了租期,所以主机2获取租期会失败

代码语言:txt复制
# 为进程4481获取sanlock租期,返回状态码不为0,即获取租期失败
[root@sanlock02 ~]# sanlock client acquire -r test:testres:/data/resource_lease:0 -p 4481
acquire pid 4481
acquire done -243

为了让主机2能够获取到sanlock租期,需要在主机1上释放sanlock租期,在主机1上执行下面的命令执行释放操作

代码语言:txt复制
 [root@sanlock01 ~]# sanlock client release -r test:testres:/data/resource_lease:0 -p 11404
release pid 11404
release done 0

# 释放后查看resource_lease文件内容,可以看到timestamp变为0,表示释放成功
[root@sanlock01 ~]# sanlock direct dump /data/resource_lease
  offset                            lockspace                                         resource  timestamp  own  gen lver
00000000                                 test                                          testres 0000000000 0001 0001 1

再次在主机2上尝试获取sanlock租期

代码语言:txt复制
# 为进程4481获取sanlock租期,返回状态码为0,即获取租期成功
[root@sanlock02 ~]# sanlock client acquire -r test:testres:/data/resource_lease:0 -p 4481
acquire pid 4481
acquire done 0

# resource_lease文件中,own已变为0002,且timestamp不为0,表示主机2获得了运行的租期
[root@sanlock02 ~]# sanlock direct dump /data/resource_lease
  offset                            lockspace                                         resource  timestamp  own  gen lver
00000000                                 test                                          testres 0000074135 0002 0001 2

前面5个步骤演示了两个主机,两个主机上分别有一个进程时,进程之间获取租期的操作。下面演示一个主机上有2个进程需要竞争共享资源的情况。

在主机1上新增一个sleep进程,进程执行3000秒后退出

代码语言:txt复制
# 新增一个sleep进程,加上前面创建的sleep进程,主机1上一共有2个sleep进程
[root@sanlock01 ~]# sanlock client command -c /bin/sleep 3000 &
[2] 11461
 ...

# 查看本机上的sanlock状态
[root@sanlock01 ~]# sanlock client status
daemon 00f18893-078c-49bc-a36c-c9d780576ae9.centos-san
p -1 helper
p -1 listener
p 11404
p 11461
p -1 status
s test:1:/data/hosts_lease:0

在主机1上,为进程11404获取租期

代码语言:txt复制
[root@sanlock01 ~]# sanlock client acquire -r test:testres:/data/resource_lease:0 -p 11404
acquire pid 11404
acquire done 0

# 再次查看resource_lease中的内容,可以看到此时own为0001,且timestamp不为0,即主机1获取到了租期
[root@sanlock01 ~]# sanlock direct dump /data/resource_lease
  offset                            lockspace                                         resource  timestamp  own  gen lver
00000000                                 test                                          testres 0000052511 0001 0001 4

在主机1上,为进程11461获取租期,由于前面进程11404已获取到租期,所以进程11461获取租期会失败

代码语言:txt复制
[root@sanlock01 ~]# sanlock client acquire -r test:testres:/data/resource_lease:0 -p 11461
acquire pid 11461
acquire done -17

在主机1上,释放进程11404获取到的租期

代码语言:txt复制
[root@sanlock01 ~]# sanlock client release -r test:testres:/data/resource_lease:0 -p 11404
release pid 11404
release done 0

# 释放后查看resource_lease文件内容,可以看到timestamp变为0,表示释放成功
[root@sanlock01 ~]# sanlock direct dump /data/resource_lease
  offset                            lockspace                                         resource  timestamp  own  gen lver
00000000                                 test                                          testres 0000000000 0001 0001 5

在主机1上,重新为进程11461获取租期,此时获取租期成功,说明sanlock可以管理同一个主机上多个进程的并发行为,但从resource_lease的文件内容中,无法辨别出当前是哪个进程获取到了租期,此时可以通过查询sanlock的状态知道是哪个进程获取到了租期

代码语言:txt复制
[root@sanlock01 ~]# sanlock client acquire -r test:testres:/data/resource_lease:0 -p 11461
acquire pid 11461
acquire done 0

# 再次查看resource_lease中的内容,可以看到此时own为0001,且timestamp不为0,即主机1获取到了租期
[root@sanlock01 ~]# sanlock direct dump /data/resource_lease
  offset                            lockspace                                         resource  timestamp  own  gen lver
00000000                                 test                                          testres 0000056678 0001 0001 6

# 通过sanlock client status命令可以知道占用租期的进程pid,最后一行显示当前进程11461占用了testres资源的租期
[root@sanlock01 ~]# sanlock client status
daemon 00f18893-078c-49bc-a36c-c9d780576ae9.centos-san
p -1 helper
p -1 listener
p 11404
p 11461
p -1 status
s test:1:/data/hosts_lease:0
r test:testres:/data/resource_lease:0:6 p 11461

如果获取到租期的进程异常退出,没有正常的释放租期,会如何呢?

前面在主机1上启动了2个sleep进程,分别是11404和11461进程,此时由进程11461获取到了sanlock租期,为了演示进程异常退出,这里直接将进程11461杀死

代码语言:txt复制
[root@sanlock01 ~]# kill -9 11461
[1]-  Killed                  sanlock client command -c /bin/sleep 3000

# kill执行后,查看sanlock状态,可以看到11461进程已经不在sanlock中了,且租期已经释放
[root@sanlock01 ~]# sanlock client status
daemon 00f18893-078c-49bc-a36c-c9d780576ae9.centos-san
p -1 helper
p -1 listener
p -1 status
p 11404
s test:1:/data/hosts_lease:0

# 查看resource_lease文件,可以发现timestamp已经被设置为0,即目前没有进程获取到租期
[root@sanlock01 ~]# sanlock direct dump /data/resource_lease
  offset                            lockspace                                         resource  timestamp  own  gen lver
00000000                                 test                                          testres 0000000000 0001 0001 9

从上一步可知,kill掉获取到租期的进程后,sanlock会自动释放该进程占用的租期,以便其它进程可以正常获取租期

代码语言:txt复制
# 11404进程获取租期成功
[root@sanlock01 ~]# sanlock client acquire -r test:testres:/data/  resource_lease:0 -p 11404
acquire pid 11404
acquire done 0

# resource_lease文件中租约设置成功
[root@sanlock01 ~]# sanlock direct dump /data/resource_lease
  offset                            lockspace                                           resource  timestamp  own  gen lver
00000000                                 test                                            testres 0000058712 0001 0001 10

# 从sanlock状态可以看到此时11404获取到了资源testres的租约
[root@sanlock01 ~]# sanlock client status
daemon 00f18893-078c-49bc-a36c-c9d780576ae9.centos-san
p -1 helper
p -1 listener
p -1 status
p 11404
s test:1:/data/hosts_lease:0
r test:testres:/data/resource_lease:0:10 p 11404

代码使用sanlock

前面使用sanlock命令行学习了sanlock的基本使用,主要步骤有:

准备sanlock需要的共享存储以及主机租期文件资源租期文件

  1. 为主机分配一个1到2000的唯一编号,并通过该编号将主机注册到sanlock集群;
  2. 将要运行的服务进程注册到sanlock中;
  3. 服务进程获取资源租期,获取成功则可执行,否则等待一定时间,再次尝试获取资源租期;

在代码中使用sanlock的流程和前面的4个步骤是一致的,下面采用python来模拟真实的业务场景

假设我们有一个服务需要向一个文件不停地写入随机数字,但是该服务所在主机很不稳定,随时可能崩溃。为了让我们的业务尽可能的一直运行下去,我们需要在多个服务器上都部署上我们的服务。但我们只有一个位于共享存储上的文件,多个服务进程不能够同时向该文件写入随机数,任意时刻都只能有一个服务进程在执行写操作,所以我们这里就需要使用sanlock来实现这个同步功能了。

代码语言:txt复制
[root@sanlock01 data]# tree
.
├── randb
│   └── resfile                 # 服务进程要操作的文件
└── sanlock
      ├── randb_hosts_lease       # sanlock用于主机管理的文件
      └── randb_resource_lease    # sanlock用于资源管理的文件

2 directories, 3 files

对sanlock路径下的文件进行初始化操作,我们将lockspace命名为randb,将resource_name命名为resfile(即我们的服务要操作的资源对象),初始化命令如下

代码语言:txt复制
[root@sanlock01 ~]# sanlock direct init -s randb:0:/data/sanlock/randb_hosts_lease:0
init done 0
[root@sanlock01 ~]# sanlock direct init -r randb:resfile:/data/sanlock/randb_resource_lease:0
init done 0
[root@sanlock01 ~]# chown sanlock:sanlock /data/sanlock/randb_hosts_lease
[root@sanlock01 ~]# chown sanlock:sanlock /data/sanlock/randb_resource_lease

将下面的代码拷贝到主机1和主机2的/root路径下,并命名为 simpleha.py,需要修改代码中的host_id为sanlock集群中1到2000的唯一值

代码语言:txt复制
# coding: utf-8

import time
import random
import socket
import sanlock


# 修改host_id为sanlock集群中1到2000之间的唯一值
host_id = 1
hosts_lease = '/data/sanlock/randb_hosts_lease'
resource_lease = '/data/sanlock/randb_resource_lease'

# 若尚未加入 sanlock 集群,则尝试获取对 host_id 的 Delta Lease
if not sanlock.inq_lockspace("randb", host_id, hosts_lease):
    print "Try to acquire host id randb:%s:%s:0" % (host_id, hosts_lease)
    # 并尝试加入 sanlock 集群
    sanlock.add_lockspace("randb", host_id, hosts_lease)

# 把本进程注册到 sanlock 服务上
sfd = sanlock.register()

while True:
    try:
        # 尝试获取 Delta Lease
        print "Try to acquire resfile lease randb:resfile:%s:0" % resource_lease
        sanlock.acquire("randb", "resfile", [(resource_lease, 0)], sfd)
    except sanlock.SanlockException:
        # 无法获取资源租期, 10秒后重试
        print "Failed to acquire leases, try again in 10s."
        time.sleep(10)
    else:
        # 成功获取资源租期,不再重试
        break

# 成功获取到了资源租期,开始向/data/randb/resfile写入随机数
hostname = socket.gethostname()
while True:
    with open("/data/randb/resfile", "a") as randf:
        randnum = random.randint(0, 100)
        msg = "%s %d" % (hostname, randnum)
        randf.write(msg   "n")
        print msg, ">> /data/randb/resfile"
    time.sleep(1)

在主机1上修改 simpleha.py 中的host_id变量,设置为1,并执行该python文件

代码语言:txt复制
[root@sanlock01 ~]# python simpleha.py
Try to acquire host id randb:1:/data/sanlock/randb_hosts_lease:0
Try to acquire resfile lease randb:resfile:/data/sanlock/randb_resource_lease:0
sanlock01.novalocal 48 >> /data/randb/resfile
sanlock01.novalocal 61 >> /data/randb/resfile
sanlock01.novalocal 84 >> /data/randb/resfile
...

# 查看randb_hosts_lease,主机1已加入sanlock集群
[root@sanlock01 ~]# sanlock direct dump /data/sanlock/randb_hosts_lease
  offset                            lockspace                                         resource  timestamp  own  gen lver
00000000                                randb  00f18893-078c-49bc-a36c-c9d780576ae9.centos-san 0000171028 0001 0001

# 查看randb_resource_lease,主机1已获取到对资源resfile的租期
[root@sanlock01 ~]# sanlock direct dump /data/sanlock/randb_resource_lease
  offset                            lockspace                                         resource  timestamp  own  gen lver
00000000                                randb                                          resfile 0000170665 0001 0001 4

# sanlock状态显示当前获得资源resfile租期的进程号为12999
[root@sanlock01 ~]# sanlock client status
daemon 00f18893-078c-49bc-a36c-c9d780576ae9.centos-san
p -1 helper
p -1 listener
p 12999
p -1 status
s randb:1:/data/sanlock/randb_hosts_lease:0
r randb:resfile:/data/sanlock/randb_resource_lease:0:4 p 12999

在主机2上修改 simpleha.py 中的host_id变量,设置为2,并执行该python文件

代码语言:txt复制
# 从输出可以看出,主机2上的simpleha.py获取资源resfile的租期失败,每隔10秒重试一次
[root@sanlock02 ~]# python simpleha.py
Try to acquire host id randb:2:/data/sanlock/randb_hosts_lease:0
Try to acquire resfile lease randb:resfile:/data/sanlock/randb_resource_lease:0
Failed to acquire leases, try again in 10s.
Try to acquire resfile lease randb:resfile:/data/sanlock/randb_resource_lease:0
Failed to acquire leases, try again in 10s.
...

# 通过randb_hosts_lease文件可以主机2已经加入到了sanlock集群中
[root@sanlock02 ~]# sanlock direct dump /data/sanlock/randb_hosts_lease
  offset                            lockspace                                         resource     timestamp  own  gen lver
00000000                                randb  00f18893-078c-49bc-a36c-c9d780576ae9.centos-san    0000171438 0001 0001
00000512                                randb  8ad72230-d8fd-48eb-ba99-0c245bc39d56.centos-san    0000194296 0002 0001

# 查看sanlock状态可以看到,simpleha服务已经注册到sanlock中了
[root@sanlock02 ~]# sanlock client status
daemon 8ad72230-d8fd-48eb-ba99-0c245bc39d56.centos-san
p -1 helper
p -1 listener
p 5555
p -1 status
s randb:2:/data/sanlock/randb_hosts_lease:0

通过直接kill主机1上的simpleha.py进程12999,模拟主机1服务故障。将主机1上的simpleha.py进程kill后,主机2上的服务进程很快就进入了写文件的流程,可见由sanlock控制的HA服务是有效的

代码语言:txt复制
[root@sanlock01 ~]# kill -9 12999

[root@sanlock02 ~]# python simpleha.py
...
Try to acquire resfile lease randb:resfile:/data/sanlock/randb_resource_lease:0
Failed to acquire leases, try again in 10s.
Try to acquire resfile lease randb:resfile:/data/sanlock/randb_resource_lease:0
sanlock02.novalocal 34 >> /data/randb/resfile
sanlock02.novalocal 75 >> /data/randb/resfile
sanlock02.novalocal 14 >> /data/randb/resfile
...

# 主机2获取到了对资源resfile的租期
[root@sanlock02 ~]# sanlock direct dump /data/sanlock/randb_resource_lease
  offset                            lockspace                                         resource     timestamp  own  gen lver
00000000                                randb                                          resfile    0000194546 0002 0001 5

# 服务进程5555已获取到资源resfile的租期
[root@sanlock02 ~]# sanlock client status
daemon 8ad72230-d8fd-48eb-ba99-0c245bc39d56.centos-san
p -1 helper
p -1 listener
p 5555
p -1 status
s randb:2:/data/sanlock/randb_hosts_lease:0
r randb:resfile:/data/sanlock/randb_resource_lease:0:5 p 5555

0 人点赞