引言

  熟悉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. Dungeon Master POJ - 2251(bfs)

    对于3维的,可以用结构体来储存,详细见下列代码. 样例可以过,不过能不能ac还不知道,疑似poj炸了, #include<iostream> #include<cstdio> ...

  2. DFS(一):深度优先搜索的基本思想

    采用搜索算法解决问题时,需要构造一个表明状态特征和不同状态之间关系的数据结构,这种数据结构称为结点.不同的问题需要用不同的数据结构描述. 根据搜索问题所给定的条件,从一个结点出发,可以生成一个或多个新 ...

  3. 用户点击获取验证码之后我们会发送一条信息到用户手机,然后就会出现一个倒计时按钮,很像支付宝手机付款效果了,下面我给大家分享两个js效果

    js代码  代码如下 复制代码 <div class="input">    <input type="button" id="bt ...

  4. [网络协议]TCP粘包分析

    关于socket粘包,socket缓冲区设置的问题,记录一下: 一 .两个简单概念长连接与短连接: 长连接     Client方与Server方先建立通讯连接,连接建立后不断开, 然后再进行报文发送 ...

  5. LNMP WEB架构构建

    LNMP WEB架构处理请求过程 1.Nginx为一款高性能Web服务器,本身是不能处理PHP的,当接收到客户端浏览器发送HTTP Request请求时,Nginx服务器响应并处理web请求,静态资源 ...

  6. C语言学习书籍推荐《C专家编程Expert C Programming Deep C Secrets》下载

    Peter Van Der Linden (作者) <C和C++经典著作 C专家编程Expert C Programming Deep C Secrets>展示了C程序员所使用的编码技巧, ...

  7. 在CentOS6.5部署Redis为开机自启

    2 - redis的生产启动方案 要把redis作为一个系统的daemon进程 去运行的,每次系统启动,redis进程一起启动,配置方案如下: 1. 在redis utils 目录下,有个redis_ ...

  8. 开发板编译./camera显示-/bin/sh: ./camera: not found解决方案

    问题: 开发板根文件系统目录: 运行./camera显示: 问题解决: 1.排除根目录路径问题: 2. 加入静态链接库即无问题,但是编译后的".o"文件大小突增,而且也不可能每次编 ...

  9. Oracle 学习笔记二

    一.oracle通用函数vnl(a,b) 用于任何类型,如果a的值不为null返回a的值否则返回b的值 条件判断oracle中可以使用 case 字段 when 条件1 then 表达式1 when ...

  10. 【CYH-02】noip2018数论模拟赛:比赛通知栏

    鉴于公开赛有时可能无法更改比赛描述呢... 所以我们准备在这里(和团队宣言里)发布一些比赛公告. 请及时关注