基於Redis消息的訂閱發布應用場景
1.應用背景
在物聯網采集管控系統中,前后端隔離的情況下,前端通過表單(比如按鈕,開關,表格等)輸入數據到數據庫(比如MySql,通過WEBAPI服務端輸入),然后采集控制端到數據庫里去掃表取數據,將數據下發給物聯網絡中的終端設備(比如風扇控制板),從而來控制風扇的開跟關。
2.困境
采集控制端需要到數據庫中去掃表。這個掃表操作會帶來幾個問題:
2.1 鎖表風險
掃表會有鎖表風險,當該DBContext被占用的時候,其他線程不能實時使用此DBContext。
2.2 實時性差
在物聯網系統中,數據會非常多,比如有10000台設備,每台設備有100個采集控制點,則控制點最多可能會達到100W數據,這樣去掃表,不僅占用DBContext上下文的時間會很長,而且實時性會很差。
2.3 增加編程復雜性
增加了采集服務端編程的復雜性。
2.4 實時效果
用戶體驗效果較差:客戶點了開關控制風扇打開,然后底端設備需要很長時間才能真正打開。
3.解決方案
使用消息訂閱發布方法。RabbitMQ比較重,故這里選用Redis的訂閱發布功能,而且很多情況下Redis已經被作為緩存在引用,詳見如下。
3.1 前端傳值給服務端
前端將實時控制值以Restful API形式通過IP地址端口號+路由(比如:192.168.2.106:5000/ControlConfig)將此值傳遞給服務端。
3.2 服務端通過消息傳給采集控制端
這里通過nuget獲得CSRedisCore,來操作Redis的訂閱發布功能。采集控制端訂閱消息。服務端發布消息。這樣操作達到了如下目的:2.1不用經過數據庫消息的實時傳遞;2.2 實時性好;2.3 編程也簡單;2.4 實時效果好。
4.詳細代碼設計
4.1 CSRedisCore
CSRedis 是 redis.io 官方推薦庫,支持 redis-trib集群、哨兵、私有分區與連接池管理技術,簡易 RedisHelper 靜態類。
https://www.nuget.org/packages/CSRedisCore/
通過Nuget獲得CSRedisCore庫
4.2 接口設計如下
詳細說明參考注釋。
using CSRedis;
namespace IBMS.Infrastruct.Redis
{
public interface IRedisMQ
{ //連接Redis
CSRedisClient ConnectCSRedis();
//訂閱頻道
void SubscribeCSRedis(string ChannelName);
//把message異步發布Redis的頻道
void PublishAsyncCSRedis(string channel, string message);
//釋放Redis
void DisposeCSRedis();
//訂閱接受下來的msg的方法
void Rcv(string Msg, string channel);
}
}
4.3 接口實現如下
詳細說明見注釋
using System;
using CSRedis;
using IBMS.Infrastruct.Appsetting;
namespace IBMS.Infrastruct.Redis
{
public class RedisMQ : IRedisMQ
{
//讀取連接Redis字符串
private readonly string connectRedis = Appsettings.app(new string[] { "AppSettings", "RedisCaching", "ConnectionString" });//按照層級的順序,依次寫出來
//定義一個Redis客戶端對象
static CSRedisClient _RedisMQ;
//連接Redis
public CSRedisClient ConnectCSRedis()
{
//如果已經連接實例,直接返回
if (_RedisMQ != null)
{
return _RedisMQ;
}
return _RedisMQ = new CSRedisClient(connectRedis);
}
//釋放Redis
public void DisposeCSRedis()
{
_RedisMQ.Dispose();
}
//異步發布消息到Redis的某個頻道
public void PublishAsyncCSRedis(string channelName, string message)
{
_RedisMQ.PublishAsync(channelName, message);
}
//如果自己需要用消息值,需要想方法返回數據
//訂閱消息的處理方法
public void Rcv(string channel, string Msg)
{
Console.WriteLine($"{DateTime.Now.ToLongDateString()}|Rcv:{channel},Msg:{Msg}");
}
//訂閱消息
public void SubscribeCSRedis(string ChannelName)
{
_RedisMQ.Subscribe((ChannelName, msg => Rcv(msg.Channel, msg.Body)));
}
}
}
4.4 ConfigureServices中依賴注入
在Startup.cs中的ConfigureServices方法進行依賴注入,如下。
services.AddScoped<IRedisMQ, RedisMQ>();
4.5 創建一個RedisMQ的消息對象
在Controller里定義創建一個消息對象,這一步的前提是需要依賴注入,依賴注入在某種意義上跟C語言的typedef有點像,將typedef會將控制權交給編譯器,編譯器定義新類型,然后程序運行之后就可以就可以隨意通過新類型來定義對象。
IRedisMQ _RedisMQ =new RedisMQ();
4.6 實現層代碼設計
// PUT: api/ControlConfig/5
[HttpPut]
public async Task Update([FromBody] ControlConfig ControlConfig)
{
_RedisMQ.ConnectCSRedis();
_RedisMQ.SubscribeCSRedis("web");
_RedisMQ.PublishAsyncCSRedis("web", $"add at{DateTime.Now}");
_RedisMQ.PublishAsyncCSRedis("web", $"{SerializeHelper.Serialize(ControlConfig)}");
Console.ReadKey();
_RedisMQ.DisposeCSRedis();
}
5.效果
5.1 打開風扇按鈕
5.2 RedisDesktopManager工具中觀察
在RedisDesktopManager的命令行窗口中輸入PSUBSCRIBE web,進行訂閱web頻道,如下
5.3 觀察web頻道輸出信息
在前端控制了風扇打開操作之后如5.1,在RedisDesktopManager觀察web頻道輸出信息
5.4 觀察實際風扇效果
風扇實時打開。
備注:采集控制端跟設備端是基於TCP長連接組網方式,協議用的是基於MODBUS的變種,比如加入我們自己的包頭包尾包類型等信息,這里不做展開
6 框架圖
補上一張框架圖,拖到瀏覽器新窗口,點擊放大即可清晰瀏覽,采用億圖制作,以便更好理解。