[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

 

代码下载

 

posted @ 2017-03-23 18:37  xiao99  阅读(1836)  评论(3编辑  收藏  举报