最近在實(shí)現(xiàn)一個(gè)簡(jiǎn)單的審批功能般贼,涉及到一些事務(wù)的處理。一個(gè)管控臺(tái)項(xiàng)目骑科,我使用的是最簡(jiǎn)單的三層架構(gòu)橡淑。使用的是Ado.net操作數(shù)據(jù)庫(kù)。先看一段我們經(jīng)常用到的事務(wù)代碼:
SqlConnection con = new Sqlconnection("數(shù)據(jù)庫(kù)連接語(yǔ)句");
con.Open();
var trans = con.BeginTransaction();
try
{
SqlCommand com = new SqlCommand(trans);
//處理插入或更新邏輯
trans.Commit();
}catch(ex){
trans.Rollback();//如果前面有異常則事務(wù)回滾
}
finally
{
con.Close();
}
我一直以來(lái)都是使用下面這種方式在Dao來(lái)處理事務(wù)纵散,其實(shí)怎么看都覺(jué)得他別扭梳码,就拿我做的審批功能來(lái)說(shuō),當(dāng)前審批人通過(guò)之后需要生成一條審批記錄(記作表ApprovalHistory)伍掀,同時(shí)將當(dāng)前申請(qǐng)單的當(dāng)前審批人指向下一個(gè)處理者(記作Apply)掰茶,而一般的三層架構(gòu)都會(huì)有自身的Service層,理想的情況應(yīng)該是在Service層用事務(wù)來(lái)處理相應(yīng)的邏輯蜜笤。
正常的處理邏輯應(yīng)該是將事務(wù)提取出來(lái)濒蒋,應(yīng)用層不應(yīng)該過(guò)多的去關(guān)心底層的實(shí)現(xiàn)。亂糟糟的代碼寫在一起分層感覺(jué)也沒(méi)啥意思了把兔。如果可以像Spring那樣使用Annotation的方式就一行代碼來(lái)實(shí)現(xiàn)事務(wù)這樣不是很好嗎沪伙。這個(gè)問(wèn)題確實(shí)想了挺久,后來(lái)有位boss和我說(shuō)了下他的實(shí)現(xiàn)县好,喜出望外围橡!先貼貼代碼實(shí)現(xiàn)
Boss的實(shí)現(xiàn)
public class ConnId
{
private int mConnId = 0;
private DateTime mCreateTime = DateTime.Now;
public int MConnId
{
get { return this.mConnId; }
}
public ConnId(int connid)
{
this.mConnId = connid;
}
}
public class DbConnection
{
private static readonly string sConnStr = "從配置中讀取連接字符串";
public static readonly long MAX_LAST_TIME_LEN = 10 * 1000 * 1000 * 60;
private ConnId mConnId = null;
private SqlConnection mConn = null;
private SqlCommand mSqlCmd= null;
private DateTime mCreateTime = DateTime.Now;
private string mTransName = "";
public ConnId MConnId
{
get { return this.mConnId; }
}
public SqlConnection Connection
{
get { return this.mConn; }
}
public SqlCommand SqlCmd
{
get { return this.mSqlCmd; }
}
public ConnectionState State
{
get
{
if (mConn == null)
{
return ConnectionState.Broken;
}
else
{
return mConn.State;
}
}
}
public DbConnection()
{
}
internal ConnId ConnOpen(HttpRequest request)
{
try
{
this.mConn = new SqlConnection(sConnStr);
this.mSqlCmd = new SqlCommand();
mSqlCmd.Connection = this.mConn;
this.mConnId = new ConnId(this.GetHashCode());
mConn.Open();
if (request == null)
{
mTransName = "null";
}
else
{
mTransName = GetSrcFileName(request);
}
mSqlCmd.Transaction =
mConn.BeginTransaction(System.Data.IsolationLevel.ReadCommitted,
mTransName);
//log
}
catch (Exception e)
{
//log
if (this.mConn.State != System.Data.ConnectionState.Closed)
{
this.mConn.Close();
this.mConn.Dispose();
}
this.mConnId = null;
}
return this.mConnId;
}
internal ConnId ConnOpen(string src)
{
try
{
this.mConn = new SqlConnection(sConnStr);
this.mSqlCmd = new SqlCommand();
mSqlCmd.Connection = this.mConn;
this.mConnId = new ConnId(this.GetHashCode());
mConn.Open();
mTransName = src;
mSqlCmd.Transaction =
mConn.BeginTransaction(System.Data.IsolationLevel.ReadCommitted,
mTransName);
//log
}
catch (Exception e)
{
//log
if (this.mConn.State != System.Data.ConnectionState.Closed)
{
this.mConn.Close();
this.mConn.Dispose();
}
this.mConnId = null;
}
return this.mConnId;
}
internal Exception Cancel()
{
Exception ex = null;
try
{
//log
mSqlCmd.Transaction.Rollback();
}
catch (Exception e)
{
//log
mSqlCmd.Transaction.Rollback();
ex = e;
}
finally
{
if (mSqlCmd != null)
{
this.mSqlCmd.Dispose();
}
if (this.mConn.State != System.Data.ConnectionState.Closed)
{
this.mConn.Close();
}
memberClear();
}
return ex;
}
internal Exception Close()
{
Exception ex = null;
try
{
//log
mSqlCmd.Transaction.Commit();
}
catch (Exception e)
{
//log
mSqlCmd.Transaction.Rollback();
ex = e;
}
finally
{
if (mSqlCmd != null)
{
this.mSqlCmd.Dispose();
}
if (this.mConn.State != System.Data.ConnectionState.Closed)
{
this.mConn.Close();
this.mConn.Dispose();
}
memberClear();
}
return ex;
}
internal bool IfExpried()
{
if (mConnId != null)
{
if (mCreateTime != DateTime.MinValue)
{
DateTime now = DateTime.Now;
if (now.Ticks - this.mCreateTime.Ticks > MAX_LAST_TIME_LEN)
{
return true;
}
}
}
return false;
}
private void memberClear()
{
mConnId = null;
mConn = null;
mSqlCmd= null;
mCreateTime = DateTime.MinValue;
}
private string GetSrcFileName(HttpRequest r)
{
FileInfo fi = new FileInfo(r.PhysicalPath);
string filename = fi.Name.Replace(fi.Extension, "");
if (filename.Length > 32)
{
filename = filename.Substring(filename.Length - 32, 32);
}
return filename;
}
private string GetSrcFileName(string src)
{
return src.Substring(src.Length - 32, 32);
}
private string GetShortTime(DateTime t)
{
string str = t.Day.ToString() + "_" + t.Hour.ToString() + ":" +
t.Minute.ToString() + ":" + t.Second.ToString();
return str;
}
}
public class ConnPool
{
private static readonly object sLocker = new object();
private static Dictionary<ConnId, DbConnection> sConnList = new Dictionary<ConnId, DbConnection>(MAX_CONCURRENT_CONN_COUNT);
private static readonly int MAX_CONCURRENT_CONN_COUNT = 1000;
public static int sCount
{
get { return sConnList.Keys.Count; }
}
public static ConnId CreateConn(HttpRequest request)
{
DbConnection dbconn = null;
ConnId key = null;
try
{
dbconn = new DbConnection();
key = dbconn.ConnOpen(request);
if (sConnList.ContainsKey(key))
{
return key;
}
if (sCount < MAX_CONCURRENT_CONN_COUNT)
{
lock (sLocker)
{
if (sCount < MAX_CONCURRENT_CONN_COUNT)
{
sConnList.Add(key, dbconn);
}
else
{
//log
}
}
}
}
catch (Exception e)
{
//log
key = null;
}
return key;
}
public static ConnId CreateConn(string src)
{
DbConnection dbconn = null;
ConnId key = null;
try
{
dbconn = new DbConnection();
key = dbconn.ConnOpen(src);
if (sCount < MAX_CONCURRENT_CONN_COUNT)
{
lock (sLocker)
{
if (sCount < MAX_CONCURRENT_CONN_COUNT)
{
sConnList.Add(key, dbconn);
}
else
{
//log
}
}
}
}
catch (Exception e)
{
//log
key = null;
}
return key;
}
public static int ReleaseConn(ConnId connid)
{
DbConnection conn = null;
try
{
lock (sLocker)
{
conn = sConnList[connid];
sConnList.Remove(connid);
}
if (conn != null)
{
conn.Close();
}
}
catch (Exception e)
{
//log
}
return 0;
}
public static int CancelConn(ConnId connid)
{
DbConnection conn = null;
try
{
lock (sLocker)
{
conn = sConnList[connid];
sConnList.Remove(connid);
}
if (conn != null)
{
conn.Cancel();
}
}
catch (Exception e)
{
//log
}
return 0;
}
public static DbConnection GetDbConn(ConnId connid)
{
return sConnList[connid];
}
}
然后使用的時(shí)候呢就像這樣就可以:
namespace MyTest
{
class Program
{
static void Main(string[] args)
{
ConnId conn = ConnPool.CreateConn("123");
try
{
var aservice = new ApplyService();
var historyService = new ApprovalHistoryService();
aservice.Update(new object(), conn);
historyService.Insert(new object(), conn);
ConnPool.ReleaseConn(conn);
}
catch (Exception ex)
{
ConnPool.CancelConn(conn);
}
}
}
}
可以看到這里主要是在ConnPool中使用一個(gè)Dictionary加雙重檢查鎖定來(lái)實(shí)現(xiàn)一個(gè)并發(fā)連接的處理,用于記錄當(dāng)前的數(shù)據(jù)庫(kù)連接缕贡。而到執(zhí)行Insert和Update時(shí)他就通過(guò)connid在ConnPool中取出對(duì)應(yīng)事務(wù)中的SqlCommand來(lái)執(zhí)行相應(yīng)的Sql 翁授。也就是說(shuō)他將數(shù)據(jù)庫(kù)連接及事務(wù)抽離,放到了一個(gè)ConnPool中管理晾咪∈詹粒基本符合我預(yù)期。
問(wèn)題來(lái)了
如果認(rèn)真看著里面的實(shí)現(xiàn)大家應(yīng)該會(huì)發(fā)現(xiàn)這里存在一些問(wèn)題谍倦。
1 并發(fā)字典的實(shí)現(xiàn)
頭一個(gè)我想到的就是雙重檢查鎖定的問(wèn)題塞赂,雖然老總說(shuō)他們用了那么久一直沒(méi)有什么問(wèn)題,但我想說(shuō)那是因?yàn)椴l(fā)量不大所以沒(méi)有發(fā)現(xiàn)問(wèn)題昼蛀,并發(fā)量大的情況下使用lock的性能是明顯下降的宴猾,這就讓我想起了Java中從HashMap 到 HashTable 再到 ConcurrentHashMap的轉(zhuǎn)變圆存。HashMap是非線程安全類,所以用在多線程環(huán)境下就很可能出現(xiàn)意想不到的結(jié)果仇哆。所以才有了HashTable辽剧,我印象當(dāng)中HashTale的實(shí)現(xiàn)是在HashMap的基礎(chǔ)上為每個(gè)方法加了synchronized關(guān)鍵字,所以每次Add或Remove都會(huì)鎖住整個(gè)內(nèi)部的數(shù)組税产,可以想象一下在多線程環(huán)境下這里面的操作會(huì)有多慢。所以才有了ConcurrentHashMap的實(shí)現(xiàn)偷崩,其內(nèi)部使用的是可重入鎖辟拷,而鎖住的是每一個(gè)segment段而不是整個(gè)數(shù)組。更重要的是鎖的實(shí)現(xiàn)(基礎(chǔ)框架是隊(duì)列同步器AbstractQueuedSynchronizer)阐斜,追究到最底層實(shí)現(xiàn)是使用CAS加自旋衫冻,一種樂(lè)觀鎖的方式來(lái)實(shí)現(xiàn),從而保證了并發(fā)性谒出。從HashMap 到 HashTable 再到 ConcurrentHashMap的轉(zhuǎn)變真讓我回味良久隅俘,從里面數(shù)據(jù)結(jié)構(gòu)的設(shè)計(jì)到并發(fā)的處理真是妙不可言。學(xué)習(xí)Java的朋友應(yīng)該知道這可以從放騰飛中的《并發(fā)編程的藝術(shù)》中了解到笤喳,初學(xué)者看可能會(huì)暈为居,我第一次看了一小部分后是拒絕的,因?yàn)榭吹孟胪律苯疲睦镌谥淞R這他媽都寫的什么鬼蒙畴,哈哈!再后來(lái)慢慢看就有所體會(huì)了呜象,而且有些地方還要反復(fù)琢磨膳凝。從這本書可以了解到很多并發(fā)編程的底層實(shí)現(xiàn),極力推薦9Ф浮5乓簟!
??所以我也并不推薦自己去實(shí)現(xiàn)一個(gè)線程安全的Dictionary休玩,因?yàn)槔镞吷婕暗教嗟募?xì)節(jié)不是我們所能預(yù)料的著淆,除非自己真的非常熟悉底層的實(shí)現(xiàn),對(duì)并發(fā)編程了然于胸哥捕∧脸椋看過(guò).Net中的 Dictionary實(shí)現(xiàn)后會(huì)發(fā)現(xiàn)它與HashMap的實(shí)現(xiàn)大體無(wú)異,雖然沒(méi)有看過(guò).Net中ConcurrentDictionary的實(shí)現(xiàn)遥赚,但是個(gè)人感覺(jué)他們的實(shí)現(xiàn)大體上應(yīng)該相差無(wú)幾羔飞。所以可以考慮使用ConcurrentDictionary來(lái)替換老總ConnPool的內(nèi)部實(shí)現(xiàn),這部分代碼就不貼了拓颓。
2 非托管資源的釋放
對(duì)于非托管資源的釋放我建議是使用繼承接口IDisposable來(lái)實(shí)現(xiàn)其Dispose()方法,具體請(qǐng)參考.Net圣經(jīng) -《CLR via C# 第4版 》孕惜。
改進(jìn)
雖然老總的實(shí)現(xiàn)能抽離的底層的實(shí)現(xiàn),但是還不夠優(yōu)雅晨炕,因?yàn)樵诖a的最后都要去手動(dòng)實(shí)現(xiàn)事務(wù)的提交和回滾衫画。那有沒(méi)有更好的辦法來(lái)實(shí)現(xiàn)不用手動(dòng)提交和回滾呢,就像Spring中事務(wù)瓮栗,只需要在方法中加注解就可以達(dá)到指定功能削罩。當(dāng)然初期先來(lái)一個(gè)簡(jiǎn)單的實(shí)現(xiàn)。.Net中的Attribute對(duì)應(yīng)的就是Java中的注解费奸,但是這個(gè)Attribute還必須具備Aop的功能弥激。這樣才可以在方法執(zhí)行前開(kāi)啟一個(gè)事務(wù),方法執(zhí)行完成后提交或回滾事務(wù)愿阐。
1. Aop
一般使用的較多的是Autofac和Castle微服,當(dāng)然還可以使用Remoting代理方式或者從ContextBoundObject中派生來(lái)實(shí)現(xiàn)。剛好找到一篇文章說(shuō)到了.net實(shí)現(xiàn)Aop的七種方式缨历。
Approach | Advantages | Disadvantages |
---|---|---|
Remoting Proxies | Easy to implement, because of the .Net framework support | Somewhat heavyweight Can only be used on interfaces or MarshalByRefObjects |
Derivingfrom ContextBoundObject | Easiest to implement Native support for call interception | Very costly in terms of performance |
Compile-time subclassing ( Rhino Proxy ) | Easiest to understand | Interfaces or virtual methods only |
Runtime subclassing ( Castle Dynamic Proxy ) | Easiest to understand Very flexible | Complex implementation (but alreadyexists) Interfaces or virtual methods only |
Hooking into the profiler API ( Type Mock ) | Extremely powerful | Performance? Complex implementation (COM API, require separate runner, etc) |
Compile time IL-weaving ( Post Sharp / Cecil ) | Very powerful Good performance | Very hard to implement |
Runtime IL-weaving ( Post Sharp / Cecil ) | Very powerful Good performance | Very hard to implement |
當(dāng)然這只是前人做的一個(gè)總結(jié)以蕴,具體的性能及優(yōu)缺點(diǎn)還需要自己去考量。
編譯時(shí)AOP工具有:PostSharp辛孵、LinFu丛肮、SheepAspect、Fody觉吭、CIL操作工具腾供。
運(yùn)行時(shí)AOP工具:Castle Windsor/DynamicProxy、StructureMap鲜滩、Unity伴鳖、Spring.NET。
?? 我用得比較多的是運(yùn)行時(shí)Aop徙硅,比如Castle榜聂、Autofac.他們都是使用動(dòng)態(tài)代理的方式來(lái)實(shí)現(xiàn)。來(lái)看看Castle是怎么實(shí)現(xiàn)的
using Castle.DynamicProxy;
using System;
class Program
{
static void Main(string[] args)
{
ProxyGenerator generator = new ProxyGenerator();
SimpleInterceptor interceptor = new SimpleInterceptor();
Person person = generator.CreateClassProxy<Person>(interceptor);
person.SayHello();
Console.Read();
}
}
public class Person
{
public virtual void SayHello()
{
Console.WriteLine("hello world.");
}
public virtual void SayName(string hometown)
{
Console.WriteLine("I'm Lynch, I'm from {0}.", hometown);
}
public void SayOther()
{
Console.WriteLine("Yeah, I'm Chinese.");
}
}
public class SimpleInterceptor : StandardInterceptor
{
protected override void PreProceed(IInvocation invocation)
{
Console.WriteLine("before invocation , method : {0}.", invocation.Method.Name);
base.PreProceed(invocation);
}
protected override void PerformProceed(IInvocation invocation)
{
Console.WriteLine("before performing ...");
base.PerformProceed(invocation);
Console.WriteLine("after performing...");
}
protected override void PostProceed(IInvocation invocation)
{
Console.WriteLine("after invacation , method : {0}.", invocation.Method.Name);
base.PostProceed(invocation);
}
}
動(dòng)態(tài)代理的方式有個(gè)不好的地方就是每次都要生成指定類型的代理類嗓蘑,要實(shí)現(xiàn)Aop的方法還必須是virtual方法. 如果有很多類很多方法需要攔截那增加和改動(dòng)的代碼就有點(diǎn)多须肆。我想達(dá)到的目標(biāo)是只要一個(gè)Attribute類,不需要生成指定類型的代理類桩皿,讓代碼看起來(lái)更加的干凈豌汇。一直以來(lái)我只知道有運(yùn)行時(shí)Aop,就沒(méi)有想到編譯時(shí)Aop泄隔,比如postsharp拒贱。使用il注入的方式就非常的靈活,粒度夠細(xì),精確到每一條指令逻澳。然后就找到了Mono.Cecil 闸天,通過(guò)改寫中間IL代碼的方式來(lái)實(shí)現(xiàn),大體思路是
- 找到標(biāo)記有指定AopAttribute的方法
- 復(fù)制該方法并生成一個(gè)新的方法copy_method斜做,復(fù)制完成后清楚原有方法
- 改寫原有方法苞氮,首先調(diào)用AopAttribute的OnStart方法,接著調(diào)用copy_method 瓤逼,調(diào)用AopAttribute的OnSuccess方法笼吟,最后調(diào)用AopAttribute的OnEnd方法
postsharp 1.5 使用注意事項(xiàng),.net Framework 必須是3.5版本霸旗,需要在csproj中增加以下內(nèi)容(1.5之后的版本需要收費(fèi))
<PropertyGroup>
<PostSharpUseCommandLine>True</PostSharpUseCommandLine>
<DontImportPostSharp>True</DontImportPostSharp>
<PostSharpDirectory>libs</PostSharpDirectory>
</PropertyGroup>
<Import Project="$(PostSharpDirectory)\PostSharp-1.5.targets" />
2. IL
說(shuō)到IL指令就要先知道什么是evaluation stack赞厕。而這個(gè)evaluation stack卻不同于我們平時(shí)理解的Call Stack(調(diào)用棧),即在調(diào)用一個(gè)方法時(shí)首先會(huì)將所有參數(shù)壓棧定硝,壓棧完成后調(diào)用指定方法,方法執(zhí)行完成清理剛剛?cè)霔5膮?shù)毫目。我寫這個(gè)IL指令的時(shí)候我也納悶了蔬啡,我就在想我大學(xué)的時(shí)候用Intel x86匯編自己實(shí)現(xiàn)小型操作系統(tǒng)的時(shí)候也沒(méi)有遇到這個(gè)東西啊,說(shuō)到棧就Call Stack镀虐,這evaluation stack(以下簡(jiǎn)稱EStack)他媽是什么鬼箱蟆。后來(lái)向朋友了解了一下才知道這個(gè)是CIL特有的東西,這個(gè)是個(gè)寄存器刮便,即Stack< Register >
空猜。這樣就說(shuō)通了,我忘了操作結(jié)果的存放恨旱,學(xué)過(guò)匯編或了解一些底層的同學(xué)應(yīng)該了解辈毯,匯編語(yǔ)言的操作結(jié)果都是存放的寄存器中,如32位的ax , bx搜贤,64位 eax谆沃、ebx等通用寄存器。而不同的CPU又有不同的指令集仪芒,如PC機(jī)使用的是x86復(fù)雜指令集唁影,而Arm使用的是Arm的精簡(jiǎn)指令集,而CLR直接將兼容不同的寄存器的工作交給使用者處理的話那使用者勢(shì)必想瘋掉掂名,所以VM做一個(gè)通用的寄存器來(lái)存放操作結(jié)果据沈,至于該使用哪個(gè)寄存器來(lái)存放使用者不需要關(guān)心。
??至于為什么設(shè)計(jì)成棧的結(jié)構(gòu)饺蔑,個(gè)人理解一個(gè)是棧有內(nèi)存限制锌介,我們一般使用到的臨時(shí)變量局部變量或者方法參數(shù)都不會(huì)太多,當(dāng)然也可能很多膀钠,太多參數(shù)的話就該考慮封裝了掏湾。二個(gè)我覺(jué)得更重要的是它非常符合調(diào)用方法前將參數(shù)出入Call Stack的操作裹虫,例如我們來(lái)執(zhí)行一下偽代碼:
int a = 123;
Service service = new Service("lynch");
var b = service.GetNumber();
var result = service.Add(a,b);
在執(zhí)行Add方法前會(huì)先調(diào)用GetNumber來(lái)獲取b的值,整個(gè)代碼的執(zhí)行指令是
IL_0001: ldc.i4.s 123 將123賦值給寄存器即放到EStack中
IL_0003: stloc.0 將EStack中索引為0的值出棧融击,并將出棧的值push到Call Stack作為Add方法的入?yún)?
限于篇幅剩下的IL代碼就不貼了筑公,從第二行的st前綴指令大家應(yīng)該可以發(fā)現(xiàn) : 這個(gè)指令包含了兩個(gè)操作,一個(gè)從EStack出棧尊浪,二個(gè)將出棧的值入棧Call Stack匣屡。EStack是通過(guò)ld入棧而st出棧,就是說(shuō)使用到某個(gè)參數(shù)的時(shí)候就將其從EStack出棧拇涤,而無(wú)需再占用椀纷鳎空間,也就釋放了棧內(nèi)存鹅士,是不是有點(diǎn)像Call Stack的操作券躁。個(gè)人一些見(jiàn)解,不足之處還望指正掉盅。
在IL指令中我們會(huì)頻繁用到如ld ( load )也拜、st(store)等前綴指令,ld前綴指令的意思就是將寄存器的值壓棧趾痘,也就是將EStack中的值push到Call Stack慢哈,而st前綴指令就是將Call Stack中的操作結(jié)果存放到寄存器EStack中。大家可以通過(guò)這篇文章做個(gè)基本了解
如果想深入了解的可以看《Inside Microsoft .NET IL Assembler》永票,中文版是《Microsoft.NET IL匯編語(yǔ)言程序設(shè)計(jì)》卵贱,不過(guò)中文版已經(jīng)絕版,網(wǎng)上可以找到很多影印版pdf侣集。
3. Aop的實(shí)現(xiàn)
這個(gè)應(yīng)該算是postsharp的簡(jiǎn)單實(shí)現(xiàn)键俱,源碼放在了Github LeoxAop上。代碼我就不貼了世分,很多地方都有注釋方妖,而且邏輯還算清晰。這里的實(shí)現(xiàn)部分參考了MSBuild + MSILInect實(shí)現(xiàn)編譯時(shí)AOP之預(yù)覽這位博主的實(shí)現(xiàn)罚攀,不過(guò)他寫的應(yīng)該過(guò)于匆忙党觅,所以代碼結(jié)構(gòu)有些凌亂,不太容易看懂斋泄,還用了很多的linq杯瞻。
??這里有一個(gè)待解決的問(wèn)題是將代碼注入到指定項(xiàng)目exe或dll后怎么讓VS調(diào)試到指定的AopAttribute代碼,也就是說(shuō)怎么生成對(duì)應(yīng)的pdb文件讓VS感知到炫掐。就像.net reflector 一樣魁莉,反編譯dll后自動(dòng)生成對(duì)應(yīng)的pdb文件,然后就可以順利的調(diào)試。我目前想到的較為簡(jiǎn)單的方法是在開(kāi)發(fā)者命令中使用ildasm 將文件反編譯為il代碼旗唁,然后再使用ilasm生成對(duì)應(yīng)的pdb文件 :
ildasm test.exe /out=test.il
ilasm test.il /pdb
不過(guò)我試過(guò)發(fā)現(xiàn)并沒(méi)有起作用畦浓,哪位朋友知道的麻煩告訴我一下,萬(wàn)分感激检疫。就因?yàn)檫@個(gè)也花了不少時(shí)間讶请,搜google搜codeproject都沒(méi)有找到相關(guān)的文章,實(shí)在沒(méi)辦法先擱放在這里屎媳,太過(guò)糾結(jié)容易崩潰夺溢。本以為很快能結(jié)篇,還不料涉及的東西有點(diǎn)多烛谊,寫代碼調(diào)試解決遇到的bug也花不少時(shí)間风响。這里是.net 的實(shí)現(xiàn),其實(shí)java也可以這么實(shí)現(xiàn)丹禀,只是要了解java的字節(jié)碼状勤,有時(shí)間再寫吧。
擴(kuò)展閱讀
4. Transaction 實(shí)現(xiàn)
終于寫到了這了荧降,迫不及待啊。感嘆時(shí)間飛快攒读。寫完這個(gè)接下來(lái)我想看的東西就是node里邊Promise和Async的實(shí)現(xiàn)。既然Aop功能已經(jīng)實(shí)現(xiàn)辛友,那我們就可以在OnStart方法開(kāi)始一個(gè)事務(wù)薄扁,在OnSuccess和OnException提交或回滾事務(wù),但是這里還有幾個(gè)問(wèn)題需要考慮:
- 就是如果使用者的在自身業(yè)務(wù)代碼就已經(jīng)做了異常捕獲該如何處理废累,是該回滾還是該提交邓梅,這個(gè)還沒(méi)想出來(lái)好的解決辦法。
- 如果有部分連接未能及時(shí)釋放又該如何處理邑滨,對(duì)于這個(gè)問(wèn)題可以考慮啟動(dòng)一個(gè)線程來(lái)監(jiān)控日缨,根據(jù)連接開(kāi)始創(chuàng)建的時(shí)間來(lái)做判斷。
- 底層dao操作需要用到當(dāng)前連接創(chuàng)建的SqlCommand掖看,要獲取到這個(gè)那就需要記錄連接Id匣距,問(wèn)題是這個(gè)Id只有在相應(yīng)Aop的On事件時(shí)才能拿到。還有沒(méi)有其他辦法呢哎壳,因?yàn)槊總€(gè)線程執(zhí)行時(shí)用的連接Id都不一樣所以我想到一個(gè)辦法就是將這個(gè)變量放到ThreadLocal中毅待,線程跑到哪里它就跟到哪,每個(gè)線程都維持著自己的連接Id归榕。如果這個(gè)問(wèn)題不解決那設(shè)計(jì)這一整個(gè)抽象事務(wù)就沒(méi)用了尸红。可能應(yīng)該還有更好的辦法,還沒(méi)來(lái)得及去看Spring中實(shí)現(xiàn)外里,如果有朋友想到更好的辦法麻煩告訴我一聲怎爵,感激不盡!
- 連接Id如何保證唯一性盅蝗。當(dāng)前我使用的是guid生成鳖链,只是一個(gè)臨時(shí)的策略》缈疲考慮到分布式架構(gòu)的話這種生成方式就不太好管理撒轮,也不穩(wěn)妥。我比較喜歡 Twitter 分布式自增Id的實(shí)現(xiàn)snowflake贼穆,說(shuō)喜歡是因?yàn)樗膶?shí)現(xiàn)粒度很細(xì)题山,但是沒(méi)有考慮到它強(qiáng)依賴機(jī)器時(shí)鐘,如果機(jī)器上時(shí)鐘回?fù)芄嗜瑫?huì)導(dǎo)致發(fā)號(hào)重復(fù)或者服務(wù)會(huì)處于不可用狀態(tài)顶瞳。當(dāng)然也可以參考MongoDb中ObjectId 的生成方式。后來(lái)有幸看到朋友轉(zhuǎn)發(fā)的一篇文章愕秫,里面有說(shuō)到唯一Id的多種生成方式慨菱,還介紹了snowflake及ObjectId的優(yōu)缺點(diǎn),最后講到一種新的生成算法Leaf戴甩,大家可以了解下
Leaf——來(lái)自美團(tuán)點(diǎn)評(píng)的分布式ID生成系統(tǒng)
??當(dāng)然如果是分布式架構(gòu)那沒(méi)這么簡(jiǎn)單了符喝,需要考慮分布式事務(wù),涉及兩階段三階段提交甜孤、分布式一致性算法 paxos协饲。不過(guò)現(xiàn)在更多的應(yīng)該考慮放棄強(qiáng)一致性的分布式事務(wù)而使用最終一致性。如eBay的實(shí)現(xiàn)缴川,在設(shè)計(jì)上就不采用分布式事務(wù)茉稠,而是通過(guò)其它途徑來(lái)解決數(shù)據(jù)一致性問(wèn)題。其中使用的最重要的技術(shù)就是消息隊(duì)列和消息應(yīng)用狀態(tài)表把夸。至于阿里和京東怎么實(shí)現(xiàn)就沒(méi)有深入了解過(guò)而线。eBay 實(shí)現(xiàn)參考 :
最后 Transaction 的實(shí)現(xiàn)請(qǐng)參見(jiàn) Leox.Transaction