ZookeeperNetEx.Recipes(C#)开源包分布式锁源码走读及使用

关于C#版本 ZookeeperNetEx.Recipes的内容太少了,想要了解怎么使用就需要去了解源码。

以下按照我的学习思路分享:

1.阅读源码

我看的java版本的,不过实现原理和过程基本都是一样的。通过看源码知道使用它时大致需要做什么。

https://github.com/shayhatsor/zookeeper/tree/trunk/src/recipes/lock

Test是用分布式锁实现选举的例子。

大致就是这5个类,先简单介绍以下:

  • LockListener  封装得到锁之后要进行的操作 lockAcquire()和lockRelease();
  • ProtocolSupport是WriteLock的父类,实现了重试等基础功能;
  • WriteLock是分布式的主体,我们在使用的时候就是new这个类,里面主要方法有lock()和unlock();
  • ZNodeName是对zk节点名的封装,分布式锁需要创建临时顺序节点,这个类就包含order,节点名比较等;
  • ZookeeperOperation是对Callback逻辑的再封装,主要方法execute();

2.使用

从上面的简单分析,大致尝试一下使用:

2.1自己实现一个LockListener

主要就是lockAcquired获得锁时执行的内容,和lockReleased释放锁时的事情。

CountDownEvent可以先不看(4.1会说)。

class WriteLockListener : LockListener
        {
            string name { get; set; }
            CountdownEvent count;

            public WriteLockListener(string name, CountdownEvent count)
            {
                this.name = name;
                this.count = count;
            }
            public Task lockAcquired()
            {
                Console.WriteLine(name + " accquired lock");
                Console.WriteLine(name + " do sth");
                Thread.Sleep(1000);
                count.Signal();                
                return null;
            }

            public Task lockReleased()
            {
                int id = Thread.GetCurrentProcessorId();
                Console.WriteLine(name + " released lock");
                return null;

            }
        }

2.2 WriteLock登场

​
ZooKeeper zk = new ZooKeeper("127.0.0.1:2181", 20000, null, false);

//new 一个WriteLock 需要传入一个zk实例
WriteLock writelock = new WriteLock(zk, "/lock", null);

//CountdownEvent count = new CountdownEvent(1); 可以先忽略

//自己实现的LockListener
WriteLockListener listener = new WriteLockListener(name,count);

//向writelock里set listener,这样writelock就知道该执行什么了
writelock.setLockListener(listener);

​

2.3执行过程:

//执行
writelock.Lock();

先看下WriteLock结构:

继承了ProtocolSupport所以ProtocolSupport的方法(如重试)可以直接用;

AsyncLock是用来上锁的,writelock.Lock(),Unlock()都需要通过它获得锁,这样就保证在获取锁执行过程中不被unlock()打断。

callback就是我们传入的自定义的LockListener了

zop也是用来执行操作的。

(1)writelock.Lock()

这里用using获得锁,using之后会自动释放锁(成功或异常都会)。

(2)retryOperation()是ProtocolSupport()的方法:

这里就是调用zop中的execute().

解释一下重试逻辑:

创建的zk客户端实例自带重连功能,所以ConnectionLossException时不需要做什么特别的,等待zk去重连;

如果重连了很久连不上,就会出现会话过期,SessionExpiredException,这时就不行了。

(3)再看一下operation的execute() (ZooKeeperOperation类在WriteLock.cs中)

这一段就是核心代码了:

public async Task<bool> execute()
			{
				do
				{
					if (writeLock.Id == null)
					{
						long sessionId = writeLock.zookeeper.getSessionId();
						string prefix = "x-" + sessionId + "-";
						// lets try look up the current ID if we failed 
						// in the middle of creating the znode
						await findPrefixInChildren(prefix, writeLock.zookeeper, writeLock.dir).ConfigureAwait(false);
                        writeLock.idName = new ZNodeName(writeLock.Id);
					}
					if (writeLock.Id != null)
					{
						List<string> names = (await writeLock.zookeeper.getChildrenAsync(writeLock.dir).ConfigureAwait(false)).Children;
						if (names.Count == 0)
						{
							LOG.warn("No children in: " + writeLock.dir + " when we've just " + "created one! Lets recreate it...");
                            // lets force the recreation of the id
                            writeLock.Id = null;
						}
						else
						{
							// lets sort them explicitly (though they do seem to come back in order ususally :)
							SortedSet<ZNodeName> sortedNames = new SortedSet<ZNodeName>();
							foreach (string name in names)
							{
								sortedNames.Add(new ZNodeName(writeLock.dir + "/" + name));
							}
                            writeLock.ownerId = sortedNames.Min.Name;
                            SortedSet<ZNodeName> lessThanMe = new SortedSet<ZNodeName>();

						    foreach (ZNodeName name in sortedNames) {
						        if (writeLock.idName.CompareTo(name) > 0) lessThanMe.Add(name);
                                else break;
						    }

						    if (lessThanMe.Count > 0)
							{
								ZNodeName lastChildName = lessThanMe.Max;
                                writeLock.lastChildId = lastChildName.Name;
								if (LOG.isDebugEnabled())
								{
									LOG.debug("watching less than me node: " + writeLock.lastChildId);
								}
								Stat stat = await writeLock.zookeeper.existsAsync(writeLock.lastChildId, new LockWatcher(writeLock)).ConfigureAwait(false);
								if (stat != null)
								{
									return false;
								}
							    LOG.warn("Could not find the" + " stats for less than me: " + lastChildName.Name);
							}
							else
							{
								if (writeLock.Owner)
								{
                                    var tempCallback = writeLock.callback.Value;
                                    if (tempCallback != null) {
                                        await tempCallback.lockAcquired().ConfigureAwait(false);
                                    }
                                    return true;
								}
							}
						}
					}
				} while (writeLock.Id == null);
				return false;
			}

I.找到父节点(如果没有会创建),创建自己的节点(findPrefixInChildren)

II.然后获取父节点的子节点,放到SortedSet中。

III.判断是否获得锁

如果当前节点不是最小的,就找到前一个节点注册一个LockWatcher()。

(注意这里有个do{  }while(writelock.Id!=null),所以如果出现再注册前一节点前,前一节点就下线了的情况,就会循环再次获取所有子节点,再找前一个)。

LockWatcher的逻辑比较简单,就是传入我们的writelock,然后触发该watcher时,process方法里就是调用writelock.Lock();

执行上锁逻辑。(这里注意如果是被watcher触发后上锁成功的是没有释放锁逻辑的,所以需要我们自己加)。

如果是最小的,那就执行

当执行完这句,所以逻辑就结束了,会回到WriteLock的Lock里

using释放锁,结束。

(4)再看一眼unlock()方法

主要逻辑就是删除节点。注意这里也要上锁。

 /// <summary>
		/// Removes the lock or associated znode if 
		/// you no longer require the lock. this also 
		/// removes your request in the queue for locking
		/// in case you do not already hold the lock. </summary>
		public async Task unlock()
		{
			using(await lockable.LockAsync().ConfigureAwait(false))
			{
				if (Id != null)
				{
					// we don't need to retry this operation in the case of failure
					// as ZK will remove ephemeral files and we don't wanna hang
					// this process when closing if we cannot reconnect to ZK
					try
					{
						ZooKeeperOperation zopdel = new DeleteNode(this);
						await zopdel.execute().ConfigureAwait(false);
					}
					catch (KeeperException.NoNodeException)
					{
						// do nothing
					}
					catch (KeeperException e)
					{
						LOG.warn("Caught: " + e, e);
						throw;
					}
					finally
					{
					    var tempCallback = callback.Value;
						if (tempCallback != null)
						{
                            await tempCallback.lockReleased().ConfigureAwait(false);
						}
						Id = null;
					}
				}
			}
		}

4.一些使用时的问题

我的实现:

LockListener

internal class WriteLockListener : LockListener
        {
            private string name;
            private Action acquireHandler;
            private Action releaseHandler;
            private CountdownEvent count;

            public WriteLockListener(string name, CountdownEvent count)
            {
                this.name = name;
                this.count = count;
            }

            public Task lockAcquired()
            {
                if (acquireHandler != null)
                {
                    acquireHandler.Invoke();
                }

                count.Signal();
                return Task.FromResult(0);
            }

            public Task lockReleased()
            {
                if (releaseHandler != null)
                {
                    releaseHandler.Invoke();
                }

                return Task.FromResult(0);
            }

            public void SetAcquireHandler(Action handler)
            {
                acquireHandler = handler;
            }

            public void SetReleaseHandler(Action handler)
            {
                releaseHandler = handler;
            }
        }
    }

