Asp.net core中RedisMQ的簡單應用

拉克斯文發表於2020-12-08

最近一個外部的專案,使用到了訊息佇列,本來是用rabbitmq實現的,但是由於是部署到別人家的伺服器上,想盡量簡化一些,專案中本來也要接入了redis快取,就嘗試使用redis來實現簡單的訊息佇列。

使用redis做訊息佇列有兩種方法,一種是使用pub/sub,另一種是使用list結構,配合brpop來消費。這兩種方式各有特點,這裡簡述一下:

  • pub/sub模式,支援多客戶端消費,但是不支援持久化,這就意味著客戶端斷開的時間內釋出的訊息將會全部捨棄掉。
  • list配合brpop,預設不支援多客戶端消費,支援持久化。這種模式的多客戶端消費可以變相實現,比如下面的虛擬碼:
#第一步push訊息到佇列
lpush  listA  msg
#第二步,一個專門的分發客戶端取出訊息,push到各個子佇列
var msg=brpop listA
lpush listA1 msg
lpush listA2 msg
......
#第三步,多個客戶端從對應的佇列消費訊息
var client1_msg= brpop listA1
var client2_msg= brpop listA2
......

訊息丟失不太可取,所以我選擇了list ,下一步需要選擇一個合適的客戶端。
Stackexchange.redis 算是一個老牌的客戶端了,但是由於其採用多路複用的模式,沒法支援Redis的blocking pops特性。所以我採用了國人寫的CSRedisCore。

首先需要在appsettings.json中新增redis的連線字串:

{
  "ConnectionStrings": {
    "redis": "{ip}:{port},password=123456,prefix=my_"
  }
}

具體配置請參考github上的文件:https://github.com/2881099/csredis

然後在startup.cs的ConfigureServices中配置redis:

       public void ConfigureServices(IServiceCollection services)
       {
            //redis配置
            RedisHelper.Initialization(new CSRedis.CSRedisClient(Configuration.GetConnectionString("redis")));
       }

當然也可以採用依賴注入的方式新增CSRedisClient例項,這個不糾結。

在專案中有好幾處使用到了佇列,所以先封裝一個消費服務:

    public abstract class RedisMQConsumer : BackgroundService
    {
        protected abstract string CacheKey { get; }

        protected ILogger<RedisMQConsumer> logger;

        public RedisMQConsumer(ILogger<RedisMQConsumer> logger)
        {
            this.logger = logger;
        }

        protected override Task ExecuteAsync(CancellationToken stoppingToken)
        {
            return Task.Run( async() =>
            {
                while (!stoppingToken.IsCancellationRequested)
                {
                    try
                    {
                        var msg = RedisHelper.BRPop(5, CacheKey);
                        try
                        {
                            if (string.IsNullOrEmpty(msg)) continue;
                            if (!Process(msg))
                            {
                                //加入錯誤處理佇列,可以在後臺寫功能手動處理
                                RedisHelper.LPush(CacheKey + "_err", msg);
                            }
                        }
                        catch (Exception exp)
                        {
                            //加入錯誤處理佇列,可以在後臺寫功能手動處理
                            RedisHelper.LPush(CacheKey + "_err", msg);
                            logger.LogError(exp, "RedisMQConsumer Execute error");
                        }
                    }
                    catch
                    {
                        //網路可能中斷
                        await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
                    }
                        
                }
            }, stoppingToken);
        }

        protected abstract bool Process(string message);
    }

然後就可以繼承RedisMQConsumer,編寫實際邏輯:

    public class AddOrderMQConsumer : RedisMQConsumer
    {
        public AddOrderMQConsumer(ILogger<RedisMQConsumer> logger) : base(logger)
        {
        }
        protected override string CacheKey => "addOrder";
        protected override bool Process(string message)
        {
            var order = JsonSerializer.Deserialize<Order>(message);
            //處理邏輯
            return true;
        }
    }

釋出訊息只是往佇列中新增項:

RedisHelper.LPush("addOrder", order);

最後把消費服務新增到startup.cs中:

       public void ConfigureServices(IServiceCollection services)
        {
            //redis配置
            RedisHelper.Initialization(new CSRedis.CSRedisClient(Configuration.GetConnectionString("redis")));
            
            //redis訊息佇列消費服務,放在redis配置下方
            services.AddHostedService<AddOrderMQConsumer>();
        }

經測試,還算穩定,小併發專案可以使用。

相關文章