环境
Newtonsoft.Json 序列化工具
StackExchange.Redis Redis工具
.net 7.0
项目结构:
RedisHelper
该帮助类中是包含的Redis的连接和存储的方法
ICache
缓存接口
public interface ICache
{
/// <summary>
/// 缓存过期时间
/// </summary>
int TimeOut { set; get; }
/// <summary>
/// 获得指定键的缓存值
/// </summary>
/// <param name="key">缓存键</param>
/// <returns>缓存值</returns>
object Get(string key);
/// <summary>
/// 获得指定键的缓存值
/// </summary>
T Get<T>(string key);
/// <summary>
/// 从缓存中移除指定键的缓存值
/// </summary>
/// <param name="key">缓存键</param>
void Remove(string key);
/// <summary>
/// 清空所有缓存对象
/// </summary>
//void Clear();
/// <summary>
/// 将指定键的对象添加到缓存中
/// </summary>
/// <param name="key">缓存键</param>
/// <param name="data">缓存值</param>
void Insert(string key, object data);
/// <summary>
/// 将指定键的对象添加到缓存中
/// </summary>
/// <param name="key">缓存键</param>
/// <param name="data">缓存值</param>
void Insert<T>(string key, T data);
/// <summary>
/// 将指定键的对象添加到缓存中,并指定过期时间
/// </summary>
/// <param name="key">缓存键</param>
/// <param name="data">缓存值</param>
/// <param name="cacheTime">缓存过期时间(秒钟)</param>
void Insert(string key, object data, int cacheTime);
/// <summary>
/// 将指定键的对象添加到缓存中,并指定过期时间
/// </summary>
/// <param name="key">缓存键</param>
/// <param name="data">缓存值</param>
/// <param name="cacheTime">缓存过期时间(秒钟)</param>
void Insert<T>(string key, T data, int cacheTime);
/// <summary>
/// 将指定键的对象添加到缓存中,并指定过期时间
/// </summary>
/// <param name="key">缓存键</param>
/// <param name="data">缓存值</param>
/// <param name="cacheTime">缓存过期时间</param>
void Insert(string key, object data, DateTime cacheTime);
/// <summary>
/// 将指定键的对象添加到缓存中,并指定过期时间
/// </summary>
/// <param name="key">缓存键</param>
/// <param name="data">缓存值</param>
/// <param name="cacheTime">缓存过期时间</param>
void Insert<T>(string key, T data, DateTime cacheTime);
/// <summary>
/// 判断key是否存在
/// </summary>
bool Exists(string key);
/// <summary>
/// 右侧入队
/// </summary>
/// <param name="queueName"></param>
/// <param name="redisValue"></param>
/// <returns></returns>
long EnqueueListRightPush(RedisKey queueName, RedisValue redisValue);
/// <summary>
/// 左侧入队
/// </summary>
/// <param name="queueName"></param>
/// <param name="redisvalue"></param>
/// <returns></returns>
long EnqueueListLeftPush(RedisKey queueName, RedisValue redisvalue);
/// <summary>
/// 获取队列长度
/// </summary>
/// <param name="queueName"></param>
/// <returns></returns>
long EnqueueListLength(RedisKey queueName);
/// <summary>
/// 左侧出队
/// </summary>
/// <param name="queueName"></param>
/// <returns></returns>
string DequeueListPopLeft(RedisKey queueName);
/// <summary>
/// 右侧出队
/// </summary>
/// <param name="queueName"></param>
/// <returns></returns>
string DequeueListPopRight(RedisKey queueName);
/// <summary>
/// Redis发布订阅 订阅
/// </summary>
/// <param name="subChannel"></param>
void RedisSub(string subChannel, Action<string> DealAction);
/// <summary>
/// Redis发布订阅 发布
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="channel"></param>
/// <param name="msg"></param>
/// <returns></returns>
long RedisPub<T>(string channel, T msg);
/// <summary>
/// Redis发布订阅 取消订阅
/// </summary>
/// <param name="channel"></param>
void Unsubscribe(string channel);
/// <summary>
/// Redis发布订阅 取消全部订阅
/// </summary>
void UnsubscribeAll();
}
CacheHelper
/// <summary>
/// 缓存
/// </summary>
public static class CacheHelper
{
private static object cacheLocker = new object();//缓存锁对象
private static ICache cache = null;//缓存接口
static CacheHelper()
{
Load();
}
/// <summary>
/// 指定数据库
/// </summary>
/// <param name="database"></param>
public static void Init(int database=0)
{
Load(database);
}
/// <summary>
/// 加载缓存
/// </summary>
/// <exception cref=""></exception>
private static void Load(int Default=0)
{
try
{
cache = Redis.GetRedis().setDatabase(Default);
}
catch (Exception ex)
{
throw ex;
}
}
public static ICache GetCache()
{
return cache;
}
/// <summary>
/// 缓存过期时间
/// </summary>
public static int TimeOut
{
get
{
return cache.TimeOut;
}
set
{
lock (cacheLocker)
{
cache.TimeOut = value;
}
}
}
/// <summary>
/// 获得指定键的缓存值
/// </summary>
/// <param name="key">缓存键</param>
/// <returns>缓存值</returns>
public static object Get(string key)
{
if (string.IsNullOrWhiteSpace(key))
return null;
return cache.Get(key);
}
/// <summary>
/// 获得指定键的缓存值
/// </summary>
/// <param name="key">缓存键</param>
/// <returns>缓存值</returns>
public static T Get<T>(string key)
{
return cache.Get<T>(key);
}
/// <summary>
/// 将指定键的对象添加到缓存中
/// </summary>
/// <param name="key">缓存键</param>
/// <param name="data">缓存值</param>
public static void Insert(string key, object data)
{
if (string.IsNullOrWhiteSpace(key) || data == null)
return;
//lock (cacheLocker)
{
cache.Insert(key, data);
}
}
/// <summary>
/// 将指定键的对象添加到缓存中
/// </summary>
/// <param name="key">缓存键</param>
/// <param name="data">缓存值</param>
public static void Insert<T>(string key, T data)
{
if (string.IsNullOrWhiteSpace(key) || data == null)
return;
//lock (cacheLocker)
{
cache.Insert<T>(key, data);
}
}
/// <summary>
/// 将指定键的对象添加到缓存中,并指定过期时间
/// </summary>
/// <param name="key">缓存键</param>
/// <param name="data">缓存值</param>
/// <param name="cacheTime">缓存过期时间(秒钟)</param>
public static void Insert(string key, object data, int cacheTime)
{
if (!string.IsNullOrWhiteSpace(key) && data != null)
{
//lock (cacheLocker)
{
cache.Insert(key, data, cacheTime);
}
}
}
/// <summary>
/// 将指定键的对象添加到缓存中,并指定过期时间
/// </summary>
/// <param name="key">缓存键</param>
/// <param name="data">缓存值</param>
/// <param name="cacheTime">缓存过期时间(秒)</param>
public static void Insert<T>(string key, T data, int cacheTime)
{
if (!string.IsNullOrWhiteSpace(key) && data != null)
{
//lock (cacheLocker)
{
cache.Insert<T>(key, data, cacheTime);
}
}
}
/// <summary>
/// 将指定键的对象添加到缓存中,并指定过期时间
/// </summary>
/// <param name="key">缓存键</param>
/// <param name="data">缓存值</param>
/// <param name="cacheTime">缓存过期时间</param>
public static void Insert(string key, object data, DateTime cacheTime)
{
if (!string.IsNullOrWhiteSpace(key) && data != null)
{
//lock (cacheLocker)
{
cache.Insert(key, data, cacheTime);
}
}
}
/// <summary>
/// 将指定键的对象添加到缓存中,并指定过期时间
/// </summary>
/// <param name="key">缓存键</param>
/// <param name="data">缓存值</param>
/// <param name="cacheTime">缓存过期时间</param>
public static void Insert<T>(string key, T data, DateTime cacheTime)
{
if (!string.IsNullOrWhiteSpace(key) && data != null)
{
//lock (cacheLocker)
{
cache.Insert<T>(key, data, cacheTime);
}
}
}
/// <summary>
/// 从缓存中移除指定键的缓存值
/// </summary>
/// <param name="key">缓存键</param>
public static void Remove(string key)
{
if (string.IsNullOrWhiteSpace(key))
return;
lock (cacheLocker)
{
cache.Remove(key);
}
}
/// <summary>
/// 判断key是否存在
/// </summary>
public static bool Exists(string key)
{
return cache.Exists(key);
}
/// <summary>
/// 右侧入队
/// </summary>
/// <param name="queueName"></param>
/// <param name="redisValue"></param>
/// <returns></returns>
public static long EnqueueListRightPush(RedisKey queueName, RedisValue redisValue)
{
return cache.EnqueueListLeftPush(queueName, redisValue);
}
/// <summary>
/// 左侧入队
/// </summary>
/// <param name="queueName"></param>
/// <param name="redisvalue"></param>
/// <returns></returns>
public static bool EnqueueListLeftPush(RedisKey queueName, RedisValue redisvalue)
{
if (cache == null)
return false;
cache.EnqueueListLeftPush(queueName, redisvalue);
return true;
}
/// <summary>
/// 获取队列长度
/// </summary>
/// <param name="queueName"></param>
/// <returns></returns>
public static long EnqueueListLength(RedisKey queueName)
{
if(cache == null)
{
return 0;
}
return cache.EnqueueListLength(queueName);
}
/// <summary>
/// 左侧出队
/// </summary>
/// <param name="queueName"></param>
/// <returns></returns>
public static string DequeueListPopLeft(RedisKey queueName)
{
return cache.DequeueListPopLeft(queueName);
}
/// <summary>
/// 右侧出队
/// </summary>
/// <param name="queueName"></param>
/// <returns></returns>
public static string DequeueListPopRight(RedisKey queueName)
{
return cache.DequeueListPopRight(queueName);
}
/// <summary>
/// Redis发布订阅 订阅
/// </summary>
/// <param name="subChannel"></param>
public static void RedisSub(string subChannel, Action<string> DealAction)
{
cache.RedisSub(subChannel, DealAction);
}
/// <summary>
/// Redis发布订阅 发布
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="channel"></param>
/// <param name="msg"></param>
/// <returns></returns>
public static long RedisPub<T>(string channel, T msg)
{
return cache.RedisPub(channel, msg);
}
/// <summary>
/// Redis发布订阅 取消订阅
/// </summary>
/// <param name="channel"></param>
public static void Unsubscribe(string channel)
{
cache.Unsubscribe(channel);
}
/// <summary>
/// Redis发布订阅 取消全部订阅
/// </summary>
public static void UnsubscribeAll()
{
cache.UnsubscribeAll();
}
}
redis
这里是reids方法的具体实现,注意这里的redis采用的是单例模式防止产生过多客户端影响系统性能。
class CacheObject<T>
{
public int ExpireTime { get; set; }
public bool ForceOutofDate { get; set; }
public T Value { get; set; }
}
public class Redis : ICache
{
int Default_Timeout = 3;//默认超时时间(单位秒)
JsonSerializerSettings jsonConfig = new JsonSerializerSettings() { ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore, NullValueHandling = NullValueHandling.Ignore };
private static readonly string address = "127.0.0.1:6379,password=123456";
private static Redis uniqueInstance;
private static readonly object locker = new object();
ConnectionMultiplexer connectionMultiplexer;
IDatabase database;
ISubscriber sub;
public static Redis GetRedis()
{
if (uniqueInstance == null)
{
lock (locker)
{
// 如果类的实例不存在则创建,否则直接返回
if (uniqueInstance == null)
{
uniqueInstance = new Redis();
}
}
}
return uniqueInstance;
}
private Redis()
{
if (address == null || string.IsNullOrWhiteSpace(address.ToString()))
throw new ApplicationException("配置文件中未找到RedisServer的有效配置");
connectionMultiplexer = ConnectionMultiplexer.Connect(address);
connectionMultiplexer.ConnectionFailed += (sender, args) =>
{
//redis断开重连
connectionMultiplexer = ConnectionMultiplexer.Connect(address);
};
database = connectionMultiplexer.GetDatabase(0);
sub = connectionMultiplexer.GetSubscriber();
}
/// <summary>
/// 切换库编号
/// </summary>
/// <param name="dbCode"></param>
/// <returns></returns>
public Redis setDatabase(int dbCode)
{
database = connectionMultiplexer.GetDatabase(dbCode);
return this;
}
/// <summary>
/// 连接超时设置
/// </summary>
public int TimeOut
{
get
{
return Default_Timeout;
}
set
{
Default_Timeout = value;
}
}
public object Get(string key)
{
return Get<object>(key);
}
public T Get<T>(string key)
{
DateTime begin = DateTime.Now;
var cacheValue = database.StringGet(key);
DateTime endCache = DateTime.Now;
var value = default(T);
if (!cacheValue.IsNull)
{
var cacheObject = JsonConvert.DeserializeObject<CacheObject<T>>(cacheValue, jsonConfig);
if (!cacheObject.ForceOutofDate)
database.KeyExpire(key, new TimeSpan(0, 0, cacheObject.ExpireTime));
value = cacheObject.Value;
}
DateTime endJson = DateTime.Now;
return value;
}
public void Insert(string key, object data)
{
var jsonData = GetJsonData(data, TimeOut, false);
database.StringSet(key, jsonData);
}
public void Insert(string key, object data, int cacheTime)
{
var timeSpan = TimeSpan.FromSeconds(cacheTime);
var jsonData = GetJsonData(data, TimeOut, true);
database.StringSet(key, jsonData, timeSpan);
}
public void Insert(string key, object data, DateTime cacheTime)
{
var timeSpan = cacheTime - DateTime.Now;
var jsonData = GetJsonData(data, TimeOut, true);
database.StringSet(key, jsonData, timeSpan);
}
public void Insert<T>(string key, T data)
{
var jsonData = GetJsonData<T>(data, TimeOut, false);
database.StringSet(key, jsonData);
}
public void Insert<T>(string key, T data, int cacheTime)
{
var timeSpan = TimeSpan.FromSeconds(cacheTime);
var jsonData = GetJsonData<T>(data, TimeOut, true);
database.StringSet(key, jsonData, timeSpan);
}
public void Insert<T>(string key, T data, DateTime cacheTime)
{
var timeSpan = cacheTime - DateTime.Now;
var jsonData = GetJsonData<T>(data, TimeOut, true);
database.StringSet(key, jsonData, timeSpan);
}
string GetJsonData(object data, int cacheTime, bool forceOutOfDate)
{
var cacheObject = new CacheObject<object>() { Value = data, ExpireTime = cacheTime, ForceOutofDate = forceOutOfDate };
return JsonConvert.SerializeObject(cacheObject, jsonConfig);//序列化对象
}
string GetJsonData<T>(T data, int cacheTime, bool forceOutOfDate)
{
var cacheObject = new CacheObject<T>() { Value = data, ExpireTime = cacheTime, ForceOutofDate = forceOutOfDate };
return JsonConvert.SerializeObject(cacheObject, jsonConfig);//序列化对象
}
/// <summary>
/// 删除
/// </summary>
/// <param name="key"></param>
public void Remove(string key)
{
database.KeyDelete(key);
}
/// <summary>
/// 判断key是否存在
/// </summary>
public bool Exists(string key)
{
return database.KeyExists(key);
}
/// <summary>
/// 右侧入队
/// </summary>
/// <param name="queueName">队列名称</param>
/// <param name="redisValue">值</param>
/// <returns></returns>
public long EnqueueListRightPush(RedisKey queueName, RedisValue redisValue)
{
return database.ListRightPush(queueName, redisValue);
}
/// <summary>
/// 左侧入队
/// </summary>
/// <param name="queueName">队列名称</param>
/// <param name="redisvalue">队列值</param>
/// <returns></returns>
public long EnqueueListLeftPush(RedisKey queueName, RedisValue redisvalue)
{
return database.ListLeftPush(queueName, redisvalue);
}
/// <summary>
/// 获取队列长度
/// </summary>
/// <param name="queueName">队列名称</param>
/// <returns></returns>
public long EnqueueListLength(RedisKey queueName)
{
if(database.KeyExists(queueName))
{
return database.ListLength(queueName);
}
else
{
return 0;
}
}
/// <summary>
/// 左侧出队
/// </summary>
/// <param name="queueName"></param>
/// <returns></returns>
public string DequeueListPopLeft(RedisKey queueName)
{
var val= database.ListRange(queueName);
if (val!=null &&val.Length>0 )
{
string redisValue = database.ListLeftPop(queueName);
if (!string.IsNullOrEmpty(redisValue))
return redisValue;
else
return string.Empty;
}
else
{
return "-1";
throw new Exception($"队列{queueName}数据为零");
}
}
public string DequeueListPopRight(RedisKey queueName)
{
var val = database.ListRange(queueName);
if (val!=null&& val.Length > 0)
{
string redisValue = database.ListRightPop(queueName);
if (!string.IsNullOrEmpty(redisValue))
return redisValue;
else
return string.Empty;
}
else
{
return "-1";
throw new Exception($"队列{queueName}数据为零");
}
}
/// <summary>
/// Redis发布订阅 订阅
/// </summary>
/// <param name="subChannel"></param>
public void RedisSub(string subChannel, Action<string> DealAction)
{
sub.Subscribe(subChannel, (channel, message) =>
{
DealAction((string)message);
sub.Publish(channel, message);
});
}
/// <summary>
/// Redis发布订阅 发布
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="channel"></param>
/// <param name="msg"></param>
/// <returns></returns>
public long RedisPub<T>(string channel, T msg)
{
return sub.Publish(channel, JsonConvert.SerializeObject(msg));
}
/// <summary>
/// Redis发布订阅 取消订阅
/// </summary>
/// <param name="channel"></param>
public void Unsubscribe(string channel)
{
sub.Unsubscribe(channel);
}
/// <summary>
/// Redis发布订阅 取消全部订阅
/// </summary>
public void UnsubscribeAll()
{
sub.UnsubscribeAll();
}
}
队列工厂
这里的方法逻辑是,外部方法调用addQueue,往队列里面添加数据,同时开启消费线程(CreateAndRunTask),当数据执行结束后将信号量重置,将线程挂起,减少性能开销
CreateAndRunTask 方法中会调用每个继承该抽象方法的子类的处理方法也就是(DealWith) 方法。
/// <summary>
/// 对列抽象工厂
/// </summary>
public abstract class QueueFactory
{
/// <summary>
/// 信号标识
/// </summary>
private Lazy<ManualResetEvent> MREvent { get; set; }
/// <summary>
/// 消费线程
/// </summary>
private Lazy<Task> _Consumption { get; set; }
/// <summary>
/// 队列名称
/// </summary>
public string QueueName { get; set; }
public QueueFactory(string _QueueName, int _DbCode = 0)
{
CacheHelper.Init(_DbCode);
QueueName = _QueueName;
_Consumption = new Lazy<Task>(() => { return CreateAndRunTask(QueueName); });
MREvent = new Lazy<ManualResetEvent>(() => { return new ManualResetEvent(false); });
}
/// <summary>
/// 队列内添加数据
/// </summary>
/// <param name="msg">消息体</param>
public virtual void addQueue(string message)
{
Task.Run(() =>
{
try
{
if (CacheHelper.EnqueueListLeftPush(QueueName, message))
{
Console.WriteLine(string.Format("[{0}]消息体加入队列:{1}", DateTime.Now.ToString(), message));
bool isSet = MREvent.Value.WaitOne(0);//检查当前状态
Console.WriteLine(string.Format("[{0}]当前信号量状态:{1}", DateTime.Now.ToString(), isSet.ToString()));
if (!isSet)
{
MREvent.Value.Set();
isSet = MREvent.Value.WaitOne(0);
Console.WriteLine(string.Format("[{0}]修改后信号量状态:{1}", DateTime.Now.ToString(), isSet.ToString()));
}
Console.WriteLine(string.Format("[{0}]当前线程状态:{1}", DateTime.Now.ToString(), _Consumption.Value.Status.ToString()));
if (_Consumption != null && _Consumption.Value.IsCompleted)
{
_Consumption.Value.Wait();
_Consumption = new Lazy<Task>(() => { return CreateAndRunTask(QueueName); });
Console.WriteLine("重新启动线程");
}
}
else
{
Console.WriteLine(string.Format("[{0}]消息体加入队列时失败消息体内容:{1}", DateTime.Now.ToString(), message));
};
}
catch (Exception ex)
{
DealWith(message);
Console.WriteLine(string.Format("[{0}]Reids未开启!兜底直接处理消息体:{1}", DateTime.Now.ToString(), ex.Message));
}
});
}
/// <summary>
/// 消费线程
/// </summary>
/// <returns></returns>
protected virtual Task CreateAndRunTask(string _QueueName)
{
return Task.Run(() =>
{
long count = CacheHelper.EnqueueListLength(_QueueName);
if (count > 0)
{
for (int i = 0; i <= count; i++)
{
string message = CacheHelper.DequeueListPopRight(_QueueName);
if (message == "-1" || string.IsNullOrWhiteSpace(message))
{
MREvent.Value.Reset();
}
else
{
DealWith(message);
}
}
count = CacheHelper.EnqueueListLength(_QueueName);
if (count > 0)
{
CreateAndRunTask(_QueueName);
}
}
else
{
MREvent.Value.Reset();
}
});
}
/// <summary>
/// 消费方法
/// </summary>
/// <param name="msg">消息体</param>
protected abstract void DealWith(string message);
/// <summary>
/// 获取队列数量
/// </summary>
/// <returns></returns>
public long GetQueueCount()
{
return CacheHelper.EnqueueListLength(QueueName);
}
}
测试方法
这里是继承自上述基类的子方法
这里重写了Dealwith
public class TmallQueue : QueueFactory
{
public TmallQueue():base("TmallQueue")
{
}
protected override void DealWith(string message)
{
Console.WriteLine(QueueName + "线程名称:" + Thread.CurrentThread.ManagedThreadId);
Console.WriteLine(QueueName + "是否为后台线程:" + Thread.CurrentThread.IsBackground);
Thread.Sleep(1000);
Console.WriteLine(QueueName+":已处理"+message);
}
}
这个方法和上面是同样的
public class MTQueue : QueueFactory
{
public MTQueue() : base("MTQueue")
{
}
protected override void DealWith(string message)
{
Console.WriteLine(QueueName+"线程名称:"+Thread.CurrentThread.ManagedThreadId);
Console.WriteLine(QueueName + "是否为后台线程:"+Thread.CurrentThread.IsBackground);
Thread.Sleep(1500);
Console.WriteLine(QueueName + ":已处理" + message);
}
}
这里是模拟添加数据
using ConsoleApp1.QueueFactory;
class Program
{
public static void Main()
{
addtest("adss");
Console.WriteLine("主线程执行结束");
Console.ReadKey();
}
static string addtest(string msg)
{
MTQueue mT = new MTQueue();
for (int i = 0; i < 50; i++)
{
mT.addQueue(mT.QueueName + "数据" + i+ msg);
Console.WriteLine(mT.QueueName + "当前队列数量:" + mT.GetQueueCount());
}
TmallQueue mTall = new TmallQueue();
for (int i = 0; i < 50; i++)
{
mTall.addQueue(mTall.QueueName + "数据" + i+ msg);
Console.WriteLine(mTall.QueueName + "当前队列数量:" + mTall.GetQueueCount());
}
return msg;
}
}
执行效果: