dotNET Core实现分布式环境下的流水号唯一
業(yè)務(wù)背景
在管理系統(tǒng)中,很多功能模塊都會(huì)涉及到各種類型的編號,例如:流程編號、訂單號、合同編號等等。編號各有各自的規(guī)則,但通常有一個(gè)流水號來確定編號的唯一性,保證流水號的唯一,在不同的環(huán)境中實(shí)現(xiàn)方式有所不同。本文將介紹在單機(jī)和分布式環(huán)境中保證流水號唯一的方式。
實(shí)現(xiàn)思路
1、在數(shù)據(jù)庫中創(chuàng)建 seqno 表,每個(gè)業(yè)務(wù)一條數(shù)據(jù),存儲(chǔ)業(yè)務(wù) code 和流水號的最大值
環(huán)境
dotNET Core:2.1
VS For Mac:2019
Docker:18.09.2
MySql:8.0.17,基于Docker構(gòu)建
Redis:3.2,基于Docker構(gòu)建
CSRedisCore:3.1.5
準(zhǔn)備工作
1、執(zhí)行下面命令構(gòu)建 Redis 容器
docker?run?-p?6379:6379??-d?--name?s2redis_test???--restart=always?redis:3.2???redis-server?--appendonly?yes2、執(zhí)行下面命令構(gòu)建 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 中創(chuàng)建數(shù)據(jù)庫seqno_test,執(zhí)行下面 SQL 創(chuàng)建表和測試數(shù)據(jù)
--?---------------------------- --?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 中創(chuàng)建兩個(gè)控制臺(tái)項(xiàng)目和一個(gè)類庫項(xiàng)目,如下圖:
單機(jī)測試
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 控制臺(tái)程序中用多線程來模擬測試
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、測試結(jié)果如下,可以看出在多線程情況下會(huì)出現(xiàn)重復(fù)的編號
單機(jī)環(huán)境加鎖測試
在 SeqNo 類中添加 GetSeqNoByLock 方法,通過 Monitor.Enter 來解決單機(jī)多線程流水號重復(fù)問題
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(); }運(yùn)行結(jié)果如下,可以看出已經(jīng)沒有出現(xiàn)重復(fù)的流水號了
多機(jī)環(huán)境測試
Monitor 只能解決進(jìn)程內(nèi)的重復(fù)性問題,現(xiàn)在用兩個(gè)控制臺(tái)程序來模擬分布式下的多機(jī)器運(yùn)行,在 RedisLockConsoleApp2 控制臺(tái)程序添加如下代碼
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(); }同時(shí)運(yùn)行兩個(gè)控制臺(tái)程序,測試結(jié)果如下:
可以看出在每一個(gè)控制臺(tái)程序內(nèi)沒有重復(fù)流水號,但兩個(gè)控制臺(tái)還是會(huì)間歇性地出現(xiàn)重復(fù)流水號。
要解決這個(gè)問題就必須使用分布式鎖。
多機(jī)環(huán)境分布式鎖測試
分布式鎖又很多實(shí)現(xiàn)方式,本例中采用 Redis 來實(shí)現(xiàn),Redis 客戶端使用的是 CSRedisCore ,在 CSRedisCore 最新的版本 3.1.5 中實(shí)現(xiàn)了分布式鎖,這讓使用變得非常的方便。
1、在 RedisLockLib 項(xiàng)目中添加 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、測試結(jié)果如下:
總結(jié)
例子非常簡單,提供一種解決問題的思路,如您有更好的方式歡迎討論。本文的示例代碼已上傳 Github ,地址如下:
https://github.com/oec2003/StudySamples/tree/master/RedisLockDemo
祝大家假期快樂!
總結(jié)
以上是生活随笔為你收集整理的dotNET Core实现分布式环境下的流水号唯一的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: .NetCore技术研究-Configu
- 下一篇: 认证方案之初步认识JWT