ssr使用mysql数据库_【第五期】基于 @vue/cli3 ssr 插件与 influxdb,接入监控系统 【SSR第四篇】...

在上一篇文章system error:【第四期】基于 @vue/cli3 插件,集成日志系统 ----【SSR第三篇】​zhuanlan.zhihu.com1854586003a98ce0cc6429f0a3b6fcd4.png

中,我们为 ssr 插件中的服务器端逻辑接入了日志系统。

接下来让我们考虑为 ssr 插件中的服务器端逻辑接入基于 influxdb 的监控系统。我们按照下面的步骤逐步讲解:什么是 influxdb

定义监控信息的内容

搭建监控系统客户端

官方提供的展示监控数据的工具

什么是 influxdbinfluxDB 是一个由 InfluxData 开发的开源时序型数据库。

它由 Go 写成,着力于高性能地查询与存储时序型数据。

InfluxDB 被广泛应用于存储系统的监控数据,IoT 行业的实时数据等场景。

------ 来自wikipedia InfluxDB

我们收集的监控信息,最终会上报到 influxdb 中,关于 influxdb,我们需要记住以下概念:influxDB: 是一个时序数据库,它存储的数据由 Measurement, tag组 以及 field组 以及一个 时间戳 组成。

Measurement: 由一个字符串表示该条记录对应的含义。比如它可以是监控数据 cpu_load,也可以是测量数据average_temperature(我们可以先将其理解为 mysql 数据库中的表 table)

tag组: 由一组键值对组成,表示的是该条记录的一系列属性信息。同样的 measurement 数据所拥有的 tag组 不一定相同,它是无模式的(Schema-free)。tag 信息是默认被索引的。

field组: 也是由一组键值对组成,表示的是该条记录具体的 value 信息(有名称)。field组 中可定义的 value 类型包括:64位整型,64位浮点型,字符串以及布尔型。Field 信息是无法被索引的。

时间戳: 就是该条记录的时间属性。如果插入数据时没有明确指定时间戳,则默认存储在数据库中的时间戳则为该条记录的入库时间。

定义监控信息的内容,以及数据来源

对于 influxdb 有了基本的了解后,我们来设计具体的监控信息内容。

我们首先需要考虑 ssr 服务端有哪些信息需要被监控,这里我们简单定义如下监控内容:请求信息(请求数量、请求耗时)

错误信息(错误数量、错误类型)

内存占用

请求数量,指的是服务端每接收到一次页面请求(这里可以不考虑非 GET 的请求),记录一次数据。

请求耗时,指的是服务端接收到请求,到开始返回响应之间的时间差。

错误数量,指的是服务端发生错误和异常的次数。

错误类型,指的是我们为错误定义的分类名称。

内存占用,指的是服务端进程占用的内存大小。(这里我们只记录服务端进程的 RSS 信息)。

那么数据源从哪里来呢?

对于 请求信息、错误信息 这两个个监控信息的内容,我们可以借助于在上一篇文章system error:【第四期】基于 @vue/cli3 插件,集成日志系统 ----【SSR第三篇】​zhuanlan.zhihu.com1854586003a98ce0cc6429f0a3b6fcd4.png

中,设计的日志系统来采集。

这个系统基于 winston 这个日志工具,winston 支持我们在写入日志前,对日志进行一些处理,具体参考creating-custom-formats

我们通过日志系统创建请求日志和错误日志,并在这两类日志的信息中,采集我们需要的数据。

为此,我们需要让我们的日志系统在初始化时支持一个函数类型的参数,在每次写入日志前,都调用这个函数。

打开 app/lib/logger.js,添加此支持,最终代码如下:

const winston = require('winston')

const { format } = winston

const { combine, timestamp, json } = format

// 我们声明一个什么都不做的 hook 函数let _hook = () => {}

const _getToday = (now = new Date()) => `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}`

// 我们借助 winston 提供的日志格式化 api ,实现了一个采集上报函数const ReportInfluxDB = format((info) => {

_hook(info)

info.host = os.hostname()

info.pid = process.pid

return info

})

const rotateMap = {

'hourly': 'YYYY-MM-DD-HH',

'daily': 'YYYY-MM-DD',

'monthly': 'YYYY-MM'

}

