最近遇到一个高并发的问题,当初的需求优惠券券号是按照 日期 DateTime.Now.ToString("yyyyMMddHHmmss") + 最大订单号+1 的方式去新增券号的。
由于业务里面还涉及到多张表操作,用了事务。
第一版上线: 发现 很多订单号重复,找到原因是因为 在并发量高的时候,从数据库获取最大订单号时会产生并发,最新的单号没有新增到数据库,另一个线程已经从数据库查询到最大订单号,这样两个订单号 就是相同的。
为了解决这个问题 ,于是,第二个版本产生。
第二版上线:在获取单号到 新增数据库这段代码加入了锁。
代码如下:
private static object objlock = new object();
lock (objlock)
{ 主体代码 }
这样跟踪了一天,发现不会产生重复的订单号 ,但是过了几天发现另外一个更加严重的问题:一旦并发量达到一定量的时候,这个方法就越来越慢,从最开始的10几毫秒,慢慢到几百毫秒,然后要十几秒,一百秒,然后服务就直接挂了。并且会影响到其他负载均衡到的几台服务器都挂掉。
通过日志跟代码的分析,发现 在新增券的代码里面原先用到了事务。然后在事务里面又新增了锁。这是一个矛盾的地方,这样直接导致代码性能下降的很快。为了解决这个问题,于是再次修改代码
第三版上线:
1:为了解决线程池超时问题,必须立刻把锁解除,于是把锁的那段代码注释。
2:为了解决券号重复的问题,使用随机数来算,字符串+ DateTime.Now.ToString("yyyyMMddHHmmss")+随机数
注意随机数的获取方式,正常清空大家都会直接:
Random rad = new Random();//实例化随机数产生器rad;
int value = rad.Next(1000, 10000);//用rad生成大于等于1000,小于等于9999的随机数;
这样获取随机数,但是随机数获取机制是:
在.NET中,随机数一般是用Random来获取,但是当在多任务的并行化编程时,问题就出现了。因为Random是基于时间作为种子来生成伪随机数的,而如果程序在多核并行时,在同一时间内的多个核中取到的时间是一样的,这样一来,生成的伪随机数就有可能会有一样的。如果业务需求中需要不可重复的随机数,那么这后果将会相当严重
所以不能直接用以上代码获取 随机数。
所以必须采取一种新的方式来获取线程安全的伪随机数。
于是用 RNGCryptoServiceProvider的加密随机生成器,再用其中的强随机序列的方法GetBytes来实现随机。
代码如下: 调用 GetRandomNumber方法 ,参数是随机数的长度,需要几位数 参数就是多少。
public static string GetRandomNumber(int length)
{
string strPwd = string.Empty;
Random rd = new Random(GetRandomSeed());
for (int i = 0; i < length; i++)
{
strPwd += rd.Next(10).ToString();
}
return strPwd;
}
public static int GetRandomSeed()
{
byte[] bytes = new byte[4];
System.Security.Cryptography.RNGCryptoServiceProvider rng = new System.Security.Cryptography.RNGCryptoServiceProvider();
rng.GetBytes(bytes);
return BitConverter.ToInt32(bytes, 0);
}
总结:这样修改后,后面就不会再出现重复的优惠券单号。 并且原先高峰期的要几秒,几十秒的时间 回落到 100毫秒以内。