Eclipse下FatJar插件的安装以及用

现,因为种种因素,你必对一个求或措施开展频率达到的走访限制。
依,
你对外提供了一个API接口,注册用户每秒钟最多好调用100不行,非注册用户每秒钟最多足调用10糟糕。
遵,
有一个格外吃服务器资源的计,在一如既往时刻不可知跨越10私调用这个方法,否则服务器满载。
遵照, 有局部独特之页面,访客并无能够屡屡的走访还是发言。
仍, 秒杀活动等进行。

,防范DDOS,当及自然频率后调用脚本iis服务器ip黑名单,防火墙黑名单。
使齐种的比方,也就是说,如何自一个断面的角度对调用的措施进行频率高达之克。而针对效率限制,服务器层面还出无比直白的缓解方式,现在自家说的则是代码层面上之效率管控。

每当Eclipse下生成jar包分很多种景象。最简单易行的事态是从未有过因此到第三方jar包,那么直接Export就得生成jar包。但是要是就此到了第三方jar包,那么就是于麻烦了,很不便利。FatJar可以缓解者问题,使用FatJar,即使含了众叔方jar包,也得以挺有益之生成jar包。

本文为起片只示范,一个凡是冲单机环境的贯彻,第二个则是根据分布式的Redis实现

安FatJar的法门来星星点点种植,可以下载文件,复制到Eclipse的plugin文件夹下,也可在线安装。下面介绍在线安装的步子。


(1)在Eclipse里面点击Help->Install New
Software,在弹出的Install界面里面,点击Add,输入名称Fat
Jar,和地址http://kurucz-grafika.de/fatjar,然后就可以自动安装了;

盖第一个API接口需求吗条例,先说下单机环境下之兑现。
依照惯性思维,我们本来会想到缓存的超时策略这种方法,但是严格来讲就是HttpRuntime.Cache而言,通过缓存的晚点策略来针对要进行频率的出现控制是不合适的。
  HttpRuntime.Cache
是应用程序级别的Asp.Net的复苏存技术,通过这技术可表明多独缓存对象,可以吗每个对象设置过时,当过岁月到晚该缓存对象就见面流失(也就算是当您拜该目标的时候吗Null)

图片 1

  为什么这么说吗?比如对准有方法(方法名:GetUserList)我们设进行1秒钟最多10次于的范围,现在我们不怕新建一个int型的Cache对象,然后设置1秒钟后过消失。那么在访问GetUserList方法前,我们虽先行判断是Cache对象的值是否超越10,如果超出10就非实施GetUserList方法,如果低于10尽管允许实施。每当访问该目标的时如果不在或者逾期就新建,这样循环,则该对象永远不容许逾10。

(2)安装成功之后,点击Windows->Preferences,弹出的窗口中要产生Fat
Jar Preferences,则表明安装成功;

1   if ((int)HttpRuntime.Cache["GetUserListNum"] > 10) //大于10请求失败
2   {
3      Console.WriteLine("禁止请求");
4   }
5   else
6   {
7      HttpRuntime.Cache["GetUserListNum"] = (int)HttpRuntime.Cache["GetUserListNum"] + 1; //否则该缓存对象的值+1
8      Console.WriteLine("允许请求");
9   }

图片 2

如此这般的思辨和落实相对来说非常简单,但是因这样的一个模子设定,那么尽管会见面世这种气象:

装好Fat
Jar以后,就得方便的针对性包含第三方jar包的工生成Jar包了。

 图片 3

(1)右键点击需要打包的工,点击Export,在弹出的对话框中选取Other->Fat
Jar Exporter;

 

图片 4

假使达到图,每个点代表同样赖走访请求,我在0秒的上
新建了一个名啊GetUserListNum的缓存对象。
在0~0.5秒之内
我顾了3不行,在0.5~1秒中,我们走访了7糟糕。此时,该目标消失,然后我们随后访问,该对象重置为0.
              
 在第1~1.5秒内,还是看了7次于,在第1.5秒~2秒里做客了3坏。

(2)点击Next,选择要由包之工;

据悉这种概括缓存过期策略的模型,在就2秒钟内,我们虽平均每秒钟都看了10潮,满足这个确定,但是倘若我们从中取一个里段,0.5秒~1.5秒里,也是1秒钟,但是可的确的访问了14糟糕!远远超越了咱设置的
1秒钟最多看10涂鸦的 限制。

图片 5

 

(3)点击Next,输入Jar
Name,如果想当肆意位置生成Jar包,选择use extern
Jar-Name,选择Main-Class,Class-Path不填,选择One-JAR;

那么怎样正确的来化解点的问题也?我们可由此模拟对话级别的信号量立即同样招数,这也即是咱们今天的主题了。
   什么是信号量?仅就因代码而言,  static
SemaphoreSlim semaphoreSlim = new SemaphoreSlim(5); 
它的意思就是代表以多线程情况下,在另外一样整日,只能以5单线程去看。

图片 6

 

(4)点击Next,可以望该工程从而到的老三方Jar包,全部打勾;

4容器4线程模型

兹,在实现代码的事先我们先筹一个模。

图片 7

  假设我们有一个用户A的管道,这个管道里装着用户A的乞求,比如用户A在同样秒钟发出了10不良呼吁,那么每一个求过来,管道里的因素还见面多一个。但是咱设定是管道最多只能容纳10独元素,而且每个元素的存活期为1秒,1秒后虽然该因素消失。那么这么设计吧,无论是速率还是多少的突进,都见面出管道长度的限量。这样一来,无论从哪一个光阴节点还是时间间隔出发,这个管道还能够满足我们的效率限制要求。

假如这里的管道,就必须与会话Id来对号入座了。每当有新会话进来的时刻即便老大成一个初管道。这个会话id根据自己场景所必然,可以是sessionId,可以是ip,也得是token。

这就是说既然这个管道是会见说话级别之,我们得得待一个器皿,来装这些管道。现在,我们为IP来定名会话管道,并将持有的管道还装在一个容器中,如图

图片 8

一经基于刚才的设定,我们还得针对容器内之各级条管道的元素进行拍卖,把过的受抹掉,为这,还需要独自为该容器开辟出一个线程来啊各国条管道进行元素的清理。而当管道的素也0时,我们不怕彻底掉该管道,以便节省容器空间。

 图片 9

自然,由于用户量多,一个器皿内或者在上万只管道,这个时候只用一个器皿来装来清理,在效率及显眼是不够的。这个时,我们就得对容器进行横向扩张了。

  比如,我们可以因Cpu核心数自动生成对应之数码之容器,然后因一个算法,对IP来开展导流。我当下cpu是4只逻辑核心,就十分成了4独容器,每当用户访问的时光,都见面最先经过一个算法,这个算法会对IP进行处理,如192.168.1.11~192.168.1.13这Ip段进第一个容器,xxx~xxx进第二单容器,依次类推,相应的,也不怕发出了4独线程去分别处理4个容器中之管道。

图片 10

 

那,最终便形成了咱的4容器4线程模型了。

今日,着眼于编码实现:

  首先我们要一个会承载这些器皿的载体,这个载体类似于连接池的概念,可以依据局部待自动生成适应数量之容器,如果来特殊要求的话语,还好当容器上切出一个容器管理之面对,在线程上切出一个线程管理的对以便为实时监察与调度。如果确如开这么一个体系,那么
容器的调度 和 线程的调度功能
是必要的,而本Demo则是完结了重大力量,像容器与线程在代码中本人也从没脱开来,算法为是一直写好的,实际设计被,对算法的统筹还是不行重要之,还有多线程模型中,怎样上锁才能够于效率最大化为是关键的。

万一此为了案例之直观就直接写很成4只容器。

public static List<Container> ContainerList = new List<Container>(); //容器载体
static Factory()
{
     for (int i = 0; i < 4; i++)
     {
        ContainerList.Add(new Container(i));  //遍历4次  生成4个容器
     }
     foreach (var item in ContainerList)
     {
        item.Run();    //开启线程
     }
}

今,我们而 有编号为 0 到 40 这样的 41只用户。那么这个导流算法
我也尽管直写很,编号0至9底用户
将他们之乞求被丢转至第一独容器,编号10~19底用户
放到第二单容器,编号20~29放至第三只容器,编号30~40的用户放第四单容器。

那这代码就是如此的:

 static Container GetContainer(int userId, out int i) //获取容器的算法
 {
     if (0 <= userId && userId < 10)    //编号0至9的用户  返回第一个容器  依次类推
     {
          i = 0;
          return ContainerList[0];
     }
     if (10 <= userId && userId < 20)
     {
          i = 1;
          return ContainerList[1];
     }
     if (20 <= userId && userId < 30)
     {
          i = 2;
          return ContainerList[2];
      }
      i = 3;
      return ContainerList[3];
  }

当我们的对话请求经过算法的导流之后,都得调用一个艺术,用于辨别管道数量。如果管道数量一度盖10,则请求失败,否则成功

  public static void Add(int userId)
  {
       if (GetContainer(userId, out int i).Add(userId))
            Console.WriteLine("容器" + i + " 用户" + userId + "  发起请求");
       else
            Console.WriteLine("容器" + i + " 用户" + userId + "  被拦截");
  }

连接下就容器Container的代码了。

此地,对容器的选型用线程安全之ConcurrentDictionary类。
  线程安全:当多只线程同时读写及一个共享元素的上,就会见油然而生数错乱,迭代报错等安全问提
  ConcurrentDictionary:除了GetOrAdd方法要慎用外,是.Net4.0总揽为缓解Dictionary线程安全而发出底初品类
  ReaderWriterLockSlim:较ReaderWriterLock优化的宣读写锁,多独线程同时做客读锁
或  一个线程访问写锁

private ReaderWriterLockSlim obj = new ReaderWriterLockSlim();  //在每个容器中申明一个读写锁
public ConcurrentDictionary<string, ConcurrentList<DateTime>> dic = new ConcurrentDictionary<string, ConcurrentList<DateTime>>(); //创建该容器 dic

接下来当您往容器上加同长条管道遭的数目是透过这个法子:

 public bool Add(int userId)
 {
     obj.EnterReadLock();//挂读锁,允许多个线程同时写入该方法
     try
     {
         ConcurrentList<DateTime> dtList = dic.GetOrAdd(userId.ToString(), new ConcurrentList<DateTime>()); //如果不存在就新建 ConcurrentList
         return dtList.CounterAdd(10, DateTime.Now); //管道容量10,当临界管道容量后 返回false
     }
     finally
     {
         obj.ExitReadLock();
     }
 }

 这里,为了以后头的线程遍历删除ConcurrentList的管道的时刻保证ConcurrentList的安全性,所以这边设加读锁。

 而ConcurrentList,因为.Net没有出List集合类的线程安全(count和add加锁),所以自己新建了一个继往开来给List<T>的安全项目,在此处
封装了3只待用的方法。

public class ConcurrentList<T> : List<T>
{
    private object obj = new object();

    public bool CounterAdd(int num, T value)
    {
        lock (obj)
        {
            if (base.Count >= num)
                return false;
            else
                base.Add(value);
            return true;
        }
    }
    public new bool Remove(T value)
    {
        lock (obj)
        {
            base.Remove(value);
            return true;
        }
    }
    public new T[] ToArray() 
    {
        lock (obj)
        {
            return base.ToArray();
        }
    }
}

说到底就是是线程的周转方式:

 public void Run()
 {
     ThreadPool.QueueUserWorkItem(c =>
     {
         while (true)
         {
             if (dic.Count > 0)
             {
                 foreach (var item in dic.ToArray())
                 {
                     ConcurrentList<DateTime> list = item.Value;
                     foreach (DateTime dt in list.ToArray())   
                     {
                         if (DateTime.Now.AddSeconds(-3) > dt)
                         {
                             list.Remove(dt);
                             Console.WriteLine("容器" + seat + " 已删除用户" + item.Key + "管道中的一条数据");
                         }
                     }
                     if (list.Count == 0)
                     {
                         obj.EnterWriteLock();
                         try
                         {
                             if (list.Count == 0)
                             {
                                 if (dic.TryRemove(item.Key, out ConcurrentList<DateTime> i))
                                 { Console.WriteLine("容器" + seat + " 已清除用户" + item.Key + "的List管道"); }
                             }
                         }
                         finally
                         {
                             obj.ExitWriteLock();
                         }
                     }
                 }

             }
             else
             {
                 Thread.Sleep(100);
             }
         }
     }
   );
 }

末尾,是职能图,一个凡是因控制台的,还一个凡冲Signalr的。

 图片 11图片 12

图片 13

分布式下Redis

方介绍了同种频率限制的型,分布式与单机相比,无非就是是载体不同,我们而将这容器的载体从程序及移植出来,来整治成一个独自的劳动还是直接借用Redis也是行之。

此地虽介绍分布式情况下,Redis的兑现。

今非昔比让Asp.Net的多线程模型,大概因为Redis的各种类型的要素非常粒度的操作导致各种加锁之纷繁,所以当网络要处理这块Redis是单线程的,基于Redis的实现则坐单线程的来由在编码角度不用太多着想到与逻辑无关的题材。

  简单介绍下,Redis是一个内存数据库,这个数据库属于非关系型数据库,它的概念不同让一般的我们体会的Mysql
Oracle
SqlServer关系型数据库,它从不Sql没有字段名从未表名这些概念,它与HttpRunTime.Cache的概念差不多一样,首先由操作及属于键值对模式,就假设
Cache[“键名”]
这样尽管能够博取到价值类似,而且得针对每个Key设置过策略,而Redis中的Key所对应的值并无是想存啥就存啥的,它支持五种植多少列:string(字符串),hash(哈希),list(列表),set(集合)及sorted
set(有序聚集)。

今日设说的凡Sorted
set有序聚集,有序聚集相比另的聚合类型的超常规点在,使用有序聚集的时节还能为插入的元素指定一个
积分score,我们拿此积分score理解也铲除序列,它其中会对积分进行排序,积分允许再次,而有序聚集中的元素虽然是绝无仅有。

  还是一样的思绪,每当有用户访问的时刻,都针对该用户之