module.exports = (dirPath = './', rotateMode = '', hookFunc) => {

// 当传递了自定义 hook 函数后,替换掉我们的默认 hook 函数 if (hookFunc) _hook = hookFunc

if (!~Object.keys(rotateMap).indexOf(rotateMode)) rotateMode = ''

let accessTransport

let combineTransport

if (rotateMode) {

require('winston-daily-rotate-file')

const pid = process.pid

dirPath += '/pid_' + pid + '_' + _getToday() + '/'

const accessLogPath = dirPath + 'access-%DATE%.log'

const combineLogPath = dirPath + 'combine-%DATE%.log'

const datePattern = rotateMap[rotateMode] || 'YYYY-MM'

accessTransport = new (winston.transports.DailyRotateFile)({

filename: accessLogPath,

datePattern: datePattern,

zippedArchive: true,

maxSize: '1g',

maxFiles: '30d'

})

combineTransport = new (winston.transports.DailyRotateFile)({

filename: combineLogPath,

datePattern: datePattern,

zippedArchive: true,

maxSize: '500m',

maxFiles: '30d'

})

}

const options = {

// 我们在这里定义日志的等级 levels: { error: 0, warning: 1, notice: 2, info: 3, debug: 4 },

format: combine(

timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }),

// 为产品环境日志挂载我们的采集上报函数 ReportInfluxDB()

),

transports: rotateMode ? [

combineTransport

] : []

}

// 开发环境,我们将日志也输出到终端,并设置上颜色 if (process.env.NODE_ENV === 'development') {

options.format = combine(

timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }),

winston.format.colorize(),

json(),

// 为开发环境日志挂载我们的采集上报函数 ReportInfluxDB()

)

// 输出到终端的信息,我们调整为 simple 格式,方便看到颜色; // 并设置打印 debug 以上级别的日志(包含 debug) options.transports.push(new winston.transports.Console({

format: format.simple(), level: 'debug'

}))

}

winston.loggers.add('access', {

levels: { access: 0 },

level: 'access',

format: combine(

timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }),

json(),

// 为请求日志挂载我们的采集上报函数 ReportInfluxDB()

),

transports: rotateMode ? [

accessTransport

] : []

})

const logger = winston.createLogger(options)

return {

logger: logger,

accessLogger: winston.loggers.get('access')

}

}

在 app/server.js 中引入 lib/logger.js 也需要调整为以下方式:

const LOG_HOOK = logInfo => {

if (logInfo.level === 'access') return process.nextTick(() => {

/* TODO: 采集请求数量和请求耗时,并上报 */

})

if (logInfo.level === 'error') return process.nextTick(() => {

/* TODO: 采集错误数量和错误类型,并上报 */

})

}

const { logger, accessLogger } = require('./lib/logger.js')('./', 'hourly', LOG_HOOK)

对于 内存占用,我们只需要通过 Nodejs 提供的 process.memoryUsage() 方法来采集。

搭建监控系统客户端

确定好了监控信息内容、数据源。剩下的就是如何设计监控系统客户端。

首先,我们创建 app/lib/reporter.js 文件,内容如下:

'use strict'

const Influx = require('influxdb-nodejs')

class Reporter {

constructor (

protocol,

appName,

host,

address,

measurementName,

fieldSchema,

tagSchema,

syncQueueLimit,

intervalMilliseconds,

syncSucceedHook = () => {},

syncfailedHook = () => {}

) {

if (!protocol) throw new Error('[InfluxDB] miss the protocol')

if (!appName) throw new Error('[InfluxDB] miss the app name')

if (!host) throw new Error('[InfluxDB] miss the host')

if (!address) throw new Error('[InfluxDB] miss the report address')

if (!measurementName) throw new Error('[InfluxDB] miss the measurement name')

this.protocol = protocol

this.appName = appName

this.host = host

this.measurementName = measurementName

this.fieldSchema = fieldSchema

this.tagSchema = tagSchema

this.syncSucceedHook = syncSucceedHook

this.syncfailedHook = syncfailedHook

// _counter between the last reported data and the next reported data this.count = 0

// default sync queue then it has over 100 records this.syncQueueLimit = syncQueueLimit || 100

// default check write queue per 60 seconds this.intervalMilliseconds = intervalMilliseconds || 60000

this.client = new Influx(address)

this.client.schema(

this.protocol,

this.fieldSchema,

this.tagSchema,

{

stripUnknown: true

}

)

this.inc = this.inc.bind(this)

this.clear = this.clear.bind(this)

this.syncQueue = this.syncQueue.bind(this)

this.writeQueue = this.writeQueue.bind(this)

// report data to influxdb by specified time interval setInterval(() => {

this.syncQueue()

}, this.intervalMilliseconds)

}

