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

    Fence Repair Time Limit: 2000 MS Memory Limit: 65536 KB 64-bit integer IO format: %I64d , %I64u Java ...

  2. Servlet初步认知

    1 背景概述 在近期的公司项目开发的过程中,笔者初步学习Servlet的开发.配置与使用,本文主要介绍了Servlet的相关概念以及优势说明并附上笔者开发简单样例.今天将笔者学习的心得总结出来与大家分 ...

  3. 【转】JS浮点数运算Bug的解决办法

    37.5*5.5=206.08 (JS算出来是这样的一个结果,我四舍五入取两位小数) 我先怀疑是四舍五入的问题,就直接用JS算了一个结果为:206.08499999999998 怎么会这样,两个只有一 ...

  4. ASP.NET CORE之上传文件夹

    最近闲余时间在做一个仿百度网盘的项目,其中就有一个上传文件夹的功能.查了下网上好像对这个问题的描述比较少,所以在此记录一下. 1.网上找来找去发现webkitdirectory这个东西,H5的一个新的 ...

  5. Distinct Substrings(spoj694)(sam(后缀自动机)||sa(后缀数组))

    Given a string, we need to find the total number of its distinct substrings. Input \(T-\) number of ...

  6. 组件基础(插槽slot)—Vue学习笔记

    刚开始我们淡淡提过<slot></slot>现在深入了解一下. slot可以进行父组件传值到子组件. 比如:我们将hiboy通过<slot>传递到组件中. < ...

  7. vue项目在IE下报错"strict 模式下不允许一个属性有多个定义"

    待解决:

  8. 关于AOP的PIAB

    AOP:面向切面编程,也就是在运行过程中,按照需要动态加载功能块 PIAB:含义可以从下面提取 Microsoft Patterns & Practices团队在2007年发布的Enterpr ...

  9. iframe 跨域调用父级方法的方案

    一.如果高层域名相同的话,可以通过document.domain来实现跨域访问 例如: 父级域名:localhost:8080 子级域名:localhost:9090 那么可以设置document.d ...

  10. 10-01 Java 类,抽象类,接口的综合小练习--运动员和教练

    运动员和教练的案例分析 运动运和教练的案例 代码实现 /* 教练和运动员案例 乒乓球运动员和篮球运动员. 乒乓球教练和篮球教练. 为了出国交流,跟乒乓球相关的人员都需要学习英语. 请用所学知识: 分析 ...