引言

  熟悉TPL Dataflow博文的朋友可能记得这是个单体程序,使用TPL Dataflow 处理工作流任务, 在使用Docker部署的过程中, 有一个问题一直无法回避:

在单体程序部署的瞬间(服务不可用)会有少量流量无法处理;更糟糕的情况下,迭代部署的这个版本有问题,上线后无法运作, 更多的流量没有得到处理。

背负神圣使命(巨大压力)的程序猿心生一计, 为何不将单体程序改成分布式:增加服务ReceiverApp只接收数据,服务WebApp只处理数据。

知识储备

消息队列和订阅发布作为老生常谈的两个知识点被反复提及,按照JMS的规范, 官方称为点对点(point to point,message queue) 和 订阅发布(publish/subscribe,channel / topic )

点对点

  消息生产者生产消息发送到Message Queue中,然后消费者从队列中取出消息并消费。

队列会保留消息,直到他们被消费或超时;

① MQ支持多消费者,每个消息只能被一个消费者处理

② 消息发送者和消费者在时间上没有依赖性,当发送者发送消息之后, 不管消费者有没有在运行(甚至不管有没有消费者),都不会影响到消息被发送到队列

③ 一般消费者在消费之后需要向队列应答成功

如果希望发送的消息都被处理,或只能被处理一次,你应该使用p2p模型。

发布/订阅

  消息生产者将消息发布到Channel,同时有多个消息消费者(订阅)该消息。和点对点方式不同,发布到 特定通道的消息会被通道订阅者实时接收。

通道 只有暂存机制,发布的消息只能被当前订阅者收到。

①每个消息可以有多个消费者

②发布者和消费者 有时间上依赖性, 针对某topic的订阅者,必须先创建相应订阅,才能消费消息

将消息发布到通道中,而不关注订阅者是谁;订阅者可收听自己感兴趣的多个通道(形成Topic), 也不关注发布者是谁。

③ 故如果没有消费者,发布的消息将得不到处理;

如果希望广播的消息被实时接收,应该采用发布-订阅模型。

头脑风暴

Redis 内置的List数据结构亦能形成轻量级MQ的效果,Redis 原生支持发布/订阅模型。

如上所述, Pub/Sub 模型 在订阅者宕机的时候,发布的消息得不到处理,故此模型不能用于 强业务的 数据接收和处理

本次采用的消息队列模型:

  • 解耦业务:  新建Receiver程序作为生产者,专注于接收并发送到队列;原有的webapp作为消费者专注数据处理。
  • 起到削峰填谷的作用, 若建立多个消费者webapp容器,还能形成负载均衡的效果。

需要关注Redis 两个命令( 左进右出,右进左出同理):

LPUSH  &  RPOP/BRPOP

Brpop 中的B 表示 “Block”, 是一个rpop命令的阻塞版本:若指定List没有新元素,在给定时间内,该命令会阻塞当前redis客户端连接,直到超时返回nil

编程实践

本次使用 ASPNetCore 完成RedisMQ的实践,引入Redis国产第三方开源库CSRedisCore.

不使用著名的StackExchange.Redis 组件库的原因:

  • 之前一直使用StackExchange.Redis, 参考了很多资料,做了很多优化,并未完全解决RedisTimeoutException问题

  • StackExchange.Redis基于其多路复用的连接机制,不支持阻塞式命令, 故采用了 CSRedisCore,该库强调了API 与Redis官方命令一致,很容易上手

生产者Receiver

生产者使用LPush 命令向Redis List数据结构写入消息。

------------------截取自Startup.cs-------------------------

public void ConfigureServices(IServiceCollection services)
{
  // Redis客户端要定义成单例, 不然在大流量并发收数的时候, 会造成redis client来不及释放。另一方面也确认api控制器不是单例模式,
  var csredis = new CSRedisClient(Configuration.GetConnectionString("redis")+",name=receiver");
  RedisHelper.Initialization(csredis);
  services.AddSingleton(csredis);

 services.AddMvc();

}

