前言

通过前面的几篇文章,讲解了一个短信服务的架构设计与实现。然而初始方案并非100%完美的,我们仍可以对该架构做一些优化与调整。

同时我也希望通过这篇文章与大家分享一下,我的架构设计理念。

源码地址:https://github.com/SkyChenSky/Sikiro.SMS/tree/optimize (与之前的是另外的分支)

架构是设计的还是演变的?

架构

该词出自于建筑学。软件架构定义是指软件系统的基础结构,是系统中的实体及实体(服务)之间的关系所进行的抽象描述。而架构设计的目的是为了解决软件系统复杂度带来的问题。

复杂度

系统复杂度主要有下面几点:

  • 高可用
  • 高性能
  • 可扩展
  • 安全性
  • 维护成本
  • 用户规模

业务规模

系统的复杂度导致的直接原因是业务规模。为了用户流畅放心的使用产品,不得不提高系统性能与安全。当系统成为人们生活不可缺一部分时,避免机房停电、挖掘机挖断电缆导致的系统不可用,不得不去思考同城跨机房同步、异地多活的高可用方案。

答案并非二选一

我认为架构,需要在已知可见的业务复杂度与用户规模的基础上进行架构设计;伴随着技术积累与成长而对系统进行架构优化;用户的日益增长,业务的不断扩充,迫使了系统的复杂度增加,为了解决系统带来新的复杂度而进行架构演变。

因此,架构方案是在已有的业务复杂度、用户规模、技术积累度、人力时间成本等几个方面的取舍决策后的结果体现。

原架构

缺点分析

  • 一般情况下,调度任务轮询数据库,90%的动作是无用功,频繁的数据库访问会对数据库增加不少压力。
  • 为了让调度任务服务进行轮循数据,需要在API优先进行数据持久化,这无疑是降低了API的性能。
  • MongoDB的Update操作相比于Insert操作时低效的,对于日志类数据应增量添加。

因此从上述可见,调度任务服务这块是优化关键点所在。

新架构图

  • 使用了RabbitMQ的队列定时任务代替调度任务来实现定时发送。
  • 抛弃了调度任务,减少了调用链,同时也减少了应用服务数据量。
  • 对SMS集合在MongoDB里进行按年月的时间划分,对于日志类数据可以在有效的时间范围外进行方便的归档、删除。同时也避免了同集合的数据量过大导致的查询效率缓慢。

队列定时任务

RabbitMQ自身并没有定时任务,然而可以通过消息的Time-To-Live(过期时间)与Dead Letter Exchange(死信交换机)的结合模拟定时发布的功能。具体原理如下:

  • 生产者发布消息,并发布到已申明消息过期时间(TTL)的缓存队列(非真正业务消费队列)
  • 消息在缓存队列等待消息过期,然后由Dead Letter Exchange将消息重新分配到实际消费队列
  • 消费者再从实际消费队列消费并完成业务

Dead Letter Exchange

Dead Letter Exchange与平常的Exchange无异,主要用于消息死亡后通过Dead Letter Exchange与x-dead-letter-routing-key重新分配到新的队列进行消费处理。

消息死亡的方式有三种:

  • 消息进入了一条已经达到最大长度的队列
  • 消息因为设置了Time-To-Live的导致过期
  • 消息因basic.reject或者basic.nack动作而拒绝

Time-To-Live

两种消息过期的方式:

队列申明x-message-ttl参数

var args = new Dictionary<string, object>();
args.Add("x-message-ttl", );
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 = ;
props.Expiration = "" model.BasicPublish(exchangeName,
routingKey, props,
messageBodyBytes);

RabbitMQ.Client队列定时任务Demo

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", }
}; 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);
}
}

Sikiro.SMS实现优化

上面介绍了队列定时任务基本原理,然而我们需要自己的项目进行修改优化。

API消息发布

EasyNetQ是一款非常良好使用性的RabbitMQ.Client封装。对队列定时任务他也已经提供了相应的方法FuturePublish给我们使用。

然而他的FuturePublish由有三种调度方式:

  • DeadLetterExchangeAndMessageTtlScheduler
  • DelayedExchangeScheduler
  • ExternalScheduler

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( * model.SendCount), model, SmsQueueModelKey.Topic);
}

SMS日志集合维度

SMS日志作为非必要业务的运维型监控数据,在需要的时候随时可以对此进行删除或者归档处理。因此以时间(年月)作为集合维度,可以很好的对日志数据进行管理。

mongoProxy.Add(MongoKey.SmsDataBase, MongoKey.SmsCollection + "_" + DateTime.Now.ToString("yyyyMM"), model);

结束

经过本系列6篇的文章,介绍了以短信服务为业务场景,基于.net core平台的一个简单架构设计、架构优化与服务实现的实践例子。希望我的分享能帮助有需要的朋友。如果有任何好的建议请到下方给我留言。

