node.js中kafka的封装和高并发消费限流优雅降级以及egg-kafka的封装说明
HI!,你好,我是zane,zanePerfor是一款我开发的一个前端性能监控平台,现在支持web浏览器端和微信小程序端。
我定义为一款完整,高性能,高可用的前端性能监控系统,这是未来会达到的目的,现今的架构也基本支持了高可用,高性能的部署。实际上还不够,在很多地方还有优化的空间,我会持续的优化和升级。
开源不易,如果你也热爱技术,拥抱开源,希望能小小的支持给个star。
项目的github地址:https://github.com/wangweianger/zanePerfor
项目开发文档说明:https://blog.seosiwei.com/performance/index.html
Kafka是由Apache软件基金会开发的一个开源流处理平台,由Scala和Java编写。
Kafka是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者规模的网站中的所有动作流数据。
这种动作(网页浏览,搜索和其他用户的行动)是在现代网络上的许多社会功能的一个关键因素。
这些数据通常是由于吞吐量的要求而通过处理日志和日志聚合来解决。
针对于zanePerfor这样的用户访问行为,页面性能监控系统,但又要求实时处理的限制,这是一个可行的解决方案。
zanePerfor中对于kafka的应用使用了kafka-node包,并在此基础上封装了egg-kafka插件。
zanePerfor初步的探索了kafka在node.js中的应用,以下内容主要讲解kafka在zanePerfor项目中的使用方式。
如果你对在node.js中使用kafka有更多的建议和心得,也希望能跟我一起分享。
zanePerfor项目中kafka应用介绍:
启用kafka配置说明:
// config/config.default.js
// kafka 配置 (report_data_type=kafka生效)
// 配置参考 https://www.npmjs.com/package/kafka-node
config.kafka = {
client: {
kafkaHost: 'localhost:9092',
},
producer: {
web: {
topic: 'zane_perfor_web',
partition: 0, // default 0
attributes: 0, // default: 0
// timestamp: Date.now(),
},
wx: {
topic: 'zane_perfor_wx',
},
},
// consumer 和 consumerGroup消费任选其一即可
// 优先选择consumer消费,两种消费配置任留一种即可
consumer: {
web: {
topic: 'zane_perfor_web',
offset: 0, // default 0
partition: 0, // default 0
isone: false, // 此参数默认不可更改
total_limit: 10000, // 消息队列消费池限制数, 0:不限制 number: 限制条数 高并发时服务优雅降级方案
},
wx: {
topic: 'zane_perfor_wx',
isone: false,
total_limit: 10000,
},
},
consumerGroup: {
web: { // ConsumerGroup(options, topics)
topic: 'zane_perfor_web',
groupId: 'WebPerformanceGroup',
commitOffsetsOnFirstJoin: true,
},
wx: {
topic: 'zane_perfor_wx',
groupId: 'WxPerformanceGroup',
commitOffsetsOnFirstJoin: true,
},
},
};
配置说明:
client参数说明:
client参数即为kafka-node中的KafkaClient参数,参考地址:https://www.npmjs.com/package/kafka-node#kafkaclient
producer生产者参数说明:
producer分web端和wx端配置
producer参数为kafka-node中的send参数,参考地址:https://www.npmjs.com/package/kafka-node#sendpayloads-cb
consumer消费者参数说明:
consumer分web端和wx端配置
consumer参数为kafka-node中的consumer参数, 参考地址:https://www.npmjs.com/package/kafka-node#consumerclient-payloads-options
consumerGroup消费者参数说明:
consumerGroup分web端和wx端配置
consumerGroup参数为kafka-node中的consumerGroup参数,参考地址:https://www.npmjs.com/package/kafka-node#consumergroupoptions-topics
关于消费者说明
config配置中有consumer和consumerGroup配置,规则如下:
- 如果consumer配置为真有限使用consumer配置
- 如果想启用consumerGroup配置,则注释或者删除consumer配置即可
kafka生产消费逻辑实现:
核心代码实现:
一:生产者
kafka的性能非常强劲,能支持超高并发,因此所有客户端上报的信息都存储到消息队列中,限流策略只使用在消费端,生产端不做限流设置。
// app/controller/api/web/report.js
// 通过kafka 消息队列消费数据
async saveWebReportDataForKafka(query) {
// 生产者
this.app.kafka.send(
'web',
JSON.stringify(query)
); // 消费者
if (!isKafkaConsumer && !this.app.config.kafka.consumer.web.isone) {
this.ctx.service.web.reportTask.saveWebReportDatasForKafka();
isKafkaConsumer = true;
this.app.config.kafka.consumer.web.isone = true;
}
}
this.app.kafka.send是封装的插件egg-kafka中的方法,功能就是生产信息
if (!isKafkaConsumer && !this.app.config.kafka.consumer.web.isone)是为了保证订阅消息的方法只执行一次,后面一但有消息产生,会自动触发订阅函数进行数据消费消费。
二:消费者
// app/service/web/report_task.js
// kafka 消费信息
async saveWebReportDatasForKafka() {
if (this.kafkaConfig.consumer) {
this.app.kafka.consumer('web', message => {
this.consumerDatas(message);
});
} else if (this.kafkaConfig.consumerGroup) {
this.app.kafka.consumerGroup('web', message => {
this.consumerDatas(message);
});
}
}
this.app.kafka.consumer 单独消费,egg-kafka中暴露的方法
this.app.kafka.consumerGroup 以分组的方式消费消息
优先使用consumer消费,其次使用consumerGroup进行消费
egg-kafka插件封装说明
为了更好、更方便的使用kafka,项目中对node-kafka进行了一层封装。
详细请参考:/lib/plugin/egg-kafka/lib/kafka.js
send代码实现如下:
send(type, data) {
assert(type, '[egg-kafka] type is must required.');
if (!data) return;
let producer = this.app.config.kafka.producer[type] || {};
let producers = [];
if (typeof (data) === 'string') {
producer.messages = data;
producers = [ producer ];
} else if (Object.prototype.toString.call(data) === '[object Object]') {
producer = Object.assign({}, producer, data);
producers = [ producer ];
} else if (Object.prototype.toString.call(data) === '[object Array]') {
for (let i = 0; i < data.length; i++) {
data[i] = Object.assign({}, producer, data[i]);
}
producers = data;
}
this.producer.send(producers, (err, data) => {
if (err) assert(err, '[egg-kafka] err. errmsg ${err}');
console.log(data);
});
}
send有两个参数,第一个参数type为发送类型,有web、wx两个值可以选择。
对data做了一定的判断,send调用可以有以下几种方式:
// 消息为String
this.app.kafka.send('web','hello world!');
// 消息为Object
this.app.kafka.send('web',{ topic:'test', messages:'hello world!' });
// 消息为Array
this.app.kafka.send('web',[{ topic: 'test', messages: 'hi', partition: 0}]);
consumer方法代码实现:
consumer(type = 'web', fn) {
assert(type, '[egg-kafka] consumers type argument must be required');
const kafkaConfig = this.app.config.kafka;
const consumer = kafkaConfig.consumer[type] || {};
const consumers = Array.isArray(consumer) ? consumer : [ consumer ];
const Consumer = kafka.Consumer;
const _consumer = new Consumer(
this.client,
consumers,
{
autoCommit: true,
}
);
_consumer.on('error', err => {
this.app.coreLogger.error(`[egg-kafka] consumer have error ${err}`);
});
_consumer.on('message', message => {
fn && fn(message);
});
}
consumerGroup代码实现:
consumerGroup(type = 'web', fn) {
assert(type, '[egg-kafka] consumers type argument must be required');
const kafkaConfig = this.app.config.kafka;
const kafkaHost = kafkaConfig.client.kafkaHost;
const consumerOption = kafkaConfig.consumerGroup[type] || {};
const topic = consumerOption.topic;
consumerOption.kafkaHost = kafkaHost;
const ConsumerGroup = kafka.ConsumerGroup;
const _consumer = new ConsumerGroup(consumerOption, topic);
_consumer.on('error', err => {
this.app.coreLogger.error(`[egg-kafka] consumer have error ${err}`);
});
_consumer.on('message', message => {
fn && fn(message);
});
}
消费限流策略:
由于kafka性能及其强悍,因此zanePerfor只对消费进行限流
代码实现:
设置消费池数量
// config.default.js
{
topic: 'zane_perfor_web',
offset: 0, // default 0
partition: 0, // default 0
isone: false, // 此参数默认不可更改
total_limit: 10000, // 消息队列消费池限制数, 0:不限制 number: 限制条数 高并发时服务优雅降级方案
}
kafka连接池数量判断
// app/service/web/report_task.js 中 getWebItemDataForKafka 方法
// kafka 连接池限制
const msgtab = query.time + query.ip;
if (this.kafkatotal && this.kafkalist.length >= this.kafkatotal) return;
this.kafkalist.push(msgtab);
数据消费完成之后删除消费标识
// app/service/web/report_task.js 中 getWebItemDataForKafka 方法
this.savePages(item, system.slow_page_time, () => {
// 释放消费池
const index = this.kafkalist.indexOf(msgtab);
if (index > -1) this.kafkalist.splice(index, 1);
});
至此实现了egg.js中对kafka的应用和封装。
node.js中kafka的封装和高并发消费限流优雅降级以及egg-kafka的封装说明的更多相关文章
- coding++:高并发解决方案限流技术--计数器--demo
1.它是限流算法中最简单最容易的一种算法 计数器实现限流 每分钟只允许10个请求 第一个请求进去的时间为startTime,在startTime + 60s内只允许10个请求 当60s内超过十个请求后 ...
- coding++:高并发解决方案限流技术---漏桶算法限流--demo
1.漏桶算法 漏桶作为计量工具(The Leaky Bucket Algorithm as a Meter)时,可以用于流量整形(Traffic Shaping)和流量控制(TrafficPolici ...
- coding++:高并发解决方案限流技术-使用RateLimiter实现令牌桶限流-Demo
RateLimiter是guava提供的基于令牌桶算法的实现类,可以非常简单的完成限流特技,并且根据系统的实际情况来调整生成token的速率. 通常可应用于抢购限流防止冲垮系统:限制某接口.服务单位时 ...
- 高并发解决方案限流技术-----使用RateLimiter实现令牌桶限流
1,RateLimiter是guava提供的基于令牌桶算法的实现类,可以非常简单的完成限流特技,并且根据系统的实际情况来调整生成token的速率.通常可应用于抢购限流防止冲垮系统:限制某接口.服务单位 ...
- 在node.js中,使用基于ORM架构的Sequelize,操作mysql数据库之增删改查
Sequelize是一个基于promise的关系型数据库ORM框架,这个库完全采用JavaScript开发并且能够用在Node.JS环境中,易于使用,支持多SQL方言(dialect),.它当前支持M ...
- 初步揭秘node.js中的事件
当你学习node.js的时候,Events是一个非常重要的需要理解的事情.非常多的Node对象触发事件,你能在文档API中找到很多例子.但是关于如何写自己的事件和监听,你可能还不太清楚.如果你不了解, ...
- 在Node.js中使用RabbitMQ系列二 任务队列
在上一篇文章在Node.js中使用RabbitMQ系列一 Hello world我有使用一个任务队列,不过当时的场景是将消息发送给一个消费者,本篇文章我将讨论有多个消费者的场景. 其实,任务队列最核心 ...
- [转]在node.js中,使用基于ORM架构的Sequelize,操作mysql数据库之增删改查
本文转自:https://www.cnblogs.com/kongxianghai/p/5582661.html Sequelize是一个基于promise的关系型数据库ORM框架,这个库完全采用Ja ...
- node.js中process进程的概念和child_process子进程模块的使用
进程,你可以把它理解成一个正在运行的程序.node.js中每个应用程序都是进程类的实例对象. node.js中有一个 process 全局对象,通过它我们可以获取,运行该程序的用户,环境变量等信息. ...
- node.js中module模块的理解
node.js中使用CommonJS规范实现模块功能,一个单独的文件就是一个单独的模块.通过require方法实现模块间的依赖管理. 通过require加载模块,是同步操作. 加载流程如下: 1.找到 ...
随机推荐
- 深度学习03-(图像梯度处理、图像轮廓、图像预处理在AI中的应用)
深度学习03-计算机视觉基本理论2 深度学习03-(计算机视觉基本理论2) 图像梯度处理 什么是图像梯度 模板运算 均值滤波 高斯滤波 中值滤波 边沿检测 锐化 图像轮廓 什么是图像轮廓 查找和绘制轮 ...
- win系统安装wordcloud报错解决方案
问题: ① 在命令行下执行pip install wordcloud出现报错如图: ②下载了错误的whl文件, 出现wordcloud-1.4.1-cp27-cp27m-win_amd64.whl i ...
- Python 使用列表一部分(切片)
使用列表的一部分(切片) 处理列表的部分元素 切片 指定第一个元素的索引和最后一个元素索引加1 列表名[索引:索引+1] 索引加1:列表中第索引个元素 (左包括右不包括) 未指定索引 列表名[:] 提 ...
- 如何实现一个sync.Once
sync.Once 是 golang里用来实现单例的同步原语.Once 常常用来初始化单例资源, 或者并发访问只需初始化一次的共享资源,或者在测试的时候初始化一次测试资源. 单例,就是某个资源或者对象 ...
- 2022-06-10:薯队长从北向南穿过一片红薯地(南北长M,东西宽N),红薯地被划分为1x1的方格, 他可以从北边的任何一个格子出发,到达南边的任何一个格子, 但每一步只能走到东南、正南、西南方向的
2022-06-10:薯队长从北向南穿过一片红薯地(南北长M,东西宽N),红薯地被划分为1x1的方格, 他可以从北边的任何一个格子出发,到达南边的任何一个格子, 但每一步只能走到东南.正南.西南方向的 ...
- 2022-01-03:比如arr = {3,1,2,4}, 下标对应是:0 1 2 3, 你最开始选择一个下标进行操作,一旦最开始确定了是哪个下标,以后都只能在这个下标上进行操作。 比如你选定1下标,
2022-01-03:比如arr = {3,1,2,4}, 下标对应是:0 1 2 3, 你最开始选择一个下标进行操作,一旦最开始确定了是哪个下标,以后都只能在这个下标上进行操作. 比如你选定1下标, ...
- LOTO示波器如何测试阻抗的频响曲线
LOTO示波器如何测试阻抗的频响曲线 模块的输入输出端口,在电路分析上,一般简单表征为电阻来进行计算和分析.但多数情况下,这些端口并不是纯电阻的特性,更精确一些,它可能是电阻电容以及电感的组合,表现为 ...
- 【GiraKoo】线程本地存储(Thread Local Storage, TLS)
[技术分享]线程本地存储(Thread Local Storage, TLS) 在项目开发中,遇到了关于TLS相关的问题.为了了解该机制的用途,在微软的官网查找了一些资料. 本文参考官方文档, 简单介 ...
- XAF Excel Importer
开源项目地址:https://gitee.com/easyxaf/excel-importer 前言 在XAF中有Excel导出,但没有Excel导入,一开始不理解,难道Excel导入很难实现吗,当我 ...
- 代码随想录算法训练营Day18 二叉树
代码随想录算法训练营 代码随想录算法训练营Day18 二叉树| 513.找树左下角的值 112. 路径总和 113.路径总和ii 106.从中序与后序遍历序列构造二叉树 105.从前序与中序遍历序列构 ...