inc () {

return ++this.count

}

clear () {

this.count = 0

}

syncQueue () {

if (!this.client.writeQueueLength) return

let len = this.client.writeQueueLength

this.client.syncWrite()

.then(() => {

this.clear()

this.syncSucceedHook({ measurement_name: this.measurementName, queue_size: len })

})

.catch(err => {

this.syncfailedHook(err)

})

}

writeQueue (fields, tags) {

fields.count = this.inc()

tags.metric_type = 'counter'

tags.app = this.appName

tags.host = this.host

this.client.write(this.measurementName).tag(tags).field(fields).queue()

if (this.client.writeQueueLength >= this.syncQueueLimit) this.syncQueue()

}

}

const createReporter = (option) => new Reporter(

option.protocol || 'http',

option.app,

option.host,

option.address,

option.measurement,

option.fieldSchema,

option.tagSchema,

option.syncQueueLimit,

option.intervalMilliseconds,

option.syncSucceedHook,

option.syncfailedHook

)

module.exports = createReporter

通过上面的代码可以看到,我们基于 influxdb-nodejs 封装了一个叫做 createReporter 的类。

通过 createReporter,我们可以创建:request reporter (请求信息上报器)

error reporter (错误信息上报器)

memory reporter(内存信息上报器)

所有这些信息,都标配如下字段信息:app 应用的名称,可以将工程项目中 pacage.json 中的 name 值作为此参数值

host 所在服务器操作系统的 hostname

address 监控信息上报的地址

measurement influxdb 中 measurement 的名称

fieldSchema field组的定义,(具体请参考write-point)

tagSchema tag组的定义,(具体请参考write-point)

syncQueueLimit 缓存上报信息的最大个数,达到这个值,会触发一次监控信息上报,默认缓存 100 条记录

intervalMilliseconds 上报信息的时间间隔,默认 1 分钟

syncSucceedHook 上报信息成功后执行的函数,可以通过此函数打印一些日志,方便跟踪上报监控信息的情况

syncfailedHook 上报信息失败后执行的函数,可以通过此函数打印一些日志,方便跟踪上报监控信息的情况

下面,让我们来看如何使用 app/lib/reporter.js 来创建我们需要的监控信息上报器。

首选,创建 influxdb 配置文件 app/config/influxdb.js,内容如下:

'use strict'

const options = {

app: '在这里填写您的应用名称',

address: '在这里填写远程 influxdb 地址',

access: {

measurement: 'requests',

fieldSchema: {

count: 'i',

process_time: 'i'

},

tagSchema: {

app: '*',

host: '*',

request_method: '*',

response_status: '*'

}

},

error: {

measurement: 'errors',

fieldSchema: {

count: 'i'

},

tagSchema: {

app: '*',

host: '*',

exception_type: '*'

}

},

memory: {

measurement: 'memory',

fieldSchema: {

rss: 'i',

heapTotal: 'i',

heapUsed: 'i',

external: 'i'

},

tagSchema: {

app: '*',

host: '*'

}

}

}

module.exports = options

对于请求信息,我们设置了:count 整型,方便统计请求数

process_time 整型,请求耗时(单位:毫秒)

request_method 任意类型,请求方法

response_status 任意类型,响应状态码

对于错误信息,我们设置了:count 整型,方便统计错误数

exception_type 任意类型,错误类型值(这需要我们在应用中定义)

对于内存信息,我们设置了:rss 后端服务进程实际占用内存

heapTotal 堆空间上限

heapUsed 已使用的堆空间

external V8管理的 C++ 对象占用空间

接着创建 app/lib/monitor.js,内容如下:

'use strict'

const createReporter = require('./reporter.js')

const os = require('os')

const _ = require('lodash')

const config = require('../config/influxdb.js')

const protocol = 'http'

