本文主要是介紹Go,從語言對比分析的角度切入。之因此選擇與Python、Erlang對比,是由於作爲高級語言,它們語言特性上有較大的類似性,不過最主要的緣由是這幾個我比較熟悉。html
Go的不少語言特性借鑑與它的三個祖先:C,Pascal和CSP。Go的語法、數據類型、控制流等繼承於C,Go的包、面對對象等思想來源於Pascal分支,而Go最大的語言特點,基於管道通訊的協程併發模型,則借鑑於CSP分支。程序員
Go/Python/Erlang語言特性對比
如《編程語言與範式》一文所說,無論語言如何層出不窮,全部語言的設計離不開2個基本面:控制流和數據類型。爲了提高語言描述能力,語言通常都提供控制抽象和數據抽象。本小節的語言特性對比也從這4個維度入手,詳見下圖(點擊見大圖)。golang
圖中咱們能夠看出,相比於Python的40個特性,Go只有31個,能夠說Go在語言設計上是至關剋制的。好比,它沒有隱式的數值轉換,沒有構造函數和析構函數,沒有運算符重載,沒有默認參數,也沒有繼承,沒有泛型,沒有異常,沒有宏,沒有函數修飾,更沒有線程局部存儲。算法
可是Go的特色也很鮮明,好比,它擁有協程、自動垃圾回收、包管理系統、一等公民的函數、棧空間管理等。編程
Go做爲靜態類型語言,保證了Go在運行效率、內存用量、類型安全都要強於Python和Erlang。數組
Go的數據類型也更加豐富,除了支持表、字典等複雜的數據結構,還支持指針和接口類型,這是Python和Erlang所沒有的。特別是接口類型特別強大,它提供了管理類型系統的手段。而指針類型提供了管理內存的手段,這讓Go進入底層軟件開發提供了強有力的支持。緩存
Go在面對對象的特性支持上作了不少反思和取捨,它沒有類、虛函數、繼承、泛型等特性。Go語言中面向對象編程的核心是組合和方法(function)。組合很相似於C語言的struct結構體的組合方式,方法相似於Java的接口(Interface),可是使用方法上與對象更加解耦,減小了對對象內部的侵入。Erlang則不支持面對對象編程範式,相比而言,Python對面對對象範式的支持最爲全面。安全
在函數式編程的特性支持上,Erlang做爲函數式語言,支持最爲全面。可是基本的函數式語言特性,如lambda、高階函數、curry等,三種語言都支持。服務器
控制流的特性支持上,三種語言都差很少。Erlang支持尾遞歸優化,這給它在函數式編程上帶來便利。而Go在經過動態擴展協程棧的方式來支持深度遞歸調用。Python則在深度遞歸調用上常常被爆棧。數據結構
Go和Erlang的併發模型都來源於CSP,可是Erlang是基於actor和消息傳遞(mailbox)的併發實現,Go是基於goroutine和管道(channel)的併發實現。無論Erlang的actor仍是Go的goroutine,都知足協程的特色:由編程語言實現和調度,切換在用戶態完成,建立銷燬開銷很小。至於Python,其多線程的切換和調度是基於操做系統實現,並且由於GIL的大坑級存在,沒法真正作到並行。
並且從筆者的併發編程體驗上看,Erlang的函數式編程語法風格和其OTP behavior框架提供的晦澀的回調(callback)使用方法,對大部分的程序員,如C/C++和Java出身的程序員來講,有必定的入門門檻和挑戰。而被稱爲「互聯網時代的C」的Go,其類C的語法和控制流,以及面對對象的編程範式,編程體驗則好不少。
Go/Python/Erlang語言語法對比
全部的語言特性都須要有形式化的表示方式,Go、Python、Erlang三種語言語法的詳細對好比下(點擊見完整大圖第一部分,第二部分,第三部分)。這裏(連接)有一個詳細的Go 與 C 的語法對比,這也是我沒有作Go vs. C對比的一個緣由。
正如Go語言的設計者之一Rob Pike所說,「軟件的複雜性是乘法級相關的」。這充分體如今語言關鍵詞(keyword)數量的控制上,Go的關鍵詞是最少的,只有25個,而Erlang是27個,Python是31個。從根本上保證了Go語言的簡單易學。
Go語言將數據類型分爲四類:基礎類型、複合類型、引用類型和接口類型。基礎類型包括:整型、浮點型、複數、字符串和布爾型。複合數據類型有數組和結構體。引用類型包括指針、切片、字典、函數、通道。其餘數據類型,如原子(atom)、比特(binary)、元組(tuple)、集合(set)、記錄(record),Go則沒有支持。
Go對C語言的不少語法特性作了改良,正如Rob Pike在《Less is Exponentially More》中提到,Go的「起點: C語言,解決一些明顯的瑕疵、刪除雜質、增長一些缺乏的特性。」,好比,switch/case的case子程序段默認break跳出,case語句支持數值範圍、條件判斷語句;全部類型默認初始化爲0,沒有未初始化變量;把類型放在變量後面的聲明語法(連接),使複雜聲明更加清晰易懂;沒有頭文件,文件的編譯以包組織,改善封裝能力;用空接口(interface {})代替void *,提升類型系統能力等等。
Go對函數,方法,接口作了清晰的區分。與Erlang相似,Go的函數做爲第一公民。函數可讓咱們將一個語句序列打包爲一個單元,而後能夠從程序中其它地方屢次調用。函數和方法的區別是指有沒有接收器,而不像其餘語言那樣是指有沒有返回值。接口類型具體描述了一系列方法的集合,而空接口interfac{}表示能夠接收任意類型。接口的這2中使用方式,用面對對象編程範式來類比的話,能夠類比於subtype polymorphism(子類型多態)和ad hoc polymorphism(非參數多態)。
從圖中示例能夠看出,Go的goroutine就是一個函數,以及在堆上爲其分配的一個堆棧。因此其系統開銷很小,能夠輕鬆的建立上萬個goroutine,而且它們並非被操做系統所調度執行。goroutine只能使用channel來發送給指定的goroutine請求來查詢更新變量。這也就是Go的口頭禪「不要使用共享數據來通訊,使用通訊來共享數據」。channel支持容量限制和range迭代器。
Go/Python/Erlang語言詞法對比
Go、Python、Erlang三種語言詞法符號的詳細對好比下(點擊見完整大圖)。Go的詞法符號是3個語言中最多的,有41個,並且符號複用的狀況也較多。相對來講,Python最少,只有31個。
Go語言在詞法和代碼格式上採起了很強硬的態度。Go語言只有一種控制可見性的手段:大寫首字母的標識符會從定義它們的包中被導出,小寫字母的則不會。這種限制包內成員的方式一樣適用於struct或者一個類型的方法。
在文件命名上,Go也有必定的規範要求,如以_test.go爲後綴名的源文件是測試文件,它們是go test測試的一部分;測試文件中以Test爲函數名前綴的函數是測試函數,用於測試程序的一些邏輯行爲是否正確;以Benchmark爲函數名前綴的函數是基準測試函數,它們用於衡量一些函數的性能。
除了關鍵字,此外,Go還有大約30多個預約義的名字,好比int和true等,主要對應內建的常量、類型和函數。
TDD Go編程示例
本小節以TDD方式4次重構開發一個斐波那契算法的方式,來簡單展現Go的特性、語法和使用方式,如Go的單元測試技術,併發編程、匿名函數、閉包等。
首先,看一下TDD最終造成的單元測試文件:
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
|
package
main
import
(
"testing"
)
func
TestFib(t *testing.T) {
var
testdatas = []
struct
{
n int
want int64
}{
{0, 0},
{1, 1},
{2, 1},
{3, 2},
{4, 3},
{16, 987},
{32, 2178309},
{45, 1134903170},
}
for
_, test :=
range
testdatas {
n := test.n
want := test.want
got := fib(n)
if
got != want {
t.Errorf(
"fib(%d)=%d, want %d\n"
, n, got, want)
}
}
}
|
基於遞歸的實現方案:
1
2
3
4
5
6
7
|
func
fib1(n int) int64 {
if
n == 0 || n == 1 {
return
int64(n)
}
return
fib1(n-1) + fib1(n-2)
}
|
測試結果:
crbsp@fib$ time go test
PASS
ok _/home/crbsp/alex/go/fib 9.705sreal 0m10.045s
user 0m9.968s
sys 0m0.068s
基於goroutine實現的併發方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
func
fib2(n int) int64 {
var
got int64
var
channel = make(
chan
int64, 2)
if
n == 0 || n == 1 {
return
int64(n)
}
runtime.GOMAXPROCS(2)
go
func
() { channel <- fib1(n - 2) }()
go
func
() { channel <- fib1(n - 1) }()
got = <-channel
got += <-channel
return
got
}
|
測試結果:
crbsp@fib$ time go test
PASS
ok _/home/crbsp/alex/go/fib 6.118sreal 0m6.674s
user 0m10.268s
sys 0m0.148s
基於迭代的實現方案:
1
2
3
4
5
6
7
8
9
|
func
fib3(n int) int64 {
var
a, b int64
a, b = 0, 1
for
i := 0; i < n; i++ {
a, b = b, a+b
}
return
a
}
|
測試結果:
crbsp@fib$ time go test
PASS
ok _/home/crbsp/alex/go/fib 0.002sreal 0m0.547s
user 0m0.328s
sys 0m0.172s
基於閉包的實現方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
func
fibWrapper4()
func
() int64 {
var
a, b int64
a, b = 0, 1
return
func
() int64 {
a, b = b, a+b
return
a
}
}
func
fib4(n int) int64 {
var
got int64
got = 0
f := fibWrapper4()
for
i := 0; i < n; i++ {
got = f()
}
return
got
}
|
測試結果:
crbsp@fib$ time go test
PASS
ok _/home/crbsp/alex/go/fib 0.002sreal 0m0.411s
user 0m0.260s
sys 0m0.140s
--完--
基於RabbitMQ.Client組件實現RabbitMQ可複用的 ConnectionPool(鏈接池)
1、本文產生起因:
以前文章《總結消息隊列RabbitMQ的基本用法》已對RabbitMQ的安裝、用法都作了詳細說明,而本文主要是針對在高併發且單次從RabbitMQ中消費消息時,出現了鏈接數不足、鏈接響應較慢、RabbitMQ服務器崩潰等各類性能問題的解方案,之因此會出現我列舉的這些問題,究基根源,實際上是TCP鏈接建立與斷開太過頻繁所致,這與咱們使用ADO.NET來訪問常規的關係型DB(如:SQL SERVER、MYSQL)有所不一樣,在訪問DB時,咱們通常都建議你們使用using包裹,目的是每次建立完DB鏈接,使用完成後自動釋放鏈接,避免沒必要要的鏈接數及資源佔用。可能有人會問,爲什麼訪問DB,能夠每次建立再斷開鏈接,都沒有問題,而一樣訪問MQ(本文所指的MQ均是RabbitMQ),每次建立再斷開鏈接,若是在高併發且建立與斷開頻率高的時候,會出現性能問題呢?其實若是瞭解了DB的鏈接建立與斷開以及MQ的鏈接建立與斷開原理就知道其中的區別了。這裏我簡要說明一下,DB鏈接與MQ鏈接 其實底層都是基於TCP鏈接,建立TCP鏈接確定是有資源消耗的,是很是昂貴的,原則上儘量少的去建立與斷開TCP鏈接,DB建立鏈接、MQ建立鏈接能夠說是同樣的,但在斷開銷燬鏈接上就有很大的不一樣,DB建立鏈接再斷開時,默認狀況下是把該鏈接回收到鏈接池中,下次若是再有DB鏈接建立請求,則先判斷DB鏈接池中是否有空閒的鏈接,如有則直接複用,若沒有才建立鏈接,這樣就達到了TCP鏈接的複用,而MQ建立鏈接都是新建立的TCP鏈接,斷開時則直接斷開TCP鏈接,簡單粗暴,看似資源清理更完全,但若在高併發高頻率每次都從新建立與斷開MQ鏈接,則性能只會愈來愈差(上面說過TCP鏈接是很是昂貴的),我在公司項目中就出現了該問題,後面在技術總監的指導下,對MQ的鏈接建立與斷開做了優化,實現了相似DB鏈接池的概念。
鏈接池,故名思義,鏈接的池子,全部的鏈接做爲一種資源集中存放在池中,須要使用時就能夠到池中獲取空閒鏈接資源,用完後再放回池中,以此達到鏈接資源的有效重用,同時也控制了資源的過分消耗與浪費(資源多少取決於池子的容量)
2、源代碼奉獻(可直接複製應用到你們的項目中)
下面就先貼出實現MQHelper(含鏈接池)的源代碼:
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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
|
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
using
System.Threading.Tasks;
using
RabbitMQ.Util;
using
RabbitMQ.Client;
using
RabbitMQ.Client.Events;
using
System.Web.Caching;
using
System.Web;
using
System.Configuration;
using
System.IO;
using
System.Collections.Concurrent;
using
System.Threading;
using
System.Runtime.CompilerServices;
namespace
Zuowj.Core
{
public
class
MQHelper
{
private
const
string
CacheKey_MQConnectionSetting =
"MQConnectionSetting"
;
private
const
string
CacheKey_MQMaxConnectionCount =
"MQMaxConnectionCount"
;
private
readonly
static
ConcurrentQueue<IConnection> FreeConnectionQueue;
//空閒鏈接對象隊列
private
readonly
static
ConcurrentDictionary<IConnection,
bool
> BusyConnectionDic;
//使用中(忙)鏈接對象集合
private
readonly
static
ConcurrentDictionary<IConnection,
int
> MQConnectionPoolUsingDicNew;
//鏈接池使用率
private
readonly
static
Semaphore MQConnectionPoolSemaphore;
private
readonly
static
object
freeConnLock =
new
object
(), addConnLock =
new
object
();
private
static
int
connCount = 0;
public
const
int
DefaultMaxConnectionCount = 30;
//默認最大保持可用鏈接數
public
const
int
DefaultMaxConnectionUsingCount = 10000;
//默認最大鏈接可訪問次數
private
static
int
MaxConnectionCount
{
get
{
if
(HttpRuntime.Cache[CacheKey_MQMaxConnectionCount] !=
null
)
{
return
Convert.ToInt32(HttpRuntime.Cache[CacheKey_MQMaxConnectionCount]);
}
else
{
int
mqMaxConnectionCount = 0;
string
mqMaxConnectionCountStr = ConfigurationManager.AppSettings[CacheKey_MQMaxConnectionCount];
if
(!
int
.TryParse(mqMaxConnectionCountStr,
out
mqMaxConnectionCount) || mqMaxConnectionCount <= 0)
{
mqMaxConnectionCount = DefaultMaxConnectionCount;
}
string
appConfigPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory,
"App.config"
);
HttpRuntime.Cache.Insert(CacheKey_MQMaxConnectionCount, mqMaxConnectionCount,
new
CacheDependency(appConfigPath));
return
mqMaxConnectionCount;
}
}
}
/// <summary>
/// 創建鏈接
/// </summary>
/// <param name="hostName">服務器地址</param>
/// <param name="userName">登陸帳號</param>
/// <param name="passWord">登陸密碼</param>
/// <returns></returns>
private
static
ConnectionFactory CrateFactory()
{
var
mqConnectionSetting = GetMQConnectionSetting();
var
connectionfactory =
new
ConnectionFactory();
connectionfactory.HostName = mqConnectionSetting[0];
connectionfactory.UserName = mqConnectionSetting[1];
connectionfactory.Password = mqConnectionSetting[2];
if
(mqConnectionSetting.Length > 3)
//增長端口號
{
connectionfactory.Port = Convert.ToInt32(mqConnectionSetting[3]);
}
return
connectionfactory;
}
private
static
string
[] GetMQConnectionSetting()
{
string
[] mqConnectionSetting =
null
;
if
(HttpRuntime.Cache[CacheKey_MQConnectionSetting] ==
null
)
{
//MQConnectionSetting=Host IP|;userid;|;password
string
mqConnSettingStr = ConfigurationManager.AppSettings[CacheKey_MQConnectionSetting];
if
(!
string
.IsNullOrWhiteSpace(mqConnSettingStr))
{
mqConnSettingStr = EncryptUtility.Decrypt(mqConnSettingStr);
//解密MQ鏈接字符串,若項目中無此需求可移除,EncryptUtility是一個AES的加解密工具類,你們網上可自行查找
if
(mqConnSettingStr.Contains(
";|;"
))
{
mqConnectionSetting = mqConnSettingStr.Split(
new
[] {
";|;"
}, StringSplitOptions.RemoveEmptyEntries);
}
}
if
(mqConnectionSetting ==
null
|| mqConnectionSetting.Length < 3)
{
throw
new
Exception(
"MQConnectionSetting未配置或配置不正確"
);
}
string
appConfigPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory,
"App.config"
);
HttpRuntime.Cache.Insert(CacheKey_MQConnectionSetting, mqConnectionSetting,
new
CacheDependency(appConfigPath));
}
else
{
mqConnectionSetting = HttpRuntime.Cache[CacheKey_MQConnectionSetting]
as
string
[];
}
return
mqConnectionSetting;
}
public
static
IConnection CreateMQConnection()
{
var
factory = CrateFactory();
factory.AutomaticRecoveryEnabled =
true
;
//自動重連
var
connection = factory.CreateConnection();
connection.AutoClose =
false
;
return
connection;
}
static
MQHelper()
{
FreeConnectionQueue =
new
ConcurrentQueue<IConnection>();
BusyConnectionDic =
new
ConcurrentDictionary<IConnection,
bool
>();
MQConnectionPoolUsingDicNew =
new
ConcurrentDictionary<IConnection,
int
>();
//鏈接池使用率
MQConnectionPoolSemaphore =
new
Semaphore(MaxConnectionCount, MaxConnectionCount,
"MQConnectionPoolSemaphore"
);
//信號量,控制同時併發可用線程數
}
public
static
IConnection CreateMQConnectionInPoolNew()
{
SelectMQConnectionLine:
MQConnectionPoolSemaphore.WaitOne();
//當<MaxConnectionCount時,會直接進入,不然會等待直到空閒鏈接出現
IConnection mqConnection =
null
;
if
(FreeConnectionQueue.Count + BusyConnectionDic.Count < MaxConnectionCount)
//若是已有鏈接數小於最大可用鏈接數,則直接建立新鏈接
{
lock
(addConnLock)
{
if
(FreeConnectionQueue.Count + BusyConnectionDic.Count < MaxConnectionCount)
{
mqConnection = CreateMQConnection();
BusyConnectionDic[mqConnection] =
true
;
//加入到忙鏈接集合中
MQConnectionPoolUsingDicNew[mqConnection] = 1;
// BaseUtil.Logger.DebugFormat("Create a MQConnection:{0},FreeConnectionCount:{1}, BusyConnectionCount:{2}", mqConnection.GetHashCode().ToString(), FreeConnectionQueue.Count, BusyConnectionDic.Count);
return
mqConnection;
}
}
}
if
(!FreeConnectionQueue.TryDequeue(
out
mqConnection))
//若是沒有可用空閒鏈接,則從新進入等待排隊
{
// BaseUtil.Logger.DebugFormat("no FreeConnection,FreeConnectionCount:{0}, BusyConnectionCount:{1}", FreeConnectionQueue.Count, BusyConnectionDic.Count);
goto
SelectMQConnectionLine;
}
else
if
(MQConnectionPoolUsingDicNew[mqConnection] + 1 > DefaultMaxConnectionUsingCount || !mqConnection.IsOpen)
//若是取到空閒鏈接,判斷是否使用次數是否超過最大限制,超過則釋放鏈接並從新建立
{
mqConnection.Close();
mqConnection.Dispose();
// BaseUtil.Logger.DebugFormat("close > DefaultMaxConnectionUsingCount mqConnection,FreeConnectionCount:{0}, BusyConnectionCount:{1}", FreeConnectionQueue.Count, BusyConnectionDic.Count);
mqConnection = CreateMQConnection();
MQConnectionPoolUsingDicNew[mqConnection] = 0;
// BaseUtil.Logger.DebugFormat("create new mqConnection,FreeConnectionCount:{0}, BusyConnectionCount:{1}", FreeConnectionQueue.Count, BusyConnectionDic.Count);
}
BusyConnectionDic[mqConnection] =
true
;
//加入到忙鏈接集合中
MQConnectionPoolUsingDicNew[mqConnection] = MQConnectionPoolUsingDicNew[mqConnection] + 1;
//使用次數加1
// BaseUtil.Logger.DebugFormat("set BusyConnectionDic:{0},FreeConnectionCount:{1}, BusyConnectionCount:{2}", mqConnection.GetHashCode().ToString(), FreeConnectionQueue.Count, BusyConnectionDic.Count);
return
mqConnection;
}
private
static
void
ResetMQConnectionToFree(IConnection connection)
{
lock
(freeConnLock)
{
bool
result =
false
;
if
(BusyConnectionDic.TryRemove(connection,
out
result))
//從忙隊列中取出
{
// BaseUtil.Logger.DebugFormat("set FreeConnectionQueue:{0},FreeConnectionCount:{1}, BusyConnectionCount:{2}", connection.GetHashCode().ToString(), FreeConnectionQueue.Count, BusyConnectionDic.Count);
}
else
{
// BaseUtil.Logger.DebugFormat("failed TryRemove BusyConnectionDic:{0},FreeConnectionCount:{1}, BusyConnectionCount:{2}", connection.GetHashCode().ToString(), FreeConnectionQueue.Count, BusyConnectionDic.Count);
}
if
(FreeConnectionQueue.Count + BusyConnectionDic.Count > MaxConnectionCount)
//若是由於高併發出現極少機率的>MaxConnectionCount,則直接釋放該鏈接
{
connection.Close();
connection.Dispose();
}
else
{
FreeConnectionQueue.Enqueue(connection);
//加入到空閒隊列,以便持續提供鏈接服務
}
MQConnectionPoolSemaphore.Release();
//釋放一個空閒鏈接信號
//Interlocked.Decrement(ref connCount);
//BaseUtil.Logger.DebugFormat("Enqueue FreeConnectionQueue:{0},FreeConnectionCount:{1}, BusyConnectionCount:{2},thread count:{3}", connection.GetHashCode().ToString(), FreeConnectionQueue.Count, BusyConnectionDic.Count,connCount);
}
}
/// <summary>
/// 發送消息
/// </summary>
/// <param name="connection">消息隊列鏈接對象</param>
/// <typeparam name="T">消息類型</typeparam>
/// <param name="queueName">隊列名稱</param>
/// <param name="durable">是否持久化</param>
/// <param name="msg">消息</param>
/// <returns></returns>
public
static
string
SendMsg(IConnection connection,
string
queueName,
string
msg,
bool
durable =
true
)
{
try
{
using
(
var
channel = connection.CreateModel())
//創建通信信道
{
// 參數從前面開始分別意思爲:隊列名稱,是否持久化,獨佔的隊列,不使用時是否自動刪除,其餘參數
channel.QueueDeclare(queueName, durable,
false
,
false
,
null
);
var
properties = channel.CreateBasicProperties();
properties.DeliveryMode = 2;
//1表示不持久,2.表示持久化
if
(!durable)
properties =
null
;
var
body = Encoding.UTF8.GetBytes(msg);
channel.BasicPublish(
""
, queueName, properties, body);
}
return
string
.Empty;
}
catch
(Exception ex)
{
return
ex.ToString();
}
finally
{
ResetMQConnectionToFree(connection);
}
}
/// <summary>
/// 消費消息
/// </summary>
/// <param name="connection">消息隊列鏈接對象</param>
/// <param name="queueName">隊列名稱</param>
/// <param name="durable">是否持久化</param>
/// <param name="dealMessage">消息處理函數</param>
/// <param name="saveLog">保存日誌方法,可選</param>
public
static
void
ConsumeMsg(IConnection connection,
string
queueName,
bool
durable, Func<
string
, ConsumeAction> dealMessage, Action<
string
, Exception> saveLog =
null
)
{
try
{
using
(
var
channel = connection.CreateModel())
{
channel.QueueDeclare(queueName, durable,
false
,
false
,
null
);
//獲取隊列
channel.BasicQos(0, 1,
false
);
//分發機制爲觸發式
var
consumer =
new
QueueingBasicConsumer(channel);
//創建消費者
// 從左到右參數意思分別是:隊列名稱、是否讀取消息後直接刪除消息,消費者
channel.BasicConsume(queueName,
false
, consumer);
while
(
true
)
//若是隊列中有消息
{
ConsumeAction consumeResult = ConsumeAction.RETRY;
var
ea = (BasicDeliverEventArgs)consumer.Queue.Dequeue();
//獲取消息
string
message =
null
;
try
{
var
body = ea.Body;
message = Encoding.UTF8.GetString(body);
consumeResult = dealMessage(message);
}
catch
(Exception ex)
{
if
(saveLog !=
null
)
{
saveLog(message, ex);
}
}
if
(consumeResult == ConsumeAction.ACCEPT)
{
channel.BasicAck(ea.DeliveryTag,
false
);
//消息從隊列中刪除
}
else
if
(consumeResult == ConsumeAction.RETRY)
{
channel.BasicNack(ea.DeliveryTag,
false
,
true
);
//消息重回隊列
}
else
{
channel.BasicNack(ea.DeliveryTag,
false
,
false
);
//消息直接丟棄
}
}
}
}
catch
(Exception ex)
{
if
(saveLog !=
null
)
{
saveLog(
"QueueName:"
+ queueName, ex);
}
throw
ex;
}
finally
{
ResetMQConnectionToFree(connection);
}
}
/// <summary>
/// 依次獲取單個消息
/// </summary>
/// <param name="connection">消息隊列鏈接對象</param>
/// <param name="QueueName">隊列名稱</param>
/// <param name="durable">持久化</param>
/// <param name="dealMessage">處理消息委託</param>
public
static
void
ConsumeMsgSingle(IConnection connection,
string
QueueName,
bool
durable, Func<
string
, ConsumeAction> dealMessage)
{
try
{
using
(
var
channel = connection.CreateModel())
{
channel.QueueDeclare(QueueName, durable,
false
,
false
,
null
);
//獲取隊列
channel.BasicQos(0, 1,
false
);
//分發機制爲觸發式
uint
msgCount = channel.MessageCount(QueueName);
if
(msgCount > 0)
{
var
consumer =
new
QueueingBasicConsumer(channel);
//創建消費者
// 從左到右參數意思分別是:隊列名稱、是否讀取消息後直接刪除消息,消費者
channel.BasicConsume(QueueName,
false
, consumer);
ConsumeAction consumeResult = ConsumeAction.RETRY;
var
ea = (BasicDeliverEventArgs)consumer.Queue.Dequeue();
//獲取消息
try
{
var
body = ea.Body;
var
message = Encoding.UTF8.GetString(body);
consumeResult = dealMessage(message);
}
catch
(Exception ex)
{
throw
ex;
}
finally
{
if
(consumeResult == ConsumeAction.ACCEPT)
{
channel.BasicAck(ea.DeliveryTag,
false
);
//消息從隊列中刪除
}
else
if
(consumeResult == ConsumeAction.RETRY)
{
channel.BasicNack(ea.DeliveryTag,
false
,
true
);
//消息重回隊列
}
else
{
channel.BasicNack(ea.DeliveryTag,
false
,
false
);
//消息直接丟棄
}
}
}
else
{
dealMessage(
string
.Empty);
}
}
}
catch
(Exception ex)
{
throw
ex;
}
finally
{
ResetMQConnectionToFree(connection);
}
}
/// <summary>
/// 獲取隊列消息數
/// </summary>
/// <param name="connection"></param>
/// <param name="QueueName"></param>
/// <returns></returns>
public
static
int
GetMessageCount(IConnection connection,
string
QueueName)
{
int
msgCount = 0;
try
{
using
(
var
channel = connection.CreateModel())
{
channel.QueueDeclare(QueueName,
true
,
false
,
false
,
null
);
//獲取隊列
msgCount = (
int
)channel.MessageCount(QueueName);
}
}
catch
(Exception ex)
{
throw
ex;
}
finally
{
ResetMQConnectionToFree(connection);
}
return
msgCount;
}
}
public
enum
ConsumeAction
{
ACCEPT,
// 消費成功
RETRY,
// 消費失敗,能夠放回隊列從新消費
REJECT,
// 消費失敗,直接丟棄
}
}
|
如今對上述代碼的核心點做一個簡要的說明:
先說一下靜態構造函數:
FreeConnectionQueue 用於存放空閒鏈接對象隊列,爲什麼使用Queue,由於當我從中取出1個空閒鏈接後,空閒鏈接數就應該少1個,這個Queue很好知足這個需求,並且這個Queue是併發安全的Queue哦(ConcurrentQueue)
BusyConnectionDic 忙(使用中)鏈接對象集合,爲什麼這裏使用字典對象呢,由於當我用完後,須要可以快速的找出使用中的鏈接對象,並能快速移出,同時從新放入到空閒隊列FreeConnectionQueue ,達到鏈接複用
MQConnectionPoolUsingDicNew 鏈接使用次數記錄集合,這個只是輔助記錄鏈接使用次數,以即可以計算一個鏈接的已使用次數,當達到最大使用次數時,則應斷開從新建立
MQConnectionPoolSemaphore 這個是信號量,這是控制併發鏈接的重要手段,鏈接池的容量等同於這個信號量的最大可並行數,保證同時使用的鏈接數不超過鏈接池的容量,若超過則會等待;
具體步驟說明:
1.MaxConnectionCount:最大保持可用鏈接數(能夠理解爲鏈接池的容量),能夠經過CONFIG配置,默認爲30;
2.DefaultMaxConnectionUsingCount:默認最大鏈接可訪問次數,我這裏沒有使用配置,而是直接使用常量固定爲1000,你們如有須要能夠改爲從CONFIG配置,參考MaxConnectionCount的屬性設置(採起了依賴緩存)
3.CreateMQConnectionInPoolNew:從鏈接池中建立MQ鏈接對象,這個是核心方法,是實現鏈接池的地方,代碼中已註釋了重要的步驟邏輯,這裏說一下實現思路:
3.1 經過MQConnectionPoolSemaphore.WaitOne() 利用信號量的並行等待方法,若是當前併發超過信號量的最大並行度(也就是做爲鏈接池的最大容量),則須要等待空閒鏈接池,防止鏈接數超過池的容量,若是併發沒有超過池的容量,則能夠進入獲取鏈接的邏輯;
3.2FreeConnectionQueue.Count + BusyConnectionDic.Count < MaxConnectionCount,若是空閒鏈接隊列+忙鏈接集合的總數小於鏈接池的容量,則能夠直接建立新的MQ鏈接,不然FreeConnectionQueue.TryDequeue(out mqConnection) 嘗試從空閒鏈接隊列中獲取一個可用的空閒鏈接使用,若空閒鏈接都沒有,則須要返回到方法首行,從新等待空閒鏈接;
3.3MQConnectionPoolUsingDicNew[mqConnection] + 1 > DefaultMaxConnectionUsingCount || !mqConnection.IsOpen 若是取到空閒鏈接,則先判斷使用次數是否超過最大限制,超過則釋放鏈接或空閒鏈接已斷開鏈接也須要從新建立,不然該鏈接可用;
3.4BusyConnectionDic[mqConnection] = true;加入到忙鏈接集合中,MQConnectionPoolUsingDicNew[mqConnection] = MQConnectionPoolUsingDicNew[mqConnection] + 1; 使用次數加1,確保每使用一次鏈接,鏈接次數能記錄
4.ResetMQConnectionToFree:重置釋放鏈接對象,這個是保證MQ鏈接用完後可以回收到空閒鏈接隊列中(即:回到鏈接池中),而不是直接斷開鏈接,這個方法很簡單就不做做過多說明。
好了,都說明了如何實現含鏈接池的MQHelper,如今再來舉幾個例子來講明如何用:
3、實際應用(簡單易上手)
獲取並消費一個消息:
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
|
public
string
GetMessage(
string
queueName)
{
string
message =
null
;
try
{
var
connection = MQHelper.CreateMQConnectionInPoolNew();
MQHelper.ConsumeMsgSingle(connection, queueName,
true
, (msg) =>
{
message = msg;
return
ConsumeAction.ACCEPT;
});
}
catch
(Exception ex)
{
BaseUtil.Logger.Error(
string
.Format(
"MQHelper.ConsumeMsgSingle Error:{0}"
, ex.Message), ex);
message =
"ERROR:"
+ ex.Message;
}
//BaseUtil.Logger.InfoFormat("第{0}次請求,從消息隊列(隊列名稱:{1})中獲取消息值爲:{2}", Interlocked.Increment(ref requestCount), queueName, message);
return
message;
}
|
發送一個消息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public
string
SendMessage(
string
queueName,
string
msg)
{
string
result =
null
;
try
{
var
connection = MQHelper.CreateMQConnectionInPoolNew();
result = MQHelper.SendMsg(connection, queueName, msg);
}
catch
(Exception ex)
{
BaseUtil.Logger.Error(
string
.Format(
"MQHelper.SendMessage Error:{0}"
, ex.Message), ex);
result = ex.Message;
}
return
result;
}
|
獲取消息隊列消息數:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public
int
GetMessageCount(
string
queueName)
{
int
result = -1;
try
{
var
connection = MQHelper.CreateMQConnectionInPoolNew();
result = MQHelper.GetMessageCount(connection, queueName);
}
catch
(Exception ex)
{
BaseUtil.Logger.Error(
string
.Format(
"MQHelper.GetMessageCount Error:{0}"
, ex.Message), ex);
result = -1;
}
return
result;
}
|
這裏說一下:BaseUtil.Logger 是Log4Net的實例對象,另外上面沒有針對持續訂閱消費消息(ConsumeMsg)做說明,由於這個其實能夠不用鏈接池也不會有問題,由於它是一個持久訂閱並持久消費的過程,不會出現頻繁建立鏈接對象的狀況。
最後要說的是,雖然說代碼貼出來,你們一看就以爲很簡單,好像沒有什麼技術含量,但若是沒有完整的思路也仍是須要花費一些時間和精力的,代碼中核心是如何簡單高效的解決併發及鏈接複用的的問題,該MQHelper有通過壓力測試並順利在我司項目中使用,完美解決了以前的問題,因爲這個方案是我在公司通宵實現的,可能有一些方面的不足,你們能夠相互交流或完善後入到本身的項目中。
封裝一個基於NLog+NLog.Mongo的日誌記錄工具類LogUtil
封裝一個基於NLog+NLog.Mongo的日誌記錄工具類LogUtil,代碼比較簡單,主要是把MongoTarget的配置、FileTarget的配置集成到類中,同時利用緩存依賴來判斷是否須要從新建立Logger類,完整代碼以下:
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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
|
using
NLog;
using
NLog.Config;
using
NLog.Mongo;
using
NLog.Targets;
using
System;
using
System.Collections.Generic;
using
System.IO;
using
System.Linq;
using
System.Reflection;
using
System.Web;
using
System.Collections.Concurrent;
using
NLog.Targets.Wrappers;
/// <summary>
/// 日誌工具類(基於NLog.Mongo組件)
/// Author:左文俊
/// Date:2017/12/11
/// </summary>
public
class
LogUtil
{
private
NLog.Logger _Logger =
null
;
private
const
string
cacheKey_NLogConfigFlag =
"NLogConfigFlag"
;
private
const
string
defaultMongoDbName =
"SysLog"
;
private
static
readonly
object
syncLocker =
new
object
();
private
static
readonly
ConcurrentDictionary<
string
, LogUtil> cacheLogUitls =
new
ConcurrentDictionary<
string
, LogUtil>();
private
string
loggerCacheDependencyFilePath =
""
;
private
bool
needWriteLogToFile =
true
;
private
string
mongoDbName = defaultMongoDbName;
private
string
mongoDbCollectionName =
""
;
private
bool
asyncWriteLog =
true
;
public
static
LogUtil GetInstance(
string
mongoDbCollName,
string
loggerCacheDependencyFilePath =
null
,
bool
needWriteLogToFile =
true
)
{
string
key =
string
.Format(
"{0}_{1}"
, defaultMongoDbName, mongoDbCollName);
return
cacheLogUitls.GetOrAdd(key,
new
LogUtil()
{
LoggerCacheDependencyFilePath =
string
.IsNullOrEmpty(loggerCacheDependencyFilePath) ? HttpContext.Current.Server.MapPath(
"~/Web.config"
) : loggerCacheDependencyFilePath,
NeedWriteLogToFile = needWriteLogToFile,
MongoDbName = defaultMongoDbName,
MongoDbCollectionName = mongoDbCollName
});
}
public
string
LoggerCacheDependencyFilePath
{
get
{
return
loggerCacheDependencyFilePath;
}
set
{
if
(!File.Exists(value))
{
throw
new
FileNotFoundException(
"日誌配置緩存依賴文件不存在:"
+ value);
}
string
oldValue = loggerCacheDependencyFilePath;
loggerCacheDependencyFilePath = value;
PropertyChanged(oldValue, loggerCacheDependencyFilePath);
}
}
public
bool
NeedWriteLogToFile
{
get
{
return
needWriteLogToFile;
}
set
{
bool
oldValue = needWriteLogToFile;
needWriteLogToFile = value;
PropertyChanged(oldValue, needWriteLogToFile);
}
}
public
string
MongoDbCollectionName
{
get
{
return
mongoDbCollectionName;
}
set
{
string
oldValue = mongoDbCollectionName;
mongoDbCollectionName = value;
PropertyChanged(oldValue, mongoDbCollectionName);
}
}
/// <summary>
/// 同一個項目只會用一個DB,故不對外公開,取默認DB
/// </summary>
private
string
MongoDbName
{
get
{
return
mongoDbName;
}
set
{
string
oldValue = mongoDbName;
mongoDbName = value;
PropertyChanged(oldValue, mongoDbName);
}
}
public
bool
AsyncWriteLog
{
get
{
return
asyncWriteLog;
}
set
{
bool
oldValue = asyncWriteLog;
asyncWriteLog = value;
PropertyChanged(oldValue, asyncWriteLog);
}
}
private
void
PropertyChanged<T>(T oldValue, T newValue)
where
T : IEquatable<T>
{
if
(!oldValue.Equals(newValue) && _Logger !=
null
)
{
lock
(syncLocker)
{
_Logger =
null
;
}
}
}
private
Logger GetLogger()
{
if
(_Logger ==
null
|| HttpRuntime.Cache[cacheKey_NLogConfigFlag] ==
null
)
{
lock
(syncLocker)
{
if
(_Logger ==
null
|| HttpRuntime.Cache[cacheKey_NLogConfigFlag] ==
null
)
{
string
mongoDbConnectionSet = ConfigUtil.GetAppSettingValue(
"MongoDbConnectionSet"
);
if
(!
string
.IsNullOrEmpty(mongoDbConnectionSet))
{
mongoDbConnectionSet = AESDecrypt(mongoDbConnectionSet);
//解密字符串,若未加密則無需解密
}
LoggingConfiguration config =
new
LoggingConfiguration();
#region 配置MONGODB的日誌輸出對象
try
{
MongoTarget mongoTarget =
new
MongoTarget();
mongoTarget.ConnectionString = mongoDbConnectionSet;
mongoTarget.DatabaseName = mongoDbName;
mongoTarget.CollectionName = mongoDbCollectionName;
mongoTarget.IncludeDefaults =
false
;
AppendLogMongoFields(mongoTarget.Fields);
Target mongoTargetNew = mongoTarget;
if
(AsyncWriteLog)
{
mongoTargetNew = WrapWithAsyncTargetWrapper(mongoTarget);
//包裝爲異步輸出對象,以便實現異步寫日誌
}
LoggingRule rule1 =
new
LoggingRule(
"*"
, LogLevel.Debug, mongoTargetNew);
config.LoggingRules.Add(rule1);
}
catch
{ }
#endregion
#region 配置File的日誌輸出對象
if
(NeedWriteLogToFile)
{
try
{
FileTarget fileTarget =
new
FileTarget();
fileTarget.Layout =
@"[${date}] <${threadid}> - ${level} - ${event-context:item=Source} - ${event-context:item=UserID}: ${message};
StackTrace:${stacktrace};Other1:${event-context:item=Other1};Other2:${event-context:item=Other2};Other3:${event-context:item=Other3}"
;
string
procName = System.Diagnostics.Process.GetCurrentProcess().ProcessName;
fileTarget.FileName =
"${basedir}/Logs/"
+ procName +
".log"
;
fileTarget.ArchiveFileName =
"${basedir}/archives/"
+ procName +
".{#}.log"
;
fileTarget.ArchiveNumbering = ArchiveNumberingMode.DateAndSequence;
fileTarget.ArchiveAboveSize = 1024 * 1024 * 10;
fileTarget.ArchiveDateFormat =
"yyyyMMdd"
;
fileTarget.ArchiveEvery = FileArchivePeriod.Day;
fileTarget.MaxArchiveFiles = 30;
fileTarget.ConcurrentWrites =
true
;
fileTarget.KeepFileOpen =
false
;
fileTarget.Encoding = System.Text.Encoding.UTF8;
Target fileTargetNew = fileTarget;
if
(AsyncWriteLog)
{
fileTargetNew = WrapWithAsyncTargetWrapper(fileTarget);
//包裝爲異步輸出對象,以便實現異步寫日誌
}
LoggingRule rule2 =
new
LoggingRule(
"*"
, LogLevel.Debug, fileTargetNew);
config.LoggingRules.Add(rule2);
}
catch
{ }
}
#endregion
LogManager.Configuration = config;
_Logger = LogManager.GetCurrentClassLogger();
HttpRuntime.Cache.Insert(cacheKey_NLogConfigFlag,
"Nlog"
,
new
System.Web.Caching.CacheDependency(loggerCacheDependencyFilePath));
}
}
}
return
_Logger;
}
private
void
AppendLogMongoFields(IList<MongoField> mongoFields)
{
mongoFields.Clear();
Type logPropertiesType =
typeof
(SysLogInfo.LogProperties);
foreach
(
var
pro
in
typeof
(SysLogInfo).GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if
(pro.PropertyType == logPropertiesType)
continue
;
string
layoutStr =
string
.Empty;
//"${event-context:item=" + pro.Name + "}";
if
(pro.Name.Equals(
"ThreadID"
) || pro.Name.Equals(
"Level"
) || pro.Name.Equals(
"MachineName"
))
{
layoutStr =
"${"
+ pro.Name.ToLower() +
"}"
;
}
else
if
(pro.Name.Equals(
"LogDT"
))
{
layoutStr =
"${date:format=yyyy-MM-dd HH\\:mm\\:ss}"
;
}
else
if
(pro.Name.Equals(
"Msg"
))
{
layoutStr =
"${message}"
;
}
if
(!
string
.IsNullOrEmpty(layoutStr))
{
mongoFields.Add(
new
MongoField(pro.Name, layoutStr, pro.PropertyType.Name));
}
}
}
private
Target WrapWithAsyncTargetWrapper(Target target)
{
var
asyncTargetWrapper =
new
AsyncTargetWrapper();
asyncTargetWrapper.WrappedTarget = target;
asyncTargetWrapper.Name = target.Name;
target.Name = target.Name +
"_wrapped"
;
target = asyncTargetWrapper;
return
target;
}
private
LogEventInfo BuildLogEventInfo(LogLevel level,
string
msg,
string
source,
string
uid,
string
detailTrace =
null
,
string
other1 =
null
,
string
other2 =
null
,
string
other3 =
null
)
{
var
eventInfo =
new
LogEventInfo();
eventInfo.Level = level;
eventInfo.Message = msg;
eventInfo.Properties[
"DetailTrace"
] = detailTrace;
eventInfo.Properties[
"Source"
] = source;
eventInfo.Properties[
"Other1"
] = other1;
eventInfo.Properties[
"Other2"
] = other2;
eventInfo.Properties[
"Other3"
] = other3;
eventInfo.Properties[
"UserID"
] = uid;
return
eventInfo;
}
public
void
Info(
string
msg,
string
source,
string
uid,
string
detailTrace =
null
,
string
other1 =
null
,
string
other2 =
null
,
string
other3 =
null
)
{
try
{
var
eventInfo = BuildLogEventInfo(LogLevel.Info, msg, source, uid, detailTrace, other1, other2, other3);
var
logger = GetLogger();
logger.Log(eventInfo);
}
catch
{ }
}
public
void
Warn(
string
msg,
string
source,
string
uid,
string
detailTrace =
null
,
string
other1 =
null
,
string
other2 =
null
,
string
other3 =
null
)
{
try
{
var
eventInfo = BuildLogEventInfo(LogLevel.Warn, msg, source, uid, detailTrace, other1, other2, other3);
var
logger = GetLogger();
logger.Log(eventInfo);
}
catch
{ }
}
public
void
Error(
string
msg,
string
source,
string
uid,
string
detailTrace =
null
,
string
other1 =
null
,
string
other2 =
null
,
string
other3 =
null
)
{
try
{
var
eventInfo = BuildLogEventInfo(LogLevel.Error, msg, source, uid, detailTrace, other1, other2, other3);
var
logger = GetLogger();
logger.Log(eventInfo);
}
catch
{ }
}
public
void
Error(Exception ex,
string
source,
string
uid,
string
other1 =
null
,
string
other2 =
null
,
string
other3 =
null
)
{
try
{
var
eventInfo = BuildLogEventInfo(LogLevel.Error, ex.Message, source, uid, ex.StackTrace, other1, other2, other3);
var
logger = GetLogger();
logger.Log(eventInfo);
}
catch
{ }
}
public
void
Log(LogLevel level,
string
msg,
string
source,
string
uid,
string
detailTrace =
null
,
string
other1 =
null
,
string
other2 =
null
,
string
other3 =
null
)
{
try
{
var
eventInfo = BuildLogEventInfo(level, msg, source, uid, detailTrace, other1, other2, other3);
var
logger = GetLogger();
logger.Log(eventInfo);
}
catch
{ }
}
public
class
SysLogInfo
{
public
DateTime LogDT {
get
;
set
; }
public
int
ThreadID {
get
;
set
; }
public
string
Level {
get
;
set
; }
public
string
Msg {
get
;
set
; }
public
string
MachineName {
get
;
set
; }
public
LogProperties Properties {
get
;
set
; }
public
class
LogProperties
{
public
string
Source {
get
;
set
; }
public
string
DetailTrace {
get
;
set
; }
public
string
UserID {
get
;
set
; }
public
string
Other1 {
get
;
set
; }
public
string
Other2 {
get
;
set
; }
public
string
Other3 {
get
;
set
; }
}
}
}
|
封裝這個日誌工具類的目的就是爲了保證日誌格式的統一,同時能夠快速的複製到各個項目中使用,而省去須要配置文件或因配置文件修改致使日誌記錄信息不一致的狀況。
從代碼中能夠看出,若一旦屬性發生改變,則緩存標識會失效,意味着會從新生成Logger對象,這樣保證了Logger時刻與設置的規則相同。
另外一點就是異步日誌記錄功能AsyncWriteLog,若是是基於配置文件,則只須要更改配置文件targets中配置async="true"即爲異步。默認或寫false都爲同步,而代碼上如何實現異步網上並無介紹,我經過分析NLOG源代碼找到關鍵點,即經過AsyncTargetWrapper異步目標包裹器來包裝一次便可。
分享基於MemoryCache(內存緩存)的緩存工具類,C# B/S 、C/S項目都可以使用!
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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
|
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Runtime.Caching;
using
System.Text;
using
System.Threading.Tasks;
namespace
AutoLogisticsPH.Common.Utils
{
/// <summary>
/// 基於MemoryCache(內存緩存)的緩存工具類
/// Author:左文俊
/// Date:2017/12/11
/// </summary>
public
static
class
MemoryCacheUtil
{
private
static
readonly
Object _locker =
new
object
(), _locker2 =
new
object
();
/// <summary>
/// 取緩存項,若是不存在則返回空
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <returns></returns>
public
static
T GetCacheItem<T>(String key)
{
try
{
return
(T)MemoryCache.Default[key];
}
catch
{
return
default
(T);
}
}
/// <summary>
/// 是否包含指定鍵的緩存項
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public
static
bool
Contains(
string
key)
{
return
MemoryCache.Default.Contains(key);
}
/// <summary>
/// 取緩存項,若是不存在則新增緩存項
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="cachePopulate"></param>
/// <param name="slidingExpiration"></param>
/// <param name="absoluteExpiration"></param>
/// <returns></returns>
public
static
T GetOrAddCacheItem<T>(String key, Func<T> cachePopulate, TimeSpan? slidingExpiration =
null
, DateTime? absoluteExpiration =
null
)
{
if
(String.IsNullOrWhiteSpace(key))
throw
new
ArgumentException(
"Invalid cache key"
);
if
(cachePopulate ==
null
)
throw
new
ArgumentNullException(
"cachePopulate"
);
if
(slidingExpiration ==
null
&& absoluteExpiration ==
null
)
throw
new
ArgumentException(
"Either a sliding expiration or absolute must be provided"
);
if
(MemoryCache.Default[key] ==
null
)
{
lock
(_locker)
{
if
(MemoryCache.Default[key] ==
null
)
{
T cacheValue = cachePopulate();
if
(!
typeof
(T).IsValueType && ((
object
)cacheValue) ==
null
)
//若是是引用類型且爲NULL則不存緩存
{
return
cacheValue;
}
var
item =
new
CacheItem(key, cacheValue);
var
policy = CreatePolicy(slidingExpiration, absoluteExpiration);
MemoryCache.Default.Add(item, policy);
}
}
}
return
(T)MemoryCache.Default[key];
}
/// <summary>
/// 取緩存項,若是不存在則新增緩存項
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="cachePopulate"></param>
/// <param name="dependencyFilePath"></param>
/// <returns></returns>
public
static
T GetOrAddCacheItem<T>(String key, Func<T> cachePopulate,
string
dependencyFilePath)
{
if
(String.IsNullOrWhiteSpace(key))
throw
new
ArgumentException(
"Invalid cache key"
);
if
(cachePopulate ==
null
)
throw
new
ArgumentNullException(
"cachePopulate"
);
if
(MemoryCache.Default[key] ==
null
)
{
lock
(_locker2)
{
if
(MemoryCache.Default[key] ==
null
)
{
T cacheValue = cachePopulate();
if
(!
typeof
(T).IsValueType && ((
object
)cacheValue) ==
null
)
//若是是引用類型且爲NULL則不存緩存
{
return
cacheValue;
}
var
item =
new
CacheItem(key, cacheValue);
var
policy = CreatePolicy(dependencyFilePath);
MemoryCache.Default.Add(item, policy);
}
}
}
return
(T)MemoryCache.Default[key];
}
/// <summary>
/// 移除指定鍵的緩存項
/// </summary>
/// <param name="key"></param>
public
static
void
RemoveCacheItem(
string
key)
{
try
{
MemoryCache.Default.Remove(key);
}
catch
{ }
}
private
static
CacheItemPolicy CreatePolicy(TimeSpan? slidingExpiration, DateTime? absoluteExpiration)
{
var
policy =
new
CacheItemPolicy();
if
(absoluteExpiration.HasValue)
{
policy.AbsoluteExpiration = absoluteExpiration.Value;
}
else
if
(slidingExpiration.HasValue)
{
policy.SlidingExpiration = slidingExpiration.Value;
}
policy.Priority = CacheItemPriority.Default;
return
policy;
}
private
static
CacheItemPolicy CreatePolicy(
string
filePath)
{
CacheItemPolicy policy =
new
CacheItemPolicy();
policy.ChangeMonitors.Add(
new
HostFileChangeMonitor(
new
List<
string
>() { filePath }));
policy.Priority = CacheItemPriority.Default;
return
policy;
}
}
}
|
支持:可指定絕對過時時間、滑動過時明間、文件依賴 三種緩存方式,目前已在公司各類生產業務項目中有使用。優勢是能夠根據數據的使用頻率設置緩存有效期,特別是文件依賴緩存,好比:鏈接字符串讀取一次後,若CONFIG文件沒有改變,則緩存永久有效,一旦CONFIG更改,則緩存失效需從新讀取,保證數據緩存的最大可用性,減小沒必要要的屢次重複讀取CONFIG。使用示例很簡單:(以下:會在第一次讀取鏈接字符串並解密後返回給connstr變量,後續直接經過緩存KEY dbConnName直接返回鏈接字符串的結果,若修改了鏈接字符串的CONFIG文件,則緩存的項會失效,會從新讀取鏈接字符串並從新加入到緩存中) string connstr= MemoryCacheUtil.GetOrAddCacheItem(dbConnName, () => { var connStrSettings = ConfigUtil.GetConnectionString(dbConnName,dbConnectionStringConfigPath); string dbProdName = connStrSettings.ProviderName; string dbConnStr = connStrSettings.ConnectionString; return EncryptUtil.Decrypt(dbConnStr); }, "緩存依賴文件路路,如:c:\app\app.config");