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.生产者消费者模式. 普通版本: 比如一个 ...
随机推荐
- 小记-虚拟机装ubuntu遇到问题
今天用虚拟机virtualbox装ubuntu遇到了一个问题,如图 解决方法: 1.首先查看创建虚拟机的时候是基本设置有没有对:(如图) 主要去看所选择的版本对应是否正确,比如32位和64位之类的. ...
- 【BZOJ-3275&3158】Number&千钧一发 最小割
3275: Number Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 748 Solved: 316[Submit][Status][Discus ...
- ZooKeeper分布式集群安装
我特意选择了稳定版...... 奇数意思是说奇数和偶数对故障的容忍度是一致的....所以建议配置奇数个,并不是必须奇数... 一.master节点上安装配置 1.下载并解压ZooKeeper-3.4. ...
- 【poj1017】 Packets
http://poj.org/problem?id=1017 (题目链接) 题意 一个工厂制造的产品形状都是长方体盒子,它们的高度都是 h,长和宽都相等,一共有六个型号,分别为1*1, 2*2, 3* ...
- UOJ149 【NOIP2015】子串
本文作者:ljh2000作者博客:http://www.cnblogs.com/ljh2000-jump/转载请注明出处,侵权必究,保留最终解释权! [问题描述]有两个仅包含小写英文字母的字符串 A ...
- MAC上快速调出终端的设置(保持和Windows的操作一致)
在Windows上可以这样操作[Win+R]键->输入[cmd/cmder]打开终端. 在MAC下需要做些设置:打开[系统偏好设置]->打开[键盘]->打开[快捷键]->找到[ ...
- android录音相关
android的麦克风在现在的生活中发挥着很大的作用,打电话,视频聊天,语音识别等等. android sdk的api里提供了很方便的调用方法,下面写一个小的DEMO. 五个按钮:开始录音,停止,播放 ...
- poj3696 快速幂的优化+欧拉函数+gcd的优化+互质
这题满满的黑科技orz 题意:给出L,要求求出最小的全部由8组成的数(eg: 8,88,888,8888,88888,.......),且这个数是L的倍数 sol:全部由8组成的数可以这样表示:((1 ...
- 帝国备份王(Empirebak) \class\functions.php、\class\combakfun.php GETSHELL vul
catalog . 漏洞描述 . 漏洞触发条件 . 漏洞影响范围 . 漏洞代码分析 . 防御方法 . 攻防思考 1. 漏洞描述 EmpireBak是一款完全免费.专门为Mysql大数据的备份与导入而设 ...
- POJ 2386 Lake Counting(深搜)
Lake Counting Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 17917 Accepted: 906 ...