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

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

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

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

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

实现思路:

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

 #region 投放奖品
/// <summary>
/// 投放奖品
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btn1_Click(object sender, EventArgs e)
{
try
{
SetSendfigModel(PrizeQueueName); //设置队列信息(奖品池)
new Thread(SetPrize) { IsBackground = true }.Start();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "出错了", MessageBoxButtons.OK);
}
} /// <summary>
///
/// </summary>
private void SetPrize()
{
string value = string.Empty;
for (int i = ; i <= PrizeCount; i++)
{
PrizeInfo prize = new PrizeInfo
{
Id = i,
Name = "我是奖品" + i,
Type = ,
PrizeNo = DateTime.Now.ToString("hhmmssfff"),
Total = PrizeCount,
Balance = PrizeCount
};
value = JsonConvert.SerializeObject(prize);
RabbitSend.Send(prize);
ShowSysMessage($"我骄傲,我是奖品:{i}/{PrizeCount}");
}
ShowSysMessage("奖品投放完成");
}
#endregion

2:模拟多用户页面请求

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

   #region 模拟多用户页面请求
/// <summary>
/// 模拟多用户页面请求
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btn2_Click(object sender, EventArgs e)
{
try
{
SetSendfigModel(UserQueueName); //设置队列信息(用户池)
ShowSysMessage("开始模拟多用户页面请求...");
new Thread(ThreadFunction) { IsBackground = true }.Start();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "出错了", MessageBoxButtons.OK);
}
} private const int threadLength = ;
private static CancellationTokenSource cts = new CancellationTokenSource(); /// <summary>
///
/// </summary>
private void ThreadFunction()
{
cts = new CancellationTokenSource();
TaskFactory taskFactory = new TaskFactory();
Task[] tasks = new Task[threadLength]; for (int i = ; i < threadLength; i++)
{
Task t1 = Task.Factory.StartNew(delegate { ParallelFunction(cts.Token); });
tasks.SetValue(t1, i);
}
taskFactory.ContinueWhenAll(tasks, TasksEnded, CancellationToken.None);
} /// <summary>
///
/// </summary>
/// <param name="tasks"></param>
void TasksEnded(Task[] tasks)
{
ShowSysMessage("所有任务已完成/或已取消!");
} /// <summary>
///
/// </summary>
private void ParallelFunction(CancellationToken ct)
{
Parallel.For(, , item =>
{
if (!ct.IsCancellationRequested)
{
string value = string.Empty;
UsersInfo user = new UsersInfo
{
Id = item,
Name = "我是:" + item
};
value = Newtonsoft.Json.JsonConvert.SerializeObject(user);
ShowSysMessage($"进来了一位用户:{value}");
RabbitSend.Send(user);
}
});
}
#endregion

3:模拟多用户抽奖

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

  #region 模拟多用户抽奖

         /// <summary>
/// 模拟多用户抽奖
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btn3_Click(object sender, EventArgs e)
{
//1:先去用户池中取出一个人 2 拿用户去抽一个奖品 3:将中奖人塞入中奖队列
new Thread(() =>
{
for (int i = ; i < ; i++)
{
SetReceivefigModel(UserQueueName);//设置队列信息(用户池)
RabbitReceive.BasicGet(LockUser);
} //Parallel.For(0, 200000, item =>
//{
// RabbitReceive.BasicGet(LockUser);
//});
})
{ IsBackground = true }.Start();
} /// <summary>
/// 先去用户池中取出一个人
/// </summary>
/// <param name="tp"></param>
private void LockUser(ValueTuple<bool, string, Dictionary<string, object>> tp)
{
try
{
if (tp.Item1)
{
ShowSysMessage($"锁定到一个用户:{tp.Item2}");
UsersInfo user = JsonConvert.DeserializeObject<UsersInfo>(tp.Item2);
if (null != user)
{
Thread.Sleep();
LockPrize(user);//拿用户去抽一个奖品
}
}
else
{
ShowSysMessage(tp.Item2);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "出错了", MessageBoxButtons.OK);
}
} /// <summary>
/// 拿用户去抽一个奖品
/// </summary>
/// <param name="user"></param>
private void LockPrize(UsersInfo user)
{
SetReceivefigModel(PrizeQueueName);//设置队列信息(奖品池)
Dictionary<string, object> data = new Dictionary<string, object> { { "User", user } };
RabbitReceive.BasicGet(LockPrize, data);
} /// <summary>
/// 锁定奖品
/// </summary>
/// <param name="value"></param>
private void LockPrize(ValueTuple<bool, string, Dictionary<string, object>> tp)
{
try
{
if (tp.Item1)
{
UsersInfo user = tp.Item3["User"] as UsersInfo;
PrizeInfo prize = JsonConvert.DeserializeObject<PrizeInfo>(tp.Item2);
if (null != user && null != prize)
{
user.PrizeInfo = prize;
ShowSysMessage($"用户{user.Name}锁定到一个奖品:{tp.Item2}");
PrizeUser(user);// 将中奖人塞入中奖队列
}
}
else
{
ShowSysMessage(tp.Item2);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "出错了", MessageBoxButtons.OK);
}
} /// <summary>
/// 将中奖人塞入中奖队列
/// </summary>
/// <param name="user"></param>
private void PrizeUser(UsersInfo user)
{
SetSendfigModel(PrizeUserQueueName); //设置队列信息(中奖人)
RabbitSend.Send(user);
Thread.Sleep();
}
#endregion

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

  #region 处理队列中数据

         /// <summary>
