C#实现请求唯一性校验支持高并发
使用场景描述:
网络请求中经常会遇到发送的请求,服务端响应是成功的,但是返回的时候出现网络故障,导致客户端无法接收到请求结果,那么客户端程序可能判断为网络故障,而重复发送同一个请求。当然如果接口中定义了请求结果查询接口,那么这种重复会相对少一些。特别是交易类的数据,这种操作更是需要避免重复发送请求。另外一种情况是用户过于快速的点击界面按钮,产生连续的相同内容请求,那么后端也需要进行过滤,这种一般出现在系统对接上,无法去控制第三方系统的业务逻辑,需要从自身业务逻辑里面去限定。
其他需求描述:
这类请求一般存在时间范围和高并发的特点,就是短时间内会出现重复的请求,因此对模块需要支持高并发性。
技术实现:
对请求的业务内容进行MD5摘要,并且将MD5摘要存储到缓存中,每个请求数据都通过这个一个公共的调用的方法进行判断。
代码实现:
公共调用代码 UniqueCheck 采用单例模式创建唯一对象,便于在多线程调用的时候,只访问一个统一的缓存库
/*
* volatile就像大家更熟悉的const一样,volatile是一个类型修饰符(type specifier)。
* 它是被设计用来修饰被不同线程访问和修改的变量。
* 如果没有volatile,基本上会导致这样的结果:要么无法编写多线程程序,要么编译器失去大量优化的机会。
*/
private static readonly object lockHelper = new object(); private volatile static UniqueCheck _instance; /// <summary>
/// 获取单一实例
/// </summary>
/// <returns></returns>
public static UniqueCheck GetInstance()
{
if (_instance == null)
{
lock (lockHelper)
{
if (_instance == null)
_instance = new UniqueCheck();
}
}
return _instance;
}
这里需要注意volatile的修饰符,在实际测试过程中,如果没有此修饰符,在高并发的情况下会出现报错。
自定义一个可以进行并发处理队列,代码如下:ConcurrentLinkedQueue
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading; namespace PackgeUniqueCheck
{
/// <summary>
/// 非加锁并发队列,处理100个并发数以内
/// </summary>
/// <typeparam name="T"></typeparam>
public class ConcurrentLinkedQueue<T>
{
private class Node<K>
{
internal K Item;
internal Node<K> Next; public Node(K item, Node<K> next)
{
this.Item = item;
this.Next = next;
}
} private Node<T> _head;
private Node<T> _tail; public ConcurrentLinkedQueue()
{
_head = new Node<T>(default(T), null);
_tail = _head;
} public bool IsEmpty
{
get { return (_head.Next == null); }
}
/// <summary>
/// 进入队列
/// </summary>
/// <param name="item"></param>
public void Enqueue(T item)
{
Node<T> newNode = new Node<T>(item, null);
while (true)
{
Node<T> curTail = _tail;
Node<T> residue = curTail.Next; //判断_tail是否被其他process改变
if (curTail == _tail)
{
//A 有其他process执行C成功,_tail应该指向新的节点
if (residue == null)
{
//C 其他process改变了tail节点,需要重新取tail节点
if (Interlocked.CompareExchange<Node<T>>(
ref curTail.Next, newNode, residue) == residue)
{
//D 尝试修改tail
Interlocked.CompareExchange<Node<T>>(ref _tail, newNode, curTail);
return;
}
}
else
{
//B 帮助其他线程完成D操作
Interlocked.CompareExchange<Node<T>>(ref _tail, residue, curTail);
}
}
}
}
/// <summary>
/// 队列取数据
/// </summary>
/// <param name="result"></param>
/// <returns></returns>
public bool TryDequeue(out T result)
{
Node<T> curHead;
Node<T> curTail;
Node<T> next;
while (true)
{
curHead = _head;
curTail = _tail;
next = curHead.Next;
if (curHead == _head)
{
if (next == null) //Queue为空
{
result = default(T);
return false;
}
if (curHead == curTail) //Queue处于Enqueue第一个node的过程中
{
//尝试帮助其他Process完成操作
Interlocked.CompareExchange<Node<T>>(ref _tail, next, curTail);
}
else
{
//取next.Item必须放到CAS之前
result = next.Item;
//如果_head没有发生改变,则将_head指向next并退出
if (Interlocked.CompareExchange<Node<T>>(ref _head,
next, curHead) == curHead)
break;
}
}
}
return true;
}
/// <summary>
/// 尝试获取最后一个对象
/// </summary>
/// <param name="result"></param>
/// <returns></returns>
public bool TryGetTail(out T result)
{
result = default(T);
if (_tail == null)
{
return false;
}
result = _tail.Item;
return true;
}
}
}
虽然是一个非常简单的唯一性校验逻辑,但是要做到高效率,高并发支持,高可靠性,以及低内存占用,需要实现这样的需求,需要做细致的模拟测试。
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Collections; namespace PackgeUniqueCheck
{
public class UniqueCheck
{
/*
* volatile就像大家更熟悉的const一样,volatile是一个类型修饰符(type specifier)。
* 它是被设计用来修饰被不同线程访问和修改的变量。
* 如果没有volatile,基本上会导致这样的结果:要么无法编写多线程程序,要么编译器失去大量优化的机会。
*/
private static readonly object lockHelper = new object(); private volatile static UniqueCheck _instance; /// <summary>
/// 获取单一实例
/// </summary>
/// <returns></returns>
public static UniqueCheck GetInstance()
{
if (_instance == null)
{
lock (lockHelper)
{
if (_instance == null)
_instance = new UniqueCheck();
}
}
return _instance;
} private UniqueCheck()
{
//创建一个线程安全的哈希表,作为字典缓存
_DataKey = Hashtable.Synchronized(new Hashtable());
Queue myqueue = new Queue();
_DataQueue = Queue.Synchronized(myqueue);
_Myqueue = new ConcurrentLinkedQueue<string>();
_Timer = new Thread(DoTicket);
_Timer.Start();
} #region 公共属性设置
/// <summary>
/// 设定定时线程的休眠时间长度:默认为1分钟
/// 时间范围:1-7200000,值为1毫秒到2小时
/// </summary>
/// <param name="value"></param>
public void SetTimeSpan(int value)
{
if (value > && value <=)
{
_TimeSpan = value;
}
}
/// <summary>
/// 设定缓存Cache中的最大记录条数
/// 值范围:1-5000000,1到500万
/// </summary>
/// <param name="value"></param>
public void SetCacheMaxNum(int value)
{
if (value > && value <= )
{
_CacheMaxNum = value;
}
}
/// <summary>
/// 设置是否在控制台中显示日志
/// </summary>
/// <param name="value"></param>
public void SetIsShowMsg(bool value)
{
Helper.IsShowMsg = value;
}
/// <summary>
/// 线程请求阻塞增量
/// 值范围:1-CacheMaxNum,建议设置为缓存最大值的10%-20%
/// </summary>
/// <param name="value"></param>
public void SetBlockNumExt(int value)
{
if (value > && value <= _CacheMaxNum)
{
_BlockNumExt = value;
}
}
/// <summary>
/// 请求阻塞时间
/// 值范围:1-max,根据阻塞增量设置请求阻塞时间
/// 阻塞时间越长,阻塞增量可以设置越大,但是请求实时响应就越差
/// </summary>
/// <param name="value"></param>
public void SetBlockSpanTime(int value)
{
if (value > )
{
_BlockSpanTime = value;
}
}
#endregion #region 私有变量
/// <summary>
/// 内部运行线程
/// </summary>
private Thread _runner = null;
/// <summary>
/// 可处理高并发的队列
/// </summary>
private ConcurrentLinkedQueue<string> _Myqueue = null;
/// <summary>
/// 唯一内容的时间健值对
/// </summary>
private Hashtable _DataKey = null;
/// <summary>
/// 内容时间队列
/// </summary>
private Queue _DataQueue = null;
/// <summary>
/// 定时线程的休眠时间长度:默认为1分钟
/// </summary>
private int _TimeSpan = ;
/// <summary>
/// 定时计时器线程
/// </summary>
private Thread _Timer = null;
/// <summary>
/// 缓存Cache中的最大记录条数
/// </summary>
private int _CacheMaxNum = ;
/// <summary>
/// 线程请求阻塞增量
/// </summary>
private int _BlockNumExt = ;
/// <summary>
/// 请求阻塞时间
/// </summary>
private int _BlockSpanTime = ;
#endregion #region 私有方法
private void StartRun()
{
_runner = new Thread(DoAction);
_runner.Start();
Helper.ShowMsg("内部线程启动成功!");
} private string GetItem()
{
string tp = string.Empty;
bool result = _Myqueue.TryDequeue(out tp);
return tp;
}
/// <summary>
/// 执行循环操作
/// </summary>
private void DoAction()
{
while (true)
{
while (!_Myqueue.IsEmpty)
{
string item = GetItem();
_DataQueue.Enqueue(item);
if (!_DataKey.ContainsKey(item))
{
_DataKey.Add(item, DateTime.Now);
}
}
//Helper.ShowMsg("当前数组已经为空,处理线程进入休眠状态...");
Thread.Sleep();
}
}
/// <summary>
/// 执行定时器的动作
/// </summary>
private void DoTicket()
{
while (true)
{
Helper.ShowMsg("当前数据队列个数:" + _DataQueue.Count.ToString());
if (_DataQueue.Count > _CacheMaxNum)
{
while (true)
{
Helper.ShowMsg(string.Format("当前队列数:{0},已经超出最大长度:{1},开始进行清理操作...", _DataQueue.Count, _CacheMaxNum.ToString()));
string item = _DataQueue.Dequeue().ToString();
if (!string.IsNullOrEmpty(item))
{
if (_DataKey.ContainsKey(item))
{
_DataKey.Remove(item);
}
if (_DataQueue.Count <= _CacheMaxNum)
{
Helper.ShowMsg("清理完成,开始休眠清理线程...");
break;
}
}
}
}
Thread.Sleep(_TimeSpan);
}
} /// <summary>
/// 线程进行睡眠等待
/// 如果当前负载压力大大超出了线程的处理能力
/// 那么需要进行延时调用
/// </summary>
private void BlockThread()
{
if (_DataQueue.Count > _CacheMaxNum + _BlockNumExt)
{
Thread.Sleep(_BlockSpanTime);
}
}
#endregion #region 公共方法
/// <summary>
/// 开启服务线程
/// </summary>
public void Start()
{
if (_runner == null)
{
StartRun();
}
else
{
if (_runner.IsAlive == false)
{
StartRun();
}
} }
/// <summary>
/// 关闭服务线程
/// </summary>
public void Stop()
{
if (_runner != null)
{
_runner.Abort();
_runner = null;
}
} /// <summary>
/// 添加内容信息
/// </summary>
/// <param name="item">内容信息</param>
/// <returns>true:缓存中不包含此值,队列添加成功,false:缓存中包含此值,队列添加失败</returns>
public bool AddItem(string item)
{
BlockThread();
item = Helper.MakeMd5(item);
if (_DataKey.ContainsKey(item))
{
return false;
}
else
{
_Myqueue.Enqueue(item);
return true;
}
}
/// <summary>
/// 判断内容信息是否已经存在
/// </summary>
/// <param name="item">内容信息</param>
/// <returns>true:信息已经存在于缓存中,false:信息不存在于缓存中</returns>
public bool CheckItem(string item)
{
item = Helper.MakeMd5(item);
return _DataKey.ContainsKey(item);
}
#endregion }
}
模拟测试代码:
private static string _example = Guid.NewGuid().ToString();
private static UniqueCheck _uck = null;
static void Main(string[] args)
{
_uck = UniqueCheck.GetInstance();
_uck.Start();
_uck.SetIsShowMsg(false);
_uck.SetCacheMaxNum();
_uck.SetBlockNumExt();
_uck.SetTimeSpan();
_uck.AddItem(_example);
Thread[] threads = new Thread[];
for (int i = ; i < ; i++)
{
threads[i] = new Thread(AddInfo);
threads[i].Start();
}
Thread checkthread = new Thread(CheckInfo);
checkthread.Start();
string value = Console.ReadLine();
checkthread.Abort();
for (int i = ; i < ; i++)
{
threads[i].Abort();
}
_uck.Stop();
}
static void AddInfo()
{
while (true)
{
_uck.AddItem(Guid.NewGuid().ToString());
}
}
static void CheckInfo()
{
while (true)
{
Console.WriteLine("开始时间:{0}...", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff"));
Console.WriteLine("插入结果:{0}", _uck.AddItem(_example));
Console.WriteLine("结束时间:{0}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff"));
//调整进程休眠时间,可以测试高并发的情况
//Thread.Sleep();
}
}
测试截图:

C#实现请求唯一性校验支持高并发的更多相关文章
- 配置开发支持高并发TCP连接的Linux应用程序全攻略
http://blog.chinaunix.net/uid-20733992-id-3447120.html http://blog.chinaunix.net/space.php?uid=16480 ...
- Linux配置支持高并发TCP连接(socket最大连接数)
Linux配置支持高并发TCP连接(socket最大连接数) Linux配置支持高并发TCP连接(socket最大连接数)及优化内核参数 2011-08-09 15:20:58| 分类:LNMP&a ...
- Nodejs:单线程为什么能支持高并发?
1.Nodejs是一个平台,构建在chrome的V8上(js语言解释器),采用事件驱动.非阻塞模型( c++库:libuv). 参考官方: Node.js is a platform built ...
- 从 Nginx 优秀的核心架构设计,揭秘其为何能支持高并发?
目录: 1. Nginx的整体架构 2. Nginx的模块化设计 3. Nginx的请求方式处理 4. Nginx事件驱动模型 5. Nginx进程处理模型 写在前面 Nginx 是一个 免费的,开源 ...
- Nginx为什么可以支持高并发
Nginx是由一个俄罗斯人专门为解决高并发而开发的 nginx 采用的是多进程+epoll,能实现高并发,其可以支持的并发上限大概是同时支持5W个连接 1 多进程 nginx 在启动后,会有一个 ma ...
- 支持高并发的IIS Web服务器常用设置
适用的IIS版本:IIS 7.0, IIS 7.5, IIS 8.0 适用的Windows版本:Windows Server 2008, Windows Server 2008 R2, Windows ...
- 支持高并发的IIS Web服务器常用设置 II
适用的IIS版本:IIS 7.0, IIS 7.5, IIS 8.0 适用的Windows版本:Windows Server 2008, Windows Server 2008 R2, Windows ...
- IIS Web服务器支持高并发设置
适用的IIS版本:IIS 7.0, IIS 7.5, IIS 8.0 适用的Windows版本:Windows Server 2008, Windows Server 2008 R2, Windows ...
- 基于GO语言实现的支持高并发订单号生成函数
1.固定24位长度订单号,毫秒+进程id+序号. 2.同一毫秒内只要不超过一万次并发,则订单号不会重复. github地址:https://github.com/w3liu/go-common/blo ...
随机推荐
- JDBC、Tomcat为什么要破坏双亲委派模型?
问题一:双亲委派模型是什么 如果一个类加载器收到了加载某个类的请求,则该类加载器并不会去加载该类,而是把这个请求委派给父类加载器,每一个层次的类加载器都是如此,因此所有的类加载请求最终都会传送到顶端的 ...
- Java网络编程之URLConnection
Java网络编程之URLConnecton 一.URLConnection简介 URLConnection是一个抽象类,表示指向URL指定资源的活动连接.URLConnection有两个不同但相关的用 ...
- 一 安装docker(详解)
一.安装docker 1 Docker 要求 CentOS 系统的内核版本高于 3.10 执行命令:uname -r 2 添加yum源: yum-config-manager --add-repo h ...
- SCRUM起源
http://www.scrumcn.com/agile/scrum-knowledge-library/scrum.html#tab-id-3 Scrum的原始含义 Scrum原始含义是指英式橄榄球 ...
- codeforces 233 C. Cycles(贪心+思维)
题目链接:http://codeforces.com/contest/233/problem/C 题意:在一个无相图中有N个长度为3 的回路,输出符合条件的图.注意此图的节点数不得超过100 题解:贪 ...
- 2015 省赛 简单的图论问题? bfs
[E] 简单的图论问题? 时间限制: 5000 ms 内存限制: 65535 K 问题描述 给一个 n 行 m 列的迷宫,每个格子要么是障碍物要么是空地.每个空地里都有一个权值.你的 任务是从找一条( ...
- AOE工程实践-NCNN组件
作者:杨科 NCNN是腾讯开源的一个为手机端极致优化的高性能神经网络前向计算框架.在AOE开源工程里,我们提供了NCNN组件,下面我们以SqueezeNet物体识别这个Sample为例,来讲一讲NCN ...
- C#基础——事件初步
事件是C#语言的重要成员之一,初学者往往不能很好的去理解和运用事件,特别是自定义事件.在这里将以较简单的方式呈现事件最基本的用法. 1.事件的定义 给事件下定义是一个较困难的事,因为它体现的是对象与对 ...
- win10 设定计划任务时提示所指定的账户名称无效,如何解决?
我想把我的 python 爬虫脚本设定为自动定时执行,我的设备是win10 操作系统,这将用到系统自带的计划任务功能.且我希望不管用户是否登录都要运行该定时任务,但在设置计划任务的属性时,遇到一个报错 ...
- [ERR] 1118 - Row size too large (> 8126). Changing some columns to TEXT or BLOB may help. In current row format, BLOB prefix of 0 bytes is stored inline.
昨天,在测试新的数据库时,迁移表遇到了这个问题.现在记录一下解决方案. 1.在配置文件中添加关闭严格模式的配置:sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS ...