北京遇上西雅圖之:當(dāng)記賬系統(tǒng)遇上并發(fā)

目前在開發(fā)的系統(tǒng)中有個(gè)結(jié)算的邏輯妈嘹,每張訂單到了賬期日后柳琢,平臺(tái)會(huì)給商家進(jìn)行結(jié)算。涉及到賬戶方面的操作包括润脸,平臺(tái)賬戶余額的扣減和商戶賬戶余額的增加染厅,以及賬戶流水的記錄。

像這個(gè)場(chǎng)景津函,如果不考慮并發(fā)的話肖粮,那么很容易出現(xiàn)數(shù)據(jù)不一致,導(dǎo)致記賬混亂尔苦。 當(dāng)然涩馆,這是比(xiāng)較(dāng)要命的!

那怎么解決這種并發(fā)呢允坚?

為了便于描述魂那,我們把場(chǎng)景簡(jiǎn)單化:db里有個(gè)Account表,記錄平臺(tái)及商戶的賬戶信息稠项; 程序邏輯為讀取出指定的賬戶記錄涯雅,修改其某個(gè)字段的值。 在這個(gè)場(chǎng)景下我們看并發(fā)如何處理展运。

想必大家都可以想到了活逆,用lock。lock將語(yǔ)句塊標(biāo)記為臨界區(qū)拗胜,獲取給定對(duì)象的互斥鎖蔗候,然后執(zhí)行語(yǔ)句塊,執(zhí)行完成后釋放鎖埂软。 這樣可以控制進(jìn)程內(nèi)多線程的并發(fā)锈遥。

北京遇上西雅圖

這里, 我再說另一種也許更好的方法————借助一個(gè)時(shí)間戳勘畔。先看代碼邏輯:

public void MyBiz(string name = "")
{
    Stopwatch watch = new Stopwatch();
    watch.Start();

    int loops = 0;//用以記錄循環(huán)次數(shù)
    int i = 0;
    while (i == 0)//在執(zhí)行db的update時(shí)所灸,成功update會(huì)返回1,否則(非異常情況下)會(huì)返回0炫七。所以爬立,每當(dāng)返回0時(shí),我們就嘗試再次執(zhí)行整個(gè)邏輯
    {
        loops++;

        #region 業(yè)務(wù)邏輯
        // 1. 讀
        var dal = new GateWay.DAL.PriceDal.PriceDAL();
        string mercode = "000001";
        t_info_meraccount accountModel = dal.GetAcInfo(mercode, "1", "3");
        if (name == "")
        {
            accountModel.MerName = accountModel.MerName + loops;
        }
        else
        {
            accountModel.MerName = name;
        }

        // 為了模擬并發(fā)诉字,這里讓線程隨機(jī)sleep
        Thread.Sleep(new Random().Next(10, 1000));

        // 2. 寫
        i = Update(accountModel);
        #endregion

        if (i == 0)
        {
            Console.WriteLine(Thread.CurrentThread.Name + " 遭遇i=0懦尝,接著重試...");
        }
    }
    watch.Stop();
    Console.WriteLine(Thread.CurrentThread.Name + " 執(zhí)行次數(shù):" + loops + " duation:" + watch.ElapsedMilliseconds);
}

private static int Update(t_info_meraccount model)
{
    string sql = "update t_info_meraccount set MerName=@name,LastTime=@LastTime where AcCode=@AcCode and LastTime=@LastTime1";
    int i = 0;
    using (var conn = ConnUtility.GateWayConntion)
    {
        conn.Open();
        i = conn.Execute(sql, new
        {
            name = model.MerName,
            LastTime = CommonDataType_DateTime.GetTimeStamp(false),
            AcCode = model.AcCode,
            LastTime1 = model.LastTime
        });
        return i;
    }
}

可以看到,代碼邏輯即是先取出一條記錄壤圃,然后修改其MerName屬性值陵霉, 然后將這個(gè)修改持久化到db。

也可以看到伍绳,這段程序里利用了時(shí)間戳踊挠。在表t_info_meraccount里有個(gè)時(shí)間戳字段LastTime varchar(20)。 在對(duì)表執(zhí)行update時(shí)冲杀,where子句除了必要的AcCode條件外效床,再追加一個(gè)LastTime∪ㄋ可以看到剩檀,當(dāng)LastTime被其他線程更改后就匹配不上了,就會(huì)update失敗旺芽,從而返回0沪猴。 那么這時(shí), while循環(huán)繼續(xù)采章, 直到返回1為止运嗜。

是否合理呢? 我們來寫個(gè)testcase悯舟,模擬多線程并發(fā)操作:

[TestMethod]
public void TestConcurrency()
{
    MyBiz("測(cè)試商戶");
    Thread.Sleep(1000);
    List<Thread> ths = new List<Thread>();
    for (int i = 0; i < 10; i++)
    {
        var thread = new Thread(() =>
        {
            try
            {
                MyBiz();
            }
            catch (Exception ex)
            {
                Console.WriteLine(Thread.CurrentThread.Name + "--" + ex.Message);
            }
        });
        thread.Name = "thread" + i;
        ths.Add(thread);
    }
    ths.ForEach(t => t.Start());
    Thread.Sleep(10 * 1000);

    //Thread.Sleep(1000);
    //Test("測(cè)試商戶");
}

測(cè)試輸出:

執(zhí)行次數(shù):1 duation:1127
thread1 執(zhí)行次數(shù):1 duation:162
thread7 遭遇i=0担租,接著重試...
thread8 遭遇i=0,接著重試...
thread9 遭遇i=0抵怎,接著重試...
thread6 遭遇i=0奋救,接著重試...
thread2 遭遇i=0,接著重試...
thread9 執(zhí)行次數(shù):2 duation:140
thread8 遭遇i=0反惕,接著重試...
thread6 遭遇i=0菠镇,接著重試...
thread7 遭遇i=0,接著重試...
thread2 遭遇i=0承璃,接著重試...
thread3 遭遇i=0利耍,接著重試...
thread3 執(zhí)行次數(shù):2 duation:699
thread5 遭遇i=0,接著重試...
thread7 遭遇i=0盔粹,接著重試...
thread2 遭遇i=0隘梨,接著重試...
thread6 遭遇i=0,接著重試...
thread8 遭遇i=0舷嗡,接著重試...
thread0 遭遇i=0轴猎,接著重試...
thread4 遭遇i=0,接著重試...
thread5 執(zhí)行次數(shù):2 duation:1146
thread2 遭遇i=0进萄,接著重試...
thread7 遭遇i=0捻脖,接著重試...
thread0 遭遇i=0锐峭,接著重試...
thread4 遭遇i=0,接著重試...
thread2 執(zhí)行次數(shù):5 duation:1398
thread7 遭遇i=0可婶,接著重試...
thread6 遭遇i=0沿癞,接著重試...
thread8 遭遇i=0,接著重試...
thread7 執(zhí)行次數(shù):6 duation:1630
thread0 遭遇i=0矛渴,接著重試...
thread4 遭遇i=0椎扬,接著重試...
thread0 執(zhí)行次數(shù):4 duation:2081
thread4 遭遇i=0,接著重試...
thread6 遭遇i=0具温,接著重試...
thread8 遭遇i=0蚕涤,接著重試...
thread8 執(zhí)行次數(shù):6 duation:2587
thread6 遭遇i=0,接著重試...
thread4 遭遇i=0铣猩,接著重試...
thread6 執(zhí)行次數(shù):7 duation:2825
thread4 執(zhí)行次數(shù):6 duation:3328

db里MerName字段的初始值是“測(cè)試商戶”揖铜,執(zhí)行testcase后值是“測(cè)試商戶1222564676”。 可見达皿,驗(yàn)證了我們代碼是ok的蛮位。

這種方式也有效地處理了并發(fā)。 那么鳞绕,和lock相比失仁,它的優(yōu)勢(shì)在哪里呢?∶呛巍lock只能控制同一進(jìn)程內(nèi)線程萄焦。 當(dāng)這段程序部署在不同的主機(jī)上時(shí),lock就顯得疲軟了冤竹。 而后者這個(gè)方案拂封,正好解決了多機(jī)部署時(shí)的并發(fā)。

最后鹦蠕,附上時(shí)間戳的生成算法:

/// <summary>  
/// 獲取當(dāng)前時(shí)間戳  
/// </summary>  
/// <param name="bflag">為真時(shí)獲取10位時(shí)間戳,為假時(shí)獲取13位時(shí)間戳.</param>  
/// <returns></returns>  
public static string GetTimeStamp(bool bflag = true)
{
    TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
    string ret = string.Empty;
    if (bflag)
        ret = Convert.ToInt64(ts.TotalSeconds).ToString();
    else
        ret = Convert.ToInt64(ts.TotalMilliseconds).ToString();

    return ret;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末冒签,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子钟病,更是在濱河造成了極大的恐慌萧恕,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肠阱,死亡現(xiàn)場(chǎng)離奇詭異票唆,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)屹徘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門走趋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人噪伊,你說我怎么就攤上這事簿煌〉ǎ” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵姨伟,是天一觀的道長(zhǎng)惩琉。 經(jīng)常有香客問我,道長(zhǎng)授滓,這世上最難降的妖魔是什么琳水? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任肆糕,我火速辦了婚禮般堆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘诚啃。我一直安慰自己淮摔,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布始赎。 她就那樣靜靜地躺著和橙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪造垛。 梳的紋絲不亂的頭發(fā)上魔招,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音五辽,去河邊找鬼办斑。 笑死,一個(gè)胖子當(dāng)著我的面吹牛杆逗,可吹牛的內(nèi)容都是我干的乡翅。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼罪郊,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼蠕蚜!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起悔橄,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤靶累,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后癣疟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體尺铣,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年争舞,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了凛忿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡竞川,死狀恐怖店溢,靈堂內(nèi)的尸體忽然破棺而出叁熔,到底是詐尸還是另有隱情,我是刑警寧澤床牧,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布荣回,位于F島的核電站,受9級(jí)特大地震影響戈咳,放射性物質(zhì)發(fā)生泄漏心软。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一著蛙、第九天 我趴在偏房一處隱蔽的房頂上張望删铃。 院中可真熱鬧,春花似錦踏堡、人聲如沸猎唁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)诫隅。三九已至,卻和暖如春帐偎,著一層夾襖步出監(jiān)牢的瞬間逐纬,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人半等。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓吁断,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容