///
/// </summary>
private void LoadData()
{
System.Timers.Timer t = new System.Timers.Timer(); //实例化Timer类,设置间隔时间为10000毫秒;
t.Elapsed += new System.Timers.ElapsedEventHandler(InitRabbit); //到达时间的时候执行事件;
t.AutoReset = true; //设置是执行一次(false)还是一直执行(true);
t.Enabled = true; //是否执行System.Timers.Timer.Elapsed事件;
} /// <summary>
/// 初始化队列中已有的数据
/// </summary>
/// <param name="source"></param>
/// <param name="e"></param>
private void InitRabbit(object source, System.Timers.ElapsedEventArgs e)
{
if (this.IsHandleCreated)
{
Invoke(new Action(() =>
{
ShowLbUserUserExchanges(RabbitSendConfig.ExchangesApi);
ShowLbQueues(RabbitSendConfig.QueuesApi);
ShowLbBindings(RabbitSendConfig.BingdingsApi);
ShowSysMessage($"[{DateTime.Now}]数据已更新....................");
}));
}
} /// <summary>
///
/// </summary>
/// <param name="apiUrl"></param>
private async void ShowLbUserUserExchanges(string apiUrl)
{
userExchanges = await GetListModel<List<ExchangeEntity>>(apiUrl);
} /// <summary>
///
/// </summary>
/// <param name="apiUrl"></param>
private async void ShowLbQueues(string apiUrl)
{
queues = await GetListModel<List<QueueEntity>>(apiUrl);
if (queues != null && queues.Any())
{
lbQueues.Items.Clear();
lbPrize.Text = "";
lbUser.Text = "";
lbPrizeUser.Text = "";
foreach (var queueEntity in queues)
{
lbQueues.Items.Add(queueEntity.name);
if (queueEntity.name == PrizeQueueName)
{
lbPrize.Text = queueEntity.messages_ready.ToString(); //奖品剩余数量
}
if (queueEntity.name == UserQueueName)
{
lbUser.Text = queueEntity.messages_ready.ToString(); //用户数量
}
if (queueEntity.name == PrizeUserQueueName)
{
lbPrizeUser.Text = queueEntity.messages_ready.ToString(); //中奖人数
}
}
}
else
{
lbQueues.Items.Clear();
lbPrize.Text = "";
lbUser.Text = "";
lbPrizeUser.Text = "";
}
} /// <summary>
///
/// </summary>
/// <param name="apiUrl"></param>
private async void ShowLbBindings(string apiUrl)
{
bindings = await GetListModel<List<BindingEntity>>(apiUrl);
if (bindings != null)
{
lbBindings.Items.Clear();
foreach (var bindingEntity in bindings)
{
lbBindings.Items.Add(string.Format("交换机:{0}---队列:{1}---Key:{2}", string.IsNullOrWhiteSpace(bindingEntity.source) ? "默认" : bindingEntity.source, bindingEntity.destination, bindingEntity.routing_key));
}
}
else
{
lbBindings.Items.Clear();
}
} /// <summary>
///
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="apiUrl"></param>
/// <returns></returns>
private async Task<T> GetListModel<T>(string apiUrl)
{
string jsonContent = await ShowApiResult(apiUrl);
return JsonConvert.DeserializeObject<T>(jsonContent);
} /// <summary>
///
/// </summary>
/// <param name="apiUrl"></param>
/// <returns></returns>
private async Task<string> ShowApiResult(string apiUrl)
{
var response = await ShowHttpClientResult(apiUrl);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
return responseBody;
} /// <summary>
///
/// </summary>
/// <param name="Url"></param>
/// <returns></returns>
private async Task<HttpResponseMessage> ShowHttpClientResult(string Url)
{
var client = new HttpClient();
var byteArray = Encoding.ASCII.GetBytes(string.Format("{0}:{1}", RabbitReceiveConfig.UserName, RabbitReceiveConfig.Password));
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
HttpResponseMessage response = await client.GetAsync(Url);
return response;
}
#endregion

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

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

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

  2:锁定奖品时要唯一;

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

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

代码环境

win10 + Visual Studio Community 2017

代码下载

