[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

 

代碼下載

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM