zoukankan      html  css  js  c++  java
  • [C#]使用RabbitMQ模拟抽奖系统的例子

      背景:在实际的项目中,经常有客户需要做抽奖的活动,大部分的都是注册送产品、送红包这些需求。这都是有直接的利益效果,所以经常会遇见系统被盗刷的情况,每一次遇见这种项目的上线都是绷紧神经,客户又都喜欢在过节的时候上这种活动,有好多次放假前夕都是在解决这种事情,甚至有一次的活动短信接口直接被恶意刷爆了。在这种恶意请求下对系统并发性要求就很高,但是即使做多方面的完善,有一个问题始终得不到根本的解决,那就是奖品池数量的控制,总是会出现超兑,或者一个奖品被多个人兑走的问题。之后尝试了多种及方法,例如:限制IP,限制次数等等。后来最有效的解决方法就是使用Redis锁住奖品逻辑,但是这种实现有点复杂,也不是很友好,因此就想到了使用消息队列的优势来实现此功能。

      做这个示例首先是为了学习,再者也是留下学习的笔记,不然后面又遗忘掉了

      这个示例是一边学习RabbitMQ,一边实现自己的需求功能的。主要功能有【投放奖品】、【模拟多户请求】、【模拟用户抽奖】,并且在这些操作中及时的展示各个队列中数据的数量变化,先上一张效果图:

    示例测试下来,始终能保证奖品的数量与实际的中奖人数是一致的,不会多出一个中奖人,也不会出现有多个人中同一个奖品的问题。

    实现方式主要就是多线程模拟用户请求,结合RabbitMQ,其中还是用了RabbitMQ的在线API进行数据的监控展示。

    实现思路:

    1:先将奖品丢入奖品池;

     1 #region 投放奖品
     2         /// <summary>
     3         /// 投放奖品
     4         /// </summary>
     5         /// <param name="sender"></param>
     6         /// <param name="e"></param>
     7         private void btn1_Click(object sender, EventArgs e)
     8         {
     9             try
    10             {
    11                 SetSendfigModel(PrizeQueueName);  //设置队列信息(奖品池)
    12                 new Thread(SetPrize) { IsBackground = true }.Start();
    13             }
    14             catch (Exception ex)
    15             {
    16                 MessageBox.Show(ex.Message, "出错了", MessageBoxButtons.OK);
    17             }
    18         }
    19 
    20         /// <summary>
    21         /// 
    22         /// </summary>
    23         private void SetPrize()
    24         {
    25             string value = string.Empty;
    26             for (int i = 1; i <= PrizeCount; i++)
    27             {
    28                 PrizeInfo prize = new PrizeInfo
    29                 {
    30                     Id = i,
    31                     Name = "我是奖品" + i,
    32                     Type = 1,
    33                     PrizeNo = DateTime.Now.ToString("hhmmssfff"),
    34                     Total = PrizeCount,
    35                     Balance = PrizeCount
    36                 };
    37                 value = JsonConvert.SerializeObject(prize);
    38                 RabbitSend.Send(prize);
    39                 ShowSysMessage($"我骄傲,我是奖品:{i}/{PrizeCount}");
    40             }
    41             ShowSysMessage("奖品投放完成");
    42         }
    43         #endregion
    View Code

    2:模拟多用户页面请求

      利用多线程实现用户随机访问抽奖系统,这里将所有用户的信息来了就做插入到用户池当中,后续进行抽奖的时候再从用户池中顺序取出。

     1   #region 模拟多用户页面请求
     2         /// <summary>
     3         /// 模拟多用户页面请求
     4         /// </summary>
     5         /// <param name="sender"></param>
     6         /// <param name="e"></param>
     7         private void btn2_Click(object sender, EventArgs e)
     8         {
     9             try
    10             {
    11                 SetSendfigModel(UserQueueName);  //设置队列信息(用户池)
    12                 ShowSysMessage("开始模拟多用户页面请求...");
    13                 new Thread(ThreadFunction) { IsBackground = true }.Start();
    14             }
    15             catch (Exception ex)
    16             {
    17                 MessageBox.Show(ex.Message, "出错了", MessageBoxButtons.OK);
    18             }
    19         }
    20 
    21         private const int threadLength = 8;
    22         private static CancellationTokenSource cts = new CancellationTokenSource();
    23 
    24         /// <summary>
    25         /// 
    26         /// </summary>
    27         private void ThreadFunction()
    28         {
    29             cts = new CancellationTokenSource();
    30             TaskFactory taskFactory = new TaskFactory();
    31             Task[] tasks = new Task[threadLength];
    32 
    33             for (int i = 0; i < threadLength; i++)
    34             {
    35                 Task t1 = Task.Factory.StartNew(delegate { ParallelFunction(cts.Token); });
    36                 tasks.SetValue(t1, i);
    37             }
    38             taskFactory.ContinueWhenAll(tasks, TasksEnded, CancellationToken.None);
    39         }
    40 
    41         /// <summary>
    42         /// 
    43         /// </summary>
    44         /// <param name="tasks"></param>
    45         void TasksEnded(Task[] tasks)
    46         {
    47             ShowSysMessage("所有任务已完成/或已取消!");
    48         }
    49 
    50         /// <summary>
    51         /// 
    52         /// </summary>
    53         private void ParallelFunction(CancellationToken ct)
    54         {
    55             Parallel.For(0, 1000, item =>
    56             {
    57                 if (!ct.IsCancellationRequested)
    58                 {
    59                     string value = string.Empty;
    60                     UsersInfo user = new UsersInfo
    61                     {
    62                         Id = item,
    63                         Name = "我是:" + item
    64                     };
    65                     value = Newtonsoft.Json.JsonConvert.SerializeObject(user);
    66                     ShowSysMessage($"进来了一位用户:{value}");
    67                     RabbitSend.Send(user);
    68                 }
    69             });
    70         }
    71         #endregion
    View Code

    3:模拟多用户抽奖

      从用户池中顺序取出一个用户进行奖品的锁定,锁定之后生成用户与奖品的关系,插入中奖池中。

      1  #region 模拟多用户抽奖
      2 
      3         /// <summary>
      4         /// 模拟多用户抽奖
      5         /// </summary>
      6         /// <param name="sender"></param>
      7         /// <param name="e"></param>
      8         private void btn3_Click(object sender, EventArgs e)
      9         {
     10             //1:先去用户池中取出一个人 2 拿用户去抽一个奖品 3:将中奖人塞入中奖队列
     11             new Thread(() =>
     12             {
     13                 for (int i = 0; i < 10000; i++)
     14                 {
     15                     SetReceivefigModel(UserQueueName);//设置队列信息(用户池)  
     16                     RabbitReceive.BasicGet(LockUser);
     17                 }
     18 
     19                 //Parallel.For(0, 200000, item =>
     20                 //{
     21                 //    RabbitReceive.BasicGet(LockUser);
     22                 //});
     23             })
     24             { IsBackground = true }.Start();
     25         }
     26 
     27         /// <summary>
     28         /// 先去用户池中取出一个人
     29         /// </summary>
     30         /// <param name="tp"></param>
     31         private void LockUser(ValueTuple<bool, string, Dictionary<string, object>> tp)
     32         {
     33             try
     34             {
     35                 if (tp.Item1)
     36                 {
     37                     ShowSysMessage($"锁定到一个用户:{tp.Item2}");
     38                     UsersInfo user = JsonConvert.DeserializeObject<UsersInfo>(tp.Item2);
     39                     if (null != user)
     40                     {
     41                         Thread.Sleep(50);
     42                         LockPrize(user);//拿用户去抽一个奖品
     43                     }
     44                 }
     45                 else
     46                 {
     47                     ShowSysMessage(tp.Item2);
     48                 }
     49             }
     50             catch (Exception ex)
     51             {
     52                 MessageBox.Show(ex.Message, "出错了", MessageBoxButtons.OK);
     53             }
     54         }
     55 
     56         /// <summary>
     57         /// 拿用户去抽一个奖品
     58         /// </summary>
     59         /// <param name="user"></param>
     60         private void LockPrize(UsersInfo user)
     61         {
     62             SetReceivefigModel(PrizeQueueName);//设置队列信息(奖品池)
     63             Dictionary<string, object> data = new Dictionary<string, object> { { "User", user } };
     64             RabbitReceive.BasicGet(LockPrize, data);
     65         }
     66 
     67         /// <summary>
     68         /// 锁定奖品
     69         /// </summary>
     70         /// <param name="value"></param>
     71         private void LockPrize(ValueTuple<bool, string, Dictionary<string, object>> tp)
     72         {
     73             try
     74             {
     75                 if (tp.Item1)
     76                 {
     77                     UsersInfo user = tp.Item3["User"] as UsersInfo;
     78                     PrizeInfo prize = JsonConvert.DeserializeObject<PrizeInfo>(tp.Item2);
     79                     if (null != user && null != prize)
     80                     {
     81                         user.PrizeInfo = prize;
     82                         ShowSysMessage($"用户{user.Name}锁定到一个奖品:{tp.Item2}");
     83                         PrizeUser(user);// 将中奖人塞入中奖队列
     84                     }
     85                 }
     86                 else
     87                 {
     88                     ShowSysMessage(tp.Item2);
     89                 }
     90             }
     91             catch (Exception ex)
     92             {
     93                 MessageBox.Show(ex.Message, "出错了", MessageBoxButtons.OK);
     94             }
     95         }
     96 
     97         /// <summary>
     98         /// 将中奖人塞入中奖队列
     99         /// </summary>
    100         /// <param name="user"></param>
    101         private void PrizeUser(UsersInfo user)
    102         {
    103             SetSendfigModel(PrizeUserQueueName);  //设置队列信息(中奖人)
    104             RabbitSend.Send(user);
    105             Thread.Sleep(50);
    106         }
    107         #endregion
    View Code

    4:使用RabbitMQ的在线API进行数据的监控展示

      1  #region 处理队列中数据
      2 
      3         /// <summary>
      4         /// 
      5         /// </summary>
      6         private void LoadData()
      7         {
      8             System.Timers.Timer t = new System.Timers.Timer(3000);   //实例化Timer类,设置间隔时间为10000毫秒;   
      9             t.Elapsed += new System.Timers.ElapsedEventHandler(InitRabbit); //到达时间的时候执行事件;   
     10             t.AutoReset = true;   //设置是执行一次(false)还是一直执行(true);   
     11             t.Enabled = true;     //是否执行System.Timers.Timer.Elapsed事件;   
     12         }
     13 
     14         /// <summary>
     15         /// 初始化队列中已有的数据
     16         /// </summary>
     17         /// <param name="source"></param>
     18         /// <param name="e"></param>
     19         private void InitRabbit(object source, System.Timers.ElapsedEventArgs e)
     20         {
     21             if (this.IsHandleCreated)
     22             {
     23                 Invoke(new Action(() =>
     24                 {
     25                     ShowLbUserUserExchanges(RabbitSendConfig.ExchangesApi);
     26                     ShowLbQueues(RabbitSendConfig.QueuesApi);
     27                     ShowLbBindings(RabbitSendConfig.BingdingsApi);
     28                     ShowSysMessage($"[{DateTime.Now}]数据已更新....................");
     29                 }));
     30             }
     31         }
     32 
     33         /// <summary>
     34         /// 
     35         /// </summary>
     36         /// <param name="apiUrl"></param>
     37         private async void ShowLbUserUserExchanges(string apiUrl)
     38         {
     39             userExchanges = await GetListModel<List<ExchangeEntity>>(apiUrl);
     40         }
     41 
     42         /// <summary>
     43         /// 
     44         /// </summary>
     45         /// <param name="apiUrl"></param>
     46         private async void ShowLbQueues(string apiUrl)
     47         {
     48             queues = await GetListModel<List<QueueEntity>>(apiUrl);
     49             if (queues != null && queues.Any())
     50             {
     51                 lbQueues.Items.Clear();
     52                 lbPrize.Text = "0";
     53                 lbUser.Text = "0";
     54                 lbPrizeUser.Text = "0";
     55                 foreach (var queueEntity in queues)
     56                 {
     57                     lbQueues.Items.Add(queueEntity.name);
     58                     if (queueEntity.name == PrizeQueueName)
     59                     {
     60                         lbPrize.Text = queueEntity.messages_ready.ToString();  //奖品剩余数量
     61                     }
     62                     if (queueEntity.name == UserQueueName)
     63                     {
     64                         lbUser.Text = queueEntity.messages_ready.ToString();  //用户数量
     65                     }
     66                     if (queueEntity.name == PrizeUserQueueName)
     67                     {
     68                         lbPrizeUser.Text = queueEntity.messages_ready.ToString();  //中奖人数
     69                     }
     70                 }
     71             }
     72             else
     73             {
     74                 lbQueues.Items.Clear();
     75                 lbPrize.Text = "0";
     76                 lbUser.Text = "0";
     77                 lbPrizeUser.Text = "0";
     78             }
     79         }
     80 
     81         /// <summary>
     82         /// 
     83         /// </summary>
     84         /// <param name="apiUrl"></param>
     85         private async void ShowLbBindings(string apiUrl)
     86         {
     87             bindings = await GetListModel<List<BindingEntity>>(apiUrl);
     88             if (bindings != null)
     89             {
     90                 lbBindings.Items.Clear();
     91                 foreach (var bindingEntity in bindings)
     92                 {
     93                     lbBindings.Items.Add(string.Format("交换机:{0}---队列:{1}---Key:{2}", string.IsNullOrWhiteSpace(bindingEntity.source) ? "默认" : bindingEntity.source, bindingEntity.destination, bindingEntity.routing_key));
     94                 }
     95             }
     96             else
     97             {
     98                 lbBindings.Items.Clear();
     99             }
    100         }
    101 
    102         /// <summary>
    103         /// 
    104         /// </summary>
    105         /// <typeparam name="T"></typeparam>
    106         /// <param name="apiUrl"></param>
    107         /// <returns></returns>
    108         private async Task<T> GetListModel<T>(string apiUrl)
    109         {
    110             string jsonContent = await ShowApiResult(apiUrl);
    111             return JsonConvert.DeserializeObject<T>(jsonContent);
    112         }
    113 
    114         /// <summary>
    115         /// 
    116         /// </summary>
    117         /// <param name="apiUrl"></param>
    118         /// <returns></returns>
    119         private async Task<string> ShowApiResult(string apiUrl)
    120         {
    121             var response = await ShowHttpClientResult(apiUrl);
    122             response.EnsureSuccessStatusCode();
    123             string responseBody = await response.Content.ReadAsStringAsync();
    124             return responseBody;
    125         }
    126 
    127         /// <summary>
    128         /// 
    129         /// </summary>
    130         /// <param name="Url"></param>
    131         /// <returns></returns>
    132         private async Task<HttpResponseMessage> ShowHttpClientResult(string Url)
    133         {
    134             var client = new HttpClient();
    135             var byteArray = Encoding.ASCII.GetBytes(string.Format("{0}:{1}", RabbitReceiveConfig.UserName, RabbitReceiveConfig.Password));
    136             client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
    137             HttpResponseMessage response = await client.GetAsync(Url);
    138             return response;
    139         }
    140         #endregion
    View Code

    基本上大致的实现逻辑就是以上这些了,但是其实还有一个逻辑的问题我没有处理

      这里要中奖用户是唯一的,实现这一点可以从两点入手

      1:用户池用户信息唯一;

      2:锁定奖品时要唯一;

    这两点都可以实现这个逻辑,但是暂时还不知道RabbitMQ是否支持消息的唯一性,或者可以通过DB/Redis来实现。

    其他具体的代码就不做展示,直接在附件中体现。

    代码环境

    win10 + Visual Studio Community 2017

    代码下载

  • 相关阅读:
    iOS
    H5
    MongoDB应用场景
    win10虚拟桌面;一不小心按错了突然只剩下桌面,启动的程序都没了
    Java中Pattern类的quote方法将任何字符串(包括正则表达式)都转换成字符串常量,不具有任何匹配功能
    MongoDB模糊查询,以及MongoDB模糊查询时带有括号的情况
    对一个结果集(List)进行手动分页
    史上最污技术解读
    Java编译时多态和运行时多态
    MongoDB常用查询,排序,group,SpringDataMongoDB update group
  • 原文地址:https://www.cnblogs.com/xiao99/p/6604355.html
Copyright © 2011-2022 走看看