AOP Lock Architecture

前言:

在系統中多少會遇到某些交易間需要互斥(不然容易造成DeadLock).

在我們當前系統中有許多動作間需要互斥,不然會有DeadLock問題

藉由已經分析DeadLock Report後,我開始構思如何讓建立Lock可以變得更容易且好理解.

所以就建構出此Lock架構.

如何在此框架使用Lock機制

我們只需要做幾個步驟:

  1. 在使用Lock類別上掛LockerInterceptor攔截器標籤.
  2. 使用Lock方法上使用LockAttribute標籤[Lock(LockKey = "Your lock Key")](Key屬性是必填的)
  3. 設定Lock屬性.

目前寫法是針對單一Server Mutiple Thread來建立互斥Lock. 假如有遇到多台Servers需要建立互斥模式可以,參考Redis的Redlock.Net.

使用方法如下,這樣在多執行緒系統中MethodA1MethodB_A就不會有同時執行問題,這樣就可以造成這兩個動作互斥.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[Intercept(typeof(LockerInterceptor))]
public class LockerContext : ILockerContext
{
[Lock(Key = "A")]
public virtual void MethodA1()
{
Thread.Sleep(5);
Console.WriteLine($"{DateTime.Now:HH:mm:ss fff} MethodA1 Done");
Thread.Sleep(5);
}

[Lock(Key = "A")]
public virtual void MethodB_A()
{
Thread.Sleep(5);
Console.WriteLine($"{DateTime.Now:HH:mm:ss fff} MethodB_A Done");
Thread.Sleep(5);
}
}

架構解說

我是使用ReaderWriterLockSlim

因為支援ReadLock不互斥,WriteLock互斥邏輯

我是如何讓使用者輸入Key來建立不同lock呢?

我是使用ConcurrentDictionary來處理此問題,每個Key都有不同Lock物件

LockAttribute

LockAttribute有幾個屬性.

  • Key:鎖名稱
  • Mode:鎖的模式
    1. LockMode.XLock:獨占鎖會排斥其他資源請求此鎖,須等待資源釋放.
    2. LockMode.Shared:Shared lock之間不互斥.
  • Order:因為支援多個LockAttribute,此屬性決定執行此方法前要求鎖順序
1
2
3
4
5
6
7
8
9
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public sealed class LockAttribute : Attribute
{
public string Key { get; set; }

public LockMode Mode { get; set; } = LockMode.XLock;

public int Order { get; set; }
}

LockerInterceptor

我們直接來IInterceptor物件最核心邏輯方法Intercept(IInvocation invocation).

利用GetCustomAttributes取得方法上所有LockAttribute並在方法執行前要求拿到,所需要Lock資源才可以執行方法,最後在finally時釋放lock資源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public void Intercept(IInvocation invocation)
{
var methodName = invocation.Method.Name;
var lockAttributes = invocation.Method.GetCustomAttributes(typeof(LockAttribute), true) as LockAttribute[];

if (IsMarkLockLockAttribute(lockAttributes))
{
var lockProviders = GetLockProviders(lockAttributes);
try
{
foreach (var lockProvider in lockProviders)
{
lockProvider.AddLock();
}
invocation.Proceed();
}
catch (Exception e)
{
_log.Exception("Something wrong!", e);
throw e;
}
finally {
foreach (var lockProvider in lockProviders)
{
lockProvider.ReleaseLock();
}
_log.Info($"{DateTime.Now:HH:mm:ss fff} {methodName} Release Lock");
}
}
else
{
invocation.Proceed();
}

}

SampleCode

我利用Nunit寫一個簡單程式Print在console讓我們方便觀看結果.

  • MethodA:Shared lock mode on key A
  • MethodA1:X lock mode on key A
  • MethodB_A:X lock mode on key A and B
  • MethodB:X lock mode on key B

理論上MethodB只會對於MethodB_A互斥,MethodB並不會跟MethodA,MethodA1有互斥反應.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
[Intercept(typeof(LockerInterceptor))]
public class LockerContext : ILockerContext
{
[Lock(Key = "A", Mode = LockMode.SharedLock)]
public virtual void MethodA ()
{
Thread.Sleep(100);
Console.WriteLine($"{DateTime.Now:HH:mm:ss fff} MethodA Done");
Thread.Sleep(100);
}

[Lock(Key = "A")]
public virtual void MethodA1()
{
Thread.Sleep(100);
Console.WriteLine($"{DateTime.Now:HH:mm:ss fff} MethodA1 Done");
Thread.Sleep(100);
}

[Lock(Key = "A")]
[Lock(Key = "B")]
public virtual void MethodB_A()
{
Thread.Sleep(100);
Console.WriteLine($"{DateTime.Now:HH:mm:ss fff} MethodB_A Done");
Thread.Sleep(100);
}

[Lock(Key = "B")]
public virtual void MethodB()
{
Thread.Sleep(100);
Console.WriteLine($"{DateTime.Now:HH:mm:ss fff} MethodB Done");
Thread.Sleep(100);
}
}

public interface ILockerContext
{
void MethodA();
void MethodA1();

void MethodB_A();

void MethodB();
}

public class AutofacConfig
{
public static IContainer Container { get; set; }

public static void Register()
{
ContainerBuilder builder = new ContainerBuilder();
builder.RegisterType<LockerInterceptor>().AsSelf();
builder.RegisterType<LockerContext>().As<ILockerContext>().EnableClassInterceptors();
builder.RegisterType<ConsoleProvider>().As<ISysLog>().SingleInstance();
Container = builder.Build();
}
}

