当记账系统遇到并发

目前在开发的系统中有个结算的逻辑,每张订单到了账期日后,平台会给商家进行结算。涉及到账户方面的操作包括,平台账户余额的扣减和商户账户余额的增加,以及账户流水的记录。

像这个场景,如果不考虑并发的话,那么很容易出现数据不一致,导致记账混乱。 当然,这是比(xiāng)较(dāng)要命的!

那怎么解决这种并发呢?

为了便于描述,我们把场景简单化:db里有个Account表,记录平台及商户的账户信息; 程序逻辑为读取出指定的账户记录,修改其某个字段的值。 在这个场景下我们看并发如何处理。

想必大家都可以想到了,用lock。lock将语句块标记为临界区,获取给定对象的互斥锁,然后执行语句块,执行完成后释放锁。 这样可以控制进程内多线程的并发。

这里, 我再说另一种也许更好的方法————借助一个时间戳。先看代码逻辑:

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

    int loops = 0;//用以记录循环次数
    int i = 0;
    while (i == 0)//在执行db的update时,成功update会返回1,否则(非异常情况下)会返回0。所以,每当返回0时,我们就尝试再次执行整个逻辑
    {
        loops++;

        #region 业务逻辑
        // 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;
        }

        // 为了模拟并发,这里让线程随机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 + " 执行次数:" + loops + " duation:" + watch.ElapsedMilliseconds);
}

private static int Update(t_info_meraccount model)
{
    string sql = "update t_info_meraccount set [email protected],[email protected] where [email protected] and [email protected]";
    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属性值, 然后将这个修改持久化到db。

也可以看到,这段程序里利用了时间戳。在表t_info_meraccount里有个时间戳字段LastTime varchar(20)。 在对表执行update时,where子句除了必要的AcCode条件外,再追加一个LastTime。可以看到,当LastTime被其他线程更改后就匹配不上了,就会update失败,从而返回0。 那么这时, while循环继续, 直到返回1为止。

是否合理呢? 我们来写个testcase,模拟多线程并发操作:

[TestMethod]
public void TestConcurrency()
{
    MyBiz("20161129测试商户");
    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("20161129测试商户");
}

测试输出:

>	 执行次数:1 duation:1127
>	thread1 执行次数:1 duation:162
>	thread7 遭遇i=0,接着重试...
>	thread8 遭遇i=0,接着重试...
>	thread9 遭遇i=0,接着重试...
>	thread6 遭遇i=0,接着重试...
>	thread2 遭遇i=0,接着重试...
>	thread9 执行次数:2 duation:140
>	thread8 遭遇i=0,接着重试...
>	thread6 遭遇i=0,接着重试...
>	thread7 遭遇i=0,接着重试...
>	thread2 遭遇i=0,接着重试...
>	thread3 遭遇i=0,接着重试...
>	thread3 执行次数: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 执行次数:2 duation:1146
>	thread2 遭遇i=0,接着重试...
>	thread7 遭遇i=0,接着重试...
>	thread0 遭遇i=0,接着重试...
>	thread4 遭遇i=0,接着重试...
>	thread2 执行次数:5 duation:1398
>	thread7 遭遇i=0,接着重试...
>	thread6 遭遇i=0,接着重试...
>	thread8 遭遇i=0,接着重试...
>	thread7 执行次数:6 duation:1630
>	thread0 遭遇i=0,接着重试...
>	thread4 遭遇i=0,接着重试...
>	thread0 执行次数:4 duation:2081
>	thread4 遭遇i=0,接着重试...
>	thread6 遭遇i=0,接着重试...
>	thread8 遭遇i=0,接着重试...
>	thread8 执行次数:6 duation:2587
>	thread6 遭遇i=0,接着重试...
>	thread4 遭遇i=0,接着重试...
>	thread6 执行次数:7 duation:2825
>	thread4 执行次数:6 duation:3328

db里最终的值是20161129测试商户1222564676。 可见,验证了我们代码是ok的。

这种方式也有效地处理了并发。 那么,和lock相比,它的优势在哪里呢? lock只能控制同一进程内线程。 当这段程序部署在不同的主机上时,lock就显得疲软了。 而后者这个方案,正好解决了多机部署时的并发。

最后,附上时间戳的生成算法:

/// <summary>
/// 获取当前时间戳
/// </summary>
/// <param name="bflag">为真时获取10位时间戳,为假时获取13位时间戳.</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;
}
时间: 2024-10-03 15:23:30

当记账系统遇到并发的相关文章

徐汉彬:Web系统大规模并发——电商秒杀与抢购(转)

[导读]徐汉彬曾在阿里巴巴和腾讯从事4年多的技术研发工作,负责过日请求量过亿的Web系统升级与重构,目前在小满科技创业,从事SaaS服务技术建设. 电商的秒杀和抢购,对我们来说,都不是一个陌生的东西.然而,从技术的角度来说,这对于Web系统是一个巨大的考验.当一个Web系统,在一秒钟内收到数以万计甚至更多请求时,系统的优化和稳定至关重要.这次我们会关注秒杀和抢购的技术实现和优化,同时,从技术层面揭开,为什么我们总是不容易抢到火车票的原因? 一.大规模并发带来的挑战 在过去的工作中,我曾经面对过5

从电商秒杀与抢购谈Web系统大规模并发

