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代码示例(重构前后对比)
Jan 23 Javascript
JS兼容浏览器的导出Excel(CSV)文件的方法
May 03 Javascript
JS判断客服QQ号在线还是离线状态的方法
Jan 13 Javascript
跟我学习javascript的Date对象
Nov 19 Javascript
【经典源码收藏】基于jQuery的项目常见函数封装集合
Jun 07 Javascript
Vuejs第七篇之Vuejs过渡动画案例全面解析
Sep 05 Javascript
jQuery中的on与bind绑定事件区别实例详解
Feb 28 Javascript
微信小程序微信支付接入开发实例详解
Apr 12 Javascript
vue页面切换到滚动页面显示顶部的实例
Mar 13 Javascript
vue.js过滤器+ajax实现事件监听及后台php数据交互实例
May 22 Javascript
微信小程序文章详情页面实现代码
Sep 10 Javascript
详解vue中在父组件点击按钮触发子组件的事件
Nov 13 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
PHP自定义大小验证码的方法详解
2013/06/07 PHP
php中使用PHPExcel读写excel(xls)文件的方法
2014/09/15 PHP
详谈PHP文件目录基础操作
2014/11/11 PHP
ThinkPHP中使用ajax接收json数据的方法
2014/12/18 PHP
php实现对两个数组进行减法操作的方法
2015/04/17 PHP
php转换颜色为其反色的方法
2015/04/27 PHP
PHP实现创建一个RPC服务操作示例
2020/02/23 PHP
jQuery Ajax之$.get()方法和$.post()方法
2009/10/12 Javascript
33个优秀的 jQuery 图片展示插件分享
2012/03/14 Javascript
输入自动提示搜索提示功能的javascript:sugggestion.js
2013/09/02 Javascript
JS实现简单的顶部定时关闭层效果
2014/06/15 Javascript
JavaScript中Promise的使用详解
2017/02/26 Javascript
@ResponseBody 和 @RequestBody 注解的区别
2017/03/08 Javascript
原生js实现简单的模态框示例
2017/09/08 Javascript
vue实现引入本地json的方法分析
2018/07/12 Javascript
js数据类型检测总结
2018/08/05 Javascript
jQuery each和js forEach用法比较
2019/02/27 jQuery
Vue实现微信支付功能遇到的坑
2019/06/05 Javascript
python根据距离和时长计算配速示例
2014/02/16 Python
Python随机生成信用卡卡号的实现方法
2015/05/14 Python
Python中shapefile转换geojson的示例
2019/01/03 Python
python 字段拆分详解
2019/12/17 Python
Matplotlib绘制雷达图和三维图的示例代码
2020/01/07 Python
浅析Django 接收所有文件,前端展示文件(包括视频,文件,图片)ajax请求
2020/03/09 Python
Python 实现一个简单的web服务器
2021/01/03 Python
HTML5 Web存储方式的localStorage和sessionStorage进行数据本地存储案例应用
2012/12/09 HTML / CSS
HTML5实现一个能够移动的小坦克示例代码
2013/09/02 HTML / CSS
澳大利亚在线百货商店:Real Smart
2017/08/13 全球购物
澳大利亚在线时尚精品店:Hello Molly
2018/02/26 全球购物
Falconeri美国官网:由羊绒和羊毛制成的针织服装
2018/04/08 全球购物
Java模拟试题
2014/11/10 面试题
陈欧广告词
2014/03/14 职场文书
机械设计专业大学生职业生涯规划书范文
2014/09/13 职场文书
暑期工社会实践报告
2015/07/13 职场文书
2015年小学实验室工作总结
2015/07/28 职场文书
MySQL sql_mode修改不生效的原因及解决
2021/05/07 MySQL