上锁线程:

public async Task<bool> TryLock() 
        {
            try
            {
                await writelock.Lock().ConfigureAwait(false);
                count.Wait();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                return false;
            }
            finally 
            {
                await writelock.unlock().ConfigureAwait(false);
            }

            return true;
        }

4.0 writelock.Lock()和writelock.unlock()一定要await

异步方法如果不加await是捕获不到异常的!

4.1主线程怎么写,它怎么知道lock操作执行完了,什么时候调用unlock()释放锁?CountDownEvent的作用?

如果幸运第一次就获得锁了,那直接Lock(), 然后unlock()就行了;但不幸的是如果利用监听,等事件到来时,Recipes为我们实现的LockWatcher是没有释放锁的逻辑的。那我们主线程怎么知道事件触发它并获得锁了,又该什么时候去释放呢?

这里可以借用一个CountDownEvent(1),或者SemoporeSlim也行。

主线程writelock.Lock()后,countDownEvent.wait();

LockWatcher被触发,调用process()->writelock.Lock()->zop.execute()->locklistener(callback).lockAcquired()

在LockListener的lockAcquired()里,当执行完了 countDownEvent.Signal();

这样当执行完后,主线程就解除阻塞了,然后再writelock.unlock()就可以了。

模拟结果:

4.2 LockAsync同步锁问题

4.1的方案里有个问题,就是其实执行countDownEvent.Signal()是在LockListener的lockAquire()方法里,然后要一步一步return 到writelock的Lock()里才释放同步锁。但countDownEvent.Signal(),执行完主线程就醒了,可以执行unlock()了。但unlock()需要先获得同步锁,这里如果cpu给不同线程分配的不好的话(非常不幸运才会出现),可能lock线程还没释放锁,主线程就去unlock了,这时因为无法获得锁,unlock()失败。

所以4.1也不能在Lock()方法里调用unlock();

我想到的解决方案就是unlock()循环重试(我也不确定有没有更好的方法)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值