热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

C#请求唯一性校验支持高并发的实现方法

这篇文章主要给大家介绍了关于C#请求唯一性校验支持高并发的实现方法,文中通过示例代码介绍的非常详细,对大家学习或者使用C#具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧

使用场景描述:

  网络请求中经常会遇到发送的请求,服务端响应是成功的,但是返回的时候出现网络故障,导致客户端无法接收到请求结果,那么客户端程序可能判断为网络故障,而重复发送同一个请求。当然如果接口中定义了请求结果查询接口,那么这种重复会相对少一些。特别是交易类的数据,这种操作更是需要避免重复发送请求。另外一种情况是用户过于快速的点击界面按钮,产生连续的相同内容请求,那么后端也需要进行过滤,这种一般出现在系统对接上,无法去控制第三方系统的业务逻辑,需要从自身业务逻辑里面去限定。

其他需求描述:

  这类请求一般存在时间范围和高并发的特点,就是短时间内会出现重复的请求,因此对模块需要支持高并发性。

技术实现:

  对请求的业务内容进行MD5摘要,并且将MD5摘要存储到缓存中,每个请求数据都通过这个一个公共的调用的方法进行判断。

代码实现:

  公共调用代码 UniqueCheck 采用单例模式创建唯一对象,便于在多线程调用的时候,只访问一个统一的缓存库