[TestFixture]
public class LockerTest
{
//You can use the following additional attributes as you write your tests:
[OneTimeSetUp]
public void OneTimeSetUp()
{
AutofacConfig.Register();
}

[Test]
public void LockGroupTest()
{
var lockerContext= AutofacConfig.Container.Resolve<ILockerContext>();

List<Task> taskList =new List<Task>();

for (int i = 0; i < 10; i++)
{
taskList.Add(Task.Factory.StartNew(() => { lockerContext.MethodA(); }));
taskList.Add(Task.Factory.StartNew(() => { lockerContext.MethodA1(); }));
taskList.Add(Task.Factory.StartNew(() => { lockerContext.MethodB_A(); }));
taskList.Add(Task.Factory.StartNew(() => { lockerContext.MethodB(); }));

}

Task.WaitAll(taskList.ToArray());

}
}

Result

我們發現MethodB_A這個方法會對於,所有key是A,B方法互斥,MethodA則不會對於MethodA,MethodB互斥.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
14:26:59 815 MethodA1 Done
14:26:59 816 MethodB Done
14:26:59 919 MethodA1 Release Lock
14:26:59 920 MethodB Release Lock
14:27:00 020 MethodB Done
14:27:00 120 MethodB Release Lock
14:27:00 220 MethodB_A Done
14:27:00 320 MethodB_A Release Lock
14:27:00 420 MethodA1 Done
14:27:00 520 MethodA1 Release Lock
14:27:00 620 MethodB_A Done
14:27:00 720 MethodB_A Release Lock
14:27:00 820 MethodB Done
14:27:00 820 MethodA1 Done
14:27:00 920 MethodB Release Lock
14:27:00 920 MethodA1 Release Lock
14:27:01 020 MethodB_A Done
14:27:01 120 MethodB_A Release Lock
14:27:01 220 MethodB_A Done
14:27:01 320 MethodB_A Release Lock
14:27:01 420 MethodB Done
14:27:01 420 MethodA1 Done
14:27:01 520 MethodB Release Lock
14:27:01 520 MethodA1 Release Lock
14:27:01 620 MethodA Done
14:27:01 620 MethodA Done
14:27:01 620 MethodA Done
14:27:01 620 MethodA Done
14:27:01 620 MethodA Done
14:27:01 720 MethodA Release Lock
14:27:01 720 MethodA Release Lock
14:27:01 720 MethodA Release Lock
14:27:01 720 MethodA Release Lock
14:27:01 720 MethodA Release Lock
14:27:01 820 MethodB Done
14:27:01 920 MethodB Release Lock
14:27:02 020 MethodB Done
14:27:02 120 MethodB Release Lock
14:27:02 220 MethodB_A Done
14:27:02 320 MethodB_A Release Lock
14:27:02 420 MethodA1 Done
14:27:02 520 MethodA1 Release Lock
14:27:02 620 MethodB_A Done
14:27:02 720 MethodB_A Release Lock
14:27:02 820 MethodB_A Done
14:27:02 920 MethodB_A Release Lock
14:27:03 020 MethodA1 Done
14:27:03 020 MethodB Done
14:27:03 120 MethodA1 Release Lock
14:27:03 120 MethodB Release Lock
14:27:03 220 MethodA1 Done
14:27:03 220 MethodB Done
14:27:03 320 MethodA1 Release Lock
14:27:03 320 MethodB Release Lock
14:27:03 420 MethodB_A Done
14:27:03 520 MethodB_A Release Lock
14:27:03 620 MethodA1 Done
14:27:03 720 MethodA1 Release Lock
14:27:03 820 MethodB_A Done
14:27:03 920 MethodB_A Release Lock
14:27:04 020 MethodA1 Done
14:27:04 020 MethodB Done
14:27:04 120 MethodA1 Release Lock
14:27:04 120 MethodB Release Lock
14:27:04 220 MethodA Done
14:27:04 220 MethodA Done
14:27:04 220 MethodA Done
14:27:04 220 MethodA Done
14:27:04 320 MethodA Release Lock
14:27:04 320 MethodA Release Lock
14:27:04 320 MethodA Release Lock
14:27:04 320 MethodA Release Lock
14:27:04 420 MethodB Done
14:27:04 520 MethodB Release Lock
14:27:04 620 MethodB_A Done
14:27:04 720 MethodB_A Release Lock
14:27:04 820 MethodA1 Done
14:27:04 920 MethodA1 Release Lock
14:27:05 020 MethodA Done
14:27:05 120 MethodA Release Lock

小結

本篇主要想要介紹使用Lock機制.

利用ReaderWriterLockSlim就可以建立如DB lock,實在非常方便.

假如想要細部了解Autofac + Interceptors(AOP) 動態代理可以參考我之前寫文章,這裡我就不多敘述了.

SourceCode LockService

此文作者:Daniel Shih(石頭)
此文地址https://isdaniel.github.io/aop-lock-mechanism/
版權聲明:本博客所有文章除特別聲明外,均採用 CC BY-NC-SA 3.0 TW 許可協議。轉載請註明出處!


如果本文對您幫助很大,可街口支付斗內鼓勵石頭^^