c#之Redis队列在邮件提醒中的应用
场景
有这样一个场景,一个邮件提醒的windows服务,获取所有开启邮件提醒的用户,循环获取这些用户的邮件,发送一条服务号消息。但问题来了,用户比较少的情况下,轮询一遍时间还能忍受,如果用户多了,那用户名称排序靠后的人,收到邮件提醒的消息,延迟时间就非常长了。
准备
方案
1、生产者线程一获取所有开启邮件提醒的用户。
2、根据配置来决定使用多少个队列,以及每个队列的容量。
3、线程一,获取未满的队列,将当前用户入队。如果所有的队列已满,则挂起2s,然后重新获取未满的队列,用户入队。
4、根据配置开启消费者线程,每个线程独立处理逻辑。如果获取的用户为空或者当前队列为空,挂起2s。否则通过EWS服务拉取该用户的邮件,并提醒。
5、如果在获取用户邮件的过程中出错,则将该用户重新入当前队列,等待下次拉取。
测试
队列
测试代码
/// <summary>
/// 消息队列管理
/// </summary>
public class MyRedisQueueBus : IDisposable
{
/// <summary>
/// 线程个数
/// </summary>
private int _threadCount;
/// <summary>
/// 每个线程中itcode的容量
/// </summary>
private int _threadCapacity;
/// <summary>
/// 线程
/// </summary>
private Thread[] _threads;
/// <summary>
/// 生产者线程
/// </summary>
private Thread _producerThread;
/// <summary>
/// 挂起时间
/// </summary>
private const int WAITSECONDE = ;
/// <summary>
/// 队列名称前缀
/// </summary>
private string _queuePrefix;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="threadCount">线程个数</param>
/// <param name="threadCapacity">每个线程处理的队列容量</param>
/// <param name="queuePrefix">每个线程处理的队列容量</param>
public MyRedisQueueBus(int threadCount, int threadCapacity, string queuePrefix)
{
this._threadCapacity = threadCapacity;
this._threadCount = threadCount;
this._queuePrefix = queuePrefix + "_{0}";
}
/// <summary>
/// 开启生产者
/// </summary>
public void StartProducer()
{
_producerThread = new Thread(() =>
{
IRedisClientFactory factory = RedisClientFactory.Instance;
EmailAlertsData emailAlertsData = new EmailAlertsData();
//白名单
string[] userIdsWhiteArray = TaskGloableParameter.WhiteList.Split(new char[] { ',', ',' },
StringSplitOptions.RemoveEmptyEntries);
//入队
using (IRedisClient client = factory.CreateRedisClient(WebConfig.RedisServer, WebConfig.RedisPort))
{
client.Password = WebConfig.RedisPwd;
client.Db = WebConfig.RedisServerDb;
while (true)
{
//获取所有开启邮件提醒的用户
List<EmailAlerts> lstEmails = emailAlertsData.GetAllStartAlerts(SyncState.ALL, userIdsWhiteArray); foreach (var item in lstEmails)
{
int queueIndex = -;
string queueName = string.Format(this._queuePrefix, queueIndex);
for (int i = ; i < _threadCount; i++)
{
queueName = string.Format(this._queuePrefix, i);
//如果当前队列没有填满,则直接跳出,使用该队列进行入队
if (client.GetListCount(queueName) < _threadCapacity)
{
queueIndex = i;
break;
}
}
//如果所有队列都已经满了,则挂起2s等待消费者消耗一部分数据,然后重新开始
if (queueIndex == -)
{
Thread.SpinWait(WAITSECONDE);
//重新获取队列
for (int i = ; i < _threadCount; i++)
{
queueName = string.Format(this._queuePrefix, i);
//如果当前队列没有填满,则直接跳出,使用该队列进行入队
if (client.GetListCount(queueName) < _threadCapacity)
{
queueIndex = i;
break;
}
}
}
else
{
//入队
client.EnqueueItemOnList(queueName, JsonConvert.SerializeObject(new MyQueueItem
{
UserId = item.itcode,
SyncState = item.Email_SyncState
}));
}
} }
} });
_producerThread.Start();
} /// <summary>
/// 开启消费者
/// </summary>
public void StartCustomer()
{
_threads = new Thread[_threadCount];
for (int i = ; i < _threads.Length; i++)
{
_threads[i] = new Thread(CustomerRun);
_threads[i].Start(i);
}
}
private void CustomerRun(object obj)
{
int threadIndex = Convert.ToInt32(obj);
string queueName = string.Format(this._queuePrefix, threadIndex); IRedisClientFactory factory = RedisClientFactory.Instance;
using (IRedisClient client = factory.CreateRedisClient(WebConfig.RedisServer, WebConfig.RedisPort))
{
while (true)
{
client.Password = WebConfig.RedisPwd;
client.Db = WebConfig.RedisServerDb;
if (client.GetListCount(queueName) > )
{
string resultJson = client.DequeueItemFromList(queueName);
//如果获取的结果为空,则挂起2s
if (string.IsNullOrEmpty(resultJson))
{
Thread.SpinWait(WAITSECONDE);
}
else
{
try
{
//耗时业务处理
MyQueueItem item = JsonConvert.DeserializeObject<MyQueueItem>(resultJson);
Console.WriteLine("Threadid:{0},User:{1}", Thread.CurrentThread.ManagedThreadId.ToString(), item.UserId);
}
catch (Exception ex)
{
//如果出错,重新入队
client.EnqueueItemOnList(queueName, resultJson); } }
}
else
{
//当前队列为空,挂起2s
Thread.SpinWait(WAITSECONDE);
}
}
} }
public void Dispose()
{
//释放资源时,销毁线程
if (this._threads != null)
{
for (int i = ; i < this._threads.Length; i++)
{
this._threads[i].Abort();
}
}
GC.Collect();
}
}
Main方法调用
static void Main(string[] args)
{
MyRedisQueueBus bus = new MyRedisQueueBus(, , "mail_reminder_queue");
bus.StartProducer();
Thread.SpinWait();
bus.StartCustomer();
Console.Read();
}
总结
通过配置的方式,确定开启的队列数和线程数,如果用户增加可以增加线程数,或者添加机器的方式解决。这样,可以解决排名靠后的用户,通过随机分发队列,有机会提前获取邮件提醒,可以缩短邮件提醒的延迟时间。当然,这种方案并不太完美,目前也只能想到这里了。这里把这个思路写出来,也是希望获取一个更好的解决方案。
上面的代码只是测试用的代码,后来发现将创建IRedisClient写在循环内,很容易出问题,频繁创建client,也以为这频繁打开关闭,如果释放不及时,那么会产生很多的redis连接,造成redis服务器负担。如果放在循环外边,这个client负责一直从队列中取数据就行,直到该线程停止。
c#之Redis队列在邮件提醒中的应用的更多相关文章
- .NET 环境中使用RabbitMQ RabbitMQ与Redis队列对比 RabbitMQ入门与使用篇
.NET 环境中使用RabbitMQ 在企业应用系统领域,会面对不同系统之间的通信.集成与整合,尤其当面临异构系统时,这种分布式的调用与通信变得越发重要.其次,系统中一般会有很多对实时性要求不高的 ...
- 后端利用Redis队列及哈希实现定时推送提醒的三个思路
周煦辰 2016年8月31日 本文介绍了一下本人在开发过程中遇到"定时推送提醒"的需求的时候所思考的三种解决方案. 明确问题 首先明确一下这个需求可能包含的几个"坑&qu ...
- laravel中redis队列的使用
一.配置文件 首先我们需要在配置文件中配置默认队列驱动为Redis,队列配置文件是config/queue.php: return [ 'default' => env('QUEUE_DRIVE ...
- odoo10中的邮件提醒
odoo10中邮件提醒配置如下: 1.配置出向邮件服务器 打开开发者模式,设置-->技术-->email-->出向邮件服务器 设置如下: 如果配置成功,点击’测试连接‘,会出现如下弹 ...
- EWS 邮件提醒
摘要 之前做的邮件提醒的项目,最近需要优化,由于使用了队列,但即时性不是特别好,有队列,就会出现先后的问题,最近调研了exchange 流通知的模式,所以想使用流通知模式和原先的拉取邮件的方法结合,在 ...
- [原创]Laravel 基于redis队列的解析
目录 参考链接 本文环境 为什么使用队列 Laravel 中的队列 分发任务 任务队列 Worker Last-Modified: 2019年5月10日11:44:18 参考链接 使用 Laravel ...
- [bigdata] 使用Redis队列来实现与机器无关的Job提交与执行 (python实现)
用例场景: 定时从远程多台机器上下载文件存入HDFS中.一开始采用shell 一对一的方式实现,但对于由于网络或者其他原因造成下载失败的任务无法进行重试,且如果某台agent机器down机,将导致它对 ...
- 2.jenkins配置邮件提醒
1.前言 在Jenkins的使用中邮件提醒是一个常用功能,Jenkins默认安装了Mailer Plugin插件用于实现此功能. 2.邮件服务器配置 首先在Jenkins的"系统管理&quo ...
- redis队列的实现
redis中文官网:http://www.redis.cn/ 关于redis队列的实现方式有两种: 1.生产者消费者模式. 2.发布者订阅者模式. 详解: 1.生产者消费者模式. 普通版本: 比如一个 ...
随机推荐
- 【caffe】mnist训练日志
@tags caffe 前面根据train_lenet.sh改写了train_lenet.py后,在根目录下执行它,得到一系列输出,内容如下: I1013 10:05:16.721294 1684 c ...
- 【BZOJ-3626】LCA 树链剖分
3626: [LNOI2014]LCA Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 1428 Solved: 526[Submit][Status ...
- 【poj1018】 Communication System
http://poj.org/problem?id=1018 (题目链接) 题意 要买n个产品,每个产品有m种价格和宽度(我也不知道翻译过来到底是什么?),设n个产品的宽度的最小值为B,n个产品的价格 ...
- 【bzoj2152】 聪聪可可
http://www.lydsy.com/JudgeOnline/problem.php?id=2152 (题目链接) 题意 给出一棵n个节点的带权树,求有多少点对的距离是3的倍数. solution ...
- 前端打包/自动化构建工具:gulp
glup可以进行打包,并且可以实现类似/script/test-adsf123.js或者/script/test.js?v=asdf123 参考: http://www.ydcss.com/archi ...
- hdu 5023 线段树
成端更新+统计区间内的值 挺模板的题... 一开始没想起来用set统计,傻傻地去排序了[大雾 #include<iostream> #include<cstdio> #incl ...
- 帝国CMS文章随机调用等一些常用标签
1.帝国CMS文章随机调用等一些常用标签 [e:loop={'news',10,18,0,'newstime>UNIX_TIMESTAMP()-86400*7','onclick desc'}] ...
- 重启电脑后,oracle数据库连接不上
oracle10g安装成功后使用正常,重启电脑后,连接不上了,电脑-服务中各个服务都手动重启了,仍然无效 报错信息:ORA-12514 TNS 监听程序当前无法识别连接描述符中请求服务 要手工去操作命 ...
- BZOJ4004: [JLOI2015]装备购买
总之就是线性基那一套贪心理论直接做就好了. 然而加强数据后很卡精度的样子. 于是重点在于这个特技:在整数模意义下搞. #include<cstdio> #include<algori ...
- 使用ultraiso制作启动盘
1.以管理员方式运行Ultralso 2.点击菜单栏里的“启动”菜单下的“写入硬盘映像”命令,打开“写入硬盘映像”对话框. “硬盘驱动器”里就是选择你要刻录的U盘,这里演示用的是一张数码相机的内存卡. ...