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回调(callback)函数概念自我理解及示例
Jul 04 Javascript
给js文件传参数(详解)
Jul 13 Javascript
jQuery获取checkbox选中的值
Jan 28 Javascript
jq给页面添加覆盖层遮罩的实例
Feb 16 Javascript
layer弹窗插件操作方法详解
May 19 Javascript
详解Vue 非父子组件通信方法(非Vuex)
May 24 Javascript
angular2 ng2 @input和@output理解及示例
Oct 10 Javascript
JS与CSS3实现图片响应鼠标移动放大效果示例
May 04 Javascript
angular ng-model 无法获取值的处理方法
Oct 02 Javascript
使用ThinkJs搭建微信中控服务的实现方法
Aug 08 Javascript
JS数组降维的实现Array.prototype.concat.apply([], arr)
Apr 28 Javascript
vue $router和$route的区别详解
Dec 02 Vue.js
详解关于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 字符转义 注意事项
2009/05/27 PHP
PHP函数实现从一个文本字符串中提取关键字的方法
2015/07/01 PHP
如何解决phpmyadmin导入数据库文件最大限制2048KB
2015/10/09 PHP
PHP表单验证内容是否为空的实现代码
2016/11/14 PHP
javascript 获取页面的高度及滚动条的位置的代码
2010/05/06 Javascript
基于jQuery实现点击同时更改两个iframe的网址
2010/07/01 Javascript
js去除重复字符串两种实现方法
2013/01/09 Javascript
CSS+jQuery实现的一个放大缩小动画效果
2013/09/24 Javascript
vuejs使用$emit和$on进行组件之间的传值的示例
2017/10/04 Javascript
10分钟上手vue-cli 3.0 入门介绍
2018/04/04 Javascript
Node.js文件编码格式的转换的方法
2018/04/27 Javascript
微信小程序实现动态获取元素宽高的方法分析
2018/12/10 Javascript
vue计算属性computed、事件、监听器watch的使用讲解
2019/01/21 Javascript
Jquery的autocomplete插件用法及参数讲解
2019/03/12 jQuery
详解微信小程序动画Animation执行过程
2020/09/23 Javascript
JavaScript函数柯里化实现原理及过程
2020/12/02 Javascript
JS中箭头函数与this的写法和理解
2021/01/14 Javascript
简单介绍Python的轻便web框架Bottle
2015/04/08 Python
基于DataFrame筛选数据与loc的用法详解
2018/05/18 Python
基于Python中求和函数sum的用法详解
2018/06/28 Python
Django中使用Whoosh进行全文检索的方法
2019/03/31 Python
Django单元测试工具test client使用详解
2019/08/02 Python
pygame实现俄罗斯方块游戏(AI篇2)
2019/10/29 Python
Python input函数使用实例解析
2019/11/22 Python
Python中join()函数多种操作代码实例
2020/01/13 Python
Pyinstaller加密打包应用的示例代码
2020/06/11 Python
Python3.8安装Pygame教程步骤详解
2020/08/14 Python
Lookfantastic西班牙官网:英国知名美妆购物网站
2018/06/13 全球购物
学校司机岗位职责
2013/11/14 职场文书
银行职员思想汇报
2013/12/31 职场文书
中学家长会邀请函
2014/01/17 职场文书
乡镇领导干部个人对照检查材料思想汇报
2014/09/23 职场文书
党的群众路线教育实践活动个人整改措施落实情况
2014/11/04 职场文书
倡议书格式及范文
2015/04/29 职场文书
生活小常识广播稿
2015/08/19 职场文书
Java无向树分析 实现最小高度树
2022/04/09 Javascript