前言
經過2個月的調整及測試,CAP 2.3 版本終於發布了,這個版本最大的特性就是對於 MongoDB 的支援,感謝部落格園團隊的keke同學對於 MongoDB 支援所提供的 PR,相信隨著部落格園的使用,CAP 會越來越多的協助到更多的人。
CAP 是一個用來解決微服務或者分布式系統中分散式交易問題的一個開源項目解決方案(github.com/dotnetcore/CAP),目前已經將近2歲了,想對 CAP 更多瞭解的同學可以看下我的這篇文章。
背景故事
在 2.3 版本中,我們對 Api 做了一些調整,為什麼做這些調整呢?我就來說一下這中間的過程
相信在用 CAP 的同學們都知道在2.2以及以前的版本中存在一個 Bug 就是在使用事務的情況下訊息持久化到資料庫後如果還沒有提交事務,那麼這個時候 CAP 就會開始向訊息佇列中發送訊息,但是有一個問題就是如果接下來事務提交失敗,這個時候其實訊息已經被發送出去了,就會導致消費端接收到了訊息,對應到 GitHub 的這個 issue。
這個 Bug 要說嚴重也嚴重,要說不嚴重也不嚴重,但是我們總要解決這個問題。怎麼解決呢?有些同學可能會說把發送訊息改到事務提交完成後不就行了,但是 CAP 是無法擷取到業務端的事務執行結果的,因為在.NET中沒有類似於Spring Transaction這種機制可以很容易的做一些擴充,所以如果想改到事務提交後,那麼就必須提供一個 API 讓使用者手動來調用進行發送。這樣看來可以很容易的解決這個問題,但是我覺得這樣對於使用者來說就要多一行代碼,需要增加學習成本以及要多理解架構內部做的一些事情,還有可能會忘記調用或者用錯。
為了讓 CAP 的使用者少寫這一行代碼,我思考了好幾個月,說一下過程吧。
對於資料庫底層驅動的代碼做過瞭解的同學可能知道,資料庫驅動在底層封裝的特別死,特別是對於事務這塊的處理,類都是 sealed 幾乎沒有辦法進行擴充,我做了一些嘗試都失敗了,最後都想 fork 一個資料庫驅動來修改發布自己的 Nuget 包了,但是這個方案最終被否定了,因為我自己都不願意用自己編譯的資料庫驅動,最終這條路行不通。
另外一個方案就是對於 Diagnostics 有瞭解的同學可能想到了,可以利用這個特性來追蹤事務提交的結果,然後在其中做一些處理就行了。但是有個什麼問題呢?目前只有 SQL Server 的驅動才支援 Diagnostics,其他的 MySql,PostgreSql 均不支援,怎麼辦呢?不可能不去管使用 MySql,PostgreSql 的那些使用者,畢竟我們自己也是使用的 MySql。
我和 Lemon 同學曾分別向 MySql 和 PostgreSql C#資料庫專案提交了對 Diagnostics 特性支援的 PR(MySql PR, PostgreSql PR),但是由於微軟對於 DiagnosticsSource API 設計的問題,導致社區對於這種 API 的方式比較反感,另外就是指導文檔中的一些原則,微軟在 SQLClient 的驅動中都沒有遵守,所以這兩個 PR 一直沒有進行合并,有興趣的也可以看下這裡的討論,這樣等下去也不知道要等到什麼時候。
還有一個原因是因為我們需要對接新的MongoDB,MongoDB對於事務的處理在API上有所不同,為了提供一致的使用者介面,所以需要作出一些改變。
以上,我們需要對 API 做出調整,我們不能一直停滯不前。下面我們來看一下2.3版本做出的改變吧。
CAP 2.3 版本中的改變1、移除了CAP 中介軟體註冊
現在,你不需要再使用 app.UseCap()
手動添加中介軟體,我們將自動註冊。
在 2.3 以前的版本中,需要在 Startup 中 Configure 中註冊 CAP,現在我們已經自動的在啟動的時候進行了註冊你不再需要手動註冊。
public void Configure(IApplicationBuilder app){ app.UseCap(); //移除了,不需要再手動註冊}
2、修改了訊息表主鍵類型
為了適配最新的MongoDB以及某些情境下的資料表遷移,我們將訊息表的主鍵Id由自增長的int類型改為了由雪花演算法產生的long類型,這在一定程度上可以提高訊息處理的效能以及邏輯的複雜性。
由 2.2 版本升級上來的同學,我們提供了資料庫遷移指令碼,你可以查看這裡來擷取資料庫遷移指令碼,然後在資料庫執行即可。
gist.github.com/yuleyule66/0e5ec7a5046dc58fcf89d51e4820c5cd
3、修改了 Publish Api
我們添加了一個新的 ICapTransaction
介面,用來控制事務的處理,同時這也是為了處理跟蹤不到交易處理介面的情況,同時我們封裝了一系列擴充方法,方便開發人員使用,下面我們看一下新的Api
using (var session = _mongoClient.StartTransaction(_capBus, autoCommit: false)){ var collection = _mongoClient.GetDatabase("test").GetCollection<BsonDocument>("test.collection"); collection.InsertOne(session, new BsonDocument { { "hello", "world" } }); _capBus.Publish("sample.rabbitmq.mongodb", DateTime.Now); session.CommitTransaction();}
這裡的 connection.StartTransaction
是一個擴充方法,這個擴充方法返回 IClientSessionHandle
介面,它代表的是MongoDB的原生事務對象,我們在做自己業務代碼的時候拿到這個對象即可使用。
命名 StartTransaction 的原因是我們希望遵循MongoDB的命名規範,便於使用者理解
using (var connection = new SqlConnection("")){ using (var transaction = connection.BeginTransaction(_capBus, autoCommit: false)) { //your business code sample connection.Execute("insert into test(name) values('test')", transaction); _capBus.Publish("sample.rabbitmq.sqlserver", DateTime.Now); transaction.Commit(); }}
這裡的 connection.BeginTransaction
是一個擴充方法,這個擴充方法返回 IDbTransaction
介面,它代表的是資料庫的原生事務對象,我們在做自己業務代碼的時候使用這個對象傳到Dapper或者Ado.net中即可。
using (var connection = new MySqlConnection("")){ using (var transaction = connection.BeginTransaction(_capBus, autoCommit: false)) { //your business code sample connection.Execute("insert into test(name) values('test')",transaction: (IDbTransaction)transaction.DbTransaction); _capBus.Publish("sample.rabbitmq.mysql", DateTime.Now); transaction.Commit(); }}
這裡的 connection.BeginTransaction
是一個擴充方法,這個擴充方法返回 ICapTransaction
介面,介面封裝的有 DbTransaction
屬性,它代表的是資料庫的原生事務對象,我們在做自己業務代碼的時候使用這個對象傳到Dapper或者Ado.net中即可。
4、增加了事務自動認可
有些情況下,為了精簡代碼,我們不想去手動調用 transaction.Commit()
方法希望CAP去協助你提交事務,那麼也是可以做到的,你只需要在 connection.BeginTransaction
的時候傳遞參數 autoCommit
為 true
即可。
需要注意的是,當使用事務自動認可功能時,你需要在你的商務邏輯執行完成之後再發送訊息,也就是說 _capBus.Publish
這行代碼需要放到最後執行。執行個體代碼:
using (var connection = new MySqlConnection("")){ using (var transaction = connection.BeginTransaction(_capBus, autoCommit: true)) { //your business code sample _capBus.Publish("sample.rabbitmq.mysql", DateTime.Now); }}
注意這裡的 autoCommit: true
,並且取消了transaction.Commit()
5、增加對 MongoDB 的支援
在微服務應用中,有時候我們的某些服務可能為了效能或者是其他原因考慮,使用的不是傳統的關係型資料庫,而且一些非關係型資料庫,比如這其中MongoDB作為代表,使用的人也最多,然後就有需求希望在儲存資料的時候也想保證資料的高一致性。
MongoDB 在 4.0 及以上版本中支援了ACID事務,這個特性使我們有理由對MongoDB提供支援,同時MongoDB的支援也是部落格園的Keke同學提供的PR,再次感謝。
有些同學可能想嘗試一下,那麼下面就來簡單的說一下,MongoDB 對 ACID事務的支援是需要叢集才能使用,所以我們需要首先搭建一個叢集,搭建叢集的文章我已經寫好了,大家可以參考這篇部落格來搭建。
叢集搭建完成之後,在 Startup.cs 檔案中的 ConfigureServices(IServiceCollection services)
中配置下即可。
public void ConfigureServices(IServiceCollection services){ services.AddSingleton<IMongoClient>(new MongoClient("mongodb://localhost:27017,localhost:27018,localhost:27019/?replicaSet=rs0")); services.AddCap(x => { x.UseMongoDB("mongodb://localhost:27017,localhost:27018,localhost:27019/?replicaSet=rs0"); x.UseRabbitMQ("localhost"); x.UseDashboard(); });}
使用方法:
注意:MongoDB 不能在事務中建立資料庫和集合,所以如果你叢集建立好之後是空的,則你需要單獨先建立資料庫和集合,可以類比一條記錄插入就會自動建立了。
var mycollection = _client.GetDatabase("test").GetCollection<BsonDocument>("test.collection");mycollection.InsertOne(new BsonDocument { { "test", "test" } });
然後
[Route("~/without/transaction")]public IActionResult WithoutTransaction(){ _capBus.Publish("sample.rabbitmq.mongodb", DateTime.Now); return Ok();}[Route("~/transaction/not/autocommit")]public IActionResult PublishNotAutoCommit(){ //NOTE: before your test, your need to create database and collection at first using (var session = _client.StartTransaction(_capBus, autoCommit: true)) { var collection = _client.GetDatabase("test").GetCollection<BsonDocument>("test.collection"); collection.InsertOne(session, new BsonDocument { { "hello", "world" } }); _capBus.Publish("sample.rabbitmq.mongodb", DateTime.Now); } return Ok();}
總結
最近一兩個月明顯感覺到使用 CAP 的人越來越多了,部落格園也出現了一些CAP的部落格文章,我們很開心能夠協助到大家
。大家在使用的過程中遇到問題希望也能夠積極的反饋,協助CAP變得越來越好。:)
如果你覺得本篇文章對您有協助的話,感謝您的【推薦】。
如果你對 .NET Core 有興趣的話可以關注我,我會週期性在部落格分享我的學習心得。
本文地址:http://www.cnblogs.com/savorboard/p/cap-2-3.html
作者部落格:Savorboard
本文原創授權為:署名 - 非商業性使用 - 禁止演繹,協議普通文本 | 協議法律文本