問題:
-
公司開了個新專案,算上我一共3個人。車間裡機臺通過流水線連通聯動的玩意。一個管理控制系統連線各個機臺和硬體。專機型別就有5種,個數差不多20個左右。
-
軟體規劃的時候採用總分的結構,管理控制系統和專機子系統之間通過訊息中介軟體通訊。本來也想TCP連線來著,但是開發時間不允許。而且每個系統都得寫一遍這個玩意。
-
訊息中介軟體有很多個,比如 Kafka、RabbitMQ、RocketMQ等國內外的訊息中介軟體。這些中介軟體無論宣稱的多麼輕量級都要啃一下,更要命的是就他娘三個人。而且後面還要這個雞兒系統可複製。
-
考慮到訊息及時性、開發難易程度、維護簡便性等因素後決定用Redis的pub/sub功能來實現.軟體結構大概如類似結構。
可用性:
-
作為訊息通知屬於安裝了Redis就有的功能,因為Redis是用在系統中儲存一些熱資料,不用單獨維護,在Windows中屬於服務直接就開了。
-
作為可以分散式叢集使用的資料庫,訊息傳遞應該比較OK了。雖然使用的client-server,但是server-server已經很好了。料想client-server也不會差
-
試驗訊息內容傳送訂閱的情況下,速度在30毫秒內,貌似可以。看其他博主說大於10K入隊比較慢,但是可以不用入訊息佇列啊,用釋出訂閱。
-
.net 下一般使用ServiceStack.Redis,要命的是4.0以後收費,可以破解的但是不支援List<T>型的資料直接存取,想用只能變成JSON字串存著。
- 如果只是用訂閱釋出功能,不儲存熱資料或者不使用List<T>的資料可以使用4.0以上的版本。文末會貼上兩個型別的下載包。想用其他的包也可以,我這裡只說一種思路。
實現:
模組結構圖展示如下
public static class MSServer { // 定義一個object物件 private static object objinstance = new object(); private static ServerState CurState = ServerState.Free; static PooledRedisClientManager prcm; private static string clientmake = string.Empty; /// <summary> /// 連線的地址 /// </summary> /// <param name="IP">地址127.0.0.1:6379</param> /// <param name="rechannels">接收通道 {"channel:1-13","channel:1-5"}</param> /// <returns></returns> public static int OpenServer(string IP ,string[] rechannels) { try { if (prcm == null) { lock (objinstance) { if (prcm == null) { prcm = CreateManager(IP, IP); CurState = ServerState.Init; return CreateLink(rechannels); } } } } catch { prcm = null; CurState = ServerState.Free; return -1; } return 1; } private static int CreateLink(string[] SourceID) { if (CurState == ServerState.Init && SourceID.Length > 0) { try { using (IRedisClient Redis = prcm.GetReadOnlyClient()) { clientmake = SourceID[0]; var info = Redis.GetClientsInfo().Where(i => i["name"] == clientmake).ToList(); info.ForEach(i => { Redis.KillClient(i["addr"]); }); Redis.SetClient(clientmake); IRedisSubscription sc = Redis.CreateSubscription(); Task.Run(() => { try { sc.SubscribeToChannels(SourceID); } catch { } }); sc.OnMessage += new Action<string, string>(showpub); } CurState = ServerState.Work; } catch { string message = string.Empty; prcm = null; CurState = ServerState.Free; return -1; } return 1; } else { return 0; } } public static Action<string, string> ReceiveMessage; static void showpub(string channel, string message) { if (ReceiveMessage != null) { ReceiveMessage(channel, message); } } private static PooledRedisClientManager CreateManager(string writeHost, string readHost) { var redisClientConfig = new RedisClientManagerConfig { MaxWritePoolSize = 1,//“寫”連結池連結數 MaxReadPoolSize = 1,//“讀”連結池連結數 DefaultDb = 0, AutoStart = true, }; //讀的客戶端只能接受特定的命令,不能用於傳送資訊 var RedisClientManager = new PooledRedisClientManager( new string[] { writeHost }//用於寫 , new string[] { readHost }//用於讀 , redisClientConfig); CurState = ServerState.Init; return RedisClientManager; } /// <summary> /// 傳送資訊 /// </summary> /// <param name="channel">通訊物件 "channel:1-13"</param> /// <param name="meesage">傳送資訊 "test send "</param> /// <returns>0 傳送失敗 1 傳送成功 -1 連線損毀 檢查網路後重建</returns> public static long PubMessage(string channel, string meesage) { if (CurState == ServerState.Work) { if (!string.IsNullOrEmpty(channel) && !string.IsNullOrEmpty(meesage)) { try { using (IRedisClient Redis = prcm.GetClient()) { Redis.SetClient(clientmake); return Redis.PublishMessage(channel, meesage); } } catch { prcm = null; CurState = ServerState.Free; return -1; } } else { return 0; } } else { return -1; } } } public enum ServerState { Free, Init, Work, Del }
有一個問題,就是連線遠端的伺服器時如果網路斷開再重連,會殘留沒用的client ,這樣如果網路斷斷續續的話,會留好多沒有清除的客戶端。
這個在3.0.504版本中Redis 中也有這個問題,不知道是基於什麼考慮的。所以需要建立連線的時候,給個客戶端名稱,再初始化的時候刪掉所有同型別的名稱。
使用的時候大概類似操作 textbox2.text = "channel:1-5" .為了簡便釋出的和監聽的都是本地的一個通道。
private void button1_Click(object sender, EventArgs e) { //11.1.7.152 192.168.12.173 int result = ServerMS.MSServer.OpenServer("127.0.0.1:6379", new string[] { textBox2.Text }); label1.Text = result.ToString(); //1匿名事件 ServerMS.MSServer.ReceiveMessage += new Action<string, string>(fuck); if (result == 0) { //傳送失敗重新傳送 檢查 通道和字串後重新傳送 } else if (result == 1) { //傳送成功 } else if (result == -1) { //連線錯誤 需要 ServerMS.MSServer.OpenServer("192.168.12.173:6379", new string[] { textBox2.Text }); } } void fuck(string channel, string message) { this.BeginInvoke(new Action(() => { textBox4.Text = channel + message; })); } public bool sdfsd = true; private void button3_Click(object sender, EventArgs e) {long result = ServerMS.MSServer.PubMessage(textBox2.Text, DateTime.Now.ToString("yyyyyMMddhhmmssfff")); if (result == 0) { //傳送失敗重新傳送 } else if (result == 1) { //傳送成功 } else if (result == -1) { //連線錯誤 需要 ServerMS.MSServer.OpenServer("192.168.12.173:6379", new string[] { textBox2.Text }); } }
為了簡便channel:是通道的固定命令 ,可以自定義channel:後面的內容,傳送就有反饋。確保所有機臺都接收到。
如果有斷線的需要程式自己重連,接收通道的客戶端不可以再給其他的使用,Redis上說Redis client 進入訂閱模式時只能接受訂閱釋出等命令指令,不接受普通的存取和其他命令
所以如果需要在讀取、寫入、釋出、執行其他的指令需要使用其他客戶端,否則就出錯了。跑了幾天了上億次的測試貌似沒有出現什麼問題。
釋出訂閱訊息不會走AOF RDB只存在於記憶體中,即發即用,用完就沒了。沒線上就沒了。需要考慮使用環境。
還用ping pong來確定連線狀態,也可以自定義資料,使用場景要自己開發,要適合自己的才是好的。
下載:
4.0 dll
連結:https://pan.baidu.com/s/1966t0pduHxQXcxcxV3ZTeQ
提取碼:js8p
5.8 dll不可以使用List<T>型別
連結:https://pan.baidu.com/s/1RFgY4V0ZO78Wvd7LOxr97g
提取碼:bxh2