管道(有序聚集)中上加一个元素,然后设置该因素的积分也目前时光。接着在程序中开始单线程,来对管道被积分小于约定时间的元素进行清理。因为规定有序聚集中的因素只能是唯一值,所以当赋值方面只要是满足uuid即可。

 图片 14

那用Redis来落实之代码那便是相仿这种:

图片 15

通过using语法糖实现IDisposable而包装的Redis分布式锁,然后里面正常的逻辑判断。

然的代码虽然也克就功能,但无足够好。Redis是只因内存的数据库,于性能而言,瓶颈在于网络
IO 上,与Get一软发出同样软呼吁相比,能免可知通过同样段脚本来实现多数逻辑吗?

有的,Redis支持 Lua脚本:
  Lua
是一致种植轻量小巧的脚本语言,用标准C语言编写并因自代码形式开放,
其计划目的是为着放置应用程序中,从而也应用程序提供灵活的扩展及定制功能。
  大致意思就是是,直接为Redis发送一段子脚本或者被它直接本地读取一截脚本从而一直促成所有的逻辑。

/// <summary>
/// 如果 大于10(AccountNum) 就返回1   否则就增加一条集合中的元素 并返回 空
/// </summary>
/// <param name="zcardKey"></param>
/// <param name="score"></param>
/// <param name="zcardValue"></param>
/// <param name="AccountNum"></param>
/// <returns></returns>
public string LuaAddAccoundSorted(string zcardKey, double score, string zcardValue, int AccountNum)
{
    string str = "local uu = redis.call('zcard',@zcardKey) if (uu >=tonumber(@AccountNum)) then return 1 else redis.call('zadd',@zcardKey,@score,@zcardValue)  end";
    var re = _instance.GetDatabase(_num).ScriptEvaluate(LuaScript.Prepare(str), new { zcardKey = zcardKey, score = score, zcardValue = zcardValue, AccountNum=AccountNum });
    return re.ToString();
}

local
uu就是发明一个啊名uu的变量的意,redis.call就是redis命令,这段脚本意思就是是只要
大于10(AccountNum) 就返回1   否则就是增加一漫漫集合中之要素 并赶回 空。

管道内元素处理的主意就是:

 /// <summary>
 /// 遍历当前所有前缀的有序集合,如果数量为0,那么就返回1 否则 就删除 满足最大分值条件区间的元素,如果该集合个数为0则消失
 /// </summary>
 /// <param name="zcardPrefix"></param>
 /// <param name="score"></param>
 /// <returns></returns>
public string LuaForeachRemove(string zcardPrefix, double score)
 {
     StringBuilder str = new StringBuilder();
     str.Append("local uu = redis.call('keys',@zcardPrefix) "); //声明一个变量 去获取 模糊查询的结果集合
     str.Append("if(#uu==0) then");    //如果集合长度=0
     str.Append("   return 1 ");
     str.Append("else ");
     str.Append("   for i=1,#uu do ");   //遍历
     str.Append("       redis.call('ZREMRANGEBYSCORE',uu[i],0,@score) ");  //删除从0 到 该score 积分区间的元素
     str.Append("       if(redis.call('zcard',uu[i])==0) then ");  //如果管道长度=0
     str.Append("           redis.call('del',uu[i]) ");   //删除
     str.Append("       end ");
     str.Append("   end ");
     str.Append("end ");
     var re = _instance.GetDatabase(_num).ScriptEvaluate(LuaScript.Prepare(str.ToString()), new { zcardPrefix = zcardPrefix + "*", score = score });
     return re.ToString();

当即2段落代码通过发送Lua脚本的款式来形成了整个过程,因为Redis的网络型原因,所以管LuaForeachRemove方法让取出来做个劳务来单独处理即可。至于那种多容器多线程的兑现,则完全可以开始多独Redis的实例来兑现。最后放上功能图。

图片 16

最后,我拿这些还为做成了单Demo。但是从未找到适合的上传网盘,所以大家可留下邮箱(留了即犯),或者直接加QQ群文件自取,讨论交流:166843154

 

自家欢喜与自身同一的人口交朋友,不为环境影响,自己是友善之良师,欢迎加群
.Net web交流群, QQ群:166843154 欲望和挣扎

 

作者:小曾
出处:http://www.cnblogs.com/1996V/p/8127576.html 欢迎转载,但任何转载必须保留完整文章及博客园出处,在显要地方显示署名以及原文链接。
.Net交流群, QQ群:166843154 欲望与挣扎 

(5)点击Finish,完成打包;

图片 17

(6)在Dos窗口下运作Jar包,成功。

图片 18