KOA+egg.js集成kafka消息队列的示例


Posted in Javascript onNovember 09, 2018

Egg.js : 基于KOA2的企业级框架

Kafka:高吞吐量的分布式发布订阅消息系统

本文章将集成egg + kafka + mysql 的日志系统例子

系统要求:日志记录,通过kafka进行消息队列控制

思路图:

KOA+egg.js集成kafka消息队列的示例

这里消费者和生产者都由日志系统提供

λ.1 环境准备

①Kafka

官网下载kafka后,解压

启动zookeeper:

bin/zookeeper-server-start.sh config/zookeeper.properties

启动Kafka server

这里config/server.properties中将num.partitions=5,我们设置5个partitions

bin/kafka-server-start.sh config/server.properties

② egg + mysql

根据脚手架搭建好egg,再多安装kafka-node,egg-mysql

mysql 用户名root 密码123456

λ.2 集成

1、根目录新建app.js,这个文件在每次项目加载时候都会运作

'use strict';
 
const kafka = require('kafka-node');
 
module.exports = app => {
 app.beforeStart(async () => {
 const ctx = app.createAnonymousContext();
 
 const Producer = kafka.Producer;
 const client = new kafka.KafkaClient({ kafkaHost: app.config.kafkaHost });
 const producer = new Producer(client, app.config.producerConfig);
 
 producer.on('error', function(err) {
  console.error('ERROR: [Producer] ' + err);
 });
 
 app.producer = producer;
 
 const consumer = new kafka.Consumer(client, app.config.consumerTopics, {
  autoCommit: false,
 });
 
 consumer.on('message', async function(message) {
  try {
  await ctx.service.log.insert(JSON.parse(message.value));
  consumer.commit(true, (err, data) => {
   console.error('commit:', err, data);
  });
  } catch (error) {
  console.error('ERROR: [GetMessage] ', message, error);
  }
 });
 
 consumer.on('error', function(err) {
  console.error('ERROR: [Consumer] ' + err);
 });
 });
};

上述代码新建了生产者、消费者。

生产者新建后加载进app全局对象。我们将在请求时候生产消息。这里只是先新建实例

消费者获取消息将访问service层的insert方法(数据库插入数据)。

具体参数可以参考kafka-node官方API,往下看会有生产者和消费者的配置参数。

2、controller · log.js

这里获取到了producer,并传往service层

'use strict';
 
const Controller = require('egg').Controller;
 
class LogController extends Controller {
 /**
 * @description Kafka控制日志信息流
 * @host /log/notice
 * @method POST
 * @param {Log} log 日志信息
 */
 async notice() {
 const producer = this.ctx.app.producer;
 const Response = new this.ctx.app.Response();
 
 const requestBody = this.ctx.request.body;
 const backInfo = await this.ctx.service.log.send(producer, requestBody);
 this.ctx.body = Response.success(backInfo);
 }
}
 
module.exports = LogController;

3、service · log.js

这里有一个send方法,这里调用了producer.send ,进行生产者生产

insert方法则是数据库插入数据

'use strict';
 
const Service = require('egg').Service;
const uuidv1 = require('uuid/v1');
 
class LogService extends Service {
 async send(producer, params) {
 const payloads = [
  {
  topic: this.ctx.app.config.topic,
  messages: JSON.stringify(params),
  },
 ];
 
 producer.send(payloads, function(err, data) {
  console.log('send : ', data);
 });
 
 return 'success';
 }
 async insert(message) {
 try {
  const logDB = this.ctx.app.mysql.get('log');
  const ip = this.ctx.ip;
 
  const Logs = this.ctx.model.Log.build({
  id: uuidv1(),
  type: message.type || '',
  level: message.level || 0,
  operator: message.operator || '',
  content: message.content || '',
  ip,
  user_agent: message.user_agent || '',
  error_stack: message.error_stack || '',
  url: message.url || '',
  request: message.request || '',
  response: message.response || '',
  created_at: new Date(),
  updated_at: new Date(),
  });
 
  const result = await logDB.insert('logs', Logs.dataValues);
 
  if (result.affectedRows === 1) {
  console.log(`SUCEESS: [Insert ${message.type}]`);
  } else console.error('ERROR: [Insert DB] ', result);
 } catch (error) {
  console.error('ERROR: [Insert] ', message, error);
 }
 }
}
 
module.exports = LogService;

4、config · config.default.js

一些上述代码用到的配置参数具体在这里,注这里开了5个partition。

'use strict';
 
module.exports = appInfo => {
 const config = (exports = {});
 
 const topic = 'logAction_p5';
 
 // add your config here
 config.middleware = [];
 
 config.security = {
 csrf: {
  enable: false,
 },
 };
 
 // mysql database configuration
 config.mysql = {
 clients: {
  basic: {
  host: 'localhost',
  port: '3306',
  user: 'root',
  password: '123456',
  database: 'merchants_basic',
  },
  log: {
  host: 'localhost',
  port: '3306',
  user: 'root',
  password: '123456',
  database: 'merchants_log',
  },
 },
 default: {},
 app: true,
 agent: false,
 };
 
 // sequelize config
 config.sequelize = {
 dialect: 'mysql',
 database: 'merchants_log',
 host: 'localhost',
 port: '3306',
 username: 'root',
 password: '123456',
 dialectOptions: {
  requestTimeout: 999999,
 },
 pool: {
  acquire: 999999,
 },
 };
 
 // kafka config
 config.kafkaHost = 'localhost:9092';
 
 config.topic = topic;
 
 config.producerConfig = {
 // Partitioner type (default = 0, random = 1, cyclic = 2, keyed = 3, custom = 4), default 0
 partitionerType: 1,
 };
 
 config.consumerTopics = [
 { topic, partition: 0 },
 { topic, partition: 1 },
 { topic, partition: 2 },
 { topic, partition: 3 },
 { topic, partition: 4 },
 ];
 
 return config;
};

5、实体类:

mode · log.js

这里使用了 Sequelize

'use strict';
 
module.exports = app => {
 const { STRING, INTEGER, DATE, TEXT } = app.Sequelize;
 
 const Log = app.model.define('log', {
 /**
  * UUID
  */
 id: { type: STRING(36), primaryKey: true },
 /**
  * 日志类型
  */
 type: STRING(100),
 /**
  * 优先等级(数字越高,优先级越高)
  */
 level: INTEGER,
 /**
  * 操作者
  */
 operator: STRING(50),
 /**
  * 日志内容
  */
 content: TEXT,
 /**
  * IP
  */
 ip: STRING(36),
 /**
  * 当前用户代理信息
  */
 user_agent: STRING(150),
 /**
  * 错误堆栈
  */
 error_stack: TEXT,
 /**
  * URL
  */
 url: STRING(255),
 /**
  * 请求对象
  */
 request: TEXT,
 /**
  * 响应对象
  */
 response: TEXT,
 /**
  * 创建时间
  */
 created_at: DATE,
 /**
  * 更新时间
  */
 updated_at: DATE,
 });
 
 return Log;
};

6、测试Python脚本:

import requests
 
from multiprocessing import Pool
from threading import Thread
 
from multiprocessing import Process
 
 
def loop():
 t = 1000
 while t:
  url = "http://localhost:7001/log/notice"
 
  payload = "{\n\t\"type\": \"ERROR\",\n\t\"level\": 1,\n\t\"content\": \"URL send ERROR\",\n\t\"operator\": \"Knove\"\n}"
  headers = {
  'Content-Type': "application/json",
  'Cache-Control': "no-cache"
  }
 
  response = requests.request("POST", url, data=payload, headers=headers)
 
  print(response.text)
 
if __name__ == '__main__':
 for i in range(10):
  t = Thread(target=loop)
  t.start()

7、建表语句:

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
 
