Redis/NewLife.Redis/Services/RedisEventBus.cs

126 lines
4.2 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using NewLife.Caching.Queues;
using NewLife.Log;
using NewLife.Messaging;
using System.Diagnostics.CodeAnalysis;
namespace NewLife.Caching.Services;
/// <summary>Redis事件上下文</summary>
public class RedisEventContext<TEvent>(IEventBus<TEvent> eventBus, Queues.Message message) : IEventContext<TEvent>
{
/// <summary>事件总线</summary>
public IEventBus<TEvent> EventBus { get; set; } = eventBus;
/// <summary>原始消息</summary>
public Queues.Message Message { get; set; } = message;
}
/// <summary>Redis事件总线</summary>
/// <typeparam name="TEvent"></typeparam>
/// <remarks>实例化消息队列事件总线</remarks>
public class RedisEventBus<TEvent>(FullRedis cache, String topic, String group) : EventBus<TEvent>
{
private RedisStream<TEvent>? _queue;
private CancellationTokenSource? _source;
/// <summary>销毁</summary>
/// <param name="disposing"></param>
protected override void Dispose(Boolean disposing)
{
base.Dispose(disposing);
_source?.TryDispose();
}
/// <summary>初始化</summary>
[MemberNotNull(nameof(_queue))]
protected virtual void Init()
{
if (_queue != null) return;
// 创建Stream队列指定消费组从最后位置开始消费
var stream = cache.GetStream<TEvent>(topic);
stream.Group = group;
stream.FromLastOffset = true;
stream.Expire = TimeSpan.FromDays(3);
_queue = stream;
if (_source != null)
_ = Task.Run(() => ConsumeMessage(_source));
}
/// <summary>发布消息到消息队列</summary>
/// <param name="event">事件</param>
/// <param name="context">上下文</param>
/// <param name="cancellationToken">取消令牌</param>
public override Task<Int32> PublishAsync(TEvent @event, IEventContext<TEvent>? context = null, CancellationToken cancellationToken = default)
{
Init();
var rs = _queue.Add(@event);
return Task.FromResult(1);
}
/// <summary>订阅消息。启动大循环,从消息队列订阅消息,再分发到本地订阅者</summary>
/// <param name="handler">处理器</param>
/// <param name="clientId">客户标识。每个客户只能订阅一次,重复订阅将会挤掉前一次订阅</param>
public override Boolean Subscribe(IEventHandler<TEvent> handler, String clientId = "")
{
if (_source == null)
{
var source = new CancellationTokenSource();
if (Interlocked.CompareExchange(ref _source, source, null) == null)
{
Init();
}
}
// 本进程订阅。从队列中消费到消息时,会发布到本进程的事件总线,这里订阅可以让目标处理器直接收到消息
return base.Subscribe(handler, clientId);
}
/// <summary>从队列中消费消息,经事件总线送给设备会话</summary>
/// <param name="source"></param>
/// <returns></returns>
protected virtual async Task ConsumeMessage(CancellationTokenSource source)
{
DefaultSpan.Current = null;
var cancellationToken = source.Token;
var stream = _queue!;
try
{
if (!stream.Group.IsNullOrEmpty()) stream.SetGroup(stream.Group);
while (!cancellationToken.IsCancellationRequested)
{
var msg = await stream!.TakeMessageAsync(15, cancellationToken).ConfigureAwait(false);
if (msg != null)
{
var msg2 = msg.GetBody<TEvent>();
if (msg2 != null)
{
// 发布到事件总线
await base.PublishAsync(msg2, new RedisEventContext<TEvent>(this, msg), cancellationToken).ConfigureAwait(false);
}
}
else
{
await Task.Delay(1_000, cancellationToken).ConfigureAwait(false);
}
}
}
catch (TaskCanceledException) { }
catch (OperationCanceledException) { }
catch (Exception ex)
{
XTrace.WriteException(ex);
}
finally
{
source.Cancel();
_queue = null;
}
}
}