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.生产者消费者模式. 普通版本: 比如一个 ...
随机推荐
- BitmapFactory
1.以文件流的方式,假设在sdcard下有test.png图片FileInputStream fis = newFileInputStream("/sdcard/test.png" ...
- 【转】Apache的Order Allow,Deny 详解
Apache的Order Allow,Deny 详解 Allow和Deny可以用于apache的conf文件或者.htaccess文件中(配合Directory, Location, Files等 ...
- bzoj1799: [Ahoi2009]self 同类分布
数位dp 先从1到162枚举各位数之和 s[i][j][k][l]表示i位数,第一位小于等于j,当前各位数字和为k,当前取模余数为l的方案数 然后脑补一下转移就行了 详见代码 #include < ...
- js-JavaScript高级程序设计学习笔记5
第七章 函数表达式 1.函数声明的一个重要特征就是函数声明提升,意思是在执行代码之前会先读取函数声明,因此可以把函数声明放在调用它的语句后面. 2.使用函数表达式创建的函数叫做匿名函数(拉姆达函数), ...
- springMVC-错误消息的显示和国际化
显示:在页面添加<form:errors path="*">会把错误消息集中显示在一块 在页面添加<form:errors path="lastname ...
- 【bzoj3675】 Apio2014—序列分割
http://www.lydsy.com/JudgeOnline/problem.php?id=3675 (题目链接) 题意 给出一个包含n个非负整数的序列,要求将其分割成k+1个序列,每次分割可以获 ...
- 【poj1020】 Anniversary Cake
http://poj.org/problem?id=1020 (题目链接) 题意 有一个S*S的大蛋糕,还有许多正方形的小蛋糕,问能否将大蛋糕完整的分成所有的小蛋糕,不能有剩余. Solution 像 ...
- 关于gcd的几个问题
这两天刷了几个关于gcd的很类似的问题,总结一下: BZOJ2818 1<=x<=n,1<=y<=n,求满足gcd(x,y)=质数的个数 BZOJ2190 1<=x< ...
- EF-CodeFirst-1 玩起来
注本文是学习旺杰兄的CodeFirst系列所写 CodeFirst CodeFirst是一种全新的玩法,代码先行使得我们更了解实体之间的关系.而且更加符合了DDD领域驱动设计的思想 .所以CodeFi ...
- MOOCULUS微积分-2: 数列与级数学习笔记 5. Another comparison test
此课程(MOOCULUS-2 "Sequences and Series")由Ohio State University于2014年在Coursera平台讲授. PDF格式教材下载 ...