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.生产者消费者模式. 普通版本: 比如一个 ...
随机推荐
- python第一天
python 解释器执行代码有两种 一种在解释器: win+R==>cmd 打开终端进行 输入python 加 路径 另一种在文件里写完再到解释器执行:win+R==>cmd 打开终端进行 ...
- ORA-01157错误,丢失undo tablespace中数据文件的解决方法
我们先来看一下这个现象: [oracle@djp ora12]$ pwd /u01/app/oracle/oradata/ora12 [oracle@djp ora12]$ mv undotbs01. ...
- Linux安装后的基本配置
1.换源 http://mirrors.zju.edu.cn http://mirrors.aliyun.com http://mirrors.ustc.edu.cn ubuntu替换/etc/apt ...
- 【poj1985】 Cow Marathon
http://poj.org/problem?id=1985 (题目链接) 题意 求树上两点间最长距离.题目背景以及输入描述请见poj1984. Solution 树的直径. 代码 // poj198 ...
- Uva1398 Meteor
扫描线法. 将流星出现在相机里的时间转化成线段,离散化端点后,扫描何时出现的流星最多.注意边界的不算,所以要先减右端点再加左端点 /*By SilverN*/ #include<iostream ...
- HDU 3530 Subsequence(单调队列)
传送门 Description There is a sequence of integers. Your task is to find the longest subsequence that s ...
- UML用例图
- CF570D:Tree Requests
传送门 DFS重标号+二分 打比赛的时候想了很多方法..DFS序,BFS序,倍增什么的都考虑了一遍,但是几乎要么是可以维护两个区间但是代码复杂度爆炸,要么就是只能维护单一维度的信息. 这道题的具体做法 ...
- Linux Canbus调试笔记
STM32之CAN---错误管理分析 牛人博客 http://blog.csdn.net/flydream0/article/details/8161418 CAN总线在嵌入式Linux下驱 ...
- 《零成本实现Web性能测试:基于Apache JMeter》读书笔记
1.性能测试概念 性能测试目的: 评估系统能力,验证系统是否符合预期性能指标 识别系统中的弱点 系统调优,改进系统性能 检测长时间运行可能发生的问题,揭示隐含问题 验证稳定性.可靠性 常见性能指标 B ...