-- ----------------------------
-- Table structure for logs
-- ----------------------------
DROP TABLE IF EXISTS `logs`;
CREATE TABLE `logs` (
 `id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
 `type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '日志类型',
 `level` int(11) NULL DEFAULT NULL COMMENT '优先等级(数字越高,优先级越高)',
 `operator` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '操作人',
 `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL COMMENT '日志信息',
 `ip` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT 'IP\r\nIP',
 `user_agent` varchar(150) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '当前用户代理信息',
 `error_stack` text CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL COMMENT '错误堆栈',
 `url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '当前URL',
 `request` text CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL COMMENT '请求对象',
 `response` text CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL COMMENT '响应对象',
 `created_at` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
 `updated_at` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
 PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;
 
SET FOREIGN_KEY_CHECKS = 1;

λ.3 后话

网上类似资料甚少,啃各种文档,探寻技术实现方式

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
Javascript 类型转换方法
Oct 24 Javascript
js添加table的行和列 具体实现方法
Jul 22 Javascript
jquery设置按钮停顿3秒不可用
Mar 07 Javascript
js实现不提交表单获取单选按钮值的方法
Aug 21 Javascript
jquery的ajax提交form表单的两种方法小结(推荐)
May 25 Javascript
分享jQuery封装好的一些常用操作
Jul 28 Javascript
jquery+html仿翻页相册功能
Dec 20 Javascript
JavaScript 数据类型详解
Mar 13 Javascript
Vuejs 页面的区域化与组件封装的实现
Sep 11 Javascript
vue-router 手势滑动触发返回功能
Sep 30 Javascript
python虚拟环境 virtualenv的简单使用
Jan 21 Javascript
原型和原型链 prototype和proto的区别详情
Nov 02 Javascript
详解关于element el-button使用$attrs的一个注意要点
Nov 09 #Javascript
webpack4.x CommonJS模块化浅析
Nov 09 #Javascript
angular4笔记系列之内置指令小结
Nov 09 #Javascript
node版本管理工具n包使用教程详解
Nov 09 #Javascript
解决Vue在封装了Axios后手动刷新页面拦截器无效的问题
Nov 08 #Javascript
vue.js层叠轮播效果的实例代码
Nov 08 #Javascript
vue-cli 构建骨架屏的方法示例
Nov 08 #Javascript
You might like
萌王史莱姆”萌王性别尴尬!那“萌战”归女组还是男?
2018/12/17 日漫
解密ThinkPHP3.1.2版本之模块和操作映射
2014/06/19 PHP
Laravel 5框架学习之向视图传送数据
2015/04/08 PHP
php实现window平台的checkdnsrr函数
2015/05/27 PHP
PHP面向对象程序设计之对象的遍历操作示例
2019/06/12 PHP
mysql输出数据赋给js变量报unterminated string literal错误原因
2010/05/22 Javascript
jquery 操作日期、星期、元素的追加的实现代码
2012/02/07 Javascript
javascript学习笔记(十九) 节点的操作实现代码
2012/06/20 Javascript
windows系统下简单nodejs安装及环境配置
2013/01/08 NodeJs
jQuery判断密码强度实现思路及代码
2013/04/24 Javascript
CSS3实现动态背景登录框的代码
2015/07/28 Javascript
JS截取与分割字符串常用技巧总结
2015/11/10 Javascript
jquery+ajax实现注册实时验证实例详解
2015/12/08 Javascript
javascript创建cookie、读取cookie
2016/03/31 Javascript
Bootstrap所支持的表单控件实例详解
2016/05/16 Javascript
利用jQuery插件imgAreaSelect实现获得选择域的图像信息
2016/12/02 Javascript
JS内部事件机制之单线程原理
2018/07/02 Javascript
webpack打包多页面的方法
2018/11/30 Javascript
vscode配置vue下的es6规范自动格式化详解
2019/03/20 Javascript
JS为什么说async/await是generator的语法糖详解
2019/07/11 Javascript
在Vue项目中使用Typescript的实现
2019/12/19 Javascript
JS闭包原理及其使用场景解析
2020/12/03 Javascript
详解Python中的__init__和__new__
2014/03/12 Python
Windows系统配置python脚本开机启动的3种方法分享
2015/03/10 Python
用python写的一个wordpress的采集程序
2016/02/27 Python
python实现自动网页截图并裁剪图片
2018/07/30 Python
用Pelican搭建一个极简静态博客系统过程解析
2019/08/22 Python
python实现tail实时查看服务器日志示例
2019/12/24 Python
LightInTheBox法国站:中国跨境电商
2020/03/05 全球购物
Chupi官网:在爱尔兰手工制作的订婚、结婚戒指和精美珠宝
2020/09/28 全球购物
4s店总经理岗位职责
2013/12/31 职场文书
趣味比赛活动方案
2014/02/15 职场文书
小学生作文评语
2014/04/18 职场文书
促销活动总结范文
2014/04/30 职场文书
销售会议开幕词
2015/01/28 职场文书
vue cli4中mockjs在dev环境和build环境的配置详情
2022/04/06 Vue.js