經過前面的幾篇文章,講解了一個短信服務的架構設計與實現。然而初始方案並不是100%完美的,咱們仍能夠對該架構作一些優化與調整。git
同時我也但願經過這篇文章與你們分享一下,個人架構設計理念。github
源碼地址:https://github.com/SkyChenSky/Sikiro.SMS/tree/optimize (與以前的是另外的分支)數據庫
該詞出自於建築學。軟件架構定義是指軟件系統的基礎結構,是系統中的實體及實體(服務)之間的關係所進行的抽象描述。而架構設計的目的是爲了解決軟件系統複雜度帶來的問題。緩存
系統複雜度主要有下面幾點:安全
系統的複雜度致使的直接緣由是業務規模。爲了用戶流暢放心的使用產品,不得不提升系統性能與安全。當系統成爲人們生活不可缺一部分時,避免機房停電、挖掘機挖斷電纜致使的系統不可用,不得不去思考同城跨機房同步、異地多活的高可用方案。網絡
我認爲架構,須要在已知可見的業務複雜度與用戶規模的基礎上進行架構設計;伴隨着技術積累與成長而對系統進行架構優化;用戶的日益增加,業務的不斷擴充,迫使了系統的複雜度增長,爲了解決系統帶來新的複雜度而進行架構演變。架構
所以,架構方案是在已有的業務複雜度、用戶規模、技術積累度、人力時間成本等幾個方面的取捨決策後的結果體現。併發
所以從上述可見,調度任務服務這塊是優化關鍵點所在。運維
RabbitMQ自身並無定時任務,然而能夠經過消息的Time-To-Live(過時時間)與Dead Letter Exchange(死信交換機)的結合模擬定時發佈的功能。具體原理以下:性能
Dead Letter Exchange與日常的Exchange無異,主要用於消息死亡後經過Dead Letter Exchange與x-dead-letter-routing-key從新分配到新的隊列進行消費處理。
消息死亡的方式有三種:
兩種消息過時的方式:
隊列申明x-message-ttl參數
var args = new Dictionary<string, object>(); args.Add("x-message-ttl", 60000); model.QueueDeclare("myqueue", false, false, false, args);
每條消息發佈聲明Expiration參數
byte[] messageBodyBytes = System.Text.Encoding.UTF8.GetBytes("Hello, world!"); IBasicProperties props = model.CreateBasicProperties(); props.ContentType = "text/plain"; props.DeliveryMode = 2; props.Expiration = "36000000" model.BasicPublish(exchangeName, routingKey, props, messageBodyBytes);
class Program { static void Main(string[] args) { var factory = new ConnectionFactory { HostName = "10.1.20.140", UserName = "admin", Password = "admin@ucsmy" }; using (var connection = factory.CreateConnection()) using (var channel = connection.CreateModel()) { var queueName = "Queue.SMS.Test"; var exchangeName = "Exchange.SMS.Test"; var key = "Route.SMS.Test"; DeclareDelayQueue(channel, exchangeName, queueName, key); DeclareReallyConsumeQueue(channel, exchangeName, queueName, key); var body = Encoding.UTF8.GetBytes("info: test dely publish!"); channel.BasicPublish(exchangeName + ".Delay", key, null, body); } } private static void DeclareDelayQueue(IModel channel, string exchangeName, string queueName, string key) { var retryDic = new Dictionary<string, object> { {"x-dead-letter-exchange", exchangeName+".dl"}, {"x-dead-letter-routing-key", key}, {"x-message-ttl", 30000} }; var ex = exchangeName + ".Delay"; var qu = queueName + ".Delay"; channel.ExchangeDeclare(ex, "topic"); channel.QueueDeclare(qu, false, false, false, retryDic); channel.QueueBind(qu, ex, key); } private static void DeclareReallyConsumeQueue(IModel channel, string exchangeName, string queueName, string key) { var ex = exchangeName + ".dl"; channel.ExchangeDeclare(ex, "topic"); channel.QueueDeclare(queueName, false, false, false); channel.QueueBind(queueName, ex, key); } }
上面介紹了隊列定時任務基本原理,然而咱們須要本身的項目進行修改優化。
EasyNetQ是一款很是良好使用性的RabbitMQ.Client封裝。對隊列定時任務他也已經提供了相應的方法FuturePublish給咱們使用。
然而他的FuturePublish由有三種調度方式:
DelayedExchangeScheduler是須要EasyNetQ項目提供的調度程序,本質上也是輪詢
ExternalScheduler是經過使用MQ的插件。
DeadLetterExchangeAndMessageTtlScheduler纔是咱們以前經過DEMO實現的方式,在EasyNetQ組件上經過下面代碼進行啓用。
services.RegisterEasyNetQ(_infrastructureConfig.Infrastructure.RabbitMQ, a =>
{
a.EnableDeadLetterExchangeAndMessageTtlScheduler();
});
下面代碼是Sikiro.SMS.Api的優化改造:
/// <summary> /// 添加短信記錄 /// </summary> /// <param name="model"></param> /// <returns></returns> [HttpPost] public ActionResult Post([FromBody] List<PostModel> model) { _smsService.Page(model.MapTo<List<PostModel>, List<AddSmsModel>>()); ImmediatelyPublish(); TimingPublish(); return Ok(); } /// <summary> /// 及時發送 /// </summary> private void ImmediatelyPublish() { _smsService.SmsList.Where(a => a.TimeSendDateTime == null).ToList().MapTo<List<SmsModel>, List<SmsQueueModel>>() .ForEach( item => { _bus.Publish(item, SmsQueueModelKey.Topic); }); } /// <summary> /// 定時發送 /// </summary> private void TimingPublish() { _smsService.SmsList.Where(a => a.TimeSendDateTime != null).ToList() .ForEach( item => { _bus.FuturePublish(item.TimeSendDateTime.Value.ToUniversalTime(), item.MapTo<SmsModel, SmsQueueModel>(), SmsQueueModelKey.Topic); }); }
重發通常是請求服務超時的狀況下使用。而致使這種緣由的主要幾點是網絡波動、服務壓力過大。由於前面任意一種緣由都沒法在短期恢復,所以對於簡單的重試 相似while(i<3)ReSend() 是沒有什麼意義的。
所以咱們須要藉助隊列定時任務+發送次數*延遲時間來完成有效的非頻繁的重發。
public void Start() { Console.WriteLine("I started"); _bus.Subscribe<SmsQueueModel>("", msg => { try { _smsService.Send(msg.MapTo<SmsQueueModel, SmsModel>()); } catch (WebException e) { e.WriteToFile(); ReSend(); } catch (Exception e) { e.WriteToFile(); } }, a => { a.WithTopic(SmsQueueModelKey.Topic); }); } private void ReSend() { var model = _smsService.Sms.MapTo<SmsModel, SmsQueueModel>(); model.SendCount++; _bus.FuturePublish(TimeSpan.FromSeconds(30 * model.SendCount), model, SmsQueueModelKey.Topic); }
SMS日誌做爲非必要業務的運維型監控數據,在須要的時候隨時能夠對此進行刪除或者歸檔處理。所以以時間(年月)做爲集合維度,能夠很好的對日誌數據進行管理。
mongoProxy.Add(MongoKey.SmsDataBase, MongoKey.SmsCollection + "_" + DateTime.Now.ToString("yyyyMM"), model);
通過本系列6篇的文章,介紹了以短信服務爲業務場景,基於.net core平臺的一個簡單架構設計、架構優化與服務實現的實踐例子。但願個人分享能幫助有須要的朋友。若是有任何好的建議請到下方給我留言。