const app = config.app

const host = os.hostname()

const address = config.address

const intervalMilliseconds = 60000

const syncQueueLimit = 100

const syncSucceedHook = info => {

console.log(JSON.stringify({ title: '[InfluxDB] sync write queue success', info: info }))

}

const syncfailedHook = err => {

console.log(JSON.stringify({ title: '[InfluxDB] sync write queue fail.', error: err.message }))

}

const accessReporter = createReporter({

protocol,

app,

host,

address,

measurement: _.get(config, 'access.measurement'),

fieldSchema: _.get(config, 'access.fieldSchema'),

tagSchema: _.get(config, 'access.tagSchema'),

syncQueueLimit,

intervalMilliseconds,

syncSucceedHook,

syncfailedHook

})

const errorReporter = createReporter({

protocol,

app,

host,

address,

measurement: _.get(config, 'error.measurement'),

fieldSchema: _.get(config, 'error.fieldSchema'),

tagSchema: _.get(config, 'error.tagSchema'),

syncQueueLimit,

intervalMilliseconds,

syncSucceedHook,

syncfailedHook

})

const memoryReporter = createReporter({

protocol,

app,

host,

address,

measurement: _.get(config, 'memory.measurement'),

fieldSchema: _.get(config, 'memory.fieldSchema'),

tagSchema: _.get(config, 'memory.tagSchema'),

syncQueueLimit,

intervalMilliseconds,

syncSucceedHook,

syncfailedHook

})

function reportAccess (accessData) {

accessReporter.writeQueue(

{

process_time: accessData.process_time

},

{

request_method: accessData.request_method,

response_status: accessData.response_status

}

)

}

function reportError (errorData) {

errorReporter.writeQueue(

{

},

{

exception_type: errorData.type || 0

}

)

}

function reportMemory () {

const memInfo = process.memoryUsage()

memoryReporter.writeQueue(

{

rss: memInfo.rss || 0,

heapTotal: memInfo.heapTotal || 0,

heapUsed: memInfo.heapUsed || 0,

external: memInfo.external || 0

},

{

}

)

}

global.reportAccess = reportAccess

global.reportError = reportError

global.reportMemory = reportMemory

最后,我们在 app/server.js 中添加具体的上报器调用代码,代码片段如下:

require('./lib/monitor.js')

const reportMemoryStatInterval = 30 * 1000

setInterval(() => {

global.reportMemory()

}, reportMemoryStatInterval)

const LOG_HOOK = logInfo => {

if (logInfo.level === 'access') return process.nextTick(() => {

global.reportAccess(logInfo)

})

if (logInfo.level === 'error') return process.nextTick(() => {

global.reportError(logInfo)

})

}

const { logger, accessLogger } = require('./lib/logger.js')('./', 'hourly', LOG_HOOK)

至此,我们在应用中设计监控信息并创建监控系统客户端的步骤就算完成了。

最终,ssr 插件的目录结构如下所示:

├── app

│ ├── config

│ │ ├── influxdb.js

│ ├── middlewares

│ │ ├── dev.ssr.js

│ │ ├── dev.static.js

│ │ └── prod.ssr.js

│ ├── lib

│ │ ├── reporter.js

│ │ ├── monitor.js

│ │ └── logger.js

│ └── server.js

├── generator

│ ├── index.js

│ └── template

│ ├── src

│ │ ├── App.vue

│ │ ├── assets

│ │ │ └── logo.png

│ │ ├── components

│ │ │ └── HelloWorld.vue

│ │ ├── entry-client.js

│ │ ├── entry-server.js

│ │ ├── main.js

│ │ ├── router

│ │ │ └── index.js

│ │ ├── store

│ │ │ ├── index.js

│ │ │ └── modules

│ │ │ └── book.js

│ │ └── views

│ │ ├── About.vue

│ │ └── Home.vue

│ └── vue.config.js

├── index.js

└── package.json

官方提供的展示监控数据的工具

展示监控数据的工具有很多,这里推荐一个官方 influxdata 提供的工具:chronograf。

关于 chronograf 的知识,本文不再展开,有兴趣的同学可以查阅官方文档学习相关细节。

水滴前端团队招募伙伴,欢迎投递简历到邮箱:fed@shuidihuzhu.com

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值