.net core实践系列之短信服务-架构优化的更多相关文章

  1. .net core实践系列之短信服务-架构设计

    前言 上篇<.net core实践系列之短信服务-为什么选择.net core(开篇)>简单的介绍了(水了一篇).net core.这次针对短信服务的架构设计和技术栈的简析. 源码地址:h ...

  2. .net core实践系列之短信服务-目录

    前言 经过两周多的业余时间,终于把该系列的文章写完了.第一次写系列,可能部分关键点并没有覆盖到,如果有疑问的朋友可以随时反馈给我.另外也感谢在我发布文章时给予我方案建议与反馈源码BUG的朋友们.下面是 ...

  3. .net core实践系列之短信服务-Sikiro.SMS.Api服务的实现

    前言 上篇<.net core实践系列之短信服务-架构设计>介绍了我对短信服务的架构设计,同时针对场景解析了我的设计理念.本篇继续讲解Api服务的实现过程. 源码地址:https://gi ...

  4. .net core实践系列之短信服务-Sikiro.SMS.Bus服务的实现

    前言 前两篇<.net core实践系列之短信服务-Sikiro.SMS.Api服务的实现>.<.net core实践系列之短信服务-Api的SDK的实现与测试>分别讲解了AP ...

  5. .net core实践系列之短信服务-Api的SDK的实现与测试

    前言 上一篇<.net core实践系列之短信服务-Sikiro.SMS.Api服务的实现>讲解了API的设计与实现,本篇主要讲解编写接口的SDK编写还有API的测试. 或许有些人会认为, ...

  6. .net core实践系列之短信服务-为什么选择.net core(开篇)

    前言 从今天我将会写.net core实战系列,以我最近完成的短信服务作为例子.该系列将会尽量以最短的时间全部发布出来.源码也将优先开源出来给大家. 源码地址:https://github.com/S ...

  7. .net core实践系列之短信服务-Sikiro.SMS.Job服务的实现

    前言 本篇会继续讲解Sikiro.SMS.Job服务的实现,在我写第一篇的时候,我就发现我当时设计的架构里Sikiro.SMS.Job这个可以选择不需要,而使用MQ代替.但是为了说明调度任务使用实现也 ...

  8. php 阿里云短信服务及阿里大鱼实现短信验证码的发送

    一:使用阿里云的短信服务 ① 申请短信签名 ②申请短信模板 ③创建Access Key,获取AccessKeyId 与 AccessKeySecret.(为了安全起见,这里建议使用子用户的Access ...

  9. PHP开发实用-阿里短信服务(Short Message Service)

    步骤 1 使用阿里云短信服务正常发短信需要 短信签名 短信模板 1申请短信签名   根据用户属性来创建符合自身属性的签名信息.企业用户需要上传相关企业资质证明,个人用户需要上传证明个人身份的证明.   ...

随机推荐

  1. git 入门教程之知识速查

    知识速查 创建版本库 初始化项目 git init 从零开始创建项目 示例 git init 克隆项目 git clone 将已有项目拷贝到本地 示例 git clone git@github.com ...

  2. (办公)重新选择一个开发工具Eclipse

    文章Eclipse内容摘抄自w3cschool的eclipse,原文地址:https://www.w3cschool.cn/eclipse/eclipse-run-configuration.html ...

  3. Javascript数组系列三之迭代方法2

    今天我们来继续 Javascript 数组系列的文章,上文 <Javascript数组系列二之迭代方法1> 我们说到一些数组的迭代方法,我们在开发项目实战的过程中熟练的使用可以大大提高我们 ...

  4. 腾讯云Centos安装gitlab

    参考了网上很多人写的安装教程,结果并不好,最后阅读了官方的英文api,才安装成功,这里记录下来,方便以后使用.我的安装环境为腾讯云主机Centos7.3 64bit gitlab官方api地址点我试试 ...

  5. css把容器级别(div...)标签固定在一个位置(在页面最右边)

    .process{ border:1px solid #B7B7B8; background:#F8F8F8; width:80px; height:250px; <!--固定定位; text- ...

  6. python 常见函数的用法

    filter(function,ls) 函数包括两个参数,分别是function和list.该函数根据function参数返回的结果是否为真来过滤list参数中的项,最后返回一个新列表. 如: map ...

  7. EOS智能合约开发(四):智能合约部署及调试(附编程示例)

    EOS智能合约开发(一):EOS环境搭建和创建节点 EOS智能合约开发(二):EOS创建和管理钱包 EOS智能合约开发(三):EOS创建和管理账号 部署智能合约的示例代码如下: $ cleos set ...

  8. 一套简单的git版本控制代码

    对于博客来说,我还是直接实践比较好,理论过多,不方便以后的查看 废话不多,直接开干 功能需求: .公司需要将jenkins打包出来的压缩包通过git上传到git服务器 .而且通过版本控制上传的文件,即 ...

  9. DVWA v1.9 新手指南

    DVWA简介 DVWA(Damn Vulnerable Web Application)是一个用来进行安全脆弱性鉴定的PHP/MySQL Web应用,旨在为安全专业人员测试自己的专业技能和工具提供合法 ...

  10. Unity基础(5) Shadow Map 概述

    这篇是自己看shadow map是的一些笔记,内容稍稍凌乱,如有错误请帮忙纠正 1.常见阴影处理方式 Shadow Map : using Z-Buffer Shadow Mapping 的原理与实践 ...