/*
   * volatile就像大家更熟悉的const一样,volatile是一个类型修饰符(type specifier)。
   * 它是被设计用来修饰被不同线程访问和修改的变量。
   * 如果没有volatile,基本上会导致这样的结果:要么无法编写多线程程序,要么编译器失去大量优化的机会。
   */
  private static readonly object lockHelper = new object();
 
  private volatile static UniqueCheck _instance;  
 
  /// 
  /// 获取单一实例
  /// 
  /// 
  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
{
 /// 
 /// 非加锁并发队列,处理100个并发数以内
 /// 
 /// 
 public class ConcurrentLinkedQueue
 {
  private class Node
  {
   internal K Item;
   internal Node Next;

   public Node(K item, Node next)
   {
    this.Item = item;
    this.Next = next;
   }
  }

  private Node _head;
  private Node _tail;

  public ConcurrentLinkedQueue()
  {
   _head = new Node(default(T), null);
   _tail = _head;
  }

  public bool IsEmpty
  {
   get { return (_head.Next == null); }
  }
  /// 
  /// 进入队列
  /// 
  /// 
  public void Enqueue(T item)
  {
   Node newNode = new Node(item, null);
   while (true)
   {
    Node curTail = _tail;
    Node residue = curTail.Next;

    //判断_tail是否被其他process改变
    if (curTail == _tail)
    {
     //A 有其他process执行C成功,_tail应该指向新的节点
     if (residue == null)
     {
      //C 其他process改变了tail节点,需要重新取tail节点
      if (Interlocked.CompareExchange>(
       ref curTail.Next, newNode, residue) == residue)
      {
       //D 尝试修改tail
       Interlocked.CompareExchange>(ref _tail, newNode, curTail);
       return;
      }
     }
     else
     {
      //B 帮助其他线程完成D操作
      Interlocked.CompareExchange>(ref _tail, residue, curTail);
     }
    }
   }
  }
  /// 
  /// 队列取数据
  /// 
  /// 
  /// 
  public bool TryDequeue(out T result)
  {
   Node curHead;
   Node curTail;
   Node 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>(ref _tail, next, curTail);
     }
     else
     {
      //取next.Item必须放到CAS之前
      result = next.Item;
      //如果_head没有发生改变,则将_head指向next并退出
      if (Interlocked.CompareExchange>(ref _head,
       next, curHead) == curHead)
       break;
     }
    }
   }
   return true;
  }
  /// 
  /// 尝试获取最后一个对象
  /// 
  /// 
  /// 
  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;  

  /// 
  /// 获取单一实例
  /// 
  /// 
  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();
   _Timer = new Thread(DoTicket);
   _Timer.Start();
  }

  #region 公共属性设置
  /// 
  /// 设定定时线程的休眠时间长度:默认为1分钟
  /// 时间范围:1-7200000,值为1毫秒到2小时
  /// 
  /// 
  public void SetTimeSpan(int value)
  {
   if (value > 0&& value <=7200000)
   {
    _TimeSpan = value;
   }
  }
  /// 
  /// 设定缓存Cache中的最大记录条数
  /// 值范围:1-5000000,1到500万
  /// 
  /// 
  public void SetCacheMaxNum(int value)
  {
   if (value > 0 && value <= 5000000)
   {
    _CacheMaxNum = value;
   }
  }
  /// 
  /// 设置是否在控制台中显示日志
  /// 
  /// 
  public void SetIsShowMsg(bool value)
  {
   Helper.IsShowMsg = value;
  }
  /// 
  /// 线程请求阻塞增量
  /// 值范围:1-CacheMaxNum,建议设置为缓存最大值的10%-20%
  /// 
  /// 
  public void SetBlockNumExt(int value)
  {
   if (value > 0 && value <= _CacheMaxNum)
   {
    _BlockNumExt = value;
   }
  }
  /// 
  /// 请求阻塞时间
  /// 值范围:1-max,根据阻塞增量设置请求阻塞时间
  /// 阻塞时间越长,阻塞增量可以设置越大,但是请求实时响应就越差
  /// 
  /// 
  public void SetBlockSpanTime(int value)
  {
   if (value > 0)
   {
    _BlockSpanTime = value;
   }
  }
  #endregion

  #region 私有变量
  /// 
  /// 内部运行线程
  /// 
  private Thread _runner = null;
  /// 
  /// 可处理高并发的队列
  /// 
  private ConcurrentLinkedQueue _Myqueue = null;
  /// 
  /// 唯一内容的时间健值对
  /// 
  private Hashtable _DataKey = null;
  /// 
  /// 内容时间队列
  /// 
  private Queue _DataQueue = null;
  /// 
  /// 定时线程的休眠时间长度:默认为1分钟
  /// 
  private int _TimeSpan = 3000;
  /// 
  /// 定时计时器线程
  /// 
  private Thread _Timer = null;
  /// 
  /// 缓存Cache中的最大记录条数
  /// 
  private int _CacheMaxNum = 500000;
  /// 
  /// 线程请求阻塞增量
  /// 
  private int _BlockNumExt = 10000;
  /// 
  /// 请求阻塞时间
  /// 
  private int _BlockSpanTime = 100;
  #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;
  }
  /// 
  /// 执行循环操作
  /// 
  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(2);
   }
  }
  /// 
  /// 执行定时器的动作
  /// 
  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);
   }
  }

  /// 
  /// 线程进行睡眠等待
  /// 如果当前负载压力大大超出了线程的处理能力
  /// 那么需要进行延时调用
  /// 
  private void BlockThread()
  {
   if (_DataQueue.Count > _CacheMaxNum + _BlockNumExt)
   {
    Thread.Sleep(_BlockSpanTime);
   }
  }
  #endregion

  #region 公共方法
  /// 
  /// 开启服务线程
  /// 
  public void Start()
  {
   if (_runner == null)
   {
    StartRun();
   }
   else
   {
    if (_runner.IsAlive == false)
    {
     StartRun();
    }
   }

  }
  /// 
  /// 关闭服务线程
  /// 
  public void Stop()
  {
   if (_runner != null)
   {
    _runner.Abort();
    _runner = null;
   }
  }

  /// 
  /// 添加内容信息
  /// 
  /// 内容信息
  /// true:缓存中不包含此值,队列添加成功,false:缓存中包含此值,队列添加失败
  public bool AddItem(string item)
  {
   BlockThread();
   item = Helper.MakeMd5(item);
   if (_DataKey.ContainsKey(item))
   {
    return false;
   }
   else
   {
    _Myqueue.Enqueue(item);
    return true;
   }
  }
  /// 
  /// 判断内容信息是否已经存在
  /// 
  /// 内容信息
  /// true:信息已经存在于缓存中,false:信息不存在于缓存中
  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(20000000);
   _uck.SetBlockNumExt(1000000);
   _uck.SetTimeSpan(6000);

   _uck.AddItem(_example);
   Thread[] threads = new Thread[20];

   for (int i = 0; i <20; 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 = 0; i <50; 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(1000);
   }
   
  }