[C#]使用RabbitMQ模拟抽奖系统的例子的更多相关文章

  1. java小项目——抽奖系统

    来了来了!这不又到考试周了吗!愁人,又得复习,复习,复习!这段时间每天都在复习线代和高数!(说是复习,说实话其实是在预习,啊哈哈哈哈哈),得有一段时间都没有学到新的知识了,代码感觉都生疏了,惆怅.博客 ...

  2. JS组件系列——图片切换特效:简易抽奖系统

    前言:前两天在网上找组件,无意中发现了我们儿时游戏机效果的“SlotMachine组件”,浏览一遍下来,勾起了博主小时候满满的回忆.于是下定决定要研究下这么一个东西,不得不再次叹息开源社区的强大,原来 ...

  3. PPT图片双屏抽奖系统现场主要操作流程介绍

    目录 第一步:前期准备工作 第二步:现场预备与辅助展示工作 第三步:现场正式抽取工作 PPT图片双屏抽奖系统-现场抽奖视频实录 第一步:前期准备工作 把第二个步骤优化处理制作好的PPT文件 [图片.p ...

  4. 【小型系统】抽奖系统-使用Java Swing完成

    一.需求分析 1. 显示候选人照片和姓名. 2. 可以使用多种模式进行抽奖,包括一人单独抽奖.两人同时抽奖.三人同时抽奖. 3. 一个人可以在不同的批次的抽奖中获取一.二.三等奖,但是不能在同一批次抽 ...

  5. 驾照理论模拟考试系统Android源码下载

    ‍‍‍驾照理论模拟考试系统Android源码下载 <ignore_js_op> 9.png (55.77 KB, 下载次数: 0) <ignore_js_op> 10.png ...

  6. 利用qemu模拟嵌入式系统制作全过程

    http://www.tinylab.org/using-qemu-simulation-inserts-the-type-system-to-produce-the-whole-process/ 利 ...

  7. 原生js+css实现重力模拟弹跳系统的登录页面

    今天小颖把之前保存的js特效视频看了一遍,跟着视频敲了敲嘻嘻,用原生js实现一个炫酷的登录页面.怎么个炫酷法呢,看看下面的图片大家就知道啦. 效果图: 不过在看代码之前呢,大家先和小颖看看css中的o ...

  8. 利用JS模拟排队系统

    我爱撸码,撸码使我感到快乐!大家好,我是Counter.今天给大家分享的是js模拟排队系统,刚开始有排队序列,序列里有vip用户和普通用户,vip用户永远位于普通用户的前面,只有当当前vip用户都办理 ...

  9. 模拟window系统的“回收站”

    若要模拟window系统的“回收站”功能,具体的要求如下: 对于列表中的图片,可以通过拖动或单击“删除”的链接,以动画的方式移至“回收站”. 对于“回收站的图片”,可以通过拖动和单击“还原”的链接,以 ...

随机推荐

  1. Delphi中break,exit,abort跳出循环的比较

    http://www.delphitop.com/html/hanshu/104.html Delphi中break,exit,abort跳出循环的比较 exit: 退出函数体abort: 遇到异常, ...

  2. 软件测试实践平台(Mooctest)FAQ

    0. 背景 我们在<软件测试技术>课程使用 MOOCTEST (mooctest.net) 作为课程的实践教学平台. 在教学过程中学生们遇到了一些问题,本文将一一总结. 注意:本文在解决问 ...

  3. Notes for Apue —— chapter 4 Files and Directories(文件和目录)

    4.1 Introduction 4.2 stat, fstat, fstatat, and lstat Functions The lstat function is similar to stat ...

  4. HSmartWindowControl 之 摄像头实时显示( 使用 WPF )

    1.添加Halcon控件,创建WPF项目 在VS2013中创建一个WPF工程,然后添加halcon的控件和工具包,参见: HSmartWindowControl之安装篇 (Visual Studio ...

  5. EF查询返回DataTable

    using (SchoolContext dbCOntext = new SchoolContext()) { string str = "select * from student&quo ...

  6. Python地理位置信息库geopy的使用(一):基本使用

    geopy是Python关于地理位置的一个第三方库,用这个库来进行地址位置信息的查询和转换非常方便,本文介绍关于geopy的常用的几种用法 geopy的安装 pip install geopy 根据地 ...

  7. 201621123018《Java程序设计》第10周学习报告

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结异常相关内容. 2. 书面作业 本次PTA作业题集异常 1. 常用异常 1.1 自己以前编写的代码中经常出现什么异常.需要捕获吗(为 ...

  8. nsurlsessiond - taking up all bandwidth!! Help ?

    https://discussions.apple.com/thread/6605949?start=0&tstart=0 #!/bin/sh launchctl unload /System ...

  9. drf-视图的理解

    1. 类视图 写视图的步骤:      1. 数据库查询,   2. 构建序列化器, 进行序列化操作, 返回数据 一. 两大基类    >1 APIView   (以常规的方法实现get  po ...

  10. CSS3标签显示模式

    HTML标签一般分为块标签和行内标签两种类型,它们也称块元素和行内元素.具体如下: 块级元素(block-level) 每个块元素通常都会独自占据一整行或多整行,可以对其设置宽度.高度.对齐等属性,常 ...