引言

  熟悉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. Python笔记【7】_反射getattr&hasattr&setattr&delattr

    Lesson0402_GetatrrWebsite.py #!/usr/bin/env/python #-*-coding:utf-8-*- #Author:LingChongShi #查看源码Ctr ...

  2. Java实现异步调用

    一.创建线程 @Test public void test0() throws Exception { System.out.println("main函数开始执行"); Thre ...

  3. Flask-登录练习

    基于蓝图CBV模式的登录 使用蓝图并用cbv模式完成登录功能 登录成功后跳转到首页 将session保存在liunx上的redis数据库 使用before_request验证是否是登陆用户 蓝图 fr ...

  4. python接口自动化(三十二)--Python发送邮件(常见四种邮件内容)番外篇——上(详解)

    简介 本篇文章与前边没有多大关联,就是对前边有关发邮件的总结和梳理.在写脚本时,放到后台运行,想知道执行情况,会通过邮件.SMS(短信).飞信.微信等方式通知管理员,用的最多的是邮件.在linux下, ...

  5. ServiceFabric极简文档-5.0 Service Fabric有状态与无状态

    Service Fabric 应用程序方案 2017/08/14 作者 Edward Chen Jack Zeng Azure Service Fabric提供了一个可靠而灵活的平台,可用于编写和运行 ...

  6. Java编程思想:内存映射文件

    import java.io.*; import java.nio.IntBuffer; import java.nio.MappedByteBuffer; import java.nio.chann ...

  7. 【基本数据结构】并查集-C++

    并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中.这一类问题近几年来反复出 ...

  8. 通过windug判断某个模块导致程序不能退出。

    1.windug附加进程. 2.~* kb 3.看堆栈

  9. C#3.0新增功能09 LINQ 基础04 基本 LINQ 查询操作

    连载目录    [已更新最新开发文章,点击查看详细] 本篇介绍 LINQ 查询表达式和一些在查询中执行的典型操作. 获取数据源 在 LINQ 查询中,第一步是指定数据源. 和大多数编程语言相同,在使用 ...

  10. css 图片裁剪显示

    用object-fit:cover object-fit属性详解 object-fit:CSS 属性指定替换元素的内容应该如何适应到其使用的高度和宽度确定的框. object-fit:fill 被替换 ...