天天看點

dotNET Core實作分布式環境下的流水号唯一

業務背景

在管理系統中,很多功能子產品都會涉及到各種類型的編号,例如:流程編号、訂單号、合同編号等等。編号各有各自的規則,但通常有一個流水号來确定編号的唯一性,保證流水号的唯一,在不同的環境中實作方式有所不同。本文将介紹在單機和分布式環境中保證流水号唯一的方式。

實作思路

1、在資料庫中建立 seqno 表,每個業務一條資料,存儲業務 code 和流水号的最大值

2、擷取某業務的流水号時,根據業務 code 查詢 seqno 表,擷取流水号傳回,并将最大值加一

3、使用 Monitor.Enter 解決單機重複性問題

4、使用 Redis 分布式鎖解決分布式部署的重複性問題

環境

  • 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 yes
           

2、執行下面指令建構 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_password
           

3、在 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 中建立兩個控制台項目和一個類庫項目,如下圖:

dotNET Core實作分布式環境下的流水号唯一

單機測試

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、測試結果如下,可以看出在多線程情況下會出現重複的編号

dotNET Core實作分布式環境下的流水号唯一

單機環境加鎖測試

在 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();
}
           

運作結果如下,可以看出已經沒有出現重複的流水号了

dotNET Core實作分布式環境下的流水号唯一

多機環境測試

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();
}
           

同時運作兩個控制台程式,測試結果如下:

dotNET Core實作分布式環境下的流水号唯一

可以看出在每一個控制台程式内沒有重複流水号,但兩個控制台還是會間歇性地出現重複流水号。

要解決這個問題就必須使用分布式鎖。

多機環境分布式鎖測試

分布式鎖又很多實作方式,本例中采用 Redis 來實作,Redis 用戶端使用的是 CSRedisCore ,在 CSRedisCore 最新的版本 3.1.5 中實作了分布式鎖,這讓使用變得非常的友善。

1、在 RedisLockLib 項目中添加 CSRedisCore 包的引用

dotNET Core實作分布式環境下的流水号唯一

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、測試結果如下:

dotNET Core實作分布式環境下的流水号唯一

總結

例子非常簡單,提供一種解決問題的思路,如您有更好的方式歡迎讨論。本文的示例代碼已上傳 Github ,位址如下:

https://github.com/oec2003/StudySamples/tree/master/RedisLockDemo

祝大家假期快樂!

dotNET Core實作分布式環境下的流水号唯一

微信公衆号:不止dotNET

作者: oec2003

出處: http://oec2003.cnblogs.com/

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連結,否則 保留追究法律責任的權利。

繼續閱讀