测试截图:

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对的支持。


推荐阅读
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • 如何在服务器主机上实现文件共享的方法和工具
    本文介绍了在服务器主机上实现文件共享的方法和工具,包括Linux主机和Windows主机的文件传输方式,Web运维和FTP/SFTP客户端运维两种方式,以及使用WinSCP工具将文件上传至Linux云服务器的操作方法。此外,还介绍了在迁移过程中需要安装迁移Agent并输入目的端服务器所在华为云的AK/SK,以及主机迁移服务会收集的源端服务器信息。 ... [详细]
  • SpringBoot整合SpringSecurity+JWT实现单点登录
    SpringBoot整合SpringSecurity+JWT实现单点登录,Go语言社区,Golang程序员人脉社 ... [详细]
  • GSIOpenSSH PAM_USER 安全绕过漏洞
    漏洞名称:GSI-OpenSSHPAM_USER安全绕过漏洞CNNVD编号:CNNVD-201304-097发布时间:2013-04-09 ... [详细]
  • 本文介绍了在RHEL 7中的系统日志管理和网络管理。系统日志管理包括rsyslog和systemd-journal两种日志服务,分别介绍了它们的特点、配置文件和日志查询方式。网络管理主要介绍了使用nmcli命令查看和配置网络接口的方法,包括查看网卡信息、添加、修改和删除配置文件等操作。 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • iOS Swift中如何实现自动登录?
    本文介绍了在iOS Swift中如何实现自动登录的方法,包括使用故事板、SWRevealViewController等技术,以及解决用户注销后重新登录自动跳转到主页的问题。 ... [详细]
  • 本文介绍了使用SSH免密登录的步骤,包括生成公私钥、传递公钥给被登录机、修改文件权限的操作。同时提醒用户注意私钥的传递方式,建议使用U盘等离线方式传递。 ... [详细]
  • Java学习笔记之使用反射+泛型构建通用DAO
    本文介绍了使用反射和泛型构建通用DAO的方法,通过减少代码冗余度来提高开发效率。通过示例说明了如何使用反射和泛型来实现对不同表的相同操作,从而避免重复编写相似的代码。该方法可以在Java学习中起到较大的帮助作用。 ... [详细]
  • 原理:dismiss再弹出,把dialog设为全局对象。if(dialog!null&&dialog.isShowing()&&!(Activity.)isFinishing()) ... [详细]
  • LVS实现负载均衡的原理LVS负载均衡负载均衡集群是LoadBalance集群。是一种将网络上的访问流量分布于各个节点,以降低服务器压力,更好的向客户端 ... [详细]
  • 本文详细介绍了在Centos7上部署安装zabbix5.0的步骤和注意事项,包括准备工作、获取所需的yum源、关闭防火墙和SELINUX等。提供了一步一步的操作指南,帮助读者顺利完成安装过程。 ... [详细]
  • Python脚本编写创建输出数据库并添加模型和场数据的方法
    本文介绍了使用Python脚本编写创建输出数据库并添加模型数据和场数据的方法。首先导入相应模块,然后创建输出数据库并添加材料属性、截面、部件实例、分析步和帧、节点和单元等对象。接着向输出数据库中添加场数据和历程数据,本例中只添加了节点位移。最后保存数据库文件并关闭文件。文章还提供了部分代码和Abaqus操作步骤。另外,作者还建立了关于Abaqus的学习交流群,欢迎加入并提问。 ... [详细]
  •     这里使用自己编译的hadoop-2.7.0版本部署在windows上,记得几年前,部署hadoop需要借助于cygwin,还需要开启ssh服务,最近发现,原来不需要借助cy ... [详细]
  • 大坑|左上角_pycharm连接服务器同步写代码(图文详细过程)
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了pycharm连接服务器同步写代码(图文详细过程)相关的知识,希望对你有一定的参考价值。pycharm连接服务 ... [详细]
author-avatar
90后的中老年人
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有