分布式锁的作用
一般来讲,锁的作用是在于解决不同的执行流之间对于同一个资源的竞争而产生的问题。分布式锁的作用就在于解决分布式程序中,分布在不同机器上的执行流对于资源的竞争问题。在mongodb的cluster上, 多个mongos都会发起balance这个过程。而一个时期内,只能有一个balance过程的存在。因此,如何解决多个mongos进程都要发起balance这个过程,需要分布式锁。
代码位置
代码版本采用的是2.4.11版本,这个比较接近我们线上运行的stable版本。
主要涉及的代码文件:
mongo/client/distlock.cpp
mongo/s/type_locks.h
mongo/s/type_lockpings.h
锁的实体
分布式锁一定会有一个存储锁的位置。无论文件锁或者是内核中的mutex或者自旋锁、读写锁,都有一个锁的"实体“, 通过代码对这个”实体“进行一些保护性质的运算,来实现锁定或者是解锁的功能。
mongodb采用的将数据库作为锁的”实体“。在mongodb sharding cluster中有一个重要的数据库config,
在这个db中有两个collection和分布式锁有关。
一个是config.locks
mongos> db.locks.find().pretty() { "_id" : "configUpgrade", "process" : "i-qikzt805:50000:1390191129:1804289383", "state" : 0, "ts" : ObjectId("52dca219420e5e3bb3e63ee9"), "when" : ISODate("2014-01-20T04:12:09.751Z"), "who" : "i-qikzt805:50000:1390191129:1804289383:mongosMain:846930886", "why" : "upgrading config database to new format v4" } { "_id" : "balancer", "process" : "qc24:50000:1399171433:1804289383", "state" : 2, "ts" : ObjectId("54115f46274b8459f178c927"), "when" : ISODate("2014-09-11T08:37:26.462Z"), "who" : "qc24:50000:1399171433:1804289383:Balancer:846930886", "why" : "doing balance round" } { "_id" : "user_data.user_data", "process" : "qc-clouddb1:30001:1409913195:236929073", "state" : 0, "ts" : ObjectId("5409c74dc3a03d987a4a2d88"), "when" : ISODate("2014-09-05T14:23:09.190Z"), "who" : "qc-clouddb1:30001:1409913195:236929073:conn40:1485371859", "why" : "migrate-{ _id: \"824cb5db-6cbd-c90c-95ad-915bd7880c31\" }" } { "_id" : "user_data.device_info", "process" : "qc24:30005:1399282507:1672775172", "state" : 0, "ts" : ObjectId("5406f9e738d2c06115a4e475"), "when" : ISODate("2014-09-03T11:22:15.837Z"), "who" : "qc24:30005:1399282507:1672775172:conn1498556:2012981408", "why" : "migrate-{ _id: \"40acae65-45de-ce42-daf6-63fe1b0e8052\" }" } mongos>
可以看到有4个锁的存在, "_id" : "configUpgrade" 顾名思义吧。"_id" : "balancer"是整个balancer过程的锁。"_id" : "user_data.user_data"和"_id" : "user_data.device_info"对应具体的进行sharding的collection的balance锁。
另一个是config.lockpings
mongos> db.lockpings.find().pretty() { "_id" : "i-qikzt805:50000:1390191129:1804289383", "ping" : ISODate("2014-01-20T09:26:20.903Z") } { "_id" : "qc14:50000:1398961193:1804289383", "ping" : ISODate("2014-09-11T08:41:07.546Z") } { "_id" : "qc24:50000:1399171433:1804289383", "ping" : ISODate("2014-09-11T08:40:56.833Z") } { "_id" : "qc23:50000:1399172957:1804289383", "ping" : ISODate("2014-09-11T08:40:55.083Z") } { "_id" : "qc15:50000:1399173835:1804289383", "ping" : ISODate("2014-09-11T08:40:54.947Z") } { "_id" : "qc16:50000:1399174043:1804289383", "ping" : ISODate("2014-09-11T08:41:04.574Z") } { "_id" : "qc24:30005:1399282507:1672775172", "ping" : ISODate("2014-09-05T08:50:18.879Z") } { "_id" : "qc-clouddb6:50000:1409730027:1804289383", "ping" : ISODate("2014-09-11T08:40:54.966Z") } { "_id" : "qc-clouddb7:50000:1409730657:1804289383", "ping" : ISODate("2014-09-11T08:40:54.868Z") } { "_id" : "qc-clouddb8:50000:1409730659:1804289383", "ping" : ISODate("2014-09-11T08:40:56.802Z") } { "_id" : "qc-clouddb8:30008:1409813212:1448386028", "ping" : ISODate("2014-09-11T08:40:54.989Z") } { "_id" : "qc-clouddb1:30001:1409913195:236929073", "ping" : ISODate("2014-09-11T08:40:54.947Z") } { "_id" : "qc-clouddb3:30003:1409918540:1296167705", "ping" : ISODate("2014-09-11T08:40:55.232Z") } { "_id" : "qc-clouddb7:30007:1409919636:1928209546", "ping" : ISODate("2014-09-11T08:40:54.762Z") } { "_id" : "qc-clouddb2:30002:1409919744:42373342", "ping" : ISODate("2014-09-11T08:40:52.771Z") } { "_id" : "qc-clouddb6:30006:1409920835:1027944352", "ping" : ISODate("2014-09-11T08:40:54.932Z") } { "_id" : "qc-clouddb5:30005:1409920983:1186461301", "ping" : ISODate("2014-09-11T08:40:54.949Z") } { "_id" : "qc-clouddb4:30004:1409921811:221589655", "ping" : ISODate("2014-09-11T08:40:55.083Z") }i-qikzt805:50000:1390191129:1804289383 是进程id, 由hostname:port:timestamp:random()组成。
集群中每个mongos和mongod会每隔一段时间修改自己进程id的ping时间。相当于保持heartbeat。
lockping会单独起一个线程,每隔一段时间去更新config.lockpings。
LockPinger线程
LockPinger线程只是做3件事:
1 更新进程在config.lockpings中的时间。
2 检查config.locks中所有锁定时间超过4天的锁,如果存在则释放掉。
3 清除掉本进程之前没有成功释放掉的锁。(待解锁列表中的所有的锁)
获取锁的步骤
获取锁的代码主要在这个函数DistributedLock::lock_try
1 从locks collection中查询锁是否存在(_id==锁名称)如果不存在,则可以获得锁.
2 如果锁存在, 分4种情况讨论:
A. 可重入,超时
B. 可重入,不超时
C. 不可重入, 超时
D. 不可重入,不超时
B可以获得锁, A/C/D都不可以获得锁
另外还涉及一个是否释放锁的问题。C情况下会释放锁, 其他情况下不会。而为什么C释放,而A不释放的原因我没有想明白。誰可以指点一下?
另外在获取锁的时候会分成两步提交: 第一步先把状态设置成1, 如果成功则再将状态设置成2。
锁超时的判断条件
锁的拥有者进程的上一次ping抵达的时间已经超过15分钟
解锁的步骤
解锁步骤比较简单:
1 查询锁是否存在
2 如果存在将状态改成2
3 失败后重试n次, 如果不成功则放在待解锁列表中