背景:在实际的项目中,经常有客户需要做抽奖的活动,大部分的都是注册送产品、送红包这些需求。这都是有直接的利益效果,所以经常会遇见系统被盗刷的情况,每一次遇见这种项目的上线都是绷紧神经,客户又都喜欢在过节的时候上这种活动,有好多次放假前夕都是在解决这种事情,甚至有一次的活动短信接口直接被恶意刷爆了。在这种恶意请求下对系统并发性要求就很高,但是即使做多方面的完善,有一个问题始终得不到根本的解决,那就是奖品池数量的控制,总是会出现超兑,或者一个奖品被多个人兑走的问题。之后尝试了多种及方法,例如:限制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. bootstrap基础学习小记(一)简介模板、全局样式

    2011年,twitter的“一小撮”工程师为了提高他们内部的分析和管理能力,用业余时间为他们的产品构建了一套易用.优雅.灵活.可扩展的前端工具集--BootStrap.Bootstrap由MARK ...

  2. ubuntu18.10安装redis遇到问题

    执行命令apt-get install redis-server 安装遇到的问题 1.出现apt-get被占用情况,用ps -a|grep apt ,杀死存在的apt进程 2.还不行就执行sudo f ...

  3. DEV通过FindFilterText自动检索gridview内容

    private void TreeView1_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e) { if (names!=nul ...

  4. 剑指offer编程题Java实现——面试题4替换空格

    题目描述 请实现一个函数,将一个字符串中的空格替换成“%20”.例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy. package Solution; ...

  5. 关于css中float的理解

    感觉css里的float是个非常神奇的东西,神奇之处在于,你知道它是什么意思,但是用的时候总是不知道怎么实现效果.又或者它会很容易地影响到别的元素和属性.所以今天打算尝试一下float的各种设置,看看 ...

  6. Python 生成器, 迭代器, 可迭代对象的区别

    1.可迭代对象 在python中, 一切皆对象, 但凡是可以用for循环进行遍历取值的对象都可以称之为可迭代对象, 可迭代对象在程序的一个执行周期中,可以无限轮次的进行循环遍历 2.迭代器 a.一个可 ...

  7. cad2019卸载/安装失败/如何彻底卸载清除干净cad2019注册表和文件的方法

    cad2019提示安装未完成,某些产品无法安装该怎样解决呢?一些朋友在win7或者win10系统下安装cad2019失败提示cad2019安装未完成,某些产品无法安装,也有时候想重新安装cad2019 ...

  8. input输入框添加内部图标

    有可能在制作网页的过程中遇到各种美化表单设计,这次我们来试着做一个demo 将input输入框添加内部图标 话不多说,看一下最终效果 我们的思路是,在一个div中,加入一个div和一个input标签, ...

  9. 整理版jq 复习贴子

    1绝对定位(abs)与相对定位(relative) 区别是相对定位参照自己的位置进行移动(当然需要设置top left这些生效)并且原来的位置保留着 偏移后会把 其它的层遮罩住 绝对定位就是的参照位置 ...

  10. 深入理解java虚拟机读书笔记--java内存区域和管理

    第二章:Java内存区域和内存溢出异常 2.2运行时数据区域 运行时数据区分为方法区,堆,虚拟机栈,本地方法栈,程序计数器 方法区和堆是线程共享的区域 虚拟机栈,本地方法栈,程序计数器是数据隔离的数据 ...