前言
經過2個月的調整及測試,CAP 2.3 版本終於發布了,這個版本最大的特性就是對於 MongoDB 的支持,感謝博客園團隊的keke同學對於 MongoDB 支持所提供的 PR,相信隨着博客園的使用,CAP 會越來越多的幫助到更多的人。
CAP 是一個用來解決微服務或者分布式系統中分布式事務問題的一個開源項目解決方案(https://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 版本升級上來的同學,我們提供了數據庫遷移腳本,你可以查看這里來獲取數據庫遷移腳本,然后在數據庫執行即可。
https://gist.github.com/yuleyule66/0e5ec7a5046dc58fcf89d51e4820c5cd
3、修改了 Publish Api
我們添加了一個新的 ICapTransaction
接口,用來控制事務的處理,同時這也是為了處理跟蹤不到事務處理接口的情況,同時我們封裝了一系列擴展方法,方便開發者使用,下面我們看一下新的Api
- MongoDB:
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的命名規范,便於使用者理解
- SQLServer:
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中即可。
- MySql 和 PostgreSql:
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
本文原創授權為:署名 - 非商業性使用 - 禁止演繹,協議普通文本 | 協議法律文本