dotNET Core实现分布式环境下的流水号唯一
業務背景
在管理系統中,很多功能模塊都會涉及到各種類型的編號,例如:流程編號、訂單號、合同編號等等。編號各有各自的規則,但通常有一個流水號來確定編號的唯一性,保證流水號的唯一,在不同的環境中實現方式有所不同。本文將介紹在單機和分布式環境中保證流水號唯一的方式。
實現思路
1、在數據庫中創建 seqno 表,每個業務一條數據,存儲業務 code 和流水號的最大值
環境
dotNET Core:2.1
VS For Mac:2019
Docker:18.09.2
MySql:8.0.17,基于Docker構建
Redis:3.2,基于Docker構建
CSRedisCore:3.1.5
準備工作
1、執行下面命令構建 Redis 容器
docker?run?-p?6379:6379??-d?--name?s2redis_test???--restart=always?redis:3.2???redis-server?--appendonly?yes2、執行下面命令構建 MySql 容器
docker?run?-d?-p?3306:3306?-e?MYSQL_USER="oec2003"?-e?MYSQL_PASSWORD="123456"?-e?MYSQL_ROOT_PASSWORD="123456"?--name?s2mysql?mysql/mysql-server?--character-set-server=utf8mb4?--collation-server=utf8mb4_general_ci?--default-authentication-plugin=mysql_native_password3、在 MySql 中創建數據庫seqno_test,執行下面 SQL 創建表和測試數據
--?---------------------------- --?Table?structure?for?seqno --?---------------------------- DROP?TABLE?IF?EXISTS?`seqno`; CREATE?TABLE?`seqno`?(`code`?varchar(50)?COLLATE?utf8mb4_general_ci?DEFAULT?NULL,`num`?int(11)?DEFAULT?NULL )?ENGINE=InnoDB?DEFAULT?CHARSET=utf8mb4?COLLATE=utf8mb4_general_ci;--?---------------------------- --?Records?of?seqno --?---------------------------- BEGIN; INSERT?INTO?`seqno`?VALUES?('order',?1); COMMIT;SET?FOREIGN_KEY_CHECKS?=?1;4、在 VS2019 中創建兩個控制臺項目和一個類庫項目,如下圖:
單機測試
1、在 SeqNo 類中添加 GetSeqByNoLock 方法
public?static?string?GetSeqNoByNoLock() {string?connectionStr?=?"server?=?localhost;?user?id?=?oec2003;?password?=?123456;?database?=?seqno_test";string?getSeqNosql?=?"select?num?from?seqno?where?code='order'";string?updateSeqNoSql?=?"update?seqno?set?num=num+1?where?code='order'";var?seqNo?=?MySQLHelper.ExecuteScalar(connectionStr,?System.Data.CommandType.Text,?getSeqNosql);MySQLHelper.ExecuteNonQuery(connectionStr,?System.Data.CommandType.Text,?updateSeqNoSql);return?seqNo.ToString(); }2、在 RedisLockConsoleApp1 控制臺程序中用多線程來模擬測試
class?Program {static?void?Main(string[]?args){Task.Run(()?=>{for?(int?i?=?0;?i?<?50;?i++){Console.WriteLine($"Thread1:SeqNo:{SeqNo.GetSeqNoByNoLock()}");}});Task.Run(()?=>{for?(int?i?=?0;?i?<?50;?i++){Console.WriteLine($"Thread2:SeqNo:{SeqNo.GetSeqNoByNoLock()}");}});Console.ReadLine();} }3、測試結果如下,可以看出在多線程情況下會出現重復的編號
單機環境加鎖測試
在 SeqNo 類中添加 GetSeqNoByLock 方法,通過 Monitor.Enter 來解決單機多線程流水號重復問題
public?static?string?GetSeqNoByLock() {string?connectionStr?=?"server?=?localhost;?user?id?=?oec2003;?password?=?123456;?database?=?seqno_test";string?getSeqNosql?=?"select?num?from?seqno?where?code='order'";string?updateSeqNoSql?=?"update?seqno?set?num=num+1?where?code='order'";var?seqNo?=?string.Empty;try{Monitor.Enter(_myLock);seqNo?=?MySQLHelper.ExecuteScalar(connectionStr,?System.Data.CommandType.Text,?getSeqNosql).ToString();MySQLHelper.ExecuteNonQuery(connectionStr,?System.Data.CommandType.Text,?updateSeqNoSql);Monitor.Exit(_myLock);}catch{Monitor.Exit(_myLock);}return?seqNo.ToString(); }運行結果如下,可以看出已經沒有出現重復的流水號了
多機環境測試
Monitor 只能解決進程內的重復性問題,現在用兩個控制臺程序來模擬分布式下的多機器運行,在 RedisLockConsoleApp2 控制臺程序添加如下代碼
static?void?Main(string[]?args) {Task.Run(()?=>{for?(int?i?=?0;?i?<?50;?i++){Console.WriteLine($"Thread1:SeqNo:{SeqNo.GetSeqNoByLock()}");}});Task.Run(()?=>{for?(int?i?=?0;?i?<?50;?i++){Console.WriteLine($"Thread2:SeqNo:{SeqNo.GetSeqNoByLock()}");}});Console.ReadLine(); }同時運行兩個控制臺程序,測試結果如下:
可以看出在每一個控制臺程序內沒有重復流水號,但兩個控制臺還是會間歇性地出現重復流水號。
要解決這個問題就必須使用分布式鎖。
多機環境分布式鎖測試
分布式鎖又很多實現方式,本例中采用 Redis 來實現,Redis 客戶端使用的是 CSRedisCore ,在 CSRedisCore 最新的版本 3.1.5 中實現了分布式鎖,這讓使用變得非常的方便。
1、在 RedisLockLib 項目中添加 CSRedisCore 包的引用
2、在 SeqNo 類中添加 GetSeqNoByRedisLock 方法
public?static?string?GetSeqNoByRedisLock() {string?connectionStr?=?"server?=?localhost;?user?id?=?oec2003;?password?=?123456;?database?=?seqno_test";string?getSeqNosql?=?"select?num?from?seqno?where?code='order'";string?updateSeqNoSql?=?"update?seqno?set?num=num+1?where?code='order'";var?seqNo=string.Empty;using?(_redisClient.Lock("test",?5000)){seqNo?=?MySQLHelper.ExecuteScalar(connectionStr,?System.Data.CommandType.Text,?getSeqNosql).ToString();MySQLHelper.ExecuteNonQuery(connectionStr,?System.Data.CommandType.Text,?updateSeqNoSql);}return?seqNo; }3、測試結果如下:
總結
例子非常簡單,提供一種解決問題的思路,如您有更好的方式歡迎討論。本文的示例代碼已上傳 Github ,地址如下:
https://github.com/oec2003/StudySamples/tree/master/RedisLockDemo
祝大家假期快樂!
總結
以上是生活随笔為你收集整理的dotNET Core实现分布式环境下的流水号唯一的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: .NetCore技术研究-Configu
- 下一篇: 认证方案之初步认识JWT