------------------截取自数据接收Controller-------------------
[Route("batch")]
[HttpPost]
public async Task BatchPutEqidAndProfileIds([FromBody]List<EqidPair> eqidPairs)
{
  if (!ModelState.IsValid)
  throw new ArgumentException("Http Body Payload Error.");
  var redisKey = $"{DateTime.Now.ToString("yyyyMMdd")}";
eqidPairs = await EqidExtractor.EqidExtractAsync(eqidPairs);
if (eqidPairs != null && eqidPairs.Any())
    RedisHelper.LPush(redisKey, eqidPairs.ToArray());
    await Task.CompletedTask;
}

消费者webapp

根据以上RedisMQ思路,事件消费方式是拉取pull,故需要轮询Redis  List数据结构,这里使用ASPNetCore内置的BackgroundService后台服务类实现后台轮询消费任务。

public class BackgroundJob : BackgroundService
{
private readonly IEqidPairHandler _eqidPairHandler;
private readonly CSRedisClient[] _cSRedisClients;
private readonly IConfiguration _conf;
private readonly ILogger _logger;
public BackgroundJob(IEqidPairHandler eqidPairHandler, CSRedisClient[] csRedisClients,IConfiguration conf,ILoggerFactory loggerFactory)
{
_eqidPairHandler = eqidPairHandler;
_cSRedisClients = csRedisClients;
_conf = conf;
_logger = loggerFactory.CreateLogger(nameof(BackgroundJob));
} protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Service starting");
if (_cSRedisClients[] == null)
{
_cSRedisClients[] = new CSRedisClient(_conf.GetConnectionString("redis") + ",defaultDatabase=" + );
}
RedisHelper.Initialization(_cSRedisClients[]); while (!stoppingToken.IsCancellationRequested)
{
var key = $"eqidpair:{DateTime.Now.ToString("yyyyMMdd")}";
var eqidpair = RedisHelper.BRPop(, key);
if (eqidpair != null)
await _eqidPairHandler.AcceptEqidParamAsync(JsonConvert.DeserializeObject<EqidPair>(eqidpair));
// 强烈建议无论如何休眠一段时间,防止突发大流量导致webApp进程CPU满载,自行根据场景设置合理休眠时间
await Task.Delay(1, stoppingToken);
}
_logger.LogInformation("Service stopping");
}
}

最后依照引言中的部署原理图,将Nginx,Receiver, WebApp使用docker-compose工具容器化

根据docker-compsoe up命令的用法,若容器正在运行且对应的Service Configuration或Image并未改变,该容器不会被ReCreate;

docker-compose  up指令只会重建(Service或Image变更)的容器。

If there are existing containers for a service, and the service’s configuration or image was changed after the container’s creation, docker-compose up picks up the changes by stopping and recreating the containers (preserving mounted volumes). To prevent Compose from picking up changes, use the --no-recreate flag.

做一次上线测试验证,修改docker-compose.yml文件Web app的容器服务,docker-compose up;

仅数据处理程序WebApp容器被重建:

 Nice,分布式改造上线,效果很明显,现在可以放心安全的迭代Web App数据处理程序。

作者:JulianHuang

码甲拙见,如有问题请下方留言大胆斧正;码字+Visio制图,均为原创,看官请不吝好评+关注,  ~。。~

本文欢迎转载,请转载页面明显位置注明原作者及原文链接

使用RedisMQ 做一次分布式改造的更多相关文章

  1. 分布式改造剧集之Redis缓存采坑记

    Redis缓存采坑记 ​ 前言 ​ 这个其实应该属于分布式改造剧集中的一集(第一集见前面博客:http://www.cnblogs.com/Kidezyq/p/8748961.html),本来按照顺序 ...

  2. 分布式改造剧集2---DIY分布式锁

    前言: ​ 好了,终于又开始播放分布式改造剧集了.前面一集中(http://www.cnblogs.com/Kidezyq/p/8748961.html)我们DIY了一个Hessian转发实现,最后我 ...

  3. 分布式改造剧集三:Ehcache分布式改造

    第三集:分布式Ehcache缓存改造 前言 ​ 好久没有写博客了,大有半途而废的趋势.忙不是借口,这个好习惯还是要继续坚持.前面我承诺的第一期的DIY分布式,是时候上终篇了---DIY分布式缓存. 探 ...

  4. 实时监控Cat之旅~对请求是否正常结束做监控(分布式的消息树)

    对基于请求的分布式消息树的分析 在MVC时有过滤器System.Web.Mvc.ActionFilterAttribute,它可以对action执行的整个过程进行拦截,执行前与执行后我们可以注入自己的 ...

  5. 如何做可靠的分布式锁,Redlock真的可行么

    本文是对 Martin Kleppmann 的文章 How to do distributed locking 部分内容的翻译和总结,上次写 Redlock 的原因就是看到了 Martin 的这篇文章 ...

  6. CYQ.Data V5 分布式自动化缓存设计介绍

    前方: 其实完成这个功能之前,我就在思考:是先把想法写了来,和大伙讨论讨论后再实现,还是实现后再写文论述自己的思维. 忽然脑后传来一个声音说:你发文后会进入发呆阶段. 所以还是静下心,让我轻轻地把代码 ...

  7. C#分布式消息队列 EQueue 2.0 发布啦

    前言 最近花了我几个月的业余时间,对EQueue做了一个重大的改造,消息持久化采用本地写文件的方式.到现在为止,总算完成了,所以第一时间写文章分享给大家这段时间我所积累的一些成果. EQueue开源地 ...

  8. 罗辑思维首席架构师:Go微服务改造实践

    转自:http://www.infoq.com/cn/news/2018/05/luojisiwei 方圆 曾先后在 Cisco,新浪微博从事基础架构研发工作.十多年一直专注于后端技术的研发,在消息通 ...

  9. 腾讯云分布式数据库TDSQL在银行传统核心系统中的应用实践

    本文是腾讯云TDSQL首席架构师张文在腾讯云Techo开发者大会现场的演讲实录,演讲主题是<TDSQL在银行传统核心系统中的应用实践>. 我是TDSQL架构师张文,同时也是TDSQL的开发 ...

随机推荐

  1. CRISP-DM--数据挖掘标准流程

    CRISP-DM--数据挖掘标准流程 在1996年的时候,SPSS,戴姆勒-克莱斯勒和NCR公司发起共同成立了一个兴趣小组,目的是为了建立数据挖掘方法和过程的标准.并在1999年正式提炼出了CRISP ...

  2. 通过CDN引入jQuery的几种方式

    百度 CDN <head> <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js" ...

  3. 通过phpmyadmin设置数据库密码后若出现phpmyadmin拒绝访问的情况

    方法一:可以修改config.inc.php配置文件中的$cfg['Servers'][$i]['password'] = '你的密码'; 方法二:将config.inc.php配置文件中的$cfg[ ...

  4. java Https工具类

    import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import ja ...

  5. 用python的matplotlib和numpy库绘制股票K线均线和成交量的整合效果(含量化验证交易策略代码)

    在用python的matplotlib和numpy库绘制股票K线均线的整合效果(含从网络接口爬取数据和验证交易策略代码)一文里,我讲述了通过爬虫接口得到股票数据并绘制出K线均线图形的方式,在本文里,将 ...

  6. 设计模式-模块方法模式(TemplateMethod)

    模块方法模式是行为模式之一,它把具有特定步骤算法中的某些必要的处理委让给抽象方法,通过子类继承对抽象方法的不同实现改变整个算法的行为. UML图: 具体代码: /** * 抽象-模块方法模式核心 */ ...

  7. 模块(二)os hashlib

    模块(二)os hashlib 1.序列化模块 1.1 json 将满足条件的数据结构转化成特殊的字符串,并且可以反序列化转回去 # 两对方法 # 1 dumps() loads() ## 多用于网络 ...

  8. .NET Core 3.0之深入源码理解Kestrel的集成与应用(二)

      前言 前一篇文章主要介绍了.NET Core继承Kestrel的目的.运行方式以及相关的使用,接下来将进一步从源码角度探讨.NET Core 3.0中关于Kestrel的其他内容,该部分内容,我们 ...

  9. Centos7 下nginx 搭建文件图片服务器

    现在服务器部署nginx yum install -y epel-release yum install nginx -y 安装完成之后 访问ip 由此可见nginx服务是可用的 修改nginx的配置 ...

  10. NET Core CSharp初级篇 1-3面向对象

    .NET Core CSharp初级篇 1-3 本节内容为面向对象初级教程 类 简介 面向对象是整个C#中最核心最有特色的一个模块了,它很好的诠释了程序与现实世界的联系. 面向对象的三大特征:继承.多 ...