🚫 Ad Blocker Detected

Please disable your AD blocker to continue using this site. Ads help us keep the content free! please press keyboard F5 to refresh page after disabled AD blocker

請關閉廣告攔截器以繼續使用本網站。廣告有助於我們保證內容免費。謝謝! 關閉後請按 F5 刷新頁面

0%

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 許可協議。轉載請註明出處!

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