最近一個外部的項目,使用到了消息隊列,原本是用rabbitmq實現的,可是因爲是部署到別人家的服務器上,想盡可能簡化一些,項目中原本也要接入了redis緩存,就嘗試使用redis來實現簡單的消息隊列。git
使用redis作消息隊列有兩種方法,一種是使用pub/sub,另外一種是使用list結構,配合brpop來消費。這兩種方式各有特色,這裏簡述一下:github
#第一步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。redis
首先須要在appsettings.json中添加redis的鏈接字符串:json
{ "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,編寫實際邏輯:app
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; } }
發佈消息只是往隊列中添加項:async
RedisHelper.LPush("addOrder", order);
最後把消費服務添加到startup.cs中:
public void ConfigureServices(IServiceCollection services) { //redis配置 RedisHelper.Initialization(new CSRedis.CSRedisClient(Configuration.GetConnectionString("redis"))); //redis消息隊列消費服務,放在redis配置下方 services.AddHostedService<AddOrderListener>(); }
經測試,還算穩定,小併發項目可使用。