从电商秒杀与抢购谈Web系统大规模并发 http://www.iamlintao.com/4242.html 一.大规模并发带来的挑战 在过去的工作中,我曾经面对过5w每秒的高并发秒杀功能,在这个过程中,整个Web系统遇到了很多的问题和挑战.如果Web系统不做针对性的优化,会轻而易举地陷入到异常状态.我们现在一起来讨论下,优化的思路和方法哈. 1. 请求接口的合理设计 一个秒杀或者抢购页面,通常分为2个部分,一个是静态的HTML等内容,另一个就是参与秒杀的Web后台请求接口. 通常静态HTML等

【问底】徐汉彬:Web系统大规模并发——电商秒杀与抢购

摘要:电商的秒杀和抢购,从技术的角度来说,会对Web系统产生巨大的考验.本期<问底>,徐汉彬将带大家关注秒杀和抢购的技术实现和优化,同时,从技术层面揭开,为什么我们总是不容易抢到火车票的原因. [导读]徐汉彬曾在阿里巴巴和腾讯从事4年多的技术研发工作,负责过日请求量过亿的Web系统升级与重构,目前在小满科技创业,从事SaaS服务技术建设. 电商的秒杀和抢购,对我们来说,都不是一个陌生的东西.然而,从技术的角度来说,这对于Web系统是一个巨大的考验.当一个Web系统,在一秒钟内收到数以万计甚至更

Vue2.0 + Element-UI + WebAPI实践:简易个人记账系统

最近正在学习Vue2.0相关知识,正好近期饿了么桌面端组件Element-UI发布,便动手做了一款简易个人记账系统,以达到实践及巩固目的. 1.开发环境 Win10 + VS2015 + Sqlserver2008R2 + WebAPI + Dapper + Vue2.0 + Element-UI 2.项目解决方案概览 简单介绍下,Account是WebAPI项目,承载前端请求:Account.BLL.Account.DAL.Account.Entity不废话:Account.Common是对D

徐汉彬:Web系统大规模并发——电商秒杀与抢购

Web系统大规模并发——电商秒杀与抢购 电商的秒杀和抢购,对我们来说,都不是一个陌生的东西.然而,从技术的角度来说,这对于Web系统是一个巨大的考验.当一个Web系统,在一秒钟内收到数以万计甚至更多请求时,系统的优化和稳定至关重要.这次我们会关注秒杀和抢购的技术实现和优化,同时,从技术层面揭开,为什么我们总是不容易抢到火车票的原因? 一.大规模并发带来的挑战 在过去的工作中,我曾经面对过5w每秒的高并发秒杀功能,在这个过程中,整个Web系统遇到了很多的问题和挑战.如果Web系统不做针对性的优化,

Java开源生鲜电商平台-OMS订单系统中并发问题和锁机制的探讨与解决方案(源码可下载)

Java开源生鲜电商平台-OMS订单系统中并发问题和锁机制的探讨与解决方案(源码可下载) 说明:Java开源生鲜电商中OMS订单系统中并发问题和锁机制的探讨与解决方案: 问题由来     假设在一个订单系统中(以火车票订单系统为例),用户A,用户B都要预定从成都到北京的火车票,A.B在不同的售票窗口均同时查询到了某车厢卧铺中.下铺位有空位.用户A正在犹豫订中铺还是下铺,这时用户B果断订购了下铺.当用户A决定订下铺时,系统提示下铺已经被预订,请重新选择铺位.在这个系统场景中,我们来探讨一下,火车票

Web系统大规模并发——电商秒杀与抢购

电商的秒杀和抢购,对我们来说,都不是一个陌生的东西.然而,从技术的角度来说,这对于Web系统是一个巨大的考验.当一个Web系统,在一秒钟内收到数以万计甚至更多请求时,系统的优化和稳定至关重要.这次我们会关注秒杀和抢购的技术实现和优化,同时,从技术层面揭开,为什么我们总是不容易抢到火车票的原因? 一.大规模并发带来的挑战 在过去的工作中,我曾经面对过5w每秒的高并发秒杀功能,在这个过程中,整个Web系统遇到了很多的问题和挑战.如果Web系统不做针对性的优化,会轻而易举地陷入到异常状态.我们现在一起

web系统常见并发问题

说说考试系统中的一些并发问题吧,这几天着重解决了一下题目这边的并发问题. 其实并发问题不外乎就是: 1.当插入一道试题时,结果这个题目所属的课程被删除掉了: 2.当删除一个课程时,先查时这个课程下的试题没有被使用可以被删除,结果在将将要执行删除语句之前,有一道试题被使用了: 对于第一个问题,其实用外键就可以解决.但是公司似乎有规定,不能够使用外键.主要原因还是外键对于数据库性能的损耗比较严重,特别是当数据量较大时. 其实外键对于我们这个考试系统也是不好使的,因为系统中的删除都是逻辑删除,只是将i

web系统大规模并发中的-秒杀与抢购

电商的秒杀和抢购,对我们来说,都不是一个陌生的东西.然而,从技术的角度来说,这对于Web系统是一个巨大的考验.当一个Web系统,在一秒钟内收到数以万计甚至更多请求时,系统的优化和稳定至关重要.这次我们会关注秒杀和抢购的技术实现和优化,同时,从技术层面揭开,为什么我们总是不容易抢到火车票的原因? 一.大规模并发带来的挑战 在过去的工作中,我曾经面对过5w每秒的高并发秒杀功能,在这个过程中,整个Web系统遇到了很多的问题和挑战.如果Web系统不做针对性的优化,会轻而易举地陷入到